/** * PlayGame prompts the user for input and interacts to play the game. * * @author Dale Reed * @version October 24, 2008 */ import java.util.Scanner; public class PlayGame { /** keyboard is used to handle user input */ private Scanner keyboard = new Scanner( System.in); /** theBoard is used to create a playing board, which in turn keeps track of the pieces */ private Board theBoard; // the playing board /** moveNumber keeps track of whose move it is, based on odd for "black" and even for "white" */ private int moveNumber = 1; // odd moves mean "black", evens mean "white" /** index of where we are moving from */ private int sourcePosition = -1; /** index of where we are moving to */ private int destinationPosition = -1; /** constant definition of number of columns, used in validating moves */ static final int NUMBER_OF_COLUMNS = 5; /** * main method chains off to method doIt() to avoid static errors. * * @param args command line arguments, which we're not using */ public static void main(String[] args) { PlayGame myInstance = new PlayGame(); myInstance.doIt(); // chain off to begin playing } /** * The "guts" of the program, calling the Board constructor and the loop handling user input. * */ void doIt() { // Display identifying information System.out.println( "Author: Dale Reed \n" + "Program: #3, Yote\n" + "TA: Englebert Humberdink, T 4-5 \n"+ "Oct 24, 2008"); System.out.println(); // Display game instructions. Modified from instructions taken from // http://homepages.di.fc.ul.pt/~jpn/gv/yote.htm System.out.println( "Welcome to the ancient African game of Yote.\n" + "\n" + "On each turn, each player may either deploy a stone from their reserve \n" + "to any empty space on the board, or move a stone already on the board. Moves must \n" + "be made vertically or horizontally, but not diagonally. Stones move either by \n" + "sliding to an adjacent empty cell or by jumping over and capturing an adjacent \n" + "opponent's stone, landing in an empty square immediately beyond. When a Stone \n" + "captures another stone, the player may then select any other opponent stone on \n" + "the board (but not in their opponent's reserve) and capture it as well. \n" + "\n" + "The player who captures all of her opponent's stones wins. If either player is unable \n" + "to move, then the game ends and the player with the most stones on the board wins. If \n" + "both players are reduced to three or less stones, then the game is a draw. \n" + "\n" + "Possible inputs for each move: \n" + " x to exit program. A message tells who has the most pieces. \n" + " n1 to place a piece at postion n1, where n1 is a number 1..20 \n" + " n1 n2 to move a piece from board position n1 to n2 \n" + "Black goes first. Let's begin...\n" + " \n"); theBoard = new Board(); // allocate memory and call the Board constructor String playerToMove; // will be "black" or "white", depending on the moveNumber String opponent; // will be opposite of playerToMove // main loop while( gameIsNotDone() ) { // see whose move it is, based on the moveNumber if( moveNumber%2 == 1) { // odd numbered move, so it is the turn of "black" playerToMove = "black"; opponent = "white"; } else { // even numbered move playerToMove = "white"; opponent = "black"; } // prompt for and get user input System.out.print(moveNumber + ". Enter move for " + playerToMove + ": "); String userInput = keyboard.nextLine(); // convert to lower case, in case it is alphabetic userInput = userInput.toLowerCase(); // see if 'x' to exit was entered if( userInput.charAt(0)=='x') { // Handle exiting the program and displaying who won seeWhoWon(); break; // break out of main playing loop } // If we got here, input wasn't an 'x', so assume it is one number or two numbers. // Input of one number is used to place a new piece on the board. Input of two // numbers is used to move an existing piece on the board. // Invalid input returns false. sourcePosition = -1; // will only be reset if we are making a move, // as opposed to placing a new piece on the board destinationPosition = -1; // validate input and set fields sourcePosition and destinationPosition. if( ! parsedInputIsValid( userInput, playerToMove)) { // input had some errors, which should have been already displayed continue; // go back up to try again for a valid move } // We now have validated input to be a valid move, and have stored the // sourcePosition (if moving an existing piece) and destinationPosition. // Handle 3 conditions: // 1. Placing a new piece on the board // 2. Moving an existing piece on the board to an adjacent open square // 3. Moving an existing piece to jump another piece on the board // Handle condition: 1. Placing a new piece on the board if( sourcePosition == -1 ) { // sourcePosition was never reset, since we are placing a new piece on the board only // Verify that there are still pieces to be placed if( ! theBoard.placePieceSuccessfully( playerToMove, destinationPosition)) { // there were no more pieces to be placed, so retry move continue; // go back up to retry } } else { // get the move direction and the piece to be moved. char moveDirection = getMoveDirection( sourcePosition, destinationPosition); Piece thePiece = theBoard.getPieceAt( sourcePosition); // Handle condition: 2. Moving an existing piece on the board to an adjacent open square if( areAdjacent( sourcePosition, destinationPosition)) { thePiece.moveTheCircle( moveDirection); } else { // Must be a jump move, since sourcePosition isn't -1 and pieces are not adjacent. // Handle condition: 3. Moving an existing piece to jump another piece on the board // First move the jumping piece, then remove the jumped piece, then prompt for // and remove an extra piece thePiece.moveTheCircle( moveDirection, 2); // move 2 squares in the indicated direction // Now remove the jumped piece int jumpedPiecePosition = findPositionOfPieceBeingJumped(sourcePosition, destinationPosition); System.out.println(" removed " + opponent + " piece from position " + jumpedPiecePosition); theBoard.removePieceAt( jumpedPiecePosition); // Now prompt for and remove an extra piece int indexOfPieceToRemove = -1; Piece thePieceToRemove = null; // loop to validate selection of piece to remove do { System.out.print(" Which other "+opponent+" piece do you want removed? "); String nextInput = keyboard.nextLine(); indexOfPieceToRemove = Integer.parseInt( nextInput); // Get that piece, so we can verify its color thePieceToRemove = theBoard.getPieceAt( indexOfPieceToRemove); // must check in two parts below, otherwise you get an error if trying to // get a piece from an empty position on the board if( (thePieceToRemove != null) && thePieceToRemove.getColor().equals( playerToMove) ) { System.out.println("*** You have to choose a "+opponent+" piece."); } else { // piece existed and was the opponent's color break; // break out of loop } } while( true); // Now actually remove this piece theBoard.removePieceAt( indexOfPieceToRemove); } }//end else // update the move moveNumber++; }//while (bothPlayersHaveMoreThan3Pieces... // main loop System.out.println("Thanks for playing. Exiting program... "); }//end method doIt() /** * See if game is done. * Game continues if both players have pieces, and at least one player has * more than 3 pieces. * If a player has 0 pieces, the other player wins. If both players have 3 or less * pieces, then the game is a draw and no one wins. * * @return true if both players still have > 3 pieces, false otherwise */ private boolean gameIsNotDone() { // verify piece counts int totalWhitePieces = theBoard.numberOfPiecesFor("white") + theBoard.piecesRemainingToPlayFor("white"); int totalBlackPieces = theBoard.numberOfPiecesFor("black") + theBoard.piecesRemainingToPlayFor("black"); if( ((totalWhitePieces <= 3) && (totalBlackPieces <= 3)) || (totalWhitePieces == 0) || (totalBlackPieces == 0) ) { // someone won, or it is a draw seeWhoWon(); return false; } else { return true; } } /** * Figure out who won, displaying the appropriate message, and exit the program */ private void seeWhoWon() { System.out.println(""); int numberOfWhitePieces = theBoard.numberOfPiecesFor("white"); int numberOfBlackPieces = theBoard.numberOfPiecesFor("black"); // first see if there is a tie, or a draw (both have 3 or less pieces) // If neither of these two cases is true, see which player won if( (numberOfWhitePieces <= 3) && (numberOfBlackPieces <= 3)) { System.out.println("Game is a draw, since both have 3 or less pieces."); } else if( numberOfWhitePieces == numberOfBlackPieces) { System.out.println("Game is a tie."); } else if( numberOfWhitePieces > numberOfBlackPieces) { // white won System.out.println("White has won with " + (numberOfWhitePieces - numberOfBlackPieces) + " more pieces!"); } else if( numberOfBlackPieces > numberOfWhitePieces) { // black won System.out.println("Black has won with " + (numberOfBlackPieces - numberOfWhitePieces) + " more pieces!"); } else { // should never get here, since all cases *should* be taken care of above. System.out.println("*** Error in seeing who won"); } } /** * Parse the user input into 1 or 2 numbers, depending on the input, and validate it. * Input of one number is used to place a new piece on the board. * Input of two numbers is used to move an existing piece on the board. * Find the index of the middle space character, if there is one, to tell the difference * between input with one number, and input with two numbers. Extract these numbers, * If there are two numbers, extract them and store in class fields sourcePosition * and destinationPosition * * @param userInput the user input, which could have one or two numbers in it * @param playerToMove the player to move, which will be "black" or "white" */ private boolean parsedInputIsValid( String userInput, String playerToMove) { // find the space, if there is one, in the user input int indexOfSpace = userInput.indexOf(' '); // stores -1 if ' ' not found if( indexOfSpace < 0) { // there should only be one number on the input line, so parse it to an integer destinationPosition = Integer.parseInt( userInput); } else { // Since there is a space in the input, there should be more than one number. // This means we are moving a piece, either to an adjacent position or to make a jump. // Break up input string into two parts String inputFirstPart = userInput.substring(0,indexOfSpace); String inputSecondPart = userInput.substring(indexOfSpace, userInput.length()); // Trim off any excess leading or trailing space that might confuse the conversion to int inputFirstPart = inputFirstPart.trim(); inputSecondPart = inputSecondPart.trim(); // Now parse the two numbers from the two parts sourcePosition = Integer.parseInt( inputFirstPart); destinationPosition = Integer.parseInt( inputSecondPart); // ensure sourcePosition is the same color as the playerToMove if( ! theBoard.colorOfPieceAt( sourcePosition).equals( playerToMove) ) { System.out.println("*** "+playerToMove+" you can only move your own piece. Please retry..."); return false; // go back to prompt for another move } // Only need to check the piece being jumped if we are making a jump, as opposed to // simply moving a piece. Simple non-jump moves implies pieces are adjacent. if( ! areAdjacent( sourcePosition, destinationPosition)) { // Ensure the piece being jumped is the opposite color. // First find the position of the piece being jumped based on the move direction. int pieceBeingJumped = findPositionOfPieceBeingJumped(sourcePosition, destinationPosition); if( pieceBeingJumped == -1) { // invalid jump move, return to retry user input return false; } // Find the opposite color String oppositeColor = "white"; if( playerToMove.equals("white") ) { oppositeColor = "black"; } // Now ensure piece being jumped is the opposite color as the playerToMove if( ! theBoard.colorOfPieceAt( pieceBeingJumped).equals( oppositeColor) ) { System.out.println("*** "+playerToMove+" you can only jump an opponent. Please retry..."); return false; // go back to prompt for another move } }//end if( ! areAdjacent(.. }//end else // The code below is executed for all 3 conditions: 1. placing a new piece, 2. moving an // existing piece to an adjacent square, and 3. jumping a piece. // See if destination square is on the board if( ! indexIsOnBoard( destinationPosition)) { System.out.println("*** Sorry, you can't move off the board. Please retry..."); return false; // go back to prompt for another move } // Also see if destination square is already occupied, which would be indicated by a color // which is not "" if( ! theBoard.colorOfPieceAt( destinationPosition).equals("") ) { System.out.println("*** Sorry, square "+destinationPosition+" is already occupied. Please retry..."); return false; // go back to prompt for another move } // passed all the input validity tests return true; }//end parseAndValidateUserInput(... /** * See whether or not the source and destination are adjacent, to differentiate between a jump * and a normal move. * * @param source index of where we are moving from * @param destination index of where we are moving to * @return whether or not the pieces are adjacent */ private boolean areAdjacent( int source, int destination) { // find the move direction, which is 'u' for up, 'd' for down, 'l' for left, 'r' for right // or ' ' for an invalid move switch( getMoveDirection( source, destination)) { case 'u': if( (source - NUMBER_OF_COLUMNS) == destination) return true; break; case 'd': if( (source + NUMBER_OF_COLUMNS) == destination) return true; break; case 'l': if( (source - 1) == destination) return true; break; case 'r': if( (source + 1) == destination) return true; break; case ' ': return false; // go back to prompt for another move default: System.out.println("*** Error in return value from getJumpMoveDirection(). Exiting..."); System.exit( -1); } // default, which should never occur. return false; } /** * Find the position of the piece being jumped, or return -1 if invalid. * * @param sourcePosition position we are moving from on a jump * @param destinationPosition position we are moving to on a jump * @return position of piece being jumped */ private int findPositionOfPieceBeingJumped( int sourcePosition, int destinationPosition) { int pieceBeingJumped = -1; // get the character indicating the move direction, or ' ' if it is an invalid move. // Also validate the move, ensuring a jump doesn't wrap around a boundary. switch( getMoveDirection( sourcePosition, destinationPosition)) { case 'u': pieceBeingJumped = sourcePosition - 5; break; case 'd': pieceBeingJumped = sourcePosition + 5; break; case 'l': pieceBeingJumped = sourcePosition - 1; break; case 'r': pieceBeingJumped = sourcePosition + 1; break; case ' ': System.out.println("*** Sorry, you are attempting an invalid jump. Please retry..."); break; default: System.out.println("*** Error in return value from getJumpMoveDirection(). Exiting..."); System.exit( -1); } // return the piece being jumped return pieceBeingJumped; } /** * Get the character representing the jump move direction: 'u' for up, 'd' for down, * 'l' for left, 'r' for right, or ' ' for an invalid move. * Note that two cases are possible: we could either be jumping, or we could be * moving to an adjacent square. */ private char getMoveDirection( int source, int destination) { // 4 cases, for each of the four directions. In each case also ensure that user is not // attempting an invalid move that would "wrap around" an edge. // For all 4 cases, ensure the destination is on the board. // Since we are using the values as displayed to the user on the screen, we // start numbering at 1 and not 0 if( ! indexIsOnBoard( destination)) { System.out.println("*** Sorry, you can't move off the board. Please retry..."); return ' '; // signifies invalid move } // check for moving or jumping RIGHT. // We also must verify pieces are in the same row and on the board. if( ( ((source + 1) == destination) || // moving adjacent ((source + 2) == destination) ) && // jumping piecesInSameRow( source, destination) ) { return 'r'; } // check for moving or jumping LEFT. // We also must verify pieces are in the same row and on the board. if( ( ((destination + 1) == source) || // moving adjacent ((destination + 2) == source) ) && // juming piecesInSameRow( source, destination) ) { return 'l'; } // check for moving or jumping DOWN. // We must also verify pieces are in the same column and on the board. if( ( ((source + NUMBER_OF_COLUMNS) == destination) || // moving adjacent ((source + (NUMBER_OF_COLUMNS*2)) == destination) ) && // jumping piecesInSameColumn( source, destination) ) { return 'd'; } // check for moving or jumping up. // We must also verify pieces are in the same column and on the board. if( ( ((source - NUMBER_OF_COLUMNS) == destination) || // moving adjacent ((source - (NUMBER_OF_COLUMNS*2)) == destination) ) && // jumping piecesInSameColumn( source, destination) ) { return 'u'; } // None of the above matched, so the default assumes an invalid move return value return ' '; // invalid move }//end getMoveDirection(.. /** * verify that two pieces are in the same row, which is useful for validating moves. */ private boolean piecesInSameRow( int piece1, int piece2) { // The formula (piece-1) / 5, for instance, gives the row 0..3 (if there are 4 rows) if( (piece1-1)/NUMBER_OF_COLUMNS == (piece2-1)/NUMBER_OF_COLUMNS) { return true; } else { return false; } } /** * verify that two pieces are in the same row, which is useful for validating jumping moves. */ private boolean piecesInSameColumn( int piece1, int piece2) { // The formula (piece-1) % 5, for instance, gives the column 0..4 (if there are 5 columns) if( (piece1-1)%NUMBER_OF_COLUMNS == (piece2-1)%NUMBER_OF_COLUMNS) { return true; } else { return false; } } /** * Verify that an index value is within the range of board values (1..20) * * @param theIndex the board index * @return true if theIndex is within range, false otherwise */ private boolean indexIsOnBoard( int theIndex) { // Since we are using the values as displayed to the user on the screen, we // start numbering at 1 and not 0 if( (theIndex <= 0) || (theIndex > 20)) { // out of range return false; // signifies an invalid move } // default return true; } }// end class TestDrawing()