package edu.dartmouth.cs.kalah; /** * Represents the state of the Kalah game. It is the model in the * model-view-controller pattern. * @author Scot Drysdale, 8/2011 */ public class KalahGame { private int [] board; // Hold the game board private int playerToMoveNum; // 0 or 1 for first and second player private KalahPlayer [] players; // Array of the two players private KalahView view; // Holds the view, so can update display // as state changes (if desired). private int pitToHighlight; // Pit to highlight in the display. // NO_HIGHLIGHT value means no pit highlighted. public final static int NO_HIGHLIGHT = -1; // Indicates no pause needed public final static int HIGHLIGHT_PAUSE = 250; // Milliseconds to pause public final static int INITSTONES = 6; // Initial number of stones public final static int NUMPITS = 14; // Number of pits total public final static int SIDEPITS = 6; // Pits on one side of the board public final static int HALFPITS = 7; // Half of the total number of pits /** * Constructs a game in the initial state * @param playerNum the player whose move it is * @param thePlayers the player objects * @param aView the view in the model-view-controller model */ public KalahGame(int playerNum, KalahPlayer [] thePlayers, KalahView aView) { int [] initBoard = {0,6,6,6,6,6,6,0,6,6,6,6,6,6}; // Original pit contents initialize(playerNum, thePlayers, initBoard); view = aView; } /** * Constructs a game given a board * @param playerNum the player whose move it is * @param thePlayers the player objects * @param initBoard the board to copy into this state */ public KalahGame(int playerNum, KalahPlayer [] thePlayers, int [] initBoard) { initialize(playerNum, thePlayers, initBoard); } /** * Initialization to call from both constructors * @param playerNum the number of the player to move * @param thePlayers the array of player objects * @param initBoard the initial board to use */ private void initialize(int playerNum, KalahPlayer [] thePlayers, int [] initBoard) { board = new int[NUMPITS]; for (int i = 0; i < NUMPITS; i++) { board[i] = initBoard[i]; } playerToMoveNum = playerNum; players = thePlayers; pitToHighlight = -1; // Indicates no pit highlighted. } public int [] getBoard() { return board; } public KalahPlayer [] getPlayers() { return players; } public int getPlayerNum () { return playerToMoveNum; } public KalahPlayer getPlayerToMove() { return players[playerToMoveNum]; } /** * Make a move, removing stones from the given pit. * @param pit the pit to remove stones from */ public void makeMove(int pit) { int playerKalah, opponentKalah; // Hold the kalah numbers for the two players int stones; // Number of stones in pit // Decide which kalah belongs to the current player playerKalah = playerToMoveNum * HALFPITS; opponentKalah = HALFPITS - playerKalah; // Make the move stones = board[pit]; board[pit] = 0; while (stones > 0) { pit--; if (pit == -1) // Handle wrapping around pit = NUMPITS - 1; if (pit != opponentKalah) // Nothing dropped in opponent's kalah { board[pit]++; // Drop a stone in the pit stones--; } } // Handle move-again and captures if (pit != playerKalah) { // If end in own kalah, move again if ((board[pit] == 1) && (board[NUMPITS - pit] > 0) && ((playerToMoveNum == 0 && (pit < HALFPITS)) || (playerToMoveNum == 1 && (pit > HALFPITS)))) { // Capture occurred. (Messy test, wasn't it?) board[playerKalah] = board[playerKalah] + board[NUMPITS - pit] + 1; board[pit] = board[NUMPITS - pit] = 0; } playerToMoveNum = 1 - playerToMoveNum; // Switch player } } /** * Make a move, removing stones from the given pit. * This version calls display after each change in the board sequentially, * so that the display routines can get it if they want to. * It is separate from the other makeMove in order to speed up * game tree search. (No need to record moves when doing lookahead.) * * @param pit the pit to remove stones from */ public void makeMoveIncrementally(int pit) { int playerKalah, opponentKalah; // Hold the kalah numbers for the two players int stones; // Number of stones in pit // Decide which kalah belongs to the current player playerKalah = playerToMoveNum * HALFPITS; opponentKalah = HALFPITS - playerKalah; // Make the move stones = board[pit]; pitToHighlight = pit; view.display(this); CSJavaLib.pause(HIGHLIGHT_PAUSE); //Wait so user can see change pitToHighlight = NO_HIGHLIGHT; view.display(this); CSJavaLib.pause(HIGHLIGHT_PAUSE); //Wait so user can see change // Display the first change from this move highlightChange(pit, 0); while (stones > 0) { pit--; if (pit == -1) // Handle wrapping around pit = NUMPITS - 1; if (pit != opponentKalah) // Nothing dropped in opponent's kalah { stones--; highlightChange(pit, board[pit]+1); // Drop a stone in the pit } } // Handle move-again and captures if (pit != playerKalah) { // If end in own kalah, move again if ((board[pit] == 1) && (board[NUMPITS - pit] > 0) && ((playerToMoveNum == 0 && (pit < HALFPITS)) || (playerToMoveNum == 1 && (pit > HALFPITS)))) { // Capture occurred. (Messy test, wasn't it?) int stonesInKalah = board[playerKalah] + board[NUMPITS - pit] + 1; // Make the three changes that occurred highlightChange(pit, 0); highlightChange(NUMPITS - pit, 0); highlightChange(playerKalah, stonesInKalah); } playerToMoveNum = 1 - playerToMoveNum; // Switch player } } /** * Helper function to highlight pit, change pit, and unhighlight * pit with appropriate pausing * @param pit the pit to highlight * @param newStones the new number of stones in the pit */ public void highlightChange(int pit, int newStones) { pitToHighlight = pit; view.display(this); CSJavaLib.pause(HIGHLIGHT_PAUSE); //Wait so user can see change board[pit] = newStones; view.display(this); CSJavaLib.pause(HIGHLIGHT_PAUSE); //Wait so user can see change pitToHighlight = -1; view.display(this); CSJavaLib.pause(HIGHLIGHT_PAUSE); //Wait so user can see change } /** * Precondition: Game must be over. * @return the score from the point of view of the current player. */ public int score() { int total = 0; // Sums the score for (int i = 0; i <= SIDEPITS; i++) total += board[i] - board[i + HALFPITS]; if (playerToMoveNum == 0) return total; else return -total; } /** * Decides if the game is over * @return true iff the game is over */ public boolean gameIsOver() { int pit; // Loop counter int upper; // upper bound on loop pit = playerToMoveNum * HALFPITS; upper = pit + SIDEPITS; do // Look for a non-empty pit { pit++; if (board[pit] > 0) return false; // If non-empty pit then game not over } while (pit < upper); return true; // If all pits empty, then game finished } /** * Gets the pit to be highlighted by the display (if any) * @return number of pit to be highlighted, or NO_HIGHLIGHT if none */ public int getPitToHightlight() { return pitToHighlight; } }