/**
* $RCSfile: ,v $
* $Revision: $
* $Date: $
*
* Copyright (C) 2004-2011 Jive Software. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.game.reversi;
import java.util.Arrays;
/**
* Model for a Reversi game. <a href="http://en.wikipedia.org/wiki/Reversi">Reversi</a>
* is a game played by two players ({@link #WHITE White} and {@link #BLACK Black}) on
* an 8x8 board. Board positions are 0-63; starting at 0 in the upper-left corner then
* travelling horizontally row by row until position 63 in the bottom-right corner of
* the board.<p>
*
* This class maintains game state as individual turns are taken until the game is
* {@link #isGameFinished() finished}. {@link #BLACK Black} always starts the game.
* Two stones for each player are always placed in the center of the game board at
* the start of each game (following standard Reversi rules).
*
* @author Matt Tucker
*/
public class ReversiModel {
/**
* Blank.
*/
public static int BLANK = 0;
/**
* White.
*/
public static int WHITE = 1;
/**
* Black.
*/
public static int BLACK = 2;
private int[] board;
private int currentPlayer;
private boolean gameFinished = false;
private int[] flipBuffer = new int[7];
private int flipBufferSize = 0;
/**
* Constructs a new game instance and sets up initial board positions.
*/
public ReversiModel() {
board = new int[64];
// Set all positions to blank.
Arrays.fill(board, BLANK);
// Setup initial positions.
board[27] = WHITE;
board[28] = BLACK;
board[35] = BLACK;
board[36] = WHITE;
// Black always starts.
currentPlayer = BLACK;
}
/**
* Returns true if the game is finished (i.e., no more moves are possible.). The winner
* is the player with the highest score when the game is finished.
*
* @return true if the game is finished.
*/
public boolean isGameFinished() {
return gameFinished;
}
/**
* Returns true if the current player can place a stone at the specified position.
*
* @param position the position on the board (0-63).
* @return true if the position is a valid move.
*/
public boolean isValidMove(int position) {
return !gameFinished && isValidMove(position, getCurrentPlayer());
}
/**
* Returns the current value at a specific board position (between 0 and 63, inclusive). The value
* is either {@link #BLANK blank}, {@link #WHITE white}, or {@link #BLACK black}.
*
* @param position the board position.
* @return the value at the specified board position.
*/
public int getBoardValue(int position) {
if (position < 0 || position > 63) {
throw new IllegalArgumentException("Invalid position: " + position + ". Valid board " +
"positions are 0 through 63");
}
return board[position];
}
/**
* Returns the current player: {@link #WHITE white} or {@link #BLACK black}. Note that
* it's possible for a player to have several turns in a row, depending on whether
* there's a valid move available for the other player. The {@link #BLACK black} player
* always starts the game.
*
* @return the current player.
*/
public int getCurrentPlayer() {
return currentPlayer;
}
/**
* Returns the black player's current score.
*
* @return the black player's current score.
*/
public int getWhiteScore() {
int score = 0;
for (int aBoard : board) {
if (aBoard == WHITE) {
score++;
}
}
return score;
}
/**
* Returns the white player's current score.
*
* @return the white player's current score.
*/
public int getBlackScore() {
int score = 0;
for (int aBoard : board) {
if (aBoard == BLACK) {
score++;
}
}
return score;
}
/**
* Causes the {@link #getCurrentPlayer() current player} to execute a move by placing a stone at
* the specified position. Executing a move will flip one or more of the other player's stones.
*
* @param position the position to make the move at.
* @return true if the move was successful; false otherwise.
*/
public boolean makeMove(int position) {
if (!isValidMove(position)) {
return false;
}
board[position] = currentPlayer;
// Execute the move
getFlipCount(position, currentPlayer, true);
// See if other user has a valid move. If so, switch to other player.
int otherPlayer = currentPlayer==WHITE ? BLACK: WHITE;
boolean hasMove = false;
for (int i=0; i<board.length; i++) {
if (isValidMove(i, otherPlayer)) {
hasMove = true;
break;
}
}
// If the other player has at least one valid move, switch players.
if (hasMove) {
currentPlayer = otherPlayer;
}
// Otherwise, stick with the current player.
else {
// Make sure the current player has a valid move. Otherwise, the game must be over.
hasMove = false;
for (int i=0; i<board.length; i++) {
if (isValidMove(i, currentPlayer)) {
hasMove = true;
break;
}
}
if (!hasMove) {
gameFinished = true;
}
}
return true;
}
/**
* Returns a String representation of the current game board (ASCII art). A fixed
* width font is necessary to display the String properly.
*
* @return a String representation of the current game board.
*/
public String printBoard() {
StringBuffer buf = new StringBuffer();
buf.append("+----------------+\n");
for (int i=0; i<64; i++) {
if (i%8 == 0) {
buf.append("|");
}
if (board[i] == BLANK) {
buf.append(" ");
}
else if (board[i] == WHITE) {
buf.append(" w");
}
else {
buf.append(" b");
}
if (i%8 == 7) {
buf.append("|\n");
}
}
buf.append("+----------------+");
return buf.toString();
}
/**
* Returns true if the specified player has a valid move at the specified position (ignoring
* whose turn it is currently).
*
* @param position the position.
* @param player the player.
* @return return true if the player has a move at the position.
*/
private boolean isValidMove(int position, int player) {
if (position < 0 || position > 63) {
return false;
}
if (board[position] != BLANK) {
return false;
}
else {
return getFlipCount(position, player, false) > 0;
}
}
/**
* Computes the number of stones that would be flipped by executing a move at the specified position.
* If <tt>doFlip</tt> is true, the game board will be updated.
*
* @param position the position for the move.
* @param player the player.
* @param doFlips true if stones should be actually flipped; false if flips should only
* be calculated hypothetically.
* @return the number of stones that were or that would be flipped by the move.
*/
private synchronized int getFlipCount(int position, int player, boolean doFlips) {
int flipCount = 0;
// Traverse vertically, horizontally, and diagonally to flip pieces.
// Going left horizontally.
boolean edge;
if (position%8 > 1) {
edge = false;
for (int i=position-1; !edge; i--) {
if (i % 8 == 0) edge = true;
if (board[i] == BLANK) {
break;
}
else if (board[i] == player) {
for (int j=0; j<flipBufferSize; j++) {
flipCount++;
if (doFlips) {
board[flipBuffer[j]] = player;
}
}
break;
}
flipBuffer[flipBufferSize++] = i;
}
// Reset flip buffer.
flipBufferSize = 0;
}
// Going right horizontally.
if (position%8 < 6) {
edge = false;
for (int i=position+1; !edge; i++) {
if (i % 8 == 7) edge = true;
if (board[i] == BLANK) {
break;
}
else if (board[i] == player) {
for (int j=0; j<flipBufferSize; j++) {
flipCount++;
if (doFlips) {
board[flipBuffer[j]] = player;
}
}
break;
}
flipBuffer[flipBufferSize++] = i;
}
// Reset flip buffer.
flipBufferSize = 0;
}
// Going up vertically.
if (position > 15) {
for (int i=position-8; i >= 0; i-=8) {
if (board[i] == BLANK) {
break;
}
else if (board[i] == player) {
for (int j=0; j<flipBufferSize; j++) {
flipCount++;
if (doFlips) {
board[flipBuffer[j]] = player;
}
}
break;
}
flipBuffer[flipBufferSize++] = i;
}
// Reset flip buffer.
flipBufferSize = 0;
}
// Going down vertically.
if (position < 48) {
for (int i=position+8; i <= 65; i+=8) {
if (board[i] == BLANK) {
break;
}
else if (board[i] == player) {
for (int j=0; j<flipBufferSize; j++) {
flipCount++;
if (doFlips) {
board[flipBuffer[j]] = player;
}
}
break;
}
flipBuffer[flipBufferSize++] = i;
}
// Reset flip buffer.
flipBufferSize = 0;
}
// Diagonal up-left
if (position > 15 && position%8 > 1) {
edge = false;
for (int i=position-9; !edge; i-=9) {
if (i < 8 || i%8==0) edge = true;
if (board[i] == BLANK) {
break;
}
else if (board[i] == player) {
for (int j=0; j<flipBufferSize; j++) {
flipCount++;
if (doFlips) {
board[flipBuffer[j]] = player;
}
}
break;
}
flipBuffer[flipBufferSize++] = i;
}
// Reset flip buffer.
flipBufferSize = 0;
}
// Diagonal up-right
if (position > 15 && position%8 < 6) {
edge = false;
for (int i=position-7; !edge; i-=7) {
if (i < 8 || i%8==7) edge = true;
if (board[i] == BLANK) {
break;
}
else if (board[i] == player) {
for (int j=0; j<flipBufferSize; j++) {
flipCount++;
if (doFlips) {
board[flipBuffer[j]] = player;
}
}
break;
}
flipBuffer[flipBufferSize++] = i;
}
// Reset flip buffer.
flipBufferSize = 0;
}
// Diagonal down-left
if (position < 48 && position%8 > 1) {
edge = false;
for (int i=position+7; !edge; i+=7) {
if (i > 55 || i%8==0) edge = true;
if (board[i] == BLANK) {
break;
}
else if (board[i] == player) {
for (int j=0; j<flipBufferSize; j++) {
flipCount++;
if (doFlips) {
board[flipBuffer[j]] = player;
}
}
break;
}
flipBuffer[flipBufferSize++] = i;
}
// Reset flip buffer.
flipBufferSize = 0;
}
// Diagonal down-right
if (position < 48 && position%8 < 6) {
edge = false;
for (int i=position+9; !edge; i+=9) {
if (i > 55 || i%8==7) edge=true;
if (board[i] == BLANK) {
break;
}
else if (board[i] == player) {
for (int j=0; j<flipBufferSize; j++) {
flipCount++;
if (doFlips) {
board[flipBuffer[j]] = player;
}
}
break;
}
flipBuffer[flipBufferSize++] = i;
}
// Reset flip buffer.
flipBufferSize = 0;
}
return flipCount;
}
}