├── .idea ├── copyright │ └── profiles_settings.xml ├── modules.xml └── compiler.xml ├── src └── com │ └── company │ ├── AI.java │ ├── RandomAI.java │ ├── Move.java │ ├── Player.java │ ├── Main.java │ ├── MinimaxAI.java │ └── Board.java ├── minimax-checkers.iml ├── README.md └── .gitignore /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/com/company/AI.java: -------------------------------------------------------------------------------- 1 | package com.company; 2 | 3 | public interface AI{ 4 | public Board.Decision makeMove(Board b); 5 | } 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /minimax-checkers.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/com/company/RandomAI.java: -------------------------------------------------------------------------------- 1 | package com.company; 2 | 3 | import java.util.List; 4 | import java.util.Random; 5 | 6 | public class RandomAI extends Player implements AI { 7 | public RandomAI(String name, Side s) 8 | { 9 | super(name, s); 10 | } 11 | public RandomAI(Side s) 12 | { 13 | super("RandomAI", s); 14 | } 15 | public Board.Decision makeMove(Board board) 16 | { 17 | Random rand = new Random(); 18 | List moves = board.getAllValidMoves(getSide()); 19 | if(moves.size() == 0) 20 | return Board.Decision.GAME_ENDED; 21 | Move m = moves.get(rand.nextInt(moves.size())); 22 | 23 | return board.makeMove(m, getSide()); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/com/company/Move.java: -------------------------------------------------------------------------------- 1 | package com.company; 2 | 3 | import java.awt.*; 4 | 5 | public class Move { 6 | 7 | private Point start; 8 | private Point end; 9 | 10 | public Move(int startRow, int startCol, int endRow, int endCol) 11 | { 12 | start = new Point(startRow, startCol); 13 | end = new Point(endRow, endCol); 14 | } 15 | public Move(Point start, Point end) 16 | { 17 | this.start = start; 18 | this.end = end; 19 | } 20 | 21 | public Point getStart() 22 | { 23 | return start; 24 | } 25 | 26 | public Point getEnd() 27 | { 28 | return end; 29 | } 30 | 31 | public String toString() 32 | { 33 | return "Start: " + start.x + ", " + start.y + " End: " + end.x + ", " + end.y; 34 | } 35 | 36 | public boolean equals(Object m) 37 | { 38 | if(!(m instanceof Move)) 39 | return false; 40 | Move x = (Move) m; 41 | if(this.getStart().equals(x.getStart()) && this.getEnd().equals(x.getEnd())) 42 | return true; 43 | 44 | return false; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/com/company/Player.java: -------------------------------------------------------------------------------- 1 | package com.company; 2 | 3 | import java.util.List; 4 | import java.util.Random; 5 | 6 | public class Player { 7 | private Side side; 8 | private String name; 9 | 10 | public Player() 11 | {} 12 | 13 | public enum Side 14 | { 15 | BLACK, WHITE 16 | } 17 | public Player(String name, Side side) 18 | { 19 | this.name = name; 20 | this.side = side; 21 | } 22 | public Player(Side side) 23 | { 24 | this.name = side.toString(); 25 | this.side = side; 26 | } 27 | 28 | public Side getSide() 29 | { 30 | return side; 31 | } 32 | 33 | public Board.Decision makeMove(Move m, Board b) 34 | { 35 | return b.makeMove(m, side); 36 | } 37 | 38 | public Board.Decision makeRandomMove(Board b) 39 | { 40 | List moves = b.getAllValidMoves(side); 41 | Random rand = new Random(); 42 | return b.makeMove(moves.get(rand.nextInt(moves.size())), side); 43 | } 44 | public String toString() 45 | { 46 | return name + "/" + side; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # minimax-checkers 2 | 3 | This project includes the implementation of a standard 12 vs 12 checkers AI in Java that uses the minimax algorithm along with fail-soft alpha-beta pruning to increase tree traversal speed. 4 | 5 | ### Requirements 6 | In order to run the project, IntelliJ must be installed. 7 | 8 | ### Installation 9 | * To open and run the project, either clone or download into a folder of your choice. 10 | * If prompted, set the Java SDK path 11 | * Click on `Edit Run Configurations`, then set the main class to be `Main.java`. 12 | * Set the module output path as `Inherit from Project`. 13 | * Set the project output path as the path to your folder appended by `/out`. This is to ensure IntelliJ knows where to create the `.class` files for execution. 14 | * Change settings in `Main.java` for bots/human, and then run. 15 | 16 | ### Features 17 | * Support for 2 players including human, minimax bot, or random bot. 18 | * Command line GUI for testing with basic commands such as `board`, `rand`, and movement commands. 19 | 20 | ### Todo 21 | * Implement quiescence search so that the AI can be more intelligent at combating the horizon effect. 22 | * Possibly a GUI in JavaFX to enhance testing. 23 | * Option for turning on/off force jumping. 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/intellij 3 | 4 | ### Intellij ### 5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 7 | 8 | # User-specific stuff: 9 | .idea/workspace.xml 10 | .idea/tasks.xml 11 | .idea/dictionaries 12 | .idea/vcs.xml 13 | .idea/jsLibraryMappings.xml 14 | 15 | # Sensitive or high-churn files: 16 | .idea/dataSources.ids 17 | .idea/dataSources.xml 18 | .idea/dataSources.local.xml 19 | .idea/sqlDataSources.xml 20 | .idea/dynamic.xml 21 | .idea/uiDesigner.xml 22 | 23 | # Gradle: 24 | .idea/gradle.xml 25 | .idea/libraries 26 | 27 | # Mongo Explorer plugin: 28 | .idea/mongoSettings.xml 29 | 30 | ## File-based project format: 31 | *.iws 32 | 33 | ## Plugin-specific files: 34 | 35 | # IntelliJ 36 | /out/ 37 | 38 | # mpeltonen/sbt-idea plugin 39 | .idea_modules/ 40 | 41 | # JIRA plugin 42 | atlassian-ide-plugin.xml 43 | 44 | # Crashlytics plugin (for Android Studio and IntelliJ) 45 | com_crashlytics_export_strings.xml 46 | crashlytics.properties 47 | crashlytics-build.properties 48 | fabric.properties 49 | 50 | ### Intellij Patch ### 51 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 52 | 53 | # *.iml 54 | # modules.xml 55 | .idea/misc.xml 56 | # *.ipr -------------------------------------------------------------------------------- /src/com/company/Main.java: -------------------------------------------------------------------------------- 1 | package com.company; 2 | 3 | import java.util.Scanner; 4 | 5 | public class Main { 6 | 7 | public static double total = 1; 8 | public static boolean multipleRoundsTest = false; 9 | 10 | public static void main(String[] args) throws InterruptedException { 11 | 12 | multipleRoundsTest = total > 1; 13 | Player one = new Player("Player 1", Player.Side.BLACK); 14 | //Player two = new Player("Player 2", Player.Side.WHITE); 15 | 16 | //MinimaxAI one = new MinimaxAI(Player.Side.BLACK, 3); 17 | MinimaxAI two = new MinimaxAI(Player.Side.WHITE, 6); 18 | 19 | //RandomAI one = new RandomAI(Player.Side.BLACK); 20 | //RandomAI two = new RandomAI(Player.Side.WHITE); 21 | 22 | //one goes first if true; 23 | boolean turn = true; 24 | 25 | //System.out.println(board.toString()); 26 | 27 | Scanner sc = new Scanner(System.in); 28 | 29 | int blackWin = 0; 30 | int whiteWin = 0; 31 | 32 | for(int t = 0; t< total; t++) 33 | { 34 | Board board = new Board(); 35 | Player current = one; 36 | if(!turn) 37 | current = two; 38 | int c = 0; 39 | println(board.toString()); 40 | while (c < 1000) 41 | { 42 | //Thread.sleep(500); 43 | c++; 44 | print(current.toString() + "'s turn: "); 45 | 46 | 47 | Board.Decision decision = null; 48 | if(current instanceof AI) { 49 | decision = ((AI) current).makeMove(board); 50 | println(); 51 | } 52 | else { 53 | String text = sc.nextLine(); 54 | if(text.equals("board")) 55 | { 56 | println(board.toString()); 57 | } 58 | if (text.equals("rand")) 59 | { 60 | decision = current.makeRandomMove(board); 61 | } 62 | else 63 | { 64 | String[] split = text.split(" "); 65 | Move m; 66 | if(split.length == 1) 67 | { 68 | m = new Move(Integer.parseInt(text.charAt(0)+""), Integer.parseInt(text.charAt(1)+""), 69 | Integer.parseInt(text.charAt(2)+""), Integer.parseInt(text.charAt(3)+"")); 70 | } 71 | else 72 | { 73 | int[] s = new int[split.length]; 74 | for(int i = 0; i< split.length; i++) 75 | { 76 | s[i] = Integer.parseInt(split[i]); 77 | } 78 | m = new Move(s[0], s[1], s[2], s[3]); 79 | 80 | 81 | } 82 | decision = current.makeMove(m, board); 83 | } 84 | 85 | 86 | 87 | } 88 | 89 | // System.out.println("Decision: " + decision); 90 | if(decision == Board.Decision.FAILED_INVALID_DESTINATION || decision == Board.Decision.FAILED_MOVING_INVALID_PIECE) 91 | { 92 | println("Move Failed"); 93 | // don't update anything 94 | } 95 | else if(decision == Board.Decision.COMPLETED) 96 | { 97 | println(board.toString()); 98 | if(board.getNumBlackPieces() == 0) 99 | { 100 | println("White wins with " + board.getNumWhitePieces() + " pieces left"); 101 | whiteWin++; 102 | break; 103 | } 104 | if(board.getNumWhitePieces() == 0) 105 | { 106 | println("Black wins with " + board.getNumBlackPieces() + " pieces left"); 107 | blackWin++; 108 | break; 109 | } 110 | if(turn) 111 | current = two; 112 | else 113 | current = one; 114 | turn = !turn; 115 | 116 | } 117 | else if(decision == Board.Decision.ADDITIONAL_MOVE) 118 | { 119 | println("Additional Move"); 120 | } 121 | else if(decision == Board.Decision.GAME_ENDED) 122 | { 123 | //current player cannot move 124 | if(current.getSide() == Player.Side.BLACK) 125 | { 126 | println("White wins"); 127 | whiteWin++; 128 | 129 | } 130 | else { 131 | println("Black wins"); 132 | blackWin++; 133 | } 134 | break; 135 | } 136 | 137 | 138 | 139 | } 140 | System.out.println("Game finished after: " + c + " rounds"); 141 | if(one instanceof MinimaxAI) 142 | System.out.println("Avg time per move: " + ((MinimaxAI)one).getAverageTimePerMove()); 143 | } 144 | System.out.println("Black won " + blackWin/total * 100+ "%" + ", White won " + whiteWin/total * 100 + "%"); 145 | 146 | } 147 | 148 | public static void println(String s) 149 | { 150 | if(!multipleRoundsTest) 151 | System.out.println(s); 152 | } 153 | public static void print(String s) 154 | { 155 | if(!multipleRoundsTest) 156 | System.out.print(s); 157 | } 158 | public static void println() 159 | { 160 | if(!multipleRoundsTest) 161 | System.out.println(); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/com/company/MinimaxAI.java: -------------------------------------------------------------------------------- 1 | package com.company; 2 | 3 | import java.awt.*; 4 | import java.awt.geom.Arc2D; 5 | import java.math.RoundingMode; 6 | import java.text.DecimalFormat; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.Random; 10 | 11 | public class MinimaxAI extends Player implements AI{ 12 | 13 | private Point skippingPoint; 14 | private int depth; 15 | private long totalTimeElapsed; 16 | private double numMovesCalled; 17 | private int pruned = 0; 18 | public MinimaxAI(String name, Side s) 19 | { 20 | super(name, s); 21 | } 22 | public MinimaxAI(Side s, int depth) 23 | { 24 | super("MinimaxAI", s); 25 | this.depth = depth; 26 | this.totalTimeElapsed = 0; 27 | } 28 | public Board.Decision makeMove(Board board) 29 | { 30 | numMovesCalled++; 31 | long startTime = System.nanoTime(); 32 | Move m = minimaxStart(board, depth, getSide(), true); 33 | totalTimeElapsed += System.nanoTime() - startTime; 34 | //System.out.println("m is: " + m); 35 | //Move move = board.getAllValidMoves(getSide()).get(m); 36 | Board.Decision decision = board.makeMove(m, getSide()); 37 | if(decision == Board.Decision.ADDITIONAL_MOVE) 38 | skippingPoint = m.getEnd(); 39 | 40 | //System.out.println("Pruned tree: " + pruned + " times"); 41 | return decision; 42 | } 43 | public String getAverageTimePerMove() 44 | { 45 | return totalTimeElapsed/numMovesCalled * Math.pow(10, -6) + " milliseconds"; 46 | } 47 | private Move minimaxStart(Board board, int depth, Side side, boolean maximizingPlayer) 48 | { 49 | double alpha = Double.NEGATIVE_INFINITY; 50 | double beta = Double.POSITIVE_INFINITY; 51 | 52 | List possibleMoves; 53 | if(skippingPoint == null) 54 | possibleMoves = board.getAllValidMoves(side); 55 | else 56 | { 57 | possibleMoves = board.getValidSkipMoves(skippingPoint.x, skippingPoint.y, side); 58 | skippingPoint = null; 59 | } 60 | //System.out.println("side: " + side + " " + possibleMoves.size()); 61 | 62 | List heuristics = new ArrayList<>(); 63 | if(possibleMoves.isEmpty()) 64 | return null; 65 | Board tempBoard = null; 66 | for(int i = 0; i < possibleMoves.size(); i++) 67 | { 68 | tempBoard = board.clone(); 69 | tempBoard.makeMove(possibleMoves.get(i), side); 70 | heuristics.add(minimax(tempBoard, depth - 1, flipSide(side), !maximizingPlayer, alpha, beta)); 71 | } 72 | //System.out.println("\nMinimax at depth: " + depth + "\n" + heuristics); 73 | 74 | double maxHeuristics = Double.NEGATIVE_INFINITY; 75 | 76 | Random rand = new Random(); 77 | for(int i = heuristics.size() - 1; i >= 0; i--) { 78 | if (heuristics.get(i) >= maxHeuristics) { 79 | maxHeuristics = heuristics.get(i); 80 | } 81 | } 82 | //Main.println("Unfiltered heuristics: " + heuristics); 83 | for(int i = 0; i < heuristics.size(); i++) 84 | { 85 | if(heuristics.get(i) < maxHeuristics) 86 | { 87 | heuristics.remove(i); 88 | possibleMoves.remove(i); 89 | i--; 90 | } 91 | } 92 | //Main.println("Filtered/max heuristics: " + heuristics); 93 | return possibleMoves.get(rand.nextInt(possibleMoves.size())); 94 | } 95 | 96 | private double minimax(Board board, int depth, Side side, boolean maximizingPlayer, double alpha, double beta) 97 | { 98 | if(depth == 0) { 99 | return getHeuristic(board); 100 | } 101 | List possibleMoves = board.getAllValidMoves(side); 102 | 103 | double initial = 0; 104 | Board tempBoard = null; 105 | if(maximizingPlayer) 106 | { 107 | initial = Double.NEGATIVE_INFINITY; 108 | for(int i = 0; i < possibleMoves.size(); i++) 109 | { 110 | tempBoard = board.clone(); 111 | tempBoard.makeMove(possibleMoves.get(i), side); 112 | 113 | double result = minimax(tempBoard, depth - 1, flipSide(side), !maximizingPlayer, alpha, beta); 114 | 115 | initial = Math.max(result, initial); 116 | alpha = Math.max(alpha, initial); 117 | 118 | if(alpha >= beta) 119 | break; 120 | } 121 | } 122 | //minimizing 123 | else 124 | { 125 | initial = Double.POSITIVE_INFINITY; 126 | for(int i = 0; i < possibleMoves.size(); i++) 127 | { 128 | tempBoard = board.clone(); 129 | tempBoard.makeMove(possibleMoves.get(i), side); 130 | 131 | double result = minimax(tempBoard, depth - 1, flipSide(side), !maximizingPlayer, alpha, beta); 132 | 133 | initial = Math.min(result, initial); 134 | alpha = Math.min(alpha, initial); 135 | 136 | if(alpha >= beta) 137 | break; 138 | } 139 | } 140 | 141 | return initial; 142 | } 143 | 144 | private double getHeuristic(Board b) 145 | { 146 | //naive implementation 147 | // if(getSide() == Side.WHITE) 148 | // return b.getNumWhitePieces() - b.getNumBlackPieces(); 149 | // return b.getNumBlackPieces() - b.getNumWhitePieces(); 150 | 151 | double kingWeight = 1.2; 152 | double result = 0; 153 | if(getSide() == Side.WHITE) 154 | result = b.getNumWhiteKingPieces() * kingWeight + b.getNumWhiteNormalPieces() - b.getNumBlackKingPieces() * 155 | kingWeight - 156 | b.getNumBlackNormalPieces(); 157 | else 158 | result = b.getNumBlackKingPieces() * kingWeight + b.getNumBlackNormalPieces() - b.getNumWhiteKingPieces() * 159 | kingWeight - 160 | b.getNumWhiteNormalPieces(); 161 | return result; 162 | 163 | } 164 | 165 | private Side flipSide(Side side) 166 | { 167 | if(side == Side.BLACK) 168 | return Side.WHITE; 169 | return Side.BLACK; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/com/company/Board.java: -------------------------------------------------------------------------------- 1 | package com.company; 2 | 3 | 4 | import java.awt.Point; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class Board { 9 | 10 | private Type[][] board; 11 | public final int SIZE = 8; 12 | 13 | private int numWhiteNormalPieces; 14 | private int numBlackNormalPieces; 15 | private int numBlackKingPieces; 16 | private int numWhiteKingPieces; 17 | 18 | private enum Type { 19 | EMPTY, WHITE, BLACK, WHITE_KING, BLACK_KING 20 | } 21 | 22 | public enum Decision { 23 | COMPLETED, 24 | FAILED_MOVING_INVALID_PIECE, 25 | FAILED_INVALID_DESTINATION, 26 | ADDITIONAL_MOVE, 27 | GAME_ENDED 28 | } 29 | 30 | public Board() { 31 | setUpBoard(); 32 | } 33 | 34 | public Board(Type[][] board) 35 | { 36 | numWhiteNormalPieces = 0; 37 | numBlackNormalPieces = 0; 38 | numBlackKingPieces = 0; 39 | numWhiteKingPieces = 0; 40 | 41 | this.board = board; 42 | for(int i = 0; i < SIZE; i++) 43 | { 44 | for(int j = 0; j< SIZE; j++) 45 | { 46 | Type piece = getPiece(i, j); 47 | if(piece == Type.BLACK) 48 | numBlackNormalPieces++; 49 | else if(piece == Type.BLACK_KING) 50 | numBlackKingPieces++; 51 | else if(piece == Type.WHITE) 52 | numWhiteNormalPieces++; 53 | else if(piece == Type.WHITE_KING) 54 | numWhiteKingPieces++; 55 | } 56 | } 57 | } 58 | 59 | private void setUpBoard() { 60 | numWhiteNormalPieces = 12; 61 | numBlackNormalPieces = 12; 62 | numBlackKingPieces = 0; 63 | numWhiteKingPieces = 0; 64 | board = new Type[SIZE][SIZE]; 65 | for (int i = 0; i < board.length; i++) { 66 | int start = 0; 67 | if (i % 2 == 0) 68 | start = 1; 69 | 70 | Type pieceType = Type.EMPTY; 71 | if (i <= 2) 72 | pieceType = Type.WHITE; 73 | else if (i >= 5) 74 | pieceType = Type.BLACK; 75 | 76 | for (int j = start; j < board[i].length; j += 2) { 77 | board[i][j] = pieceType; 78 | } 79 | } 80 | 81 | populateEmptyOnBoard(); 82 | } 83 | 84 | private void setUpTestBoard() { 85 | numBlackKingPieces = 1; 86 | numWhiteKingPieces = 1; 87 | board = new Type[SIZE][SIZE]; 88 | board[6][1] = Type.WHITE_KING; 89 | board[4][3] = Type.BLACK_KING; 90 | populateEmptyOnBoard(); 91 | 92 | } 93 | 94 | private void populateEmptyOnBoard() { 95 | for (int i = 0; i < board.length; i++) { 96 | for (int j = 0; j < board[i].length; j++) { 97 | if (board[i][j] == null) 98 | board[i][j] = Type.EMPTY; 99 | } 100 | } 101 | } 102 | 103 | public Type getPiece(int row, int col) { 104 | return board[row][col]; 105 | } 106 | 107 | public Type getPiece(Point point) { 108 | return board[point.x][point.y]; 109 | } 110 | 111 | public Type[][] getBoard() 112 | { 113 | return board; 114 | } 115 | 116 | public int getNumWhitePieces() { 117 | return numWhiteKingPieces + numWhiteNormalPieces; 118 | } 119 | 120 | public int getNumBlackPieces() { 121 | return numBlackKingPieces + numBlackNormalPieces; 122 | } 123 | 124 | public int getNumWhiteKingPieces() 125 | { 126 | return numWhiteKingPieces; 127 | } 128 | public int getNumBlackKingPieces() 129 | { 130 | return numBlackKingPieces; 131 | } 132 | public int getNumWhiteNormalPieces() 133 | { 134 | return numWhiteNormalPieces; 135 | } 136 | public int getNumBlackNormalPieces() 137 | { 138 | return numBlackNormalPieces; 139 | } 140 | 141 | // returns true if move successful 142 | public Decision makeMove(Move move, Player.Side side) { 143 | if(move == null) { 144 | return Decision.GAME_ENDED; 145 | } 146 | Point start = move.getStart(); 147 | int startRow = start.x; 148 | int startCol = start.y; 149 | Point end = move.getEnd(); 150 | int endRow = end.x; 151 | int endCol = end.y; 152 | 153 | //can only move own piece and not empty space 154 | if (!isMovingOwnPiece(startRow, startCol, side) || getPiece(startRow, startCol) == Type.EMPTY) 155 | return Decision.FAILED_MOVING_INVALID_PIECE; 156 | 157 | List possibleMoves = getValidMoves(startRow, startCol, side); 158 | //System.out.println(possibleMoves); 159 | 160 | Type currType = getPiece(startRow, startCol); 161 | 162 | if (possibleMoves.contains(move)) { 163 | boolean jumpMove = false; 164 | //if it contains move then it is either 1 move or 1 jump 165 | if (startRow + 1 == endRow || startRow - 1 == endRow) { 166 | board[startRow][startCol] = Type.EMPTY; 167 | board[endRow][endCol] = currType; 168 | } else { 169 | jumpMove = true; 170 | board[startRow][startCol] = Type.EMPTY; 171 | board[endRow][endCol] = currType; 172 | Point mid = findMidSquare(move); 173 | 174 | Type middle = getPiece(mid); 175 | if (middle == Type.BLACK) 176 | numBlackNormalPieces--; 177 | else if(middle == Type.BLACK_KING) 178 | numBlackKingPieces--; 179 | else if(middle == Type.WHITE) 180 | numWhiteNormalPieces--; 181 | else if(middle == Type.WHITE_KING) 182 | numWhiteKingPieces--; 183 | board[mid.x][mid.y] = Type.EMPTY; 184 | } 185 | 186 | if (endRow == 0 && side == Player.Side.BLACK) { 187 | board[endRow][endCol] = Type.BLACK_KING; 188 | numBlackNormalPieces--; 189 | numBlackKingPieces++; 190 | } 191 | 192 | else if (endRow == SIZE - 1 && side == Player.Side.WHITE) { 193 | board[endRow][endCol] = Type.WHITE_KING; 194 | numWhiteNormalPieces--; 195 | numWhiteKingPieces++; 196 | } 197 | if (jumpMove) { 198 | List additional = getValidSkipMoves(endRow, endCol, side); 199 | if (additional.isEmpty()) 200 | return Decision.COMPLETED; 201 | return Decision.ADDITIONAL_MOVE; 202 | } 203 | return Decision.COMPLETED; 204 | } else 205 | return Decision.FAILED_INVALID_DESTINATION; 206 | } 207 | 208 | public List getAllValidMoves(Player.Side side) 209 | { 210 | 211 | Type normal = side == Player.Side.BLACK ? Type.BLACK : Type.WHITE; 212 | Type king = side == Player.Side.BLACK ? Type.BLACK_KING : Type.WHITE_KING; 213 | 214 | List possibleMoves = new ArrayList<>(); 215 | for(int i = 0; i < SIZE; i++) 216 | { 217 | for(int j = 0; j < SIZE; j++) 218 | { 219 | Type t = getPiece(i, j); 220 | if(t == normal || t == king) 221 | possibleMoves.addAll(getValidMoves(i, j, side)); 222 | } 223 | } 224 | 225 | 226 | return possibleMoves; 227 | } 228 | 229 | // requires there to actually be a mid square 230 | private Point findMidSquare(Move move) { 231 | 232 | Point ret = new Point((move.getStart().x + move.getEnd().x) / 2, 233 | (move.getStart().y + move.getEnd().y) / 2); 234 | 235 | return ret; 236 | } 237 | 238 | private boolean isMovingOwnPiece(int row, int col, Player.Side side) { 239 | Type pieceType = getPiece(row, col); 240 | if (side == Player.Side.BLACK && pieceType != Type.BLACK && pieceType != Type.BLACK_KING) 241 | return false; 242 | else if (side == Player.Side.WHITE && pieceType != Type.WHITE && pieceType != Type.WHITE_KING) 243 | return false; 244 | return true; 245 | } 246 | 247 | public List getValidMoves(int row, int col, Player.Side side) { 248 | Type type = board[row][col]; 249 | Point startPoint = new Point(row, col); 250 | if (type == Type.EMPTY) 251 | throw new IllegalArgumentException(); 252 | 253 | List moves = new ArrayList<>(); 254 | 255 | //4 possible moves, 2 if not king 256 | if (type == Type.WHITE || type == Type.BLACK) { 257 | //2 possible moves 258 | int rowChange = type == Type.WHITE ? 1 : -1; 259 | 260 | int newRow = row + rowChange; 261 | if (newRow >= 0 || newRow < SIZE) { 262 | int newCol = col + 1; 263 | if (newCol < SIZE && getPiece(newRow, newCol) == Type.EMPTY) 264 | moves.add(new Move(startPoint, new Point(newRow, newCol))); 265 | newCol = col - 1; 266 | if (newCol >= 0 && getPiece(newRow, newCol) == Type.EMPTY) 267 | moves.add(new Move(startPoint, new Point(newRow, newCol))); 268 | } 269 | 270 | } 271 | //must be king 272 | else { 273 | //4 possible moves 274 | 275 | int newRow = row + 1; 276 | if (newRow < SIZE) { 277 | int newCol = col + 1; 278 | if (newCol < SIZE && getPiece(newRow, newCol) == Type.EMPTY) 279 | moves.add(new Move(startPoint, new Point(newRow, newCol))); 280 | newCol = col - 1; 281 | if (newCol >= 0 && getPiece(newRow, newCol) == Type.EMPTY) 282 | moves.add(new Move(startPoint, new Point(newRow, newCol))); 283 | } 284 | newRow = row - 1; 285 | if (newRow >= 0) { 286 | int newCol = col + 1; 287 | if (newCol < SIZE && getPiece(newRow, newCol) == Type.EMPTY) 288 | moves.add(new Move(startPoint, new Point(newRow, newCol))); 289 | newCol = col - 1; 290 | if (newCol >= 0 && getPiece(newRow, newCol) == Type.EMPTY) 291 | moves.add(new Move(startPoint, new Point(newRow, newCol))); 292 | } 293 | 294 | 295 | } 296 | 297 | moves.addAll(getValidSkipMoves(row, col, side)); 298 | return moves; 299 | } 300 | 301 | public List getValidSkipMoves(int row, int col, Player.Side side) { 302 | List move = new ArrayList<>(); 303 | Point start = new Point(row, col); 304 | 305 | List possibilities = new ArrayList<>(); 306 | 307 | if(side == Player.Side.WHITE && getPiece(row, col) == Type.WHITE) 308 | { 309 | possibilities.add(new Point(row + 2, col + 2)); 310 | possibilities.add(new Point(row + 2, col - 2)); 311 | } 312 | else if(side == Player.Side.BLACK && getPiece(row, col) == Type.BLACK) 313 | { 314 | possibilities.add(new Point(row - 2, col + 2)); 315 | possibilities.add(new Point(row - 2, col - 2)); 316 | } 317 | else if(getPiece(row, col) == Type.BLACK_KING || getPiece(row, col) == Type.WHITE_KING) 318 | { 319 | possibilities.add(new Point(row + 2, col + 2)); 320 | possibilities.add(new Point(row + 2, col - 2)); 321 | possibilities.add(new Point(row - 2, col + 2)); 322 | possibilities.add(new Point(row - 2, col - 2)); 323 | } 324 | 325 | for (int i = 0; i < possibilities.size(); i++) { 326 | Point temp = possibilities.get(i); 327 | Move m = new Move(start, temp); 328 | if (temp.x < SIZE && temp.x >= 0 && temp.y < SIZE && temp.y >= 0 && getPiece(temp.x, temp.y) == Type.EMPTY 329 | && isOpponentPiece(side, getPiece(findMidSquare(m)))) { 330 | move.add(m); 331 | } 332 | } 333 | 334 | //System.out.println("Skip moves: " + move); 335 | return move; 336 | } 337 | 338 | // return true if the piece is opponents 339 | private boolean isOpponentPiece(Player.Side current, Type opponentPiece) { 340 | if (current == Player.Side.BLACK && (opponentPiece == Type.WHITE || opponentPiece == Type.WHITE_KING)) 341 | return true; 342 | if (current == Player.Side.WHITE && (opponentPiece == Type.BLACK || opponentPiece == Type.BLACK_KING)) 343 | return true; 344 | return false; 345 | } 346 | 347 | public String toString() { 348 | StringBuilder b = new StringBuilder(); 349 | b.append(" "); 350 | for (int i = 0; i < board.length; i++) { 351 | b.append(i + " "); 352 | } 353 | b.append("\n"); 354 | for (int i = 0; i < board.length; i++) { 355 | for (int j = -1; j < board[i].length; j++) { 356 | String a = ""; 357 | if (j == -1) 358 | a = i + ""; 359 | else if (board[i][j] == Type.WHITE) 360 | a = "w"; 361 | else if (board[i][j] == Type.BLACK) 362 | a = "b"; 363 | else if (board[i][j] == Type.WHITE_KING) 364 | a = "W"; 365 | else if (board[i][j] == Type.BLACK_KING) 366 | a = "B"; 367 | else 368 | a = "_"; 369 | 370 | b.append(a); 371 | b.append(" "); 372 | } 373 | b.append("\n"); 374 | } 375 | return b.toString(); 376 | } 377 | 378 | public Board clone() 379 | { 380 | Type[][] newBoard = new Type[SIZE][SIZE]; 381 | for(int i = 0; i < SIZE; i++) 382 | { 383 | for(int j = 0; j< SIZE; j++) 384 | { 385 | newBoard[i][j] = board[i][j]; 386 | } 387 | } 388 | Board b = new Board(newBoard); 389 | return b; 390 | } 391 | } 392 | --------------------------------------------------------------------------------