#include #include #include "Game.h" #include "Types.h" std::array Game::neighbours; std::array Game::fieldVectors; Game::Game() : active(-1, -1), mustTake(false) { // sets the basic game up: // 0 - start game // 1 - player vs ai // 0 - player starts std::cout << "0\n1\n0\n"; // allows for each neighbor iterating neighbours[0].set(1, 0); neighbours[1].set(0, 1); neighbours[2].set(-1, 0); neighbours[3].set(0, -1); neighbours[4].set(1, 1); neighbours[5].set(-1, 1); neighbours[6].set(1, -1); neighbours[7].set(-1, -1); // allows for each for all fields uint index = 0; for(uint y = 0; y < 5; y++) { for(uint x = 0; x < 9; x++) { fieldVectors[index++].set(x, y); } } } void Game::readLine() { String line; while(true) { // read input by each character char c = std::cin.get(); if(c == '\n') { // parse the command if the line ended if(parseLine(line)) { return; } // clear the buffer for the next command line.clear(); } else if(c != '\r') { // line carriages are ignored line.append(c); } } } bool Game::parseLine(const String& line) { // identify when the server expects an answer or a new game field is available if(line == " 0 1 2 3 4 5 6 7 8") { fields.read(); fields.print(active); } else if(line == "Please enter origin x-axis") { makeSelection(); } else if(line == "Please enter destination x-axis") { makeMove(); } else if(line == "Please enter wether you want to Withdraw or Approach [W/A]") { takeStone(); } else if(line == "Do you want to continue with your turn [Y/N]?") { // we always want to continue if possible std::cout << "Y\n"; } else if(line == "************************Player 1 won!**********************" || line == "************************Player 2 won!**********************") { // stop the game if anybody wins - the server does not do that std::cerr << line << "\n"; return true; } return false; } bool Game::isInRange(const Vector& v) const { return v.x >= 0 && v.x <= 8 && v.y >= 0 && v.y <= 4; } bool Game::areNeighbors(const Vector& from, const Vector& to) const { // fields are not neighbors with themselves if(from == to) { return false; } Vector diff = from - to; // fields with a bigger distance than one cannot be neighbors if(diff.x < -1 || diff.x > 1 || diff.y < -1 || diff.y > 1) { return false; } // either left, right, upper or lower neighbor if(diff.x == 0 || diff.y == 0) { return true; } // check if the field has diagonal connections (both coords are either even or uneven numbers) return (from.x & 1) == (from.y & 1); } uint Game::removeLine(const Vector& from, const Vector& to, Fields::State state) { // walk along the line and removes opponent stones Vector current = to; Vector unit = to - from; // counter for removed stones uint rank = 0; while(true) { current += unit; // stop removing on wrong stones or the end of the game field if(!isInRange(current) || !fields.hasState(current, state)) { return rank; } fields.setState(current, Fields::EMPTY); rank++; } } uint Game::getRank(const Vector& from, const Vector& to, Fields::State state) const { // same as removeLine but the removable stones are only counted Vector current = to; Vector unit = to - from; uint rank = 0; while(true) { current += unit; if(!isInRange(current) || !fields.hasState(current, state)) { return rank; } rank++; } } uint Game::getStoneTakes(const Vector& from, const Vector& to) const { // an invalid turn cannot take any stones if(!isInRange(to) || !fields.hasState(to, Fields::EMPTY) || !areNeighbors(from, to) || lastLocations.contains(to) || (to - from) == direction) { return 0; } // the turn must take stones Fields::State enemy = (fields.hasState(from, Fields::BLACK) ? Fields::WHITE : Fields::BLACK); if(getRank(from, to, enemy) == 0 && getRank(to, from, enemy) == 0) { return 0; } // actually do the turn on a copied game, store the rank (= taken stones) Game copy = *this; uint rank = copy.move(from, to); // recursively check all neighbors on the copied game for another move, take the best move uint maxRank = 0; for(Vector& m : neighbours) { uint rank = copy.getStoneTakes(to, to + m); if(rank > maxRank) { maxRank = rank; } } return rank + maxRank; } uint Game::getStoneFreedom(const Vector& from, Fields::State state) const { // no freedom if the stone is not yours if(!fields.hasState(from, state)) { return 0; } // count directions the stone can move to uint counter = 0; for(const Vector& m : neighbours) { Vector to = from + m; if(isInRange(to) && areNeighbors(from, to) && fields.hasState(to, Fields::EMPTY)) { counter++; } } return counter; } uint Game::getFreedom(Fields::State state) const { uint sum = 0; for(const Vector& v : fieldVectors) { sum += getStoneFreedom(v, state); } return sum; } uint Game::move(const Vector& from, const Vector& to) { // remember direction to block the same move direction = to - from; // remember the old location to block moving to it again lastLocations.add(from); // actually from the stone fields.setState(to, fields.getState(from)); fields.setState(from, Fields::EMPTY); // get the enemy by inverting the goal state Fields::State enemy = (fields.hasState(to, Fields::BLACK) ? Fields::WHITE : Fields::BLACK); // compare quality of the two sides, take better side uint rankA = getRank(from, to, enemy); uint rankB = getRank(to, from, enemy); if(rankA >= rankB) { return removeLine(from, to, enemy); } return removeLine(to, from, enemy); } void Game::makeSelection() { // new turn starts - reset previously blocked turns lastLocations.clear(); direction.set(0, 0); // remember if the following turn must take a stone mustTake = isTakingPossible(); // calculate rank for all possible selections, take best selection Vector selection(0, 0); int maxRank = INT_MIN; for(Vector& to : fieldVectors) { if(!fields.hasState(to, Fields::EMPTY)) { continue; } // scan neighbors of empty fields for white stones for(Vector& m : neighbours) { Vector from = to + m; // prevent invalid stone locations if(!isInRange(from) || !fields.hasState(from, Fields::WHITE) || !areNeighbors(from, to)) { continue; } // prevent non taking turns if taking is a must if(mustTake && getRank(from, to, Fields::BLACK) + getRank(to, from, Fields::BLACK) == 0) { continue; } // calculate rank and potentially store the new max value int rank = getQuality(from, to); if(rank > maxRank) { maxRank = rank; selection = from; } } } // mark the stone as active active = selection; // send the selection to the server std::cerr << "Selecting (" << selection.x << ", " << selection.y << ")\n"; // print the selection and the current game field for logging purposes fields.print(active); std::cout << selection.x << "\n" << selection.y << "\n"; } void Game::makeMove() { // check all neighbors of active selection and choose the move with the highest rank Vector maxTo(0, 0); int maxRank = INT_MIN; for(Vector& m : neighbours) { Vector to = active + m; // prevent invalid moves if(!isInRange(to) || !fields.hasState(to, Fields::EMPTY) || lastLocations.contains(to) || !areNeighbors(active, to) || m == direction) { continue; } // if this is not the first move or a must take move - prevent non taking move if(!lastLocations.isEmpty() || mustTake) { int rank = getRank(active, to, Fields::BLACK) + getRank(to, active, Fields::BLACK); if(rank == 0) { continue; } } // calculate rank and potentially store the new max value int rank = getQuality(active, to); if(rank > maxRank) { maxRank = rank; maxTo = to; } } // remember the old location to prevent moving to it lastLocations.add(active); // remember the direction to prevent moving in it again direction = maxTo - active; // mark the location as active active = maxTo; // send the move to the server std::cerr << "Move to (" << maxTo.x << ", " << maxTo.y << ")\n"; // log the move std::cout << maxTo.x << "\n" << maxTo.y << "\n"; } void Game::takeStone() { // calculate the better taking side ... uint rankA = getRank(active, active + direction, Fields::BLACK); uint rankB = getRank(active + direction, active, Fields::BLACK); // and send it to the server if(rankA >= rankB) { std::cout << "W\n"; } else { std::cout << "A\n"; } } int Game::getQuality(const Vector& from, const Vector& to) const { // base quality is how much stones this turn can take (+ following moves) int quality = getStoneTakes(from, to); // actually do the turn on a copy Game copy = *this; copy.move(from, to); // reset previous location and direction to simulate an opponent turn copy.lastLocations.clear(); copy.direction.set(0, 0); // calculate white and black freedom on the potential new board int whiteFreedom = copy.getFreedom(Fields::WHITE); int blackFreedom = copy.getFreedom(Fields::BLACK); // calculates the strongest opponent turn uint maxRank = copy.getBestEnemyRank(); // opponent count is important in the endgame int black = countBlack(); if(black >= 8) { black = 1; } // finalize the quality formula return quality - maxRank * black + (whiteFreedom - blackFreedom) / 4; } int Game::countBlack() const { int counter = 0; for(Vector& v : fieldVectors) { counter += fields.hasState(v, Fields::BLACK); } return counter; } bool Game::isTakingPossible() const { // scan all fields for possible taking moves for(Vector& to : fieldVectors) { if(!fields.hasState(to, Fields::EMPTY)) { continue; } // check if any white neighbored stone can move to the found empty stone for(Vector& m : neighbours) { Vector from = to + m; if(!isInRange(from) || !areNeighbors(from, to) || !fields.hasState(from, Fields::WHITE)) { continue; } // return true if the white stone can take black stones in any direction if(getRank(from, to, Fields::BLACK) + getRank(to, from, Fields::BLACK) > 0) { return true; } } } return false; } uint Game::getBestEnemyRank() const { uint maxRank = 0; // scan all fields for best rank for(Vector& to : fieldVectors) { if(!fields.hasState(to, Fields::EMPTY)) { continue; } // scan neighbors of found empty fields for(Vector& m : neighbours) { Vector from = to + m; // prevent invalid moves if(!isInRange(from) || !fields.hasState(from, Fields::BLACK)) { continue; } // calculate and store the potentially better rank uint rank = getStoneTakes(from, to); if(rank > maxRank) { maxRank = rank; } } } return maxRank; }