├── .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 |
--------------------------------------------------------------------------------