/* * CSI 4106: Assignment 2 * * Name: Casey Li * Student Number: 2570042 * * Coures: CSI4106 * Prof: Stan Szpakowicz * * MINIMAX KONO GAME * * KonoGame Class: The actual running game * ComputerPlayer Class: The class used to have a computer play a game * Position Class: The representation of a Kono board, with a collection of pieces. * The board has a minimax value evaluated by getMinimaxValue * GamePiece Class: Used to hold the position/color of a game piece * KonoConstants Class: Contains constants used in the game * KeyboardReader Class: (Not coded by Casey Li). Obtained from * http://www4.comp.polyu.edu.hk/~csgngai/courses/COMP201B/asgns/asgn1.html * Used to get input from the user. * * For the MARKER: * * The computer works by: * ComputerPlayer.computeMove() * which calls Position.getMinimaxValue for each of its generated children * * Position.getMinimaxValue is the actual Minimax algorithm implemented WITH ALPHA-BETA * pruning. * * The Static Evaluation is based on 3 things: * - The number of pieces and their position * - Whether or not the piece can move * - Whether or not the piece can take an opponent's piece * * Please refer to Position.getMinimaxValue for details. * * For full instructions on playing the game, run the game, and type "Y" when asked * "Do you want to see the instructions?[Y/n]" * * Generally, you input 4 numbers separated by spaces: * (Initial Row) (Initial Col) (Final Row) (Final Col) * * where the grid is as follows: * * (0,0)--(0,1)--(0,2)--(0,3) * | | | | * | | | | * (1,0)--(1,1)--(1,2)--(1,3) * | | | | * | | | | * (2,0)--(2,1)--(2,2)--(2,3) * | | | | * | | | | * (3,0)--(3,1)--(3,2)--(3,3) * * For more information, contact Casey Li * * cli047@uottawa.ca * */ //Imports import java.util.*; import java.io.*; public class KONO { /*************************************************************************************/ /* */ /* Main Method */ /* */ /*************************************************************************************/ public static void main(String[] args) { KONO x = new KONO(); x.runKonoGame(args); } public void runKonoGame(String[] args) { KonoGame game = new KonoGame(); game.runProg(args); } /////////////////////////////////////////////////////////////////////////////////////// /*************************************************************************************/ /* */ /* KonoGame class */ /* */ /*************************************************************************************/ class KonoGame { public void runProg(String[] args) { System.out.println("*************************"); System.out.println("* *"); System.out.println("* K O N O *"); System.out.println("* *"); System.out.println("*************************"); KeyboardReader userInput = new KeyboardReader(System.in); System.out.println("\nBienvenue à KONO."); while(true) { System.out.println("Voulez-vous voir les instructions [Y / N]"); String instruct = userInput.readLine(); if(instruct.equalsIgnoreCase("Y")) { System.out.println("Le conseil est organisé dans le mode suivant:"); System.out.println("\n(0,0)--(0,1)--(0,2)--(0,3)"); System.out.println(" | | | |"); System.out.println(" | | | |"); System.out.println("(1,0)--(1,1)--(1,2)--(1,3)"); System.out.println(" | | | |"); System.out.println(" | | | |"); System.out.println("(2,0)--(2,1)--(2,2)--(2,3)"); System.out.println(" | | | |"); System.out.println(" | | | |"); System.out.println("(3,0)--(3,1)--(3,2)--(3,3)"); System.out.println("\nUn point sur la carte est indiquée par (Ligne, Colonne) " + "\noù le coin supérieur gauche est (0,0)"+ "\net en bas à droite est (3,3)"); System.out.println("Pour entrer un déménagement, vous inscrire quatre entier valeurs séparées par des espaces:"); System.out.println(" "); System.out.println("ligne et la colonne initiale préciser votre pièce en mouvement" + "\n la dernière ligne et colonne spécifiées où vous vous déplacez"); break; } if(instruct.equalsIgnoreCase("N")) break; System.out.println("Désolé, votre choix d'entrée n'a pas été reconnu."); } //Select Number of PLayers int numOfPlayers = 0; while(true) { System.out.println("\nEntre le nombre de joueurs humains"); String SnumOfPlayers = userInput.readLine(); try { numOfPlayers = Integer.parseInt(SnumOfPlayers); if((numOfPlayers >= 0) && (numOfPlayers <= 2)) break; else System.out.println("Désolé, votre choix n'était pas dans la plage donnée."); } catch(NumberFormatException ex) { System.out.println("Désolé, votre choix d'entrée n'a pas été reconnu."); } } //Potential Computer Players ComputerPlayer CPU1=null; ComputerPlayer CPU2=null; int CPUdifficulty=1; if(numOfPlayers < 2) { //Allow user to select difficulty while(true) { System.out.println("\nS’il vous plaît choisir ordinateur difficulté / minimax profondeur de coupure [1-10]:"); System.out.println("Note: 6 ci-dessus exige de grandes quantités de mémoire"); String Sdifficulty = userInput.readLine(); try { CPUdifficulty = Integer.parseInt(Sdifficulty); break; } catch(NumberFormatException ex) { System.out.println("Désolé, votre mise en difficulté n'a pas été reconnu."); } } CPU1 = new ComputerPlayer("Noire", CPUdifficulty); if(numOfPlayers == 0) CPU2 = new ComputerPlayer("Blanc", CPUdifficulty); } String userColor=""; if(numOfPlayers == 1) { //Allow user to select color while(true) { System.out.println("\nS’il vous plaît choisir votre couleur [N / B]:"); userColor = userInput.readLine(); if(userColor.equalsIgnoreCase("B")) { userColor = "Blanc"; break; } if(userColor.equalsIgnoreCase("N")) { userColor = "Noire"; CPU1 = new ComputerPlayer("Blanc", CPUdifficulty); break; } System.out.println("Désolé, votre couleur de sélection n'était pas valable"); } } //Start Board Position CurrentBoard = new Position(true); //Start Turn boolean gameOver = false; String turn = "Blanc"; String winner = ""; Random generator = new Random(); //Start Game while(!gameOver) { System.out.println("\n"+CurrentBoard); CurrentBoard.getChildrenPositions().clear(); if((CurrentBoard.numberOfPieces("Blanc") == 1) && (CurrentBoard.numberOfPieces("Noire") == 1)) { System.out.println("\nC'est un tirage au sort!"); winner = "aucun"; break; } System.out.println("\nC'est " + turn + "tour"); //Computer Game if(numOfPlayers == 0) { if(turn.equals(CPU1.getColor())) { CurrentBoard = CPU1.computeMove(CurrentBoard, generator.nextInt()); if(CurrentBoard == null) { System.out.println("...CPU1 se déplace pas."); gameOver = true; winner = KonoConstants.oppColor(CPU1.getColor()); } } else { CurrentBoard = CPU2.computeMove(CurrentBoard, generator.nextInt()); if(CurrentBoard == null) { System.out.println("...CPU2 se déplace pas."); gameOver = true; winner = KonoConstants.oppColor(CPU2.getColor()); } } } //1 Player Game if(numOfPlayers > 0) { boolean humanPlayer = false; if(numOfPlayers == 2) { humanPlayer = true; userColor = turn; } if((numOfPlayers == 1) && turn.equals(userColor)) humanPlayer = true; if(humanPlayer) { boolean validMove = false; //Move array is a special data structure //specifying a move: // moveArray[0] = Initial Horizontal Position // moveArray[1] = Initial Vertical Position // moveArray[2] = Final Horizontal Position // moveArray[3] = Final Vertical Position int[] moveArray = new int[4]; //Validate Move while(!validMove) { CurrentBoard.generateChildrenPositions(userColor); //User can't move, game is over. if(CurrentBoard.getChildrenPositions().size() == 0) { gameOver = true; winner = KonoConstants.oppColor(userColor); System.out.println("...vous n'avez pas de tours"); break; } System.out.println("S'il vous plaît, entrez votre déplacement:"); System.out.println(" "); String userMove = userInput.readLine(); StringTokenizer userMoveTok = new StringTokenizer(userMove, " "); //Check if a valid move has been made if(userMoveTok.countTokens() != 4) { System.out.println("\nDésolé, vous n'avez pas donne 4 numéros valables"); } else { //Parse Move String SinitHoriPos = userMoveTok.nextToken(); String SinitVertPos = userMoveTok.nextToken(); String SfinalHoriPos = userMoveTok.nextToken(); String SfinalVertPos = userMoveTok.nextToken(); try { moveArray[0] = Integer.parseInt(SinitHoriPos); moveArray[1] = Integer.parseInt(SinitVertPos); moveArray[2] = Integer.parseInt(SfinalHoriPos); moveArray[3] = Integer.parseInt(SfinalVertPos); int moveType = CurrentBoard.validMove(moveArray[0], moveArray[1], moveArray[2], moveArray[3], userColor); if(moveType == 1) { validMove = true; //Once move is validated, move there CurrentBoard.moveGamePiece(moveArray[0], moveArray[1], moveArray[2], moveArray[3], userColor, false); } else if(moveType == 2) { validMove = true; //Once move is validated, move there CurrentBoard.moveGamePiece(moveArray[0], moveArray[1], moveArray[2], moveArray[3], userColor, true); } else System.out.println("\nDéplacement non valide"); } catch(NumberFormatException ex) { System.out.println("\nDésolé, vous n'avez pas donne 4 numéros"); } } } } //Computer's Turn else { System.out.println("\nL'ordinateur est pensé ..."); CurrentBoard = CPU1.computeMove(CurrentBoard, generator.nextInt()); if(CurrentBoard == null) { System.out.println("...l'ordinateur n'a pas de mouvements."); gameOver = true; winner = "Blanc"; } } } turn = KonoConstants.oppColor(turn); } System.out.println("\nLe gagnant est " + winner + "!\n"); } } /*************************************************************************************/ /* */ /* ComputerPlayer class */ /* */ /*************************************************************************************/ class ComputerPlayer { private String color; private int difficultySetting; //Accessors======================================================================= public String getColor() { return this.color; } public int getDifficultySetting() { return this.difficultySetting; } //Constructor======================================================================= public ComputerPlayer(String color, int difficultySetting) { this.color = color; this.difficultySetting = difficultySetting; } //Compute Move=================================================================== //Calls MINIMAX public Position computeMove(Position p, int randomNumber) { Vector randomSelectionOfEqualMax = new Vector(); int[] moveArray = new int[4]; p.generateChildrenPositions(color); int maxValue = -999999; Position bestMove = null; Vector visitedPositions = new Vector(); //Find best move for(int i=0; i 3)) return 0; //Initial coordinates match an actual game piece if(this.findGamePiece(currentHoriPos, currentVertPos, color) == null) return 0; //Single move int horiMovement = Math.abs(finalHoriPos - currentHoriPos); int vertMovement = Math.abs(finalVertPos - currentVertPos); int totalMovement = horiMovement + vertMovement; //If it is a single movement, and that space is not occupied if(totalMovement == 1) { if(!this.spaceOccupied[finalHoriPos][finalVertPos]) return 1; return 0; } //If it is a take move else if(totalMovement == 3) { //Left or right take if(vertMovement == 3) { if(this.canTakeLeft(currentHoriPos, currentVertPos, color)) return 2; if(this.canTakeRight(currentHoriPos, currentVertPos, color)) return 2; return 0; } //Up or down take else { if(this.canTakeUp(currentHoriPos, currentVertPos, color)) return 2; if(this.canTakeDown(currentHoriPos, currentVertPos, color)) return 2; return 0; } } else return 0; } //If a piece can move left public boolean canMoveLeft(int currentHoriPos, int currentVertPos) { if((currentVertPos>0) && (!this.spaceOccupied[currentHoriPos][currentVertPos-1])) return true; return false; } //If a piece can move right public boolean canMoveRight(int currentHoriPos, int currentVertPos) { if((currentVertPos<3) && (!this.spaceOccupied[currentHoriPos][currentVertPos+1])) return true; return false; } //If a piece can move up public boolean canMoveUp(int currentHoriPos, int currentVertPos) { if((currentHoriPos>0) && (!this.spaceOccupied[currentHoriPos-1][currentVertPos])) return true; return false; } //If a piece can move down public boolean canMoveDown(int currentHoriPos, int currentVertPos) { if((currentHoriPos<3) && (!this.spaceOccupied[currentHoriPos+1][currentVertPos])) return true; return false; } //If a piece can take a piece to the left public boolean canTakeLeft(int currentHoriPos, int currentVertPos, String color) { if( (currentVertPos == 3) && //Piece beside if same color (this.findGamePiece(currentHoriPos, 2, color) != null) && //Two spaces over is empty (!this.spaceOccupied[currentHoriPos][1]) && //Three spaces over is opposite color (this.findGamePiece(currentHoriPos, 0, KonoConstants.oppColor(color)) != null)) return true; return false; } //If a piece can take a piece to the right public boolean canTakeRight(int currentHoriPos, int currentVertPos, String color) { if( (currentVertPos == 0) && //Piece beside if same color (this.findGamePiece(currentHoriPos, 1, color) != null) && //Two spaces over is empty (!this.spaceOccupied[currentHoriPos][2]) && //Three spaces over is opposite color (this.findGamePiece(currentHoriPos, 3, KonoConstants.oppColor(color)) != null)) return true; return false; } //If a piece can take a piece above public boolean canTakeUp(int currentHoriPos, int currentVertPos, String color) { if( (currentHoriPos == 3) && //Piece above if same color (this.findGamePiece(2, currentVertPos, color) != null) && //Two spaces above is empty (!this.spaceOccupied[1][currentVertPos]) && //Three spaces above is opposite color (this.findGamePiece(0, currentVertPos, KonoConstants.oppColor(color)) != null)) return true; return false; } //If a piece can take a piece below public boolean canTakeDown(int currentHoriPos, int currentVertPos, String color) { if( (currentHoriPos == 0) && //Piece below if same color (this.findGamePiece(1, currentVertPos, color) != null) && //Two spaces below is empty (!this.spaceOccupied[2][currentVertPos]) && //Three spaces below is opposite color (this.findGamePiece(3, currentVertPos, KonoConstants.oppColor(color)) != null)) return true; return false; } //If a piece can move at all public boolean canMove(int currentHoriPos, int currentVertPos, String color) { return ( canMoveSingle(currentHoriPos, currentVertPos) || canTake(currentHoriPos, currentVertPos, color)); } //If a piece can move a single space public boolean canMoveSingle(int currentHoriPos, int currentVertPos) { return ( canMoveLeft(currentHoriPos, currentVertPos) || canMoveRight(currentHoriPos, currentVertPos) || canMoveUp(currentHoriPos, currentVertPos) || canMoveDown(currentHoriPos, currentVertPos)); } //If a piece can take another piece public boolean canTake(int currentHoriPos, int currentVertPos, String color) { return ( canTakeLeft(currentHoriPos, currentVertPos, color) || canTakeRight(currentHoriPos, currentVertPos, color) || canTakeUp(currentHoriPos, currentVertPos, color) || canTakeDown(currentHoriPos, currentVertPos, color)); } //Other Operators============================================================== /* * MINIMAX METHOD * This recursive method implements the minimax algorithm * to a depth cut-off. * It also uses Alpha-Beta pruning. */ public int getMinimaxValue(String color, int currentDepth, int cutOffDepth, String minmax, int alpha, int beta) { int ownColorScore=0; int oppositeColorScore=0; GamePiece tempGP; //Base Case // // STATIC EVALUATION Based on // -Number of Pieces // -Number of Pieces that can move // -Number of Pieces that can take other pieces // // It calculates these factors for your own color, and then // for your opponents color, and takes the difference. // //For safety reasons, add ">" condition if(currentDepth >= cutOffDepth) { for(int i=0; i maxValue) /original without Alpha-beta // maxValue = cpValue; /original without Alpha-beta if(cpValue > alpha) alpha = cpValue; if(alpha > beta) break; } //return maxValue; /original without Alpha-beta return alpha; } //GENERATE OPPONENTS (OPP COLOR) MOVES AND FIND THE SMALLEST MINMAX VALUE //Min Turn if(minmax.equals("Min")) { //Generate children of Minimax Tree this.generateChildrenPositions(KonoConstants.oppColor(color)); //Min can't move if(this.getChildrenPositions().size() == 0) return 8888; //Evaluate Children Positions //int minValue = 8888; /original without Alpha-beta for(int i=0; i beta) break; } //return minValue; /original without Alpha-beta return beta; } return 0; } } /* * This method check to see if 2 positions are equal * based on the Game pieces. */ public boolean equals(Position p) { //Different number of game pieces if(this.getGamePieces().size() != p.getGamePieces().size()) return false; for(int i=0; i