#include "gamedata.h" #include "mathutils.h" #include "debug.h" #include #include #include using std::list; int GameData::PLAYER1_COLOUR = 0x4a483f; int GameData::PLAYER2_COLOUR = 0x090c7a; int GameData::BASE_BUILD_RADIUS = 75; int GameData::NODE_RADIUS = 10; GameVertex::GameVertex(int x, int y, int z, int r, int colour, int score, VertexType type, Player* player) : Vertex(x, y, z, r, colour, score) { this->type = type; this->player = player; } GameData::GameData() : Graph(true) { current = NULL; mode = MODE_BUILD; player1 = Player("player 1", PLAYER1_COLOUR); player2 = Player("player 2", PLAYER2_COLOUR); turn = &player1; build_type = VERTEX_PRODUCER; // first vertex is always a producer } GameData::~GameData() { } Vertex* GameData::get_current_vertex(bool only_mine) const { if (only_mine) { if (current != NULL && dynamic_cast(current)->player == turn) return current; return NULL; } return current; } void GameData::clear_current_vertex() { mode = MODE_SELECT; current = NULL; } void GameData::toggle_turn() { if (!turn->has_played()) turn->set_played(); if (!endgame()) { if (turn == &player1) turn = &player2; else if (turn == &player2) turn = &player1; if (!turn->has_played()) { mode = MODE_BUILD; build_type = VERTEX_PRODUCER; } else { mode = MODE_SELECT; build_type = VERTEX_NONE; } } current = NULL; } void GameData::handle_click(int x, int y) { int r = 10; int colour; colour = turn->get_colour(); Vertex* v = vertex_at(x, y, 0); // fixme - energy expenditure should happen in each of these cases except // MODE_SELECT switch (mode) { case MODE_SELECT: // select_vertex handles making sure a point exists at (x,y) select_vertex(x, y); break; case MODE_BUILD: add_vertex(x, y, 0, r, colour); break; case MODE_ATTACK: if (v == NULL || dynamic_cast(v)->player == turn) return; if (v->colour != colour) attack_vertex(v); break; case MODE_MOVE: if (current == NULL) return; if (MathUtils::distance(current->x, current->y, current->z, x, y, 0) <= get_range()) { current->x = x; current->y = y; toggle_turn(); } break; } } bool GameData::select_vertex(int x, int y) { Vertex * v = vertex_at(x, y, 0); if (v == NULL) return false; current = v; return true; } bool GameData::add_vertex(int x, int y, int z, int r, int colour) { if (build_type == VERTEX_NONE) return false; GameVertex* v = new GameVertex(x, y, z, r, colour, 10, build_type, turn); if (current == NULL) { // this is the special case for adding the first vertex for each player if (!turn->has_played()) { if (Graph::add_vertex(v)) { #ifdef DEBUG fprintf(stderr, "debug: GameData::add_vertex(): strength=%2.f\n", calculate_strength(*(vertices.rbegin()))); #endif toggle_turn(); return true; } } // really, we shouldn't be able to get here. return false just in case delete v; return false; } // same here - just a logic check if (dynamic_cast(current)->player != turn) { delete v; return false; } // This is the range check... if (MathUtils::distance(current->x, current->y, 0, v->x, v->y, 0) > get_range()) { delete v; return false; } if (Graph::add_vertex(v, current)) { #ifdef DEBUG fprintf(stderr, "debug: GameData::add_vertex(): strength=%.2f\n", calculate_strength(*(vertices.rbegin()))); #endif toggle_turn(); return true; } delete v; return false; } float GameData::calculate_armor(Vertex* node) { float str = calculate_strength(node); float armor = str / 10; if (armor < 1) armor = 1; return armor; } float GameData::calculate_strength(Vertex* node) { list visited; // Special case - a one-node tree just returns its own score! if (node->neighbors.empty()) return (float)node->score; return calculate_strength_r(node, 0, visited); } // Oh the recursive recursion! float GameData::calculate_strength_r(Vertex* node, unsigned int depth, list& visited) { // Find which vertices we need to visit from here list neighbors = node->neighbors; list to_visit; visited.push_back(node); for (list::iterator cursor = neighbors.begin(); cursor != neighbors.end(); cursor++) { Vertex* v = *cursor; // if this is true, we haven't visited the vertex on the other end of // this edge yet if (find(visited.begin(), visited.end(), v) == visited.end()) { to_visit.push_back(v); } } // This is the base case - this node has no unvisited neighbors if (to_visit.empty()) { assert(depth > 0); return (float)(node->score) / depth; } // Else, iterate through to_visit and visit them all, summing their // effective strengths adjusted for depth. // Since our trees are acyclic, this can't loop. float modscore = (float)node->score; if (depth > 0) modscore /= depth; for (list::iterator cursor = to_visit.begin(); cursor != to_visit.end(); cursor++) { Vertex* v = *cursor; modscore += calculate_strength_r(v, depth+1, visited); } return modscore; } // This class contains logic checks to keep the mode aligned with // what is reasonable. Special cases inside the GameData class should just // do mode = MODE_ void GameData::set_mode(Mode m) { // Stay in MODE_SELECT (or maybe MODE_BUILD) when current is null if (current == NULL) return; // If we're leaving MODE_BUILD, we should set the build_type back to none if (mode == MODE_ATTACK && m != mode) build_type = VERTEX_NONE; // The other modes all require current to match the player if (dynamic_cast(current)->player == turn) mode = m; } int GameData::get_range(Vertex* node) { if (node == NULL) node = current; if (node == NULL) return 0; else if (mode == MODE_MOVE) return turn->get_energy(); else if (mode == MODE_BUILD) return BASE_BUILD_RADIUS; else if (mode == MODE_ATTACK) { int range = BASE_BUILD_RADIUS; list neighbors = node->neighbors; for(list::iterator cursor = neighbors.begin(); cursor != neighbors.end(); cursor++) { Vertex* v = *cursor; range -= (100 - MathUtils::distance(v->x, v->y, v->z, node->x, node->y, node->z)) / 2; } if (range < 0) range = 0; return range; } } void GameData::attack_vertex(Vertex* target) { float atk = calculate_strength(current); float armor = calculate_armor(target); int damage = (int)(atk / armor); target->score -= damage; if (target->score <= 0) remove_vertex(target); #ifdef DEBUG fprintf(stderr, "debug: GameData::attack_vertex(): atk=%.2f, armor=%.2f, damage=%d\n", atk, armor, damage); #endif toggle_turn(); } bool GameData::endgame() { if (!(player1.has_played() && player2.has_played())) return false; if (get_colour(player1.get_colour()).empty()) { debug("Gamedata::endgame(): player 2 wins\n"); return true; } if (get_colour(player2.get_colour()).empty()) { debug("Gamedata::endgame(): player 1 wins\n"); return true; } return false; }