package reversi.model; /** * Class for reversi game. A human player tries to beat a bot with modifiable * difficulty level. */ public class Reversi implements Board { private Player bot; private Player human; /** * Player with the next move. */ private Player currentPlayer; private Player firstPlayer; /** * Playboard saves the tiles. */ private Player[][] playBoard; /** * Gametree for the bot to calculate future moves. */ private GameTree gameTree; /** * Vectors to traverse the 8 directions around a tile. */ private Field[] vectors; /** * Saves if game is over. */ private boolean gameOver; /** * Standard weighting of the playboard. Used by the bot to calculate * gametree. */ private double[][] weighting = new double[][] {{9999, 5, 500, 200, 200, 500, 5, 9999}, {5, 1, 50, 150, 150, 50, 1, 5}, {500, 50, 250, 100, 100, 250, 50, 500}, {200, 150, 100, 50, 50, 100, 150, 200}, {200, 150, 100, 50, 50, 100, 150, 200}, {500, 50, 250, 100, 100, 250, 50, 500}, {5, 1, 50, 150, 150, 50, 1, 5}, {9999, 5, 500, 200, 200, 500, 5, 9999}}; /** * Difficulty level for the human. Represents the depth of the gametree. */ private int level = 3; /** * Constructs a new reversi game. * * @param human Human player * @param bot Machine player * @param firstPlayer Player that starts */ public Reversi(Player human, Player bot, Player firstPlayer) { this.bot = bot; this.human = human; this.firstPlayer = firstPlayer; this.playBoard = setInitialPositions(); currentPlayer = firstPlayer; this.vectors = generateVectors(); } /** * {@inheritDoc} */ public final Player getFirstPlayer() { return firstPlayer; } /** * {@inheritDoc} */ public final Player next() { return currentPlayer; } /** * {@inheritDoc} */ public final boolean move(final int row, final int col) { if (currentPlayer != human) { return false; } if (!isValidMove(row, col)) { new IllegalMoveException("Error! invalid move at (" + (row + 1) + ", " + (col + 1) + ")."); return false; } performMove(row, col); return true; } /** * {@inheritDoc} */ public final void machineMove() { if (currentPlayer != bot) { return; } gameTree = new GameTree(this, level); Field bestMove = gameTree.getRoot() .getChildrensHighestScore().getMove(); performMove(bestMove.row(), bestMove.col()); } /** * {@inheritDoc} */ public final void setLevel(final int level) { this.level = level; } /** * {@inheritDoc} */ public final boolean gameOver() { return gameOver; } /** * {@inheritDoc} */ public final Player getWinner() { if (getNumberOfHumanTiles() > getNumberOfMachineTiles()) { return human; } else if (getNumberOfHumanTiles() == getNumberOfMachineTiles()) { return null; } else { return bot; } } /** * {@inheritDoc} */ public final int getNumberOfHumanTiles() { return numberOfTiles(human); } /** * {@inheritDoc} */ public final int getNumberOfMachineTiles() { return numberOfTiles(bot); } /** * {@inheritDoc} */ public final int getNumberOfTilesSet() { int n = (getNumberOfHumanTiles() + getNumberOfMachineTiles()); return n; } /** * {@inheritDoc} */ public final Player getSlot(final int row, final int col) { return playBoard[row][col]; } @Override public final Reversi clone() { Reversi boardCopy = new Reversi(human, bot, firstPlayer); boardCopy.currentPlayer = currentPlayer; Player[][] playBoardCopy = new Player[SIZE][SIZE]; for (int row = 0; row < SIZE; row++) { for (int col = 0; col < SIZE; col++) { playBoardCopy[row][col] = playBoard[row][col]; } } boardCopy.playBoard = playBoardCopy; return boardCopy; } /** * Generate the vectors for the 8 directions. array[0] = 0°, * array[1] = 45°... * * @return boolean-array with all vector fields */ private Field[] generateVectors() { Field[] vector = { new Field(-1, 0), new Field(-1, +1), new Field(0, +1), new Field(+1, +1), new Field(+1, 0), new Field(+1, -1), new Field(0, -1), new Field(-1, -1)}; return vector; } /** * Sets two tiles/player at the beginning of the match in the middle of * of the playboard. * * @return Player-array */ private Player[][] setInitialPositions() { Player[][] playBoard = new Player[SIZE][SIZE]; for (int row = 0; row < SIZE; row++) { for (int col = 0; col < SIZE; col++) { if (((SIZE / 2) - 1 == row || SIZE / 2 == row) && ((SIZE - col - 1) == row)) { playBoard[row][col] = firstPlayer; } else if ((SIZE / 2 - 1 == row || SIZE / 2 == row) && (col == row)) { playBoard[row][col] = getEnemy(firstPlayer); } else { playBoard[row][col] = null; } } } return playBoard; } /** * * @param self Player * @return Enemy of the Player */ private Player getEnemy(final Player self) { if (self == human) { return bot; } else { return human; } } /** * Switches the currentplayer and checks if the next player can do a valid * move. If both players cannot move the game is over. * game is over */ private void switchPlayer() { currentPlayer = getEnemy(currentPlayer); if (!movePossible()) { currentPlayer = getEnemy(currentPlayer); if (!movePossible()) { gameOver = true; } } } /** * Switch a tile to the current player. * * @param row Row of field * @param col Column of field */ private void switchTile(final int row, final int col) { playBoard[row][col] = currentPlayer; } /** * Switch all corresponding enemy tiles around a field. * * @param row Row of field * @param col Column of field */ private void switchTiles(final int row, final int col) { boolean[] surrounders = validMoves(row, col); switchTile(row, col); for (int i = 0; i < SIZE; i++) { if (surrounders[i]) { int x = row + vectors[i].row(); int y = col + vectors[i].col(); while (playBoard[x][y] != currentPlayer) { switchTile(x, y); x = x + vectors[i].row(); y = y + vectors[i].col(); } } } } private int numberOfTiles(Player player) { int mount = 0; for (int row = 0; row < SIZE; row++) { for (int col = 0; col < SIZE; col++) { if (playBoard[row][col] == player) { mount++; } } } return mount; } /** * * @param row Row of the field * @param col Column of the field * @return true if field with row and * col is not set by one of the * player
else if field is set */ private boolean isFree(final int row, final int col) { if (playBoard[row][col] == null) { return true; } return false; } /** * Checks the hole playBoard if the current player can do a valid move. * * @return true if another move is possible. */ private boolean movePossible() { for (int row = 0; row < SIZE; row++) { for (int col = 0; col < SIZE; col++) { if (isValidMove(row, col)) { return true; } } } return false; } /** * Walk the board in a direction. * * @param row Row of field * @param col Column of field * @param x row direction to walk * @param y column direction to walk * @return if the direction has a valid move */ private boolean boardWalker(int row, int col, final int x, final int y) { row = row + x; col = col + y; int counter = 0; while (row < SIZE && row >= 0 && col < SIZE && col >= 0) { if (playBoard[row][col] == null) { return false; } else if (playBoard[row][col] == currentPlayer) { return (counter > 0); } row = row + x; col = col + y; counter++; } return false; } /** * Checks a boolean array if it contains a value that is true. * * @param array Array * @return true if any value is true */ private boolean checkBoolArray(final boolean[] array) { for (int i = 0; i < array.length; i++) { if (array[i]) { return true; } } return false; } /** * * @param row Row of field * @param col Column of field * @return if the currentPlayer can do a move on field(row, col) */ final boolean isValidMove(final int row, final int col) { return isFree(row, col) && checkBoolArray(validMoves(row, col)); } /** * Searches a field for valid moves. * * @param row Row of field * @param col Column of field * @return Array of enemy tiles:
* array[0] is at 0°, array[1] is diagonal 45°,
* array[2] right 90° and so on */ private boolean[] validMoves(final int row, final int col) { boolean[] neighbourhood = new boolean[8]; for (int i = 0; i < neighbourhood.length; i++) { neighbourhood[i] = boardWalker(row, col, vectors[i].row(), vectors[i].col()); } return neighbourhood; } /** * Switches all tiles and changes switches player if possible. * * @param row Row of field * @param col Column of field */ void performMove(final int row, final int col) { switchTiles(row, col); switchPlayer(); } /** * Calculates the score of a reversi. * * @return score */ double score() { double score = (scoreT() + scoreM() + scoreP()); return score; } /** * Calculates the weighting score. * * @return scoreT */ private double scoreT() { double scoreSelf = 0; double scoreEnemy = 0; for (int row = 0; row < SIZE; row++) { for (int col = 0; col < SIZE; col++) { if (playBoard[row][col] == bot) { scoreSelf += weighting[row][col]; } else if (playBoard[row][col] == human) { scoreEnemy += weighting[row][col]; } } } return (scoreSelf - (1.5 * scoreEnemy)); } /** * Calculates the mobility score. * * @return scoreM */ private double scoreM() { double scoreSelf = 0; double scoreEnemy = 0; Player tmp = currentPlayer; int n = getNumberOfTilesSet(); for (int row = 0; row < SIZE; row++) { for (int col = 0; col < SIZE; col++) { currentPlayer = bot; if (isValidMove(row, col)) { scoreSelf++; } currentPlayer = human; if (isValidMove(row, col)) { scoreEnemy++; } currentPlayer = bot; } } currentPlayer = tmp; return (64.0 / n) * (3.0 * scoreSelf - 4.0 * scoreEnemy); } /** * Calculates the future-potential score. * * @return scoreP */ private double scoreP() { double scoreSelf = 0; double scoreEnemy = 0; int n = getNumberOfTilesSet(); for (int row = 0; row < SIZE; row++) { for (int col = 0; col < SIZE; col++) { if (playBoard[row][col] == human) { for (int i = 0; i < 8; i++) { int x = row + vectors[i].row(); int y = col + vectors[i].col(); if ((x >= 0 && x < SIZE && y >= 0 && y < SIZE) && (playBoard[x][y] == null)) { scoreSelf++; } } } else if (playBoard[row][col] == bot) { for (int i = 0; i < 8; i++) { int x = row + vectors[i].row(); int y = col + vectors[i].col(); if ((x >= 0 && x < SIZE && y >= 0 && y < SIZE) && (playBoard[x][y] == null)) { scoreEnemy++; } } } } } return (64.0 / (2 * n)) * (2.5 * scoreSelf - 3.0 * scoreEnemy); } }