527 lines
14 KiB
Java
527 lines
14 KiB
Java
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 <code>boolean-array</code> 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 <code>Player-array</code>
|
|
*/
|
|
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 <code>field</code>.
|
|
*
|
|
* @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 <code>true</code> if field with <code>row</code> and
|
|
* <code>col</code> is not set by one of the
|
|
* player <br /> 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 <code>true</code> 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 <code>true</code> 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 <code>Array</code> of enemy tiles: <br />
|
|
* array[0] is at 0°, array[1] is diagonal 45°,<br />
|
|
* 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 <code>score</code>
|
|
*/
|
|
double score() {
|
|
double score = (scoreT() + scoreM() + scoreP());
|
|
return score;
|
|
}
|
|
|
|
/**
|
|
* Calculates the weighting score.
|
|
*
|
|
* @return <code>scoreT</code>
|
|
*/
|
|
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 <code>scoreM</code>
|
|
*/
|
|
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 <code>scoreP</code>
|
|
*/
|
|
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);
|
|
}
|
|
} |