96 Commits

Author SHA1 Message Date
e391e703e1 feat: add actions
All checks were successful
Linux arm64 / Build (push) Successful in 1m50s
2025-05-04 10:29:29 +02:00
a595b62fca refactor misc 2025-05-03 20:49:03 +02:00
b18b53f195 feat: pgn parser 2025-05-03 20:46:20 +02:00
b2a6b23681 fix: lastmove cache bug 2025-05-03 18:36:38 +02:00
16ea1700d8 refactor: alphabeta time printing 2025-05-02 19:56:00 +02:00
64bed4ea3a refactor: add signals to alphabetaai 2025-05-02 19:47:03 +02:00
2a3ea10389 fix warnings 2025-04-30 20:43:44 +02:00
3b225d9e23 feat: add audio 2025-04-30 20:41:10 +02:00
3b38e0da1f Super IA (#5)
Reviewed-on: #5
Co-authored-by: Persson-dev <sim16.prib@gmail.com>
Co-committed-by: Persson-dev <sim16.prib@gmail.com>
2025-04-30 18:28:01 +00:00
ecb0fdc623 Begin Audio (#7)
Reviewed-on: #7
Co-authored-by: Persson-dev <sim16.prib@gmail.com>
Co-committed-by: Persson-dev <sim16.prib@gmail.com>
2025-04-30 18:27:30 +00:00
4cbad80aaf begin 3d 2025-04-22 15:02:24 +02:00
2b5c2ad663 fix gradle input 2025-04-22 14:44:57 +02:00
Janet-Doe
0e2b07741d addo en passant test 2025-04-22 14:42:13 +02:00
b83726925e fix console 2025-04-18 10:33:10 +02:00
30eb12145d add hungry ai 2025-04-17 08:53:21 +02:00
17ef882d44 remove board from constructor 2025-04-16 19:29:31 +02:00
bee5e613bb refactor dumb ai castling 2025-04-16 19:25:04 +02:00
923ace22f1 add general ai 2025-04-16 19:21:47 +02:00
78d48fafc6 swing: export game at the end 2025-04-16 18:14:15 +02:00
ae55e0d94c change draw 2025-04-16 11:25:31 +02:00
f54ddc1f59 ai castling 2025-04-16 11:25:05 +02:00
Janet-Doe
ebb013cae5 oops 2025-04-16 11:01:16 +02:00
Janet-Doe
076288a400 tests + ado equals override 2025-04-16 10:55:27 +02:00
3f06a9eb17 fix pgn 2025-04-16 10:43:56 +02:00
2502d2c5d3 fix en passant again 2025-04-16 10:43:48 +02:00
577ef0545f working pgn 2025-04-15 21:36:05 +02:00
edf95a1691 fix en passant 2025-04-15 21:33:49 +02:00
87992b181e better simulator signal 2025-04-15 18:57:45 +02:00
ae0e76a452 fix console promote 2025-04-15 18:31:09 +02:00
1a038a3de1 very basic pgn export 2025-04-14 11:56:19 +02:00
d8c927083a begin pgn 2025-04-14 11:21:30 +02:00
Janet-Doe
3bea2eeb2d working castling on console + app to play either mode 2025-04-14 11:19:45 +02:00
91678a42b2 fix undo draw check 2025-04-14 11:02:35 +02:00
07016be5d8 add castling commands 2025-04-14 10:34:54 +02:00
Janet-Doe
2578e8cf6f finished todo console 2025-04-13 17:17:34 +02:00
2271f610e7 add missing onGameEnd statement 2025-04-13 17:14:07 +02:00
8b8eb428e5 refactor draw 2025-04-13 15:58:26 +02:00
b336784a5d check draw 2025-04-13 12:57:13 +02:00
224a09c711 add simulators for testing 2025-04-13 12:30:12 +02:00
a23c334994 Command refactor 2025-04-13 12:29:47 +02:00
Janet-Doe
9f44548843 color changes in console + promotion 2025-04-13 00:56:44 +02:00
39c289cc47 better termination 2025-04-12 13:04:48 +02:00
de733fcea2 fix merge 2025-04-12 12:33:45 +02:00
ec2c1abe78 Merge remote-tracking branch 'origin/brindille' 2025-04-12 12:29:48 +02:00
7b7280d807 promote popup fix 2025-04-12 12:23:22 +02:00
d2485a4d75 thread pool + ai vs ai 2025-04-12 12:15:58 +02:00
Janet-Doe
7b04ec4e6c merge console application 2025-04-12 12:15:54 +02:00
Janet-Doe
e064902610 fixed console application
# Conflicts:
#	app/src/main/java/chess/view/consolerender/Console.java
2025-04-12 12:15:47 +02:00
acd20ef7fa GameAdaptator abstract 2025-04-12 11:20:22 +02:00
5cf00309b3 simplify mains 2025-04-12 11:20:12 +02:00
8190090adc refactor OutputSystem 2025-04-12 11:12:36 +02:00
0d3d77781f fix build 2025-04-12 10:48:47 +02:00
Janet-Doe
98b28c5fba console piece movement should work 2025-04-10 13:00:35 +02:00
17d8333342 iaaaaaaaaaaaaaa 2025-04-10 12:26:09 +02:00
3226e72d32 couleurs 2025-04-10 12:02:08 +02:00
Janet-Doe
0fbea2ca0f update colors for console version 2025-04-10 11:54:06 +02:00
Janet-Doe
746fa4d330 console 2025-04-10 11:41:47 +02:00
7aa129bbc6 fix moves preview 2025-04-10 11:07:44 +02:00
Janet-Doe
19b45371bb update color 2025-04-10 11:00:37 +02:00
4b84a30e07 osekour 2025-04-10 10:56:59 +02:00
e8e79a1e9e synchronisation 2025-04-07 12:01:51 +02:00
6584c5cb91 gradle: export runnable jar 2025-04-06 13:08:12 +02:00
58cc9f3f17 feat: asset manager 2025-04-06 12:58:47 +02:00
6eae7e386f refactor: move promote to chessboard 2025-04-06 12:43:11 +02:00
6cb1dd826f add castling + fix promote undo 2025-04-06 12:33:00 +02:00
a2224cf618 don't save failed commands 2025-04-06 11:40:24 +02:00
65362677a5 example game 2025-04-05 19:38:26 +02:00
381e5ed0b8 remove try catch 2025-04-05 19:22:15 +02:00
d94f7d733b en passant rule 2025-04-05 19:20:41 +02:00
2ec7be27ca feat: add undo 2025-04-05 10:53:41 +02:00
8c2c6946d7 remove "instanceof Pawn" 2025-04-05 10:26:47 +02:00
9af06e36f8 change project structure 2025-04-05 10:18:02 +02:00
a0af8caf57 post exec 2025-04-05 10:12:25 +02:00
63a1e261e8 add move preconditions 2025-04-04 20:57:20 +02:00
7b07423175 add CommandResult.NeedsAction 2025-04-04 20:55:46 +02:00
416cfadc9b nuke CommandResult.java 2025-04-04 20:33:24 +02:00
810934aea1 better undo 2025-04-04 20:06:32 +02:00
48a215eae5 remove game signals 2025-04-04 19:42:23 +02:00
55ef180f57 better gui interaction 2025-04-04 15:06:34 +02:00
5b006034ad undo command def 2025-04-04 14:48:48 +02:00
873ffc05d3 player command 2025-04-04 14:46:58 +02:00
55774b4605 refactor game 2025-04-03 22:15:50 +02:00
0d72e015f1 promote gui 2025-04-03 22:06:34 +02:00
927ba129f6 fix pieces pos 2025-04-03 21:18:45 +02:00
36e04376c3 add popups 2025-04-03 21:14:04 +02:00
a81da804f0 refactor game 2025-04-03 21:02:04 +02:00
9179b3cda9 promote not that uggly 2025-04-02 17:19:26 +02:00
97cafb903a juste better 2025-04-02 10:54:14 +02:00
1b9ff5bdd1 lots of things 2025-04-01 23:11:41 +02:00
2c6b64fa7d pretty cool 2025-03-31 22:28:08 +02:00
5598d4f5eb ignore class files 2025-03-28 08:59:05 +01:00
dcdf118274 init board cells 2025-03-25 22:37:31 +01:00
0bef89c46f basic model 2025-03-25 22:36:28 +01:00
dc2ea660ff optimize shader 2025-03-20 14:44:06 +01:00
b98e1aaade change window title 2025-03-20 14:42:46 +01:00
0c35c38ccd small refactor 2025-03-20 14:40:32 +01:00
112 changed files with 5026 additions and 49 deletions

3
.gitattributes vendored
View File

@@ -3,7 +3,6 @@
#
# Linux start script should use lf
/gradlew text eol=lf
# These are Windows script files and should use crlf
*.bat text eol=crlf
*.glb filter=lfs diff=lfs merge=lfs -text

25
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Linux arm64
run-name: Build And Test
on: [push]
jobs:
Build:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Build with Gradle
run: ./gradlew assemble
- name: Test
run: ./gradlew test

7
.gitignore vendored
View File

@@ -3,3 +3,10 @@
# Ignore Gradle build output directory
build
app/bin
.vscode
audio/*.wav
app/audio/*.wav

View File

@@ -36,6 +36,25 @@ dependencies {
application {
// Define the main class for the application.
mainClass = "chess.App"
applicationName = "3DChess"
}
run {
standardInput = System.in
}
// Apply a specific Java toolchain to ease working on different environments.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
jar {
manifest {
attributes 'Main-Class': application.mainClass
}
}
tasks.named('test') {

View File

@@ -1,16 +1,33 @@
/*
* This Java source file was generated by the Gradle 'init' task.
*/
package chess;
import chess.render.*;
import java.util.Scanner;
import chess.view.consolerender.Colors;
public class App {
public String getGreeting() {
return "Hello World!";
}
public static void main(String[] args) {
new Window().run();
System.out.println(Colors.RED + "Credits: Grenier Lilas, Pribylski Simon." + Colors.RESET);
System.out.println("""
Pick the version to use:
1 - Console
2 - Window
3 - 3D.""");
Scanner scan = new Scanner(System.in);
String line = scan.nextLine();
scan.close();
switch (line) {
case "1", "Console", "console":
ConsoleMain.main(args);
break;
case "2", "Window", "window":
SwingMain.main(args);
break;
case "3", "3D", "3d":
OpenGLMain.main(args);
break;
default:
System.out.println("Invalid input");
break;
}
}
}

View File

@@ -0,0 +1,21 @@
/*
* This Java source file was generated by the Gradle 'init' task.
*/
package chess;
import chess.controller.CommandExecutor;
import chess.controller.commands.NewGameCommand;
import chess.model.Game;
import chess.view.consolerender.Console;
public class ConsoleMain {
public static void main(String[] args) {
Game game = new Game();
CommandExecutor commandExecutor = new CommandExecutor(game);
Console console = new Console(commandExecutor);
commandExecutor.addListener(console);
commandExecutor.executeCommand(new NewGameCommand());
}
}

View File

@@ -0,0 +1,9 @@
package chess;
import chess.view.DDDrender.Window;
public class OpenGLMain {
public static void main(String[] args) {
new Window().run();
}
}

View File

@@ -0,0 +1,45 @@
package chess;
import chess.ai.minimax.AlphaBetaAI;
import chess.ai.minimax.AlphaBetaConsolePrinter;
import chess.controller.CommandExecutor;
import chess.controller.commands.NewGameCommand;
import chess.controller.event.GameAdaptator;
import chess.model.Color;
import chess.model.Game;
import chess.pgn.PgnExport;
import chess.view.audio.GameAudio;
import chess.view.simplerender.Window;
public class SwingMain {
public static void main(String[] args) {
Game game = new Game();
CommandExecutor commandExecutor = new CommandExecutor(game);
Window window = new Window(commandExecutor, true);
commandExecutor.addListener(window);
AlphaBetaAI ai = new AlphaBetaAI(commandExecutor, Color.Black, 5);
commandExecutor.addListener(ai);
AlphaBetaConsolePrinter aiResults = new AlphaBetaConsolePrinter(ai);
aiResults.connect();
// AI ai2 = new AlphaBetaAI(commandExecutor, Color.White, 5);
// commandExecutor.addListener(ai2);
// Window window2 = new Window(ai2.getSimulation(), false);
// ai2.getSimulation().addListener(window2);
commandExecutor.addListener(new GameAdaptator(){
@Override
public void onGameEnd() {
System.out.println(PgnExport.exportGame(game));
}
});
commandExecutor.addListener(new GameAudio());
commandExecutor.executeCommand(new NewGameCommand());
}
}

View File

@@ -0,0 +1,81 @@
package chess.ai;
import java.util.List;
import chess.controller.Command;
import chess.controller.CommandExecutor;
import chess.controller.Command.CommandResult;
import chess.controller.commands.GetPieceAtCommand;
import chess.controller.commands.GetPlayerMovesCommand;
import chess.controller.commands.GetAllowedCastlingsCommand;
import chess.controller.commands.GetAllowedCastlingsCommand.CastlingResult;
import chess.controller.event.GameAdaptator;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
import chess.model.Piece;
public abstract class AI extends GameAdaptator{
protected final CommandExecutor commandExecutor;
protected final Color color;
public AI(CommandExecutor commandExecutor, Color color) {
this.commandExecutor = commandExecutor;
this.color = color;
}
protected abstract void play();
protected abstract void promote(Coordinate pawnCoords);
@Override
public void onPlayerTurn(Color color, boolean undone) {
if (this.color != color || undone)
return;
play();
}
@Override
public void onPromotePawn(Coordinate pieceCoords) {
Piece pawn = pieceAt(pieceCoords);
if (pawn.getColor() != this.color)
return;
promote(pieceCoords);
}
protected Piece pieceAt(Coordinate coordinate) {
GetPieceAtCommand command = new GetPieceAtCommand(coordinate);
sendCommand(command);
return command.getPiece();
}
protected List<Move> getAllowedMoves() {
return getAllowedMoves(this.commandExecutor);
}
protected List<Move> getAllowedMoves(CommandExecutor commandExecutor) {
GetPlayerMovesCommand cmd = new GetPlayerMovesCommand();
sendCommand(cmd, commandExecutor);
return cmd.getMoves();
}
protected CastlingResult getAllowedCastlings() {
GetAllowedCastlingsCommand cmd2 = new GetAllowedCastlingsCommand();
sendCommand(cmd2);
return cmd2.getCastlingResult();
}
protected CommandResult sendCommand(Command command) {
return sendCommand(command, this.commandExecutor);
}
protected CommandResult sendCommand(Command command, CommandExecutor commandExecutor) {
CommandResult result = commandExecutor.executeCommand(command);
if(result == CommandResult.NotAllowed){
System.out.println("eeeeee");
}
return result;
}
}

View File

@@ -0,0 +1,61 @@
package chess.ai;
import java.util.List;
import java.util.Random;
import chess.controller.CommandExecutor;
import chess.controller.commands.CastlingCommand;
import chess.controller.commands.GetAllowedCastlingsCommand.CastlingResult;
import chess.controller.commands.MoveCommand;
import chess.controller.commands.PromoteCommand;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
public class DumbAI extends AI {
private final Random random = new Random();
public DumbAI(CommandExecutor commandExecutor, Color color) {
super(commandExecutor, color);
}
@Override
protected void play() {
CastlingResult castlings = getAllowedCastlings();
List<Move> moves = getAllowedMoves();
switch (castlings) {
case Both: {
int randomMove = this.random.nextInt(moves.size() + 2);
if (randomMove < moves.size() - 2)
break;
sendCommand(new CastlingCommand(randomMove == moves.size()));
return;
}
case Small:
case Big: {
int randomMove = this.random.nextInt(moves.size() + 1);
if (randomMove != moves.size())
break;
sendCommand(new CastlingCommand(castlings == CastlingResult.Big));
return;
}
default:
break;
}
int randomMove = this.random.nextInt(moves.size());
sendCommand(new MoveCommand(moves.get(randomMove)));
}
@Override
protected void promote(Coordinate pawnCoords) {
int promote = this.random.nextInt(PromoteType.values().length);
sendCommand(new PromoteCommand(PromoteType.values()[promote]));
}
}

View File

@@ -0,0 +1,61 @@
package chess.ai;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import chess.controller.CommandExecutor;
import chess.controller.commands.MoveCommand;
import chess.controller.commands.PromoteCommand;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
import chess.model.Piece;
public class HungryAI extends AI {
private final PieceCost pieceCost;
private final Random random;
public HungryAI(CommandExecutor commandExecutor, Color color) {
super(commandExecutor, color);
this.pieceCost = new PieceCost(color);
this.random = new Random();
}
private int getMoveCost(Move move) {
Piece piece = pieceAt(move.getDeadPieceCoords());
return - (int) pieceCost.getCost(piece);
}
private List<Move> getBestMoves() {
List<Move> moves = getAllowedMoves();
List<Move> bestMoves = new ArrayList<>();
int bestCost = 0;
for (Move move : moves) {
int moveCost = getMoveCost(move);
if (moveCost == bestCost) {
bestMoves.add(move);
} else if (moveCost > bestCost) {
bestMoves.clear();
bestMoves.add(move);
bestCost = moveCost;
}
}
return bestMoves;
}
@Override
protected void play() {
List<Move> bestMoves = getBestMoves();
int randomMove = this.random.nextInt(bestMoves.size());
this.commandExecutor.executeCommand(new MoveCommand(bestMoves.get(randomMove)));
}
@Override
protected void promote(Coordinate pawnCoords) {
sendCommand(new PromoteCommand(PromoteType.Queen));
}
}

View File

@@ -0,0 +1,67 @@
package chess.ai;
import chess.model.Color;
import chess.model.Piece;
import chess.model.PieceVisitor;
import chess.model.pieces.Bishop;
import chess.model.pieces.King;
import chess.model.pieces.Knight;
import chess.model.pieces.Pawn;
import chess.model.pieces.Queen;
import chess.model.pieces.Rook;
public class PieceCost implements PieceVisitor<Float> {
private final Color player;
public static final float BISHOP = 30;
public static final float KING = 900;
public static final float KNIGHT = 30;
public static final float PAWN = 10;
public static final float QUEEN = 90;
public static final float ROOK = 50;
public PieceCost(Color color) {
this.player = color;
}
public float getCost(Piece piece) {
if (piece == null)
return 0;
float cost = visit(piece);
if (piece.getColor() != player)
cost = -cost;
return cost;
}
@Override
public Float visitPiece(Bishop bishop) {
return BISHOP;
}
@Override
public Float visitPiece(King king) {
return KING;
}
@Override
public Float visitPiece(Knight knight) {
return KNIGHT;
}
@Override
public Float visitPiece(Pawn pawn) {
return PAWN;
}
@Override
public Float visitPiece(Queen queen) {
return QUEEN;
}
@Override
public Float visitPiece(Rook rook) {
return ROOK;
}
}

View File

@@ -0,0 +1,129 @@
package chess.ai;
import java.util.Arrays;
import java.util.List;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Piece;
import chess.model.PieceVisitor;
import chess.model.pieces.Bishop;
import chess.model.pieces.King;
import chess.model.pieces.Knight;
import chess.model.pieces.Pawn;
import chess.model.pieces.Queen;
import chess.model.pieces.Rook;
public class PiecePosCost implements PieceVisitor<List<Float>> {
private final Color color;
private static final List<Float> BISHOP = Arrays.asList(
-2.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -2.0f,
-1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f,
-1.0f, 0.0f, 0.5f, 1.0f, 1.0f, 0.5f, 0.0f, -1.0f,
-1.0f, 0.5f, 0.5f, 1.0f, 1.0f, 0.5f, 0.5f, -1.0f,
-1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, -1.0f,
-1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f,
-1.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, -1.0f,
-2.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -2.0f);
private static final List<Float> KING = Arrays.asList(
-3.0f, -4.0f, -4.0f, -5.0f, -5.0f, -4.0f, -4.0f, -3.0f,
-3.0f, -4.0f, -4.0f, -5.0f, -5.0f, -4.0f, -4.0f, -3.0f,
-3.0f, -4.0f, -4.0f, -5.0f, -5.0f, -4.0f, -4.0f, -3.0f,
-3.0f, -4.0f, -4.0f, -5.0f, -5.0f, -4.0f, -4.0f, -3.0f,
-2.0f, -3.0f, -3.0f, -4.0f, -4.0f, -3.0f, -3.0f, -2.0f,
-1.0f, -2.0f, -2.0f, -2.0f, -2.0f, -2.0f, -2.0f, -1.0f,
2.0f, 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.0f, 2.0f,
2.0f, 3.0f, 1.0f, 0.0f, 0.0f, 1.0f, 3.0f, 2.0f);
private static final List<Float> KNIGHT = Arrays.asList(
-5.0f, -4.0f, -3.0f, -3.0f, -3.0f, -3.0f, -4.0f, -5.0f,
-4.0f, -2.0f, 0.0f, 0.0f, 0.0f, 0.0f, -2.0f, -4.0f,
-3.0f, 0.0f, 1.0f, 1.5f, 1.5f, 1.0f, 0.0f, -3.0f,
-3.0f, 0.5f, 1.5f, 2.0f, 2.0f, 1.5f, 0.5f, -3.0f,
-3.0f, 0.0f, 1.5f, 2.0f, 2.0f, 1.5f, 0.0f, -3.0f,
-3.0f, 0.5f, 1.0f, 1.5f, 1.5f, 1.0f, 0.5f, -3.0f,
-4.0f, -2.0f, 0.0f, 0.5f, 0.5f, 0.0f, -2.0f, -4.0f,
-5.0f, -4.0f, -3.0f, -3.0f, -3.0f, -3.0f, -4.0f, -5.0f);
private static final List<Float> PAWN = Arrays.asList(
0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
5.0f, 5.0f, 5.0f, 5.0f, 5.0f, 5.0f, 5.0f, 5.0f,
1.0f, 1.0f, 2.0f, 3.0f, 3.0f, 2.0f, 1.0f, 1.0f,
0.5f, 0.5f, 1.0f, 2.5f, 2.5f, 1.0f, 0.5f, 0.5f,
0.0f, 0.0f, 1.0f, 2.0f, 2.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -1.0f, 0.0f, 0.0f, -1.0f, -0.5f, 0.5f,
0.5f, 1.0f, 1.0f, -2.0f, -2.0f, 1.0f, 1.0f, 0.5f,
0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
private static final List<Float> QUEEN = Arrays.asList(
-2.0f, -1.0f, -1.0f, -0.5f, -0.5f, -1.0f, -1.0f, -2.0f,
-1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f,
-1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, -1.0f,
-0.5f, 0.0f, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, -0.5f,
0.0f, 0.0f, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, -0.5f,
-1.0f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, -1.0f,
-1.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f,
-2.0f, -1.0f, -1.0f, -0.5f, -0.5f, -1.0f, -1.0f, -2.0f);
private static final List<Float> ROOK = Arrays.asList(
0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.5f,
-0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.5f,
-0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.5f,
-0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.5f,
-0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.5f,
-0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.5f,
0.0f, 0.0f, 0.0f, 0.5f, 0.5f, 0.0f, 0.0f, 0.0f);
public PiecePosCost(Color color) {
this.color = color;
}
public float getEvaluation(Piece piece, Coordinate coordinate) {
if (piece == null)
return 0;
List<Float> positions = visit(piece);
int x = piece.getColor() == Color.Black ? (Coordinate.VALUE_MAX - 1 - coordinate.getX()) : coordinate.getX();
int y = piece.getColor() == Color.Black ? (Coordinate.VALUE_MAX - 1 - coordinate.getY()) : coordinate.getY();
Coordinate newCoords = new Coordinate(x, y);
assert newCoords.isValid();
float result = positions.get(newCoords.toIndex());
if (piece.getColor() != color)
return -result;
return result;
}
@Override
public List<Float> visitPiece(Bishop bishop) {
return BISHOP;
}
@Override
public List<Float> visitPiece(King king) {
return KING;
}
@Override
public List<Float> visitPiece(Knight knight) {
return KNIGHT;
}
@Override
public List<Float> visitPiece(Pawn pawn) {
return PAWN;
}
@Override
public List<Float> visitPiece(Queen queen) {
return QUEEN;
}
@Override
public List<Float> visitPiece(Rook rook) {
return ROOK;
}
}

View File

@@ -0,0 +1,92 @@
package chess.ai.minimax;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import chess.ai.AI;
import chess.controller.CommandExecutor;
import chess.controller.commands.MoveCommand;
import chess.controller.commands.PromoteCommand;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
import common.Signal1;
public class AlphaBetaAI extends AI {
private final int searchDepth;
private static final float MAX_FLOAT = Float.MAX_VALUE;
private static final float MIN_FLOAT = -MAX_FLOAT;
private final ExecutorService threadPool;
public final Signal1<Integer> onStartEval = new Signal1<>();
public final Signal1<Float> onCompleteEval = new Signal1<>();
public final Signal1<Float> onProgress = new Signal1<>();
public AlphaBetaAI(CommandExecutor commandExecutor, Color color, int searchDepth) {
super(commandExecutor, color);
this.searchDepth = searchDepth;
int threadCount = Runtime.getRuntime().availableProcessors();
this.threadPool = Executors.newFixedThreadPool(threadCount,
new AlphaBetaThreadCreator(commandExecutor, color, threadCount));
}
private Move getBestMove() {
List<Move> moves = getAllowedMoves();
List<Future<Float>> moveEvaluations = new ArrayList<>(50);
float bestMoveValue = MIN_FLOAT;
Move bestMove = null;
this.onStartEval.emit(moves.size());
for (Move move : moves) {
moveEvaluations.add(this.threadPool.submit(() -> {
return AlphaBetaThreadCreator.getMoveValue(move, this.searchDepth);
}));
}
for (int i = 0; i < moves.size(); i++) {
this.onProgress.emit((float) i / (float) moves.size());
Move move = moves.get(i);
float value = MIN_FLOAT;
try {
value = moveEvaluations.get(i).get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
if (value > bestMoveValue) {
bestMoveValue = value;
bestMove = move;
}
}
this.onCompleteEval.emit(bestMoveValue);
return bestMove;
}
@Override
public void onGameEnd() {
this.threadPool.close();
}
@Override
protected void play() {
Move move = getBestMove();
sendCommand(new MoveCommand(move));
}
@Override
protected void promote(Coordinate pawnCoords) {
sendCommand(new PromoteCommand(PromoteType.Queen));
}
}

View File

@@ -0,0 +1,27 @@
package chess.ai.minimax;
public class AlphaBetaConsolePrinter {
private final AlphaBetaAI ai;
private long lastTime;
public void connect() {
ai.onStartEval.connect((moveCount) -> {
this.lastTime = System.currentTimeMillis();
System.out.println("Evaluating " + moveCount + " moves ...");
});
ai.onProgress.connect((progress) -> {
System.out.printf("Progress : %.2f %% \r", progress * 100.0f);
});
ai.onCompleteEval.connect((bestMove) -> {
System.out.println("Best move : " + bestMove + " ");
System.out.println("Took " + (System.currentTimeMillis() - this.lastTime) + "ms");
});
}
public AlphaBetaConsolePrinter(AlphaBetaAI ai) {
this.ai = ai;
}
}

View File

@@ -0,0 +1,101 @@
package chess.ai.minimax;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import chess.ai.PieceCost;
import chess.ai.PiecePosCost;
import chess.model.ChessBoard;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
import chess.model.Piece;
public class AlphaBetaThread extends Thread {
private final GameSimulation simulation;
private final PieceCost pieceCost;
private final PiecePosCost piecePosCost;
private static final int GREAT_MOVE = 9999;
private static final float MAX_FLOAT = Float.MAX_VALUE;
private static final float MIN_FLOAT = -MAX_FLOAT;
public AlphaBetaThread(Runnable task, GameSimulation simulation, Color color) {
super(task);
this.simulation = simulation;
this.pieceCost = new PieceCost(color);
this.piecePosCost = new PiecePosCost(color);
}
private float getEndGameEvaluation() {
Color currentTurn = this.simulation.getPlayerTurn();
if (this.simulation.getBoard().isKingInCheck(currentTurn))
return GREAT_MOVE;
return getBoardEvaluation() - PieceCost.PAWN;
}
private float getBoardEvaluation() {
final ChessBoard board = this.simulation.getBoard();
float result = 0;
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
Coordinate coordinate = new Coordinate(i, j);
Piece piece = board.pieceAt(coordinate);
result += pieceCost.getCost(piece) + piecePosCost.getEvaluation(piece, coordinate);
}
}
return result;
}
public float getMoveValue(Move move, int searchDepth) {
this.simulation.tryMove(move);
float value = -negaMax(searchDepth - 1, MIN_FLOAT, MAX_FLOAT);
this.simulation.undoMove();
return value;
}
private float negaMax(int depth, float alpha, float beta) {
float value = MIN_FLOAT;
List<Move> moves = this.simulation.getAllowedMoves();
if (moves.isEmpty())
return -getEndGameEvaluation();
List<Entry<Move, Float>> movesCost = new ArrayList<>(moves.size());
for (Move move : moves) {
this.simulation.tryMove(move);
movesCost.add(Map.entry(move, -getBoardEvaluation()));
this.simulation.undoMove();
}
Collections.sort(movesCost, (first, second) -> {
return Float.compare(first.getValue(), second.getValue());
});
if (depth == 1)
return -movesCost.getFirst().getValue();
for (var moveEntry : movesCost) {
Move move = moveEntry.getKey();
this.simulation.tryMove(move);
value = Float.max(value, -negaMax(depth - 1, -beta, -alpha));
this.simulation.undoMove();
alpha = Float.max(alpha, value);
if (alpha >= beta)
return value;
}
return value;
}
public GameSimulation getSimulation() {
return simulation;
}
}

View File

@@ -0,0 +1,36 @@
package chess.ai.minimax;
import java.util.concurrent.ThreadFactory;
import chess.controller.CommandExecutor;
import chess.model.Color;
import chess.model.Move;
public class AlphaBetaThreadCreator implements ThreadFactory{
private final Color color;
private final GameSimulation simulations[];
private int currentThread = 0;
public AlphaBetaThreadCreator(CommandExecutor commandExecutor, Color color, int threadCount) {
this.color = color;
simulations = new GameSimulation[threadCount];
for (int i = 0; i < threadCount; i++) {
simulations[i] = new GameSimulation();
commandExecutor.addListener(simulations[i]);
}
}
public static float getMoveValue(Move move, int searchDepth) {
AlphaBetaThread t = (AlphaBetaThread) Thread.currentThread();
return t.getMoveValue(move, searchDepth);
}
@Override
public Thread newThread(Runnable r) {
AlphaBetaThread t = new AlphaBetaThread(r, simulations[currentThread], color);
currentThread++;
return t;
}
}

View File

@@ -0,0 +1,103 @@
package chess.ai.minimax;
import java.util.List;
import chess.controller.Command;
import chess.controller.Command.CommandResult;
import chess.controller.CommandExecutor;
import chess.controller.commands.CastlingCommand;
import chess.controller.commands.GetPlayerMovesCommand;
import chess.controller.commands.MoveCommand;
import chess.controller.commands.NewGameCommand;
import chess.controller.commands.PromoteCommand;
import chess.controller.commands.UndoCommand;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.controller.event.EmptyGameDispatcher;
import chess.controller.event.GameAdaptator;
import chess.model.ChessBoard;
import chess.model.Color;
import chess.model.Game;
import chess.model.Move;
import chess.model.PermissiveGame;
public class GameSimulation extends GameAdaptator {
private final CommandExecutor simulation;
private final Game gameSimulation;
public GameSimulation() {
this.gameSimulation = new PermissiveGame();
this.simulation = new CommandExecutor(gameSimulation, new EmptyGameDispatcher());
}
protected CommandResult sendCommand(Command command) {
CommandResult result = this.simulation.executeCommand(command);
if (result == CommandResult.NotAllowed) {
System.out.println("eeeeee");
}
return result;
}
public void tryMove(Move move) {
sendCommand(new MoveCommand(move));
if (this.gameSimulation.getBoard().pawnShouldBePromoted())
sendCommand(new PromoteCommand(PromoteType.Queen));
}
public void undoMove() {
sendCommand(new UndoCommand());
}
@Override
public void onPawnPromoted(PromoteType promotion) {
sendCommand(new PromoteCommand(promotion));
}
@Override
public void onCastling(boolean bigCastling) {
sendCommand(new CastlingCommand(bigCastling));
}
@Override
public void onMove(Move move, boolean captured) {
sendCommand(new MoveCommand(move));
}
@Override
public void onGameStart() {
sendCommand(new NewGameCommand());
}
@Override
public void onPlayerTurn(Color color, boolean undone) {
if (undone)
sendCommand(new UndoCommand());
}
public CommandExecutor getCommandExecutor() {
return simulation;
}
public Game getGame() {
return gameSimulation;
}
public ChessBoard getBoard() {
return this.gameSimulation.getBoard();
}
public Color getPlayerTurn() {
return this.gameSimulation.getPlayerTurn();
}
public List<Move> getAllowedMoves() {
GetPlayerMovesCommand cmd = new GetPlayerMovesCommand();
sendCommand(cmd);
return cmd.getMoves();
}
public void close() {
this.simulation.close();
}
}

View File

@@ -0,0 +1,23 @@
package chess.controller;
import chess.controller.event.GameListener;
import chess.model.Game;
public abstract class Command {
public enum CommandResult {
/**
* The command was successfull. Should update display and switch player turn.
*/
Moved,
/** The command was successfull. Should not update anything */
NotMoved,
/** The command was successfull. Should only update display */
ActionNeeded,
/** The command was not successfull */
NotAllowed;
}
public abstract CommandResult execute(Game game, GameListener outputSystem);
}

View File

@@ -0,0 +1,124 @@
package chess.controller;
import java.util.List;
import chess.controller.Command.CommandResult;
import chess.controller.commands.UndoCommand;
import chess.controller.event.AsyncGameDispatcher;
import chess.controller.event.GameDispatcher;
import chess.controller.event.GameListener;
import chess.model.Game;
import chess.model.Game.GameStatus;
public class CommandExecutor {
private Game game;
private final GameDispatcher dispatcher;
public CommandExecutor() {
this(null);
}
public CommandExecutor(Game game) {
this(game, new AsyncGameDispatcher());
}
public CommandExecutor(Game game, GameDispatcher dispatcher) {
this.game = game;
this.dispatcher = dispatcher;
}
public synchronized CommandResult executeCommand(Command command) {
assert this.game != null : "No input game specified !";
CommandResult result = command.execute(this.game, this.dispatcher);
// non player commands are not supposed to return move result
assert result != CommandResult.Moved || command instanceof PlayerCommand || command instanceof UndoCommand;
processResult(command, result);
if (command instanceof PlayerCommand playerCommand && result != CommandResult.NotAllowed)
this.game.addAction(playerCommand);
return result;
}
public void executeCommands(List<? extends Command> commands) {
for (Command command : commands) {
CommandResult result = executeCommand(command);
assert result != CommandResult.NotAllowed;
}
}
private void processResult(Command command, CommandResult result) {
switch (result) {
case NotAllowed:
case NotMoved:
return;
case ActionNeeded:
this.dispatcher.onBoardUpdate();
return;
case Moved:
boolean notifyPlayerTurn = true;
this.dispatcher.onBoardUpdate();
if (!(command instanceof UndoCommand) && checkGameStatus()) {
this.dispatcher.onGameEnd();
notifyPlayerTurn = false;
}
switchPlayerTurn(command instanceof UndoCommand, notifyPlayerTurn);
return;
}
}
private void switchPlayerTurn(boolean undone, boolean notifyPlayerTurn) {
this.game.switchPlayerTurn();
if (notifyPlayerTurn)
this.dispatcher.onPlayerTurn(this.game.getPlayerTurn(), undone);
}
/**
*
* @return True if the game is over
*/
private boolean checkGameStatus() {
GameStatus gameStatus = this.game.checkGameStatus();
switch (gameStatus) {
case Draw:
this.dispatcher.onDraw();
return true;
case Check:
this.dispatcher.onKingInCheck();
return false;
case CheckMate:
this.dispatcher.onKingInMat();
this.dispatcher.onWin(this.game.getPlayerTurn());
return true;
case OnGoing:
return false;
case Pat:
this.dispatcher.onPatSituation();
return true;
}
return false;
}
public void addListener(GameListener listener) {
this.dispatcher.addListener(listener);
}
public void setGame(Game game) {
this.game = game;
}
public void close() {
this.dispatcher.close();
}
}

View File

@@ -0,0 +1,15 @@
package chess.controller;
import chess.controller.event.GameListener;
import chess.model.Game;
public abstract class PlayerCommand extends Command{
public CommandResult undo(Game game, GameListener outputSystem) {
CommandResult result = undoImpl(game, outputSystem);
game.updateLastMove();
return result;
}
protected abstract CommandResult undoImpl(Game game, GameListener outputSystem);
}

View File

@@ -0,0 +1,71 @@
package chess.controller.commands;
import chess.controller.PlayerCommand;
import chess.controller.event.GameListener;
import chess.model.ChessBoard;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Game;
import chess.model.Move;
public class CastlingCommand extends PlayerCommand {
private Move kingMove;
private Move rookMove;
private final boolean bigCastling;
public CastlingCommand(boolean bigCastling) {
this.bigCastling = bigCastling;
}
@Override
public CommandResult execute(Game game, GameListener outputSystem) {
final ChessBoard board = game.getBoard();
// we must promote the pending pawn before
if (board.pawnShouldBePromoted())
return CommandResult.NotAllowed;
if (bigCastling && !board.canBigCastle(game.getPlayerTurn()))
return CommandResult.NotAllowed;
if (!bigCastling && !board.canSmallCastle(game.getPlayerTurn()))
return CommandResult.NotAllowed;
int rookBeginX = bigCastling ? 0 : 7;
int rookEndX = bigCastling ? 3 : 5;
int kingBeginX = 4;
int kingEndX = bigCastling ? 2 : 6;
int colorLine = game.getPlayerTurn() == Color.White ? 7 : 0;
Coordinate kingCoords = new Coordinate(kingBeginX, colorLine);
Coordinate rookCoords = new Coordinate(rookBeginX, colorLine);
this.kingMove = new Move(kingCoords, new Coordinate(kingEndX, colorLine));
this.rookMove = new Move(rookCoords, new Coordinate(rookEndX, colorLine));
board.applyMove(this.kingMove);
board.applyMove(this.rookMove);
board.setLastMove(this.kingMove);
outputSystem.onCastling(this.bigCastling);
return CommandResult.Moved;
}
public boolean isBigCastling() {
return bigCastling;
}
@Override
protected CommandResult undoImpl(Game game, GameListener outputSystem) {
game.getBoard().undoMove(this.kingMove, null);
game.getBoard().undoMove(this.rookMove, null);
return CommandResult.Moved;
}
}

View File

@@ -0,0 +1,37 @@
package chess.controller.commands;
import chess.controller.Command;
import chess.controller.event.GameListener;
import chess.model.Game;
public class GetAllowedCastlingsCommand extends Command{
public enum CastlingResult {
None, Small, Big, Both;
}
private CastlingResult castlingResult;
public GetAllowedCastlingsCommand() {}
@Override
public CommandResult execute(Game game, GameListener outputSystem) {
boolean canSmallCastle = game.getBoard().canSmallCastle(game.getPlayerTurn());
boolean canBigCastle = game.getBoard().canBigCastle(game.getPlayerTurn());
int result = 0;
if (canSmallCastle)
result += 1;
if (canBigCastle)
result += 2;
this.castlingResult = CastlingResult.values()[result];
return CommandResult.NotMoved;
}
public CastlingResult getCastlingResult() {
return castlingResult;
}
}

View File

@@ -0,0 +1,41 @@
package chess.controller.commands;
import java.util.ArrayList;
import java.util.List;
import chess.controller.Command;
import chess.controller.event.GameListener;
import chess.model.ChessBoard;
import chess.model.Coordinate;
import chess.model.Game;
import chess.model.Piece;
public class GetAllowedMovesPieceCommand extends Command {
private final Coordinate start;
private List<Coordinate> destinations;
public GetAllowedMovesPieceCommand(Coordinate start) {
this.start = start;
this.destinations = new ArrayList<>();
}
@Override
public CommandResult execute(Game game, GameListener outputSystem) {
final ChessBoard board = game.getBoard();
Piece piece = board.pieceAt(start);
if (piece == null)
return CommandResult.NotAllowed;
if (piece.getColor() != game.getPlayerTurn())
return CommandResult.NotAllowed;
this.destinations = board.getAllowedMoves(start);
return CommandResult.NotMoved;
}
public List<Coordinate> getDestinations() {
return destinations;
}
}

View File

@@ -0,0 +1,33 @@
package chess.controller.commands;
import chess.controller.Command;
import chess.controller.event.GameListener;
import chess.model.Coordinate;
import chess.model.Game;
import chess.model.Piece;
public class GetPieceAtCommand extends Command{
private final Coordinate pieceCoords;
private Piece piece;
public GetPieceAtCommand(Coordinate pieceCoords) {
this.pieceCoords = pieceCoords;
this.piece = null;
}
@Override
public CommandResult execute(Game game, GameListener outputSystem) {
if (!pieceCoords.isValid())
return CommandResult.NotAllowed;
this.piece = game.getBoard().pieceAt(pieceCoords);
return CommandResult.NotMoved;
}
public Piece getPiece() {
return piece;
}
}

View File

@@ -0,0 +1,24 @@
package chess.controller.commands;
import java.util.List;
import chess.controller.Command;
import chess.controller.event.GameListener;
import chess.model.Game;
import chess.model.Move;
public class GetPlayerMovesCommand extends Command {
private List<Move> moves;
@Override
public CommandResult execute(Game game, GameListener outputSystem) {
this.moves = game.getBoard().getAllowedMoves(game.getPlayerTurn());
return CommandResult.NotMoved;
}
public List<Move> getMoves() {
return moves;
}
}

View File

@@ -0,0 +1,107 @@
package chess.controller.commands;
import chess.controller.PlayerCommand;
import chess.controller.event.GameListener;
import chess.model.ChessBoard;
import chess.model.Coordinate;
import chess.model.Game;
import chess.model.Move;
import chess.model.Piece;
import chess.model.visitor.PiecePathChecker;
public class MoveCommand extends PlayerCommand {
private final Move move;
private Piece deadPiece;
public MoveCommand(Move move) {
this.move = move;
this.deadPiece = null;
}
public Move getMove() {
return move;
}
public Piece getDeadPiece() {
return deadPiece;
}
@Override
public CommandResult execute(Game game, GameListener outputSystem) {
CommandResult result = processMove(game, outputSystem);
switch (result) {
case NotAllowed:
outputSystem.onMoveNotAllowed(this.move);
return result;
case Moved:
game.saveTraitPiecesPos();
return result;
case ActionNeeded:
case NotMoved:
return result;
}
return null;
}
private CommandResult processMove(Game game, GameListener outputSystem) {
final ChessBoard board = game.getBoard();
// we must promote the pending pawn before
if (game.pawnShouldBePromoted())
return CommandResult.NotAllowed;
Piece piece = board.pieceAt(move.getStart());
if (piece == null)
return CommandResult.NotAllowed;
if (piece.getColor() != game.getPlayerTurn())
return CommandResult.NotAllowed;
boolean valid = new PiecePathChecker(board, move).isValid();
if (!valid)
return CommandResult.NotAllowed;
this.deadPiece = board.pieceAt(move.getDeadPieceCoords());
board.applyMove(move);
if (board.isKingInCheck(game.getPlayerTurn())) {
board.undoLastMove();
return CommandResult.NotAllowed;
}
if (tryPromote(game, outputSystem)) {
outputSystem.onMove(this.move, this.deadPiece != null);
return CommandResult.ActionNeeded;
}
board.setLastMove(this.move);
outputSystem.onMove(this.move, this.deadPiece != null);
return CommandResult.Moved;
}
@Override
protected CommandResult undoImpl(Game game, GameListener outputSystem) {
final ChessBoard board = game.getBoard();
game.undoTraitPiecesPos();
board.undoMove(move, deadPiece);
return CommandResult.Moved;
}
private boolean tryPromote(Game game, GameListener outputSystem) {
Coordinate pawnPos = game.getBoard().pawnPromotePosition();
if (pawnPos == null)
return false;
outputSystem.onPromotePawn(pawnPos);
return true;
}
}

View File

@@ -0,0 +1,58 @@
package chess.controller.commands;
import chess.controller.Command;
import chess.controller.event.GameListener;
import chess.model.ChessBoard;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Game;
import chess.model.pieces.Bishop;
import chess.model.pieces.King;
import chess.model.pieces.Knight;
import chess.model.pieces.Pawn;
import chess.model.pieces.Queen;
import chess.model.pieces.Rook;
public class NewGameCommand extends Command {
public CommandResult execute(Game game, GameListener outputSystem) {
final ChessBoard board = game.getBoard();
board.clearBoard();
for (int i = 0; i < 8; i++) {
board.pieceComes(new Pawn(Color.Black), new Coordinate(i, 1));
board.pieceComes(new Pawn(Color.White), new Coordinate(i, Coordinate.VALUE_MAX - 2));
}
board.pieceComes(new Rook(Color.Black), new Coordinate(0, 0));
board.pieceComes(new Rook(Color.Black), new Coordinate(Coordinate.VALUE_MAX - 1, 0));
board.pieceComes(new Rook(Color.White), new Coordinate(0, Coordinate.VALUE_MAX - 1));
board.pieceComes(new Rook(Color.White), new Coordinate(Coordinate.VALUE_MAX - 1, Coordinate.VALUE_MAX - 1));
board.pieceComes(new Knight(Color.Black), new Coordinate(1, 0));
board.pieceComes(new Knight(Color.Black), new Coordinate(Coordinate.VALUE_MAX - 2, 0));
board.pieceComes(new Knight(Color.White), new Coordinate(1, Coordinate.VALUE_MAX - 1));
board.pieceComes(new Knight(Color.White), new Coordinate(Coordinate.VALUE_MAX - 2, Coordinate.VALUE_MAX - 1));
board.pieceComes(new Bishop(Color.Black), new Coordinate(2, 0));
board.pieceComes(new Bishop(Color.Black), new Coordinate(Coordinate.VALUE_MAX - 3, 0));
board.pieceComes(new Bishop(Color.White), new Coordinate(2, Coordinate.VALUE_MAX - 1));
board.pieceComes(new Bishop(Color.White), new Coordinate(Coordinate.VALUE_MAX - 3, Coordinate.VALUE_MAX - 1));
board.pieceComes(new Queen(Color.Black), new Coordinate(3, 0));
board.pieceComes(new King(Color.Black), new Coordinate(4, 0));
board.pieceComes(new Queen(Color.White), new Coordinate(3, Coordinate.VALUE_MAX - 1));
board.pieceComes(new King(Color.White), new Coordinate(4, Coordinate.VALUE_MAX - 1));
game.reset();
outputSystem.onGameStart();
outputSystem.onPlayerTurn(game.getPlayerTurn(), false);
return CommandResult.NotMoved;
}
}

View File

@@ -0,0 +1,107 @@
package chess.controller.commands;
import chess.controller.PlayerCommand;
import chess.controller.event.GameListener;
import chess.model.ChessBoard;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Game;
import chess.model.Piece;
import chess.model.pieces.Bishop;
import chess.model.pieces.Knight;
import chess.model.pieces.Queen;
import chess.model.pieces.Rook;
import chess.model.visitor.PawnIdentifier;
public class PromoteCommand extends PlayerCommand {
public enum PromoteType {
Queen,
Rook,
Bishop,
Knight
}
private final PromoteType promoteType;
private Coordinate pieceCoords;
private Piece oldPawn;
public PromoteCommand(PromoteType promoteType) {
this.promoteType = promoteType;
assert this.promoteType != null;
this.pieceCoords = null;
this.oldPawn = null;
}
@Override
public CommandResult execute(Game game, GameListener outputSystem) {
final ChessBoard board = game.getBoard();
this.pieceCoords = board.pawnPromotePosition();
if (this.pieceCoords == null)
return CommandResult.NotAllowed;
Piece pawn = board.pieceAt(this.pieceCoords);
if (!new PawnIdentifier(game.getPlayerTurn()).isPawn(pawn))
return CommandResult.NotAllowed;
int destY = this.pieceCoords.getY();
int enemyLine = pawn.getColor() == Color.White ? 0 : 7;
if (destY != enemyLine)
return CommandResult.NotAllowed;
this.oldPawn = pawn;
board.pieceComes(createPiece(this.promoteType, pawn.getColor()), this.pieceCoords);
outputSystem.onPawnPromoted(this.promoteType);
// invalidate the last move cache
board.setLastMove(null);
return CommandResult.Moved;
}
private Piece createPiece(PromoteType promoteType, Color color) {
switch (promoteType) {
case Queen:
return new Queen(color);
case Bishop:
return new Bishop(color);
case Knight:
return new Knight(color);
case Rook:
return new Rook(color);
default:
return null;
}
}
@Override
protected CommandResult undoImpl(Game game, GameListener outputSystem) {
final ChessBoard board = game.getBoard();
Piece promoted = board.pieceAt(this.pieceCoords);
assert promoted != null;
game.undoTraitPiecesPos();
board.pieceComes(this.oldPawn, this.pieceCoords);
game.getLastAction().undo(game, outputSystem);
return CommandResult.Moved;
}
public PromoteType getPromoteType() {
return promoteType;
}
}

View File

@@ -0,0 +1,24 @@
package chess.controller.commands;
import chess.controller.Command;
import chess.controller.event.GameListener;
import chess.model.Color;
import chess.model.Game;
public class SurrenderCommand extends Command {
private final Color player;
public SurrenderCommand(Color player) {
this.player = player;
}
@Override
public CommandResult execute(Game game, GameListener outputSystem) {
outputSystem.onSurrender(player);
outputSystem.onWin(Color.getEnemy(player));
outputSystem.onGameEnd();
return CommandResult.NotMoved;
}
}

View File

@@ -0,0 +1,19 @@
package chess.controller.commands;
import chess.controller.Command;
import chess.controller.PlayerCommand;
import chess.controller.event.GameListener;
import chess.model.Game;
public class UndoCommand extends Command{
@Override
public CommandResult execute(Game game, GameListener outputSystem) {
PlayerCommand lastAction = game.getLastAction();
if (lastAction == null)
return CommandResult.NotAllowed;
return lastAction.undo(game, outputSystem);
}
}

View File

@@ -0,0 +1,113 @@
package chess.controller.event;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
public class AsyncGameDispatcher extends GameDispatcher {
private final List<GameListener> listeners;
private final ExecutorService executor;
public AsyncGameDispatcher() {
this.listeners = new ArrayList<>();
this.executor = Executors.newSingleThreadExecutor();
}
@Override
public void addListener(GameListener listener) {
this.listeners.add(listener);
}
private void asyncForEachCall(Consumer<GameListener> func) {
this.executor.execute(() -> this.listeners.forEach(func));
}
@Override
public void onPlayerTurn(Color color, boolean undone) {
asyncForEachCall((l) -> l.onPlayerTurn(color, undone));
}
@Override
public void onWin(Color color) {
asyncForEachCall((l) -> l.onWin(color));
}
@Override
public void onKingInCheck() {
asyncForEachCall((l) -> l.onKingInCheck());
}
@Override
public void onKingInMat() {
asyncForEachCall((l) -> l.onKingInMat());
}
@Override
public void onPatSituation() {
asyncForEachCall((l) -> l.onPatSituation());
}
@Override
public void onSurrender(Color color) {
asyncForEachCall((l) -> l.onSurrender(color));
}
@Override
public void onGameStart() {
asyncForEachCall((l) -> l.onGameStart());
}
@Override
public void onPromotePawn(Coordinate pieceCoords) {
asyncForEachCall((l) -> l.onPromotePawn(pieceCoords));
}
@Override
public void onBoardUpdate() {
asyncForEachCall((l) -> l.onBoardUpdate());
}
@Override
public void onGameEnd() {
asyncForEachCall((l) -> l.onGameEnd());
}
@Override
public void onMove(Move move, boolean captured) {
asyncForEachCall((l) -> l.onMove(move, captured));
}
@Override
public void onMoveNotAllowed(Move move) {
asyncForEachCall((l) -> l.onMoveNotAllowed(move));
}
@Override
public void onDraw() {
asyncForEachCall((l) -> l.onDraw());
}
@Override
public void onCastling(boolean bigCastling) {
asyncForEachCall((l) -> l.onCastling(bigCastling));
}
@Override
public void onPawnPromoted(PromoteType promotion) {
asyncForEachCall((l) -> l.onPawnPromoted(promotion));
}
@Override
public void close() {
this.executor.shutdown();
}
}

View File

@@ -0,0 +1,78 @@
package chess.controller.event;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
public class EmptyGameDispatcher extends GameDispatcher {
@Override
public void onBoardUpdate() {
}
@Override
public void onDraw() {
}
@Override
public void onGameEnd() {
}
@Override
public void onGameStart() {
}
@Override
public void onKingInCheck() {
}
@Override
public void onKingInMat() {
}
@Override
public void onMove(Move move, boolean captured) {
}
@Override
public void onMoveNotAllowed(Move move) {
}
@Override
public void onPatSituation() {
}
@Override
public void onPlayerTurn(Color color, boolean undone) {
}
@Override
public void onPromotePawn(Coordinate pieceCoords) {
}
@Override
public void onSurrender(Color coward) {
}
@Override
public void onWin(Color winner) {
}
@Override
public void onCastling(boolean bigCastling) {
}
@Override
public void onPawnPromoted(PromoteType promotion) {
}
@Override
public void addListener(GameListener listener) {
}
@Override
public void close() {
}
}

View File

@@ -0,0 +1,55 @@
package chess.controller.event;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
public abstract class GameAdaptator implements GameListener {
@Override
public void onPlayerTurn(Color color, boolean undone) {}
@Override
public void onWin(Color color) {}
@Override
public void onKingInCheck() {}
@Override
public void onKingInMat() {}
@Override
public void onPatSituation() {}
@Override
public void onSurrender(Color color) {}
@Override
public void onGameStart() {}
@Override
public void onPromotePawn(Coordinate pieceCoords) {}
@Override
public void onBoardUpdate() {}
@Override
public void onGameEnd() {}
@Override
public void onMove(Move move, boolean captured) {}
@Override
public void onMoveNotAllowed(Move move) {}
@Override
public void onDraw() {}
@Override
public void onCastling(boolean bigCastling) {}
@Override
public void onPawnPromoted(PromoteType promotion) {}
}

View File

@@ -0,0 +1,9 @@
package chess.controller.event;
public abstract class GameDispatcher extends GameAdaptator {
public abstract void addListener(GameListener listener);
public abstract void close();
}

View File

@@ -0,0 +1,104 @@
package chess.controller.event;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
public interface GameListener {
/**
* Invoked when the display of the board should be updated
*/
void onBoardUpdate();
/**
* Invoked when a draw occurs (same position is repeated three times)
*/
void onDraw();
/**
* Invoked when the game has ended (by a win or a draw)
*/
void onGameEnd();
/**
* Invoked when the game has started
*/
void onGameStart();
/**
* Invoked when a king is in check
*/
void onKingInCheck();
/**
* Invoked when a checkmate occurs
*/
void onKingInMat();
/**
* Invoked when a valid move on the board occurs
*
* @param move the move to be processed
* @param captured whether the move is a result of a capture
*/
void onMove(Move move, boolean captured);
/**
* Invoked when a sent move is not allowed
*
* @param move the move to be processed
*/
void onMoveNotAllowed(Move move);
/**
* Invoked when a pat situation occurs
*/
void onPatSituation();
/**
* Invoked when it's the player turn
*
* @param color the color of the player who should play
* @param undone true if it's a result of an undo command
*/
void onPlayerTurn(Color color, boolean undone);
/**
* Invoked when a pawn should be promoted
*
* @param pieceCoords the coordinates of the pawn
*/
void onPromotePawn(Coordinate pieceCoords);
/**
* Invoked when a players surrenders
*
* @param coward the player who gave up
*/
void onSurrender(Color coward);
/**
* Invoked when a player wins (by checkmate or if the other one surrenders)
*
* @param winner
*/
void onWin(Color winner);
/**
* Invoked when a castling is done
*
* @param bigCastling if it's queen side castling
*/
void onCastling(boolean bigCastling);
/**
* Invoked when a pawn is promoted
*
* @param promotion the type of promotion
* @param player the player who promoted the pawns
*/
void onPawnPromoted(PromoteType promotion);
}

View File

@@ -0,0 +1,343 @@
package chess.model;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import chess.model.pieces.King;
import chess.model.visitor.PawnIdentifier;
import chess.model.visitor.PiecePathChecker;
public class ChessBoard {
public static class Cell {
private Piece piece;
public Cell() {
this.piece = null;
}
public Piece getPiece() {
return piece;
}
public void setPiece(Piece piece) {
this.piece = piece;
}
}
private final Cell[][] cells;
private Move lastVirtualMove;
private Move lastMove;
private Piece lastEjectedPiece;
private List<Move> cachedAllowedMoves = null;
private final Coordinate kingPos[];
public ChessBoard() {
this.cells = new Cell[Coordinate.VALUE_MAX][Coordinate.VALUE_MAX];
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
this.cells[i][j] = new Cell();
}
}
this.lastVirtualMove = null;
this.lastMove = null;
this.lastEjectedPiece = null;
this.kingPos = new Coordinate[Color.values().length];
}
public void applyMove(Move move) {
assert move.isValid() : "Invalid move !";
Piece deadPiece = pieceAt(move.getDeadPieceCoords());
if (deadPiece != null) {
this.lastEjectedPiece = deadPiece;
} else {
this.lastEjectedPiece = null;
}
Piece movingPiece = pieceAt(move.getStart());
pieceLeaves(move.getDeadPieceCoords());
pieceLeaves(move.getStart());
pieceComes(movingPiece, move.getFinish());
movingPiece.move();
this.lastVirtualMove = move;
}
public void undoLastMove() {
assert this.lastVirtualMove != null : "Can't undo at the beginning!";
undoMove(this.lastVirtualMove, this.lastEjectedPiece);
}
public void undoMove(Move move, Piece deadPiece) {
Piece movingPiece = pieceAt(move.getFinish());
pieceComes(movingPiece, move.getStart());
pieceLeaves(move.getFinish());
pieceComes(deadPiece, move.getDeadPieceCoords());
assert movingPiece != null;
movingPiece.unMove();
}
public boolean isCellEmpty(Coordinate coordinate) {
return pieceAt(coordinate) == null;
}
public Piece pieceAt(Coordinate coordinate) {
if (!coordinate.isValid())
return null;
return cellAt(coordinate).getPiece();
}
public void clearBoard() {
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
pieceLeaves(new Coordinate(i, j));
}
}
}
private Cell cellAt(Coordinate coordinate) {
return this.cells[coordinate.getX()][coordinate.getY()];
}
public void pieceComes(Piece piece, Coordinate coordinate) {
cellAt(coordinate).setPiece(piece);
if (piece instanceof King)
this.kingPos[piece.getColor().ordinal()] = coordinate;
}
public void pieceLeaves(Coordinate coordinate) {
cellAt(coordinate).setPiece(null);
}
public Coordinate findKing(Color color) {
return kingPos[color.ordinal()];
}
public List<Coordinate> getAllowedStarts(Coordinate finish, Color color) {
List<Coordinate> starts = new ArrayList<>();
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
Coordinate attackCoords = new Coordinate(i, j);
Piece attackPiece = pieceAt(attackCoords);
if (attackPiece == null || attackPiece.getColor() != color)
continue;
Move move = new Move(attackCoords, finish);
PiecePathChecker piecePathChecker = new PiecePathChecker(this,
move);
if (!piecePathChecker.isValid())
continue;
applyMove(move);
if (!isKingInCheck(color))
starts.add(attackCoords);
undoLastMove();
}
}
return starts;
}
public boolean isKingInCheck(Color color) {
Coordinate kingPos = findKing(color);
assert kingPos.isValid() : "King position is invalid!";
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
Coordinate attackCoords = new Coordinate(i, j);
Piece attackPiece = pieceAt(attackCoords);
if (attackPiece == null || attackPiece.getColor() == color)
continue;
PiecePathChecker checker = new PiecePathChecker(this, new Move(attackCoords, kingPos));
if (checker.isValid())
return true;
}
}
return false;
}
public boolean hasAllowedMoves(Color player) {
return !getAllowedMoves(player).isEmpty();
}
public List<Move> getAllowedMoves(Color player) {
if (this.cachedAllowedMoves != null) {
return this.cachedAllowedMoves;
}
List<Move> result = new ArrayList<>();
for (int x = 0; x < Coordinate.VALUE_MAX; x++) {
for (int y = 0; y < Coordinate.VALUE_MAX; y++) {
Coordinate start = new Coordinate(x, y);
Piece piece = pieceAt(start);
if (piece == null || piece.getColor() != player)
continue;
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
Coordinate destination = new Coordinate(i, j);
Move move = new Move(start, destination);
Piece destPiece = pieceAt(destination);
if (destPiece != null && destPiece.getColor() == player)
continue;
PiecePathChecker piecePathChecker = new PiecePathChecker(this,
move);
if (!piecePathChecker.isValid())
continue;
applyMove(move);
if (!isKingInCheck(player))
result.add(move);
undoLastMove();
}
}
}
}
this.cachedAllowedMoves = result;
return result;
}
public List<Coordinate> getAllowedMoves(Coordinate pieceCoords) {
Piece piece = pieceAt(pieceCoords);
if (piece == null)
return null;
Color player = piece.getColor();
List<Coordinate> result = new ArrayList<>();
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
Coordinate destination = new Coordinate(i, j);
Move move = new Move(pieceCoords, destination);
PiecePathChecker piecePathChecker = new PiecePathChecker(this,
move);
if (!piecePathChecker.isValid())
continue;
applyMove(move);
if (!isKingInCheck(player))
result.add(destination);
undoLastMove();
}
}
return result;
}
private boolean canCastle(Color color, int rookX, Direction kingDirection) {
if (isKingInCheck(color))
return false;
int colorLine = color == Color.White ? 7 : 0;
Coordinate kingCoords = new Coordinate(4, colorLine);
Coordinate rookCoords = new Coordinate(rookX, colorLine);
Piece king = pieceAt(kingCoords);
Piece rook = pieceAt(rookCoords);
if (king == null || rook == null || king.hasMoved() || rook.hasMoved())
return false;
for (int step = 1; step <= 2; step++) {
Coordinate dest = Coordinate.fromIndex(kingCoords.toIndex() + step * kingDirection.getIndexOffset());
Piece obstacle = pieceAt(dest);
if (obstacle != null)
return false;
applyMove(new Move(kingCoords, dest));
if (isKingInCheck(color)) {
undoLastMove();
return false;
}
undoLastMove();
}
Coordinate rookObstacleCoords = Coordinate.fromIndex(rookCoords.toIndex() - kingDirection.getIndexOffset());
Piece obstacle = pieceAt(rookObstacleCoords);
return obstacle == null;
}
public boolean canSmallCastle(Color color) {
return canCastle(color, 7, Direction.Right);
}
public boolean canBigCastle(Color color) {
return canCastle(color, 0, Direction.Left);
}
public boolean pawnShouldBePromoted() {
return pawnPromotePosition() != null;
}
/**
*
* @return Null if there is no pawn to promote
*/
public Coordinate pawnPromotePosition() {
Coordinate piecePos = pawnPromotePosition(Color.White);
if (piecePos != null)
return piecePos;
return pawnPromotePosition(Color.Black);
}
/**
*
* @return Null if there is no pawn to promote
*/
private Coordinate pawnPromotePosition(Color color) {
int enemyLineY = color == Color.White ? 0 : 7;
PawnIdentifier identifier = new PawnIdentifier(color);
for (int x = 0; x < Coordinate.VALUE_MAX; x++) {
Coordinate pieceCoords = new Coordinate(x, enemyLineY);
Piece piece = pieceAt(pieceCoords);
if (identifier.isPawn(piece))
return pieceCoords;
}
return null;
}
@Override
public int hashCode() {
int result = 0;
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
Piece piece = pieceAt(new Coordinate(i, j));
if (piece == null)
continue;
result = Objects.hash(result, piece.getColor(), new Coordinate(i, j), piece);
}
}
return result;
}
public Move getLastMove() {
return this.lastMove;
}
public void setLastMove(Move lastMove) {
this.lastMove = lastMove;
this.cachedAllowedMoves = null;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ChessBoard board)
return board.hashCode() == this.hashCode();
return false;
}
}

View File

@@ -0,0 +1,10 @@
package chess.model;
public enum Color {
White,
Black;
public static Color getEnemy(Color color) {
return color == White ? Black : White;
}
}

View File

@@ -0,0 +1,52 @@
package chess.model;
import java.util.Objects;
public class Coordinate {
private final int x;
private final int y;
public static int VALUE_MAX = 8;
public Coordinate(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public boolean isValid() {
return 0 <= this.x && this.x < VALUE_MAX && 0 <= this.y && this.y < VALUE_MAX;
}
public static Coordinate fromIndex(int index) {
return new Coordinate(index % VALUE_MAX, index / VALUE_MAX);
}
public int toIndex() {
return this.y * VALUE_MAX + this.x;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Coordinate coo) {
return this.x == coo.x && this.y == coo.y;
}
return false;
}
public String toString() {
return "(" + this.x + ", " + this.y + ")";
}
@Override
public int hashCode() {
return Objects.hash(this.x, this.y);
}
}

View File

@@ -0,0 +1,54 @@
package chess.model;
public enum Direction {
Unset(65),
Front(-8), Back(8), Left(-1), Right(1),
FrontLeft(-9), FrontRight(-7), BackLeft(7), BackRight(9);
private final int indexOffset;
Direction(int indexOffset) {
this.indexOffset = indexOffset;
}
public int getIndexOffset() {
return indexOffset;
}
public static Direction fromInt(int direction) {
for (Direction dir : Direction.values()) {
if (dir.getIndexOffset() == direction)
return dir;
}
return null;
}
public static Direction findDirection(Move move) {
assert move.isValid() : "Move is invalid!";
int diffX = move.getFinish().getX() - move.getStart().getX();
int diffY = move.getFinish().getY() - move.getStart().getY();
if (diffX == 0 && diffY < 0)
return Direction.Front;
if (diffX == 0 && diffY > 0)
return Direction.Back;
if (diffX < 0 && diffY == 0)
return Direction.Left;
if (diffX > 0 && diffY == 0)
return Direction.Right;
if (diffX < 0 && -diffX == diffY)
return Direction.BackLeft;
if (diffX > 0 && diffX == diffY)
return Direction.BackRight;
if (diffY < 0 && diffX == diffY)
return Direction.FrontLeft;
if (diffX > 0 && diffX == -diffY)
return Direction.FrontRight;
return Direction.Unset;
}
}

View File

@@ -0,0 +1,129 @@
package chess.model;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import chess.controller.PlayerCommand;
import chess.controller.commands.MoveCommand;
public class Game {
private final ChessBoard board;
private Color playerTurn;
private final Stack<PlayerCommand> movesHistory;
private final Map<Integer, Integer> traitsPos;
private static final int DRAW_REPETITONS = 3;
public enum GameStatus {
Draw, Check, CheckMate, OnGoing, Pat;
}
public Game() {
this.board = new ChessBoard();
this.movesHistory = new Stack<>();
this.traitsPos = new HashMap<>();
}
public ChessBoard getBoard() {
return board;
}
public Color getPlayerTurn() {
return playerTurn;
}
public void reset() {
resetPlayerTurn();
this.traitsPos.clear();
}
public void resetPlayerTurn() {
this.playerTurn = Color.White;
}
/**
*
* @param color the current player turn
* @return true if a draw should be declared
*/
public void saveTraitPiecesPos() {
int piecesHash = this.board.hashCode();
Integer count = this.traitsPos.get(piecesHash);
this.traitsPos.put(piecesHash, count == null ? 1 : count + 1);
}
/**
* @return whether the game is in a draw situation
*/
private boolean checkDraw() {
return this.traitsPos.containsValue(DRAW_REPETITONS);
}
/**
*
* @return true if a draw should occur
*/
public void switchPlayerTurn() {
playerTurn = Color.getEnemy(playerTurn);
}
// this is the bottleneck of algorithms using this chess engine
public GameStatus checkGameStatus(Color color) {
if (checkDraw())
return GameStatus.Draw;
if (this.board.isKingInCheck(color))
if (this.board.hasAllowedMoves(color))
return GameStatus.Check;
else
return GameStatus.CheckMate;
if (!board.hasAllowedMoves(color))
return GameStatus.Pat;
return GameStatus.OnGoing;
}
public GameStatus checkGameStatus() {
return checkGameStatus(Color.getEnemy(getPlayerTurn()));
}
public void addAction(PlayerCommand command) {
this.movesHistory.add(command);
}
public PlayerCommand getLastAction() {
if (this.movesHistory.isEmpty())
return null;
return this.movesHistory.pop();
}
public void updateLastMove() {
if (this.movesHistory.isEmpty())
return;
PlayerCommand last = this.movesHistory.getLast();
if (last instanceof MoveCommand move) {
this.board.setLastMove(move.getMove());
}
}
public void undoTraitPiecesPos() {
int piecesHash = this.board.hashCode();
Integer count = this.traitsPos.get(piecesHash);
if (count != null)
this.traitsPos.put(piecesHash, count - 1);
}
public List<PlayerCommand> getMoves() {
return this.movesHistory;
}
public boolean pawnShouldBePromoted() {
return this.board.pawnShouldBePromoted();
}
}

View File

@@ -0,0 +1,63 @@
package chess.model;
public class Move {
private final Coordinate start;
private final Coordinate finish;
private Coordinate deadPieceCoords;
public Move(Coordinate start, Coordinate finish) {
this.start = start;
this.finish = finish;
this.deadPieceCoords = finish;
}
public boolean isValid() {
return this.start.isValid() && this.finish.isValid() && !this.start.equals(this.finish);
}
public Coordinate getStart() {
return start;
}
public Coordinate getFinish() {
return finish;
}
public int traversedCells() {
assert isValid() : "Move is invalid!";
int diffX = getFinish().getX() - getStart().getX();
int diffY = getFinish().getY() - getStart().getY();
assert Math.abs(diffX) < Coordinate.VALUE_MAX : "Move is too big!";
assert Math.abs(diffY) < Coordinate.VALUE_MAX : "Move is too big!";
if (diffX == 0)
return Math.abs(diffY);
if (diffY == 0)
return Math.abs(diffX);
if (Math.abs(diffX) == Math.abs(diffY))
return Math.abs(diffX);
return 0;
}
public Coordinate getMiddle() {
return Coordinate.fromIndex((getStart().toIndex() + getFinish().toIndex()) / 2);
}
public void setDeadPieceCoords(Coordinate deadCoords) {
this.deadPieceCoords = deadCoords;
}
public Coordinate getDeadPieceCoords() {
return deadPieceCoords;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Move other)
return this.start.equals(other.start) && this.finish.equals(other.finish);
return false;
}
}

View File

@@ -0,0 +1,20 @@
package chess.model;
public class PermissiveGame extends Game {
@Override
public GameStatus checkGameStatus(Color color) {
return GameStatus.OnGoing;
}
@Override
public void undoTraitPiecesPos() {
}
@Override
public void saveTraitPiecesPos() {
}
}

View File

@@ -0,0 +1,34 @@
package chess.model;
public abstract class Piece {
private final Color color;
private int moved;
public Piece(Color color) {
this.color = color;
this.moved = 0;
}
public void move() {
this.moved++;
}
public Color getColor() {
return color;
}
public boolean hasMoved() {
return moved > 0;
}
public void unMove() {
this.moved--;
}
public abstract <T> T accept(PieceVisitor<T> visitor);
@Override
public abstract boolean equals(Object other);
}

View File

@@ -0,0 +1,28 @@
package chess.model;
import chess.model.pieces.Bishop;
import chess.model.pieces.King;
import chess.model.pieces.Knight;
import chess.model.pieces.Pawn;
import chess.model.pieces.Queen;
import chess.model.pieces.Rook;
public interface PieceVisitor<T> {
default T visit(Piece piece) {
return piece.accept(this);
}
T visitPiece(Bishop bishop);
T visitPiece(King king);
T visitPiece(Knight knight);
T visitPiece(Pawn pawn);
T visitPiece(Queen queen);
T visitPiece(Rook rook);
}

View File

@@ -0,0 +1,27 @@
package chess.model.pieces;
import chess.model.Color;
import chess.model.Piece;
import chess.model.PieceVisitor;
public class Bishop extends Piece {
public Bishop(Color color) {
super(color);
}
@Override
public <T> T accept(PieceVisitor<T> visitor) {
return visitor.visitPiece(this);
}
@Override
public int hashCode() {
return 0;
}
public boolean equals(Object obj) {
return (obj instanceof Bishop && ((Bishop) obj).getColor() == this.getColor());
}
}

View File

@@ -0,0 +1,26 @@
package chess.model.pieces;
import chess.model.Color;
import chess.model.Piece;
import chess.model.PieceVisitor;
public class King extends Piece {
public King(Color color) {
super(color);
}
@Override
public <T> T accept(PieceVisitor<T> visitor) {
return visitor.visitPiece(this);
}
@Override
public int hashCode() {
return 1;
}
public boolean equals(Object obj) {
return (obj instanceof King && ((King) obj).getColor() == this.getColor());
}
}

View File

@@ -0,0 +1,26 @@
package chess.model.pieces;
import chess.model.Color;
import chess.model.Piece;
import chess.model.PieceVisitor;
public class Knight extends Piece {
public Knight(Color color) {
super(color);
}
@Override
public <T> T accept(PieceVisitor<T> visitor) {
return visitor.visitPiece(this);
}
@Override
public int hashCode() {
return 2;
}
public boolean equals(Object obj) {
return (obj instanceof Knight && ((Knight) obj).getColor() == this.getColor());
}
}

View File

@@ -0,0 +1,30 @@
package chess.model.pieces;
import chess.model.Color;
import chess.model.Piece;
import chess.model.PieceVisitor;
public class Pawn extends Piece {
public Pawn(Color color) {
super(color);
}
@Override
public <T> T accept(PieceVisitor<T> visitor) {
return visitor.visitPiece(this);
}
public int multiplier() {
return getColor() == Color.White ? 1 : -1;
}
@Override
public int hashCode() {
return 3;
}
public boolean equals(Object obj) {
return (obj instanceof Pawn && ((Pawn) obj).getColor() == this.getColor());
}
}

View File

@@ -0,0 +1,26 @@
package chess.model.pieces;
import chess.model.Color;
import chess.model.Piece;
import chess.model.PieceVisitor;
public class Queen extends Piece {
public Queen(Color color) {
super(color);
}
@Override
public <T> T accept(PieceVisitor<T> visitor) {
return visitor.visitPiece(this);
}
@Override
public int hashCode() {
return 4;
}
public boolean equals(Object obj) {
return (obj instanceof Queen && ((Queen) obj).getColor() == this.getColor());
}
}

View File

@@ -0,0 +1,26 @@
package chess.model.pieces;
import chess.model.Color;
import chess.model.Piece;
import chess.model.PieceVisitor;
public class Rook extends Piece {
public Rook(Color color) {
super(color);
}
@Override
public <T> T accept(PieceVisitor<T> visitor) {
return visitor.visitPiece(this);
}
@Override
public int hashCode() {
return 5;
}
public boolean equals(Object other) {
return (other instanceof Rook && ((Rook) other).getColor() == this.getColor());
}
}

View File

@@ -0,0 +1,52 @@
package chess.model.visitor;
import chess.model.Color;
import chess.model.Piece;
import chess.model.PieceVisitor;
import chess.model.pieces.*;
public class KingIdentifier implements PieceVisitor<Boolean> {
private final Color color;
public KingIdentifier(Color color) {
this.color = color;
}
public boolean isKing(Piece piece) {
if (piece == null)
return false;
return visit(piece);
}
@Override
public Boolean visitPiece(Bishop bishop) {
return false;
}
@Override
public Boolean visitPiece(King king) {
return king.getColor() == color;
}
@Override
public Boolean visitPiece(Knight knight) {
return false;
}
@Override
public Boolean visitPiece(Pawn pawn) {
return false;
}
@Override
public Boolean visitPiece(Queen queen) {
return false;
}
@Override
public Boolean visitPiece(Rook rook) {
return false;
}
}

View File

@@ -0,0 +1,52 @@
package chess.model.visitor;
import chess.model.Color;
import chess.model.Piece;
import chess.model.PieceVisitor;
import chess.model.pieces.*;
public class PawnIdentifier implements PieceVisitor<Boolean> {
private final Color color;
public PawnIdentifier(Color color) {
this.color = color;
}
public boolean isPawn(Piece piece) {
if (piece == null)
return false;
return visit(piece);
}
@Override
public Boolean visitPiece(Bishop bishop) {
return false;
}
@Override
public Boolean visitPiece(King king) {
return false;
}
@Override
public Boolean visitPiece(Knight knight) {
return false;
}
@Override
public Boolean visitPiece(Pawn pawn) {
return pawn.getColor() == color;
}
@Override
public Boolean visitPiece(Queen queen) {
return false;
}
@Override
public Boolean visitPiece(Rook rook) {
return false;
}
}

View File

@@ -0,0 +1,119 @@
package chess.model.visitor;
import chess.model.Coordinate;
import chess.model.Direction;
import chess.model.Move;
import chess.model.Piece;
import chess.model.PieceVisitor;
import chess.model.pieces.Bishop;
import chess.model.pieces.King;
import chess.model.pieces.Knight;
import chess.model.pieces.Pawn;
import chess.model.pieces.Queen;
import chess.model.pieces.Rook;
public class PermissiveRuleChecker implements PieceVisitor<Boolean> {
private final Move move;
public PermissiveRuleChecker(Move move) {
this.move = move;
assert move.isValid() : "Move is invalid!";
}
public boolean isValidFor(Piece piece) {
return visit(piece);
}
@Override
public Boolean visitPiece(Bishop bishop) {
Direction moveDirection = Direction.findDirection(this.move);
switch (moveDirection) {
case FrontLeft:
case BackLeft:
case FrontRight:
case BackRight:
return true;
default:
return false;
}
}
@Override
public Boolean visitPiece(King king) {
return this.move.traversedCells() == 1;
}
@Override
public Boolean visitPiece(Knight knight) {
Coordinate piecePos = move.getStart();
final Coordinate[] positions = {
new Coordinate(piecePos.getX() - 1, piecePos.getY() - 2),
new Coordinate(piecePos.getX() - 1, piecePos.getY() + 2),
new Coordinate(piecePos.getX() + 1, piecePos.getY() - 2),
new Coordinate(piecePos.getX() + 1, piecePos.getY() + 2),
new Coordinate(piecePos.getX() + 2, piecePos.getY() - 1),
new Coordinate(piecePos.getX() + 2, piecePos.getY() + 1),
new Coordinate(piecePos.getX() - 2, piecePos.getY() - 1),
new Coordinate(piecePos.getX() - 2, piecePos.getY() + 1),
};
for (int i = 0; i < positions.length; i++) {
if (this.move.getFinish().equals(positions[i]))
return true;
}
return false;
}
@Override
public Boolean visitPiece(Pawn pawn) {
Direction moveDirection = Direction.findDirection(this.move);
int directionIndexOffset = moveDirection.getIndexOffset();
int distance = this.move.traversedCells();
// Revoke moving backwards
if (directionIndexOffset * pawn.multiplier() > 0)
return false;
// Allowing straight moves
if (Math.abs(directionIndexOffset) == Math.abs(Direction.Front.getIndexOffset())) {
if (pawn.hasMoved())
return distance == 1;
return distance == 1 || distance == 2;
}
// Allowing small diagonal moves
if (directionIndexOffset * pawn.multiplier() == Direction.FrontLeft.getIndexOffset()
|| directionIndexOffset * pawn.multiplier() == Direction.FrontRight.getIndexOffset()) {
return distance == 1;
}
return false;
}
@Override
public Boolean visitPiece(Queen queen) {
Direction moveDirection = Direction.findDirection(this.move);
return moveDirection != Direction.Unset;
}
@Override
public Boolean visitPiece(Rook rook) {
Direction moveDirection = Direction.findDirection(this.move);
switch (moveDirection) {
case Front:
case Back:
case Left:
case Right:
return true;
default:
return false;
}
}
}

View File

@@ -0,0 +1,151 @@
package chess.model.visitor;
import chess.model.ChessBoard;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Direction;
import chess.model.Move;
import chess.model.Piece;
import chess.model.PieceVisitor;
import chess.model.pieces.Bishop;
import chess.model.pieces.King;
import chess.model.pieces.Knight;
import chess.model.pieces.Pawn;
import chess.model.pieces.Queen;
import chess.model.pieces.Rook;
public class PiecePathChecker implements PieceVisitor<Boolean> {
private final ChessBoard board;
private final Move move;
public PiecePathChecker(ChessBoard board, Move move) {
this.move = move;
this.board = board;
}
public boolean isValid() {
if (this.move.getStart().equals(this.move.getFinish()))
return false;
Piece piece = this.board.pieceAt(move.getStart());
if (piece == null)
return false;
return visit(piece);
}
@Override
public Boolean visitPiece(Bishop bishop) {
return basicCheck(bishop);
}
@Override
public Boolean visitPiece(King king) {
return destCheck(king);
}
@Override
public Boolean visitPiece(Knight knight) {
return destCheck(knight);
}
@Override
public Boolean visitPiece(Queen queen) {
return basicCheck(queen);
}
@Override
public Boolean visitPiece(Rook rook) {
return basicCheck(rook);
}
@Override
public Boolean visitPiece(Pawn pawn) {
if (!new PermissiveRuleChecker(this.move).isValidFor(pawn))
return false;
Direction moveDirection = Direction.fromInt(Direction.findDirection(move).getIndexOffset() * pawn.multiplier());
if (moveDirection == Direction.Front)
return testPath(pawn.getColor()) && this.board.pieceAt(this.move.getFinish()) == null;
assert moveDirection == Direction.FrontLeft || moveDirection == Direction.FrontRight;
if (checkEnPassant())
return true;
Piece destPiece = this.board.pieceAt(this.move.getFinish());
if (destPiece == null)
return false;
return destPiece.getColor() != pawn.getColor();
}
private boolean checkEnPassant() {
Move lastMove = this.board.getLastMove();
if (lastMove == null)
return false;
Piece pieceToEat = this.board.pieceAt(lastMove.getFinish());
if (pieceToEat == null || !(pieceToEat instanceof Pawn))
return false;
Piece pawn = this.board.pieceAt(this.move.getStart());
if (pieceToEat.getColor() == pawn.getColor())
return false;
Direction lastMoveDir = Direction.findDirection(lastMove);
if ((lastMoveDir != Direction.Front && lastMoveDir != Direction.Back) || lastMove.traversedCells() != 2)
return false;
Coordinate middle = lastMove.getMiddle();
if (middle.equals(this.move.getFinish())
&& new PawnIdentifier(pieceToEat.getColor()).isPawn(pieceToEat)) {
this.move.setDeadPieceCoords(lastMove.getFinish());
return true;
}
return false;
}
private boolean destCheck(Piece piece) {
if (!new PermissiveRuleChecker(this.move).isValidFor(piece))
return false;
Piece destPiece = board.pieceAt(this.move.getFinish());
if (destPiece == null)
return true;
return destPiece.getColor() != piece.getColor();
}
private boolean basicCheck(Piece piece) {
if (!new PermissiveRuleChecker(this.move).isValidFor(piece))
return false;
return testPath(piece.getColor());
}
private boolean testPath(Color color) {
Direction moveDirection = Direction.findDirection(this.move);
int distance = this.move.traversedCells();
int stepIndex = move.getStart().toIndex();
for (int step = 0; step < distance; step++) {
stepIndex += moveDirection.getIndexOffset();
if (Coordinate.fromIndex(stepIndex).equals(move.getFinish())) {
Piece pieceDest = this.board.pieceAt(move.getFinish());
if (pieceDest == null)
return true;
return pieceDest.getColor() != color;
}
if (!this.board.isCellEmpty(Coordinate.fromIndex(stepIndex)))
return false;
}
return false;
}
}

View File

@@ -0,0 +1,242 @@
package chess.pgn;
import java.util.List;
import chess.controller.CommandExecutor;
import chess.controller.PlayerCommand;
import chess.controller.Command.CommandResult;
import chess.controller.commands.CastlingCommand;
import chess.controller.commands.GetPieceAtCommand;
import chess.controller.commands.GetPlayerMovesCommand;
import chess.controller.commands.MoveCommand;
import chess.controller.commands.NewGameCommand;
import chess.controller.commands.PromoteCommand;
import chess.controller.event.EmptyGameDispatcher;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Game;
import chess.model.Move;
import chess.model.Piece;
import chess.model.pieces.Pawn;
public class PgnExport {
// public static void main(String[] args) {
// final Game game = new Game();
// final CommandExecutor commandExecutor = new CommandExecutor(game);
// DumbAI ai1 = new DumbAI(commandExecutor, Color.White);
// commandExecutor.addListener(ai1);
// DumbAI ai2 = new DumbAI(commandExecutor, Color.Black);
// commandExecutor.addListener(ai2);
// commandExecutor.addListener(new GameAdaptator() {
// @Override
// public void onGameEnd() {
// System.out.println(exportGame(game));
// commandExecutor.close();
// }
// });
// commandExecutor.executeCommand(new NewGameCommand());
// }
private static final PiecePgnName piecePgnName = new PiecePgnName();
private static Piece pieceAt(CommandExecutor commandExecutor, Coordinate coordinate) {
GetPieceAtCommand cmd = new GetPieceAtCommand(coordinate);
commandExecutor.executeCommand(cmd);
return cmd.getPiece();
}
private static List<Move> playerMoves(CommandExecutor commandExecutor) {
GetPlayerMovesCommand cmd = new GetPlayerMovesCommand();
commandExecutor.executeCommand(cmd);
return cmd.getMoves();
}
private static String gameEnd(Game game) {
switch (game.checkGameStatus(game.getPlayerTurn())) {
case Draw:
case Pat:
return "1/2-1/2";
case CheckMate:
if (game.getPlayerTurn() == Color.White)
return "0-1";
return "1-0";
default:
return "";
}
}
private static String resolveAmbiguity(CommandExecutor cmdExec, Move pieceMove) {
Piece movingPiece = pieceAt(cmdExec, pieceMove.getStart());
assert movingPiece != null;
if (movingPiece instanceof Pawn)
return "";
List<Move> moves = playerMoves(cmdExec);
for (Move move : moves) {
if (move.equals(pieceMove) || !move.getFinish().equals(pieceMove.getFinish()))
continue;
Piece otherPiece = pieceAt(cmdExec, move.getStart());
// checking type of piece
if (!otherPiece.getClass().equals(movingPiece.getClass()))
continue;
String startPos = toString(pieceMove.getStart());
if (move.getStart().getX() != pieceMove.getStart().getX())
// not on the same column
return Character.toString(startPos.charAt(0));
else
return Character.toString(startPos.charAt(1));
}
return "";
}
private static String capture(MoveCommand move, Piece movingPiece) {
String result = "";
if (move.getDeadPiece() != null) {
if (movingPiece instanceof Pawn) {
result += toString(move.getMove().getStart()).charAt(0);
}
result += "x";
}
return result;
}
private static String promote(PlayerCommand nextCommand) {
if (nextCommand != null && nextCommand instanceof PromoteCommand promoteCommand) {
String result = "=";
result += switch (promoteCommand.getPromoteType()) {
case Bishop -> "B";
case Knight -> "N";
case Queen -> "Q";
case Rook -> "R";
};
return result;
}
return "";
}
private static String castling(CastlingCommand castlingCommand) {
String result = "O-O";
if (castlingCommand.isBigCastling())
result += "-O";
return result;
}
private static String checkCheckMate(Game game) {
switch (game.checkGameStatus(game.getPlayerTurn())) {
case CheckMate:
return "#";
case Check:
return "+";
default:
return "";
}
}
private record MoveResult(String move, CommandResult commandResult) {
}
private static MoveResult printMove(PlayerCommand cmd, PlayerCommand nextCommand, Game virtualGame,
CommandExecutor executor) {
String result = "";
if (cmd instanceof MoveCommand move) {
Piece movingPiece = virtualGame.getBoard().pieceAt(move.getMove().getStart());
assert movingPiece != null;
// piece name
result += piecePgnName.visit(movingPiece);
// ambiguious start
result += resolveAmbiguity(executor, move.getMove());
// capture
result += capture(move, movingPiece);
// end cell
result += toString(move.getMove().getFinish());
// promote
result += promote(nextCommand);
} else if (cmd instanceof CastlingCommand castlingCommand) {
result += castling(castlingCommand);
}
CommandResult commandResult = executor.executeCommand(cmd);
// check or checkmate
result += checkCheckMate(virtualGame);
result += " ";
return new MoveResult(result, commandResult);
}
public static String exportGame(Game game) {
Game virtualGame = new Game();
CommandExecutor executor = new CommandExecutor(virtualGame, new EmptyGameDispatcher());
executor.executeCommand(new NewGameCommand());
List<PlayerCommand> commands = game.getMoves();
String result = "";
int tour = 1;
String lastMove = null;
for (int i = 0; i < commands.size(); i++) {
PlayerCommand cmd = commands.get(i);
PlayerCommand nextCommand = null;
if (i != commands.size() - 1) {
nextCommand = commands.get(i + 1);
}
MoveResult moveResult = printMove(cmd, nextCommand, virtualGame, executor);
if (moveResult.commandResult() == CommandResult.Moved && virtualGame.getPlayerTurn() == Color.Black) {
result += tour + ".";
tour++;
}
if (moveResult.commandResult() == CommandResult.ActionNeeded) {
lastMove = moveResult.move();
continue;
}
if (lastMove != null && moveResult.commandResult() == CommandResult.Moved){
result += lastMove;
lastMove = null;
continue;
}
result += moveResult.move();
}
return result + " " + gameEnd(virtualGame);
}
public static String toString(Coordinate coordinate) {
String letters = "abcdefgh";
String numbers = "87654321";
return Character.toString(letters.charAt(coordinate.getX())) + numbers.charAt(coordinate.getY());
}
}

View File

@@ -0,0 +1,22 @@
package chess.pgn;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import chess.controller.CommandExecutor;
import chess.view.AssetManager;
public class PgnFileSimulator extends PgnSimulator{
private static String readResource(String path) {
StringBuilder builder = new StringBuilder();
BufferedReader reader = new BufferedReader(new InputStreamReader(AssetManager.getResource(path)));
reader.lines().forEach((line) -> builder.append(line + "\n"));
return builder.toString();
}
public PgnFileSimulator(CommandExecutor commandExecutor, String fileName) {
super(commandExecutor, readResource(fileName));
}
}

View File

@@ -0,0 +1,172 @@
package chess.pgn;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import chess.controller.CommandExecutor;
import chess.controller.PlayerCommand;
import chess.controller.commands.CastlingCommand;
import chess.controller.commands.MoveCommand;
import chess.controller.commands.NewGameCommand;
import chess.controller.commands.PromoteCommand;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.controller.event.EmptyGameDispatcher;
import chess.model.ChessBoard;
import chess.model.Coordinate;
import chess.model.Game;
import chess.model.Move;
import chess.model.Piece;
import chess.model.pieces.Bishop;
import chess.model.pieces.King;
import chess.model.pieces.Knight;
import chess.model.pieces.Pawn;
import chess.model.pieces.Queen;
import chess.model.pieces.Rook;
public class PgnImport {
private static final Map<String, Class<? extends Piece>> pieceMap = Map.of(
"K", King.class,
"Q", Queen.class,
"R", Rook.class,
"B", Bishop.class,
"N", Knight.class);
private static final Map<String, PromoteType> promoteMap = Map.of(
"Q", PromoteType.Queen,
"R", PromoteType.Rook,
"B", PromoteType.Bishop,
"N", PromoteType.Knight);
public static List<PlayerCommand> importGame(String pgnContent) {
String[] parts = pgnContent.split("\n\n");
// we just ignore headers
return getMoves(parts[parts.length - 1]);
}
private static List<PlayerCommand> getMoves(String unparsedMoves) {
String[] moves = unparsedMoves.replaceAll("\\{.*?\\}", "") // Remove comments
.replaceAll("\\n", " ") // Remove new lines
.split("[\\s.]+"); // Split by whitespace and dots (trimming it also)
Game virtualGame = new Game();
CommandExecutor commandExecutor = new CommandExecutor(virtualGame, new EmptyGameDispatcher());
List<PlayerCommand> instructions = new ArrayList<>();
commandExecutor.executeCommand(new NewGameCommand());
for (int i = 0; i < moves.length; i++) {
if (i % 3 == 0)
continue;
String move = moves[i];
if (move.equals("1-0") || move.equals("0-1") || move.equals("1/2-1/2")) {
break; // End of the game
}
List<PlayerCommand> cmds = parseMove(move, virtualGame);
commandExecutor.executeCommands(cmds);
instructions.addAll(cmds);
}
return instructions;
}
private static List<PlayerCommand> parseMove(String move, Game game) {
if (move.equals("O-O-O"))
return Arrays.asList(new CastlingCommand(true));
if (move.equals("O-O"))
return Arrays.asList(new CastlingCommand(false));
move = move.replaceAll("[x|#|\\+]", "");
PromoteCommand promoteCommand = null;
if (move.contains("=")) {
String promoteString = move.substring(move.length() - 1);
promoteCommand = new PromoteCommand(promoteMap.get(promoteString));
move = move.substring(0, move.length() - 2);
}
Class<? extends Piece> pieceType = pieceMap.get(move.substring(0, 1));
if (pieceType == null)
pieceType = Pawn.class;
else
move = move.substring(1);
assert move.length() == 3 || move.length() == 2;
Coordinate ambiguity = new Coordinate(-1, -1);
// ambiguity
if (move.length() == 3) {
ambiguity = getAmbiguityPattern(move.charAt(0));
move = move.substring(1);
}
Coordinate dest = stringToCoordinate(move);
Coordinate start = getStartCoord(dest, pieceType, ambiguity, game);
List<PlayerCommand> cmds = new ArrayList<>();
cmds.add(new MoveCommand(new Move(start, dest)));
if (promoteCommand != null)
cmds.add(promoteCommand);
return cmds;
}
private static Coordinate getStartCoord(Coordinate dest, Class<? extends Piece> pieceType, Coordinate ambiguity,
Game game) {
final ChessBoard board = game.getBoard();
List<Coordinate> starts = board.getAllowedStarts(dest, game.getPlayerTurn());
assert !starts.isEmpty() : "No moves allowed!";
for (Coordinate start : starts) {
Piece piece = board.pieceAt(start);
if (piece.getClass().equals(pieceType) && coordPatternMatch(start, ambiguity))
return start;
}
assert false : "There is a small problem ...";
return null;
}
private static int getXCoord(char xPos) {
return xPos - 'a';
}
private static int getYCoord(char yPos) {
return Coordinate.VALUE_MAX - 1 - (yPos - '1');
}
private static boolean coordPatternMatch(Coordinate coord, Coordinate pattern) {
if (pattern.getX() != -1 && coord.getX() != pattern.getX())
return false;
if (pattern.getY() != -1 && coord.getY() != pattern.getY())
return false;
return true;
}
private static Coordinate getAmbiguityPattern(char amb) {
if (Character.isDigit(amb))
return new Coordinate(-1, getYCoord(amb));
return new Coordinate(getXCoord(amb), -1);
}
private static Coordinate stringToCoordinate(String coordinates) {
char xPos = coordinates.charAt(0);
char yPos = coordinates.charAt(1);
return new Coordinate(getXCoord(xPos), getYCoord(yPos));
}
}

View File

@@ -0,0 +1,28 @@
package chess.pgn;
import java.util.List;
import chess.controller.CommandExecutor;
import chess.controller.PlayerCommand;
import chess.controller.commands.NewGameCommand;
import chess.controller.event.GameAdaptator;
import chess.model.Game;
public class PgnSimulator extends GameAdaptator {
private final CommandExecutor commandExecutor;
private final String pgn;
public PgnSimulator(CommandExecutor commandExecutor, String pgn) {
this.commandExecutor = commandExecutor;
this.pgn = pgn;
}
@Override
public void onGameStart() {
List<PlayerCommand> cmds = PgnImport.importGame(this.pgn);
this.commandExecutor.executeCommands(cmds);
}
}

View File

@@ -0,0 +1,43 @@
package chess.pgn;
import chess.model.PieceVisitor;
import chess.model.pieces.Bishop;
import chess.model.pieces.King;
import chess.model.pieces.Knight;
import chess.model.pieces.Pawn;
import chess.model.pieces.Queen;
import chess.model.pieces.Rook;
public class PiecePgnName implements PieceVisitor<String> {
@Override
public String visitPiece(Bishop bishop) {
return "B";
}
@Override
public String visitPiece(King king) {
return "K";
}
@Override
public String visitPiece(Knight knight) {
return "N";
}
@Override
public String visitPiece(Pawn pawn) {
return "";
}
@Override
public String visitPiece(Queen queen) {
return "Q";
}
@Override
public String visitPiece(Rook rook) {
return "R";
}
}

View File

@@ -1,13 +0,0 @@
package chess.render;
public class VertexAttribPointer {
public int index;
public int size;
public int offset;
public VertexAttribPointer(int index, int size, int offset) {
this.index = index;
this.size = size;
this.offset = offset;
}
}

View File

@@ -0,0 +1,38 @@
package chess.view;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class AssetManager {
private static final String gradleBase = "app/src/main/resources/";
public static InputStream getResource(String name) {
// we first search it in files
InputStream inputStream = getFileInputStream(name);
if (inputStream != null)
return inputStream;
inputStream = getFileInputStream(gradleBase + name);
if (inputStream != null)
return inputStream;
// then in the jar
return ClassLoader.getSystemResourceAsStream(name);
}
private static InputStream getFileInputStream(String path) {
File f = new File(path);
if (f.exists()) {
FileInputStream fis;
try {
fis = new FileInputStream(f);
return fis;
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}

View File

@@ -1,4 +1,4 @@
package chess.render;
package chess.view.DDDrender;
import org.joml.Matrix4f;
import org.joml.Vector3f;

View File

@@ -1,4 +1,4 @@
package chess.render;
package chess.view.DDDrender;
import org.lwjgl.opengl.GL30;

View File

@@ -1,10 +1,11 @@
package chess.render;
package chess.view.DDDrender;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT;
import org.joml.Vector3f;
import org.lwjgl.opengl.*;
import org.lwjgl.opengl.GL30;
import static org.lwjgl.opengl.GL30.*;
import chess.render.shader.BoardShader;
import chess.view.DDDrender.shader.BoardShader;
public class Renderer {
private BoardShader shader;

View File

@@ -1,4 +1,4 @@
package chess.render;
package chess.view.DDDrender;
import java.util.ArrayList;
import java.util.List;

View File

@@ -0,0 +1,5 @@
package chess.view.DDDrender;
public record VertexAttribPointer(int index, int size, int offset) {
}

View File

@@ -1,8 +1,7 @@
package chess.render;
package chess.view.DDDrender;
import static org.lwjgl.opengl.GL11.GL_FLOAT;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
@@ -42,9 +41,9 @@ public class VertexBuffer {
public void BindVertexAttribs() {
for (VertexAttribPointer vertexAttribPointer : vertexAttribs) {
GL30.glEnableVertexAttribArray(vertexAttribPointer.index);
GL30.glVertexAttribPointer(vertexAttribPointer.index, vertexAttribPointer.size, GL_FLOAT, false,
this.dataStride * 4, vertexAttribPointer.offset);
GL30.glEnableVertexAttribArray(vertexAttribPointer.index());
GL30.glVertexAttribPointer(vertexAttribPointer.index(), vertexAttribPointer.size(), GL_FLOAT, false,
this.dataStride * 4, vertexAttribPointer.offset());
}
}
}

View File

@@ -1,13 +1,10 @@
package chess.render;
package chess.view.DDDrender;
import org.lwjgl.*;
import org.lwjgl.glfw.*;
import org.lwjgl.opengl.*;
import org.lwjgl.system.*;
import chess.render.Camera;
import chess.render.Renderer;
import java.nio.*;
import static org.lwjgl.glfw.Callbacks.*;
@@ -29,6 +26,10 @@ public class Window {
this.cam = new Camera();
}
public static void main(String[] args) {
new Window().run();
}
public void run() {
System.out.println("LWJGL " + Version.getVersion() + "!");
@@ -59,7 +60,7 @@ public class Window {
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // the window will be resizable
// Create the window
window = glfwCreateWindow(1000, 1000, "Chess4J", NULL, NULL);
window = glfwCreateWindow(1000, 1000, "3DChess", NULL, NULL);
if (window == NULL)
throw new RuntimeException("Failed to create the GLFW window");

View File

@@ -1,4 +1,4 @@
package chess.render.shader;
package chess.view.DDDrender.shader;
import org.joml.Matrix4f;
@@ -12,7 +12,7 @@ public class BoardShader extends ShaderProgram {
uniform mat4 camMatrix;
out vec3 pass_color;
flat out vec3 pass_color;
void main(void){
gl_Position = camMatrix * vec4(position, 1.0);
@@ -23,7 +23,7 @@ public class BoardShader extends ShaderProgram {
private static String fragmentShader = """
#version 330
in vec3 pass_color;
flat in vec3 pass_color;
out vec4 out_color;

View File

@@ -1,4 +1,4 @@
package chess.render.shader;
package chess.view.DDDrender.shader;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;

View File

@@ -0,0 +1,63 @@
package chess.view.audio;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import chess.view.AssetManager;
public class AudioFiles {
private static final String baseURL = "https://images.chesscomfiles.com/chess-themes/sounds/_WAV_/default/";
private static final String[] files = { "game-start", "game-end", "capture", "castle", "move-self",
"move-check", "promote", "illegal" };
private static final String filesExtension = ".wav";
private static final String saveDir = "audio/";
public static boolean initFiles() {
createSaveDir();
for (String file : files) {
if (!initFile(file + filesExtension))
return false;
}
return true;
}
private static boolean downloadFile(String fileName) {
System.out.println("[GameAudio] missing " + fileName + " Downloading ...");
try (BufferedInputStream in = new BufferedInputStream(new URI(baseURL + fileName).toURL().openStream());
FileOutputStream fileOutputStream = new FileOutputStream(saveDir + fileName)) {
byte dataBuffer[] = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) {
fileOutputStream.write(dataBuffer, 0, bytesRead);
}
return true;
} catch (IOException | URISyntaxException e) {
e.printStackTrace();
return false;
}
}
private static void createSaveDir() {
new File(saveDir).mkdir();
}
private static boolean initFile(String fileName) {
if (isFileDownloaded(saveDir + fileName))
return true;
return downloadFile(fileName);
}
private static boolean isFileDownloaded(String fileName) {
return AssetManager.getResource(fileName) != null;
}
public static InputStream getAudio(String audioName) {
return AssetManager.getResource(saveDir + audioName + filesExtension);
}
}

View File

@@ -0,0 +1,44 @@
package chess.view.audio;
import java.io.BufferedInputStream;
import java.io.InputStream;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
public class AudioPlayer {
public static void playSound(InputStream audio) {
new Thread(new Runnable() {
// The wrapper thread is unnecessary, unless it blocks on the
// Clip finishing; see comments.
public void run() {
try {
Clip clip = AudioSystem.getClip();
BufferedInputStream bufferedInputStream = new BufferedInputStream(audio);
AudioInputStream inputStream = AudioSystem.getAudioInputStream(bufferedInputStream);
clip.open(inputStream);
clip.start();
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
}).start();
}
private static AudioInputStream convertToPCM(AudioInputStream audioInputStream) {
AudioFormat m_format = audioInputStream.getFormat();
if ((m_format.getEncoding() != AudioFormat.Encoding.PCM_SIGNED) &&
(m_format.getEncoding() != AudioFormat.Encoding.PCM_UNSIGNED)) {
AudioFormat targetFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
m_format.getSampleRate(), 16,
m_format.getChannels(), m_format.getChannels() * 2,
m_format.getSampleRate(), m_format.isBigEndian());
audioInputStream = AudioSystem.getAudioInputStream(targetFormat, audioInputStream);
}
return audioInputStream;
}
}

View File

@@ -0,0 +1,63 @@
package chess.view.audio;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.controller.event.GameAdaptator;
import chess.model.Move;
public class GameAudio extends GameAdaptator {
private final boolean functional;
public GameAudio() {
this.functional = AudioFiles.initFiles();
if(!this.functional){
System.err.println("[GameAudio] Failed to initialize audio files. Aborting ...");
}
}
private void playSound(String soundName) {
if (!this.functional)
return;
AudioPlayer.playSound(AudioFiles.getAudio(soundName));
}
@Override
public void onGameStart() {
playSound("game-start");
}
@Override
public void onGameEnd() {
playSound("game-end");
}
@Override
public void onMoveNotAllowed(Move move) {
playSound("illegal");
}
@Override
public void onMove(Move move, boolean captured) {
if (captured) {
playSound("capture");
return;
}
playSound("move-self");
}
@Override
public void onKingInCheck() {
playSound("move-check");
}
@Override
public void onPawnPromoted(PromoteType promotion) {
playSound("promote");
}
@Override
public void onCastling(boolean bigCastling) {
playSound("castle");
}
}

View File

@@ -0,0 +1,38 @@
package chess.view.consolerender;
public class Colors {
// Reset
public static final String RESET = "\u001B[0m"; // Text Reset
// Regular Colors
public static final String BLACK = "\033[38;2;0;0;0m";
public static final String WHITE = "\033[38;2;255;255;255m";
public static final String RED = "\033[38;2;255;0;0m";
public static final String GREEN = "\033[38;2;0;255;0m";
public static final String YELLOW = "\033[38;2;255;255;0m";
public static final String BLUE = "\033[38;2;0;0;255m";
public static final String PURPLE = "\033[38;2;255;0;255m";
public static final String CYAN = "\033[38;2;0;255;255m";
// Background
public static final String BLACK_BACKGROUND = "\033[40m"; // BLACK
public static final String LIGHT_GRAY_BACKGROUND = "\033[48;2;180;180;180m";
public static final String DARK_GRAY_BACKGROUND = "\033[48;2;120;120;120m";
public static final String RED_BACKGROUND = "\033[41m"; // RED
public static final String GREEN_BACKGROUND = "\033[42m"; // GREEN
public static final String YELLOW_BACKGROUND = "\033[43m"; // YELLOW
public static final String BLUE_BACKGROUND = "\033[44m"; // BLUE
public static final String PURPLE_BACKGROUND = "\033[45m"; // PURPLE
public static final String CYAN_BACKGROUND = "\033[46m"; // CYAN
public static final String WHITE_BACKGROUND = "\033[47m"; // WHITE
// High Intensity backgrounds
public static final String BLACK_BACKGROUND_BRIGHT = "\033[0;100m";// BLACK
public static final String RED_BACKGROUND_BRIGHT = "\033[0;101m";// RED
public static final String GREEN_BACKGROUND_BRIGHT = "\033[0;102m";// GREEN
public static final String YELLOW_BACKGROUND_BRIGHT = "\033[0;103m";// YELLOW
public static final String BLUE_BACKGROUND_BRIGHT = "\033[0;104m";// BLUE
public static final String PURPLE_BACKGROUND_BRIGHT = "\033[0;105m"; // PURPLE
public static final String CYAN_BACKGROUND_BRIGHT = "\033[0;106m"; // CYAN
public static final String WHITE_BACKGROUND_BRIGHT = "\033[0;107m"; // WHITE
}

View File

@@ -0,0 +1,343 @@
package chess.view.consolerender;
import chess.controller.Command;
import chess.controller.CommandExecutor;
import chess.controller.commands.*;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.controller.event.GameListener;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
import chess.model.Piece;
import java.util.List;
import java.util.Objects;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Console implements GameListener {
private final Scanner scanner = new Scanner(System.in);
private final CommandExecutor commandExecutor;
private final ConsolePieceName consolePieceName = new ConsolePieceName();
private boolean captureInput;
private final ExecutorService executor;
public Console(CommandExecutor commandExecutor, boolean captureInput) {
this.commandExecutor = commandExecutor;
this.executor = Executors.newSingleThreadExecutor();
this.captureInput = captureInput;
}
public Console(CommandExecutor commandExecutor) {
this(commandExecutor, true);
}
private Piece pieceAt(int x, int y) {
return pieceAt(new Coordinate(x, y));
}
private Command.CommandResult sendCommand(Command command) {
return this.commandExecutor.executeCommand(command);
}
private Piece pieceAt(Coordinate coordinate) {
GetPieceAtCommand command = new GetPieceAtCommand(coordinate);
sendCommand(command);
return command.getPiece();
}
public Coordinate stringToCoordinate(String coordinates) throws Exception {
char xPos = coordinates.charAt(0);
char yPos = coordinates.charAt(1);
int x;
if (xPos >= 'A' && xPos <= 'Z') {
x = xPos - 'A';
} else if (xPos >= 'a' && xPos <= 'z') {
x = xPos - 'a';
} else {
throw new Exception("Invalid input");
}
if (!(yPos >= '1' && yPos <= '9')) {
throw new Exception("Invalid input");
}
int y = Coordinate.VALUE_MAX - 1 - (yPos - '1');
return new Coordinate(x, y);
}
@Override
public void onPlayerTurn(Color color, boolean undone) {
if (!captureInput)
return;
System.out.println(Colors.RED + "Player turn: " + color + Colors.RESET);
this.executor.submit(() -> {
boolean endTurn;
String line = "0";
do {
if (!line.isEmpty()) {
System.out.println("""
Pick your choice:
1 - Move
2 - Show potential moves
3 - Surrender
""");
System.out.flush();
}
line = scanner.nextLine();
endTurn = switch (line) {
case "1" -> playerPickedMove(color);
case "2" -> playerPickedShowMoves(color);
case "3" -> playerPickedSurrender(color);
default -> false;
};
} while (!endTurn);
});
}
private boolean playerPickedSurrender(Color color) {
sendCommand(new SurrenderCommand(color));
return true;
}
public boolean playerPickedMove(Color color) {
try {
System.out.println("Piece to move, or \"castling\" for a castling");
String answer = scanner.nextLine();
if (answer.equalsIgnoreCase("castling")) {
return onAskedCastling();
}
Coordinate start = stringToCoordinate(answer);
System.out.println("New position: ");
Coordinate end = stringToCoordinate(scanner.nextLine());
Command.CommandResult result = sendCommand(new MoveCommand(new Move(start, end)));
return switch (Objects.requireNonNull(result)) {
case Command.CommandResult.Moved, Command.CommandResult.ActionNeeded -> true;
default -> false;
};
} catch (Exception e) {
System.out.println(e.getMessage());
return false;
}
}
private boolean playerPickedShowMoves(Color color) {
try {
System.out.println("Piece to examine: ");
Coordinate piece = stringToCoordinate(scanner.nextLine());
GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(piece);
if (sendCommand(movesCommand) == Command.CommandResult.NotAllowed) {
System.out.println("Not allowed.");
return false;
}
List<Coordinate> allowedMoves = movesCommand.getDestinations();
if (allowedMoves.isEmpty()) {
System.out.println("No moves allowed for this piece.");
return false;
}
displayMoves(piece, allowedMoves);
return false;
} catch (Exception e) {
System.out.println(e.getMessage());
return false;
}
}
@Override
public void onWin(Color color) {
System.out.println(Colors.RED + "Victory of player " + color + Colors.RESET);
}
@Override
public void onKingInCheck() {
System.out.println(Colors.RED + "Check!" + Colors.RESET);
}
@Override
public void onKingInMat() {
System.out.println(Colors.RED + "Checkmate!" + Colors.RESET);
}
@Override
public void onPatSituation() {
System.out.println("Pat! It's a draw!");
}
@Override
public void onSurrender(Color color) {
System.out.println("The " + color + " player has surrendered!");
}
@Override
public void onGameStart() {
System.out.println("Game start!");
onBoardUpdate();
}
@Override
public void onGameEnd() {
System.out.println("Thank you for playing!");
this.commandExecutor.close();
this.executor.shutdown();
}
@Override
public void onPromotePawn(Coordinate pieceCoords) {
System.out.println("The pawn on the " + pieceCoords + " coordinates needs to be promoted.");
System.out.println("Enter 'B' to promote it into a Bishop, 'N' for a Knight, 'Q' for a Queen, 'R' for a Rook.");
System.out.flush();
this.executor.submit(() -> {
PromoteType newPiece;
boolean valid = false;
do {
try {
String promotion = scanner.next();
newPiece = switch (promotion) {
case "B", "b", "Bishop", "bishop" -> PromoteType.Bishop;
case "N", "n", "Knight", "knight" -> PromoteType.Knight;
case "Q", "q", "Queen", "queen" -> PromoteType.Queen;
case "R", "r", "Rook", "rook" -> PromoteType.Rook;
default -> throw new Exception();
};
valid = true;
sendCommand(new PromoteCommand(newPiece));
} catch (Exception e) {
System.out.println("Invalid input!");
}
} while (!valid);
});
}
@Override
public void onBoardUpdate() {
if (!this.captureInput)
return;
StringBuilder string = new StringBuilder();
string.append(" a b c d e f g h \n");
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
string.append(8 - i).append(" ");
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
Piece p = pieceAt(j, i);
if ((i + j) % 2 == 0) {
string.append(Colors.LIGHT_GRAY_BACKGROUND);
} else {
string.append(Colors.DARK_GRAY_BACKGROUND);
}
if (p == null) {
string.append(" " + Colors.RESET);
} else {
string.append(" ").append(consolePieceName.getString(p)).append(" ").append(Colors.RESET);
}
}
string.append("\n");
}
System.out.println(string);
System.out.flush();
}
public void displayMoves(Coordinate piece, List<Coordinate> moves) {
StringBuilder string = new StringBuilder();
string.append(" a b c d e f g h \n");
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
string.append(8 - i).append(" ");
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
Coordinate currentCell = new Coordinate(j, i);
Piece p = pieceAt(j, i);
if (moves.contains(currentCell)) {
string.append(Colors.YELLOW_BACKGROUND);
} else {
if ((i + j) % 2 == 0) {
string.append(Colors.LIGHT_GRAY_BACKGROUND);
} else {
string.append(Colors.DARK_GRAY_BACKGROUND);
}
}
if (p == null) {
string.append(" " + Colors.RESET);
} else {
if (currentCell.equals(piece)) {
string.append(Colors.RED_BACKGROUND).append(" ").append(consolePieceName.getString(p))
.append(" ").append(Colors.RESET);
} else {
string.append(" ").append(consolePieceName.getString(p)).append(" ").append(Colors.RESET);
}
}
}
string.append("\n");
}
System.out.println(string);
}
@Override
public void onMove(Move move, boolean captured) {
}
@Override
public void onMoveNotAllowed(Move move) {
System.out.println("Move not allowed.");
}
@Override
public void onDraw() {
System.out.println("Repeated positions!");
}
private boolean onAskedCastling() {
GetAllowedCastlingsCommand cmd = new GetAllowedCastlingsCommand();
sendCommand(cmd);
return switch (cmd.getCastlingResult()) {
case Small -> onSmallCastling();
case Big -> onBigCastling();
case Both -> onBothCastling();
default -> {
System.out.println("No castling allowed.");
yield false;
}
};
}
private boolean onSmallCastling() {
System.out.println("Small castling allowed. Confirm with \"y\":");
String answer = scanner.nextLine();
if (!(answer.equalsIgnoreCase("y") || answer.equalsIgnoreCase("yes"))) {
return false;
} else {
return (commandExecutor.executeCommand(new CastlingCommand(false)) != Command.CommandResult.Moved);
}
}
private boolean onBigCastling() {
System.out.println("Big castling allowed. Confirm with \"y\":");
String answer = scanner.nextLine();
if (!(answer.equalsIgnoreCase("y") || answer.equalsIgnoreCase("yes"))) {
return false;
} else {
return (commandExecutor.executeCommand(new CastlingCommand(true)) != Command.CommandResult.Moved);
}
}
private boolean onBothCastling() {
System.out.println("Both castlings allowed. Pick \"s\" to play a castling, \"b\" to play a big castling.");
String answer = scanner.nextLine();
return switch (answer) {
case "s", "S", "small", "Small", "castling", "normal", "Normal" ->
(commandExecutor.executeCommand(new CastlingCommand(false)) != Command.CommandResult.Moved);
case "b", "B", "big", "Big", "big castling", "Big castling" ->
(commandExecutor.executeCommand(new CastlingCommand(true)) != Command.CommandResult.Moved);
default -> false;
};
}
public void setCaptureInput(boolean captureInput) {
this.captureInput = captureInput;
}
@Override
public void onCastling(boolean bigCastling) {}
@Override
public void onPawnPromoted(PromoteType promotion) {}
}

View File

@@ -0,0 +1,48 @@
package chess.view.consolerender;
import chess.model.Color;
import chess.model.Piece;
import chess.model.PieceVisitor;
import chess.model.pieces.*;
public class ConsolePieceName implements PieceVisitor<String> {
public String getString(Piece piece){
if (piece.getColor()== Color.Black){
return Colors.BLACK + visit(piece);
}
else {
return Colors.WHITE + visit(piece);
}
}
@Override
public String visitPiece(Bishop bishop) {
return "B";
}
@Override
public String visitPiece(King king) {
return "K";
}
@Override
public String visitPiece(Knight knight) {
return "N";
}
@Override
public String visitPiece(Pawn pawn) {
return "P";
}
@Override
public String visitPiece(Queen queen) {
return "Q";
}
@Override
public String visitPiece(Rook rook) {
return "R";
}
}

View File

@@ -0,0 +1,79 @@
package chess.view.simplerender;
import java.awt.Image;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import chess.model.Color;
import chess.model.Piece;
import chess.model.PieceVisitor;
import chess.model.pieces.Bishop;
import chess.model.pieces.King;
import chess.model.pieces.Knight;
import chess.model.pieces.Pawn;
import chess.model.pieces.Queen;
import chess.model.pieces.Rook;
import chess.view.AssetManager;
public class PieceIcon implements PieceVisitor<String> {
private static final String basePath = "pieces2D/";
private static final Map<String, Icon> cache = new HashMap<>();
public Icon getIcon(Piece piece) throws IOException {
if (piece == null)
return null;
String path = basePath + colorToString(piece.getColor()) + "-" + visit(piece) + ".png";
return getIcon(path);
}
private Icon getIcon(String path) throws IOException {
Icon image = cache.get(path);
if (image != null)
return image;
image = new ImageIcon(new ImageIcon(AssetManager.getResource(path).readAllBytes()).getImage()
.getScaledInstance(100, 100, Image.SCALE_SMOOTH));
cache.put(path, image);
return image;
}
private String colorToString(Color color) {
return color == Color.Black ? "black" : "white";
}
@Override
public String visitPiece(Bishop bishop) {
return "bishop";
}
@Override
public String visitPiece(King king) {
return "king";
}
@Override
public String visitPiece(Knight knight) {
return "knight";
}
@Override
public String visitPiece(Pawn pawn) {
return "pawn";
}
@Override
public String visitPiece(Queen queen) {
return "queen";
}
@Override
public String visitPiece(Rook rook) {
return "rook";
}
}

View File

@@ -0,0 +1,327 @@
package chess.view.simplerender;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import chess.controller.Command;
import chess.controller.Command.CommandResult;
import chess.controller.CommandExecutor;
import chess.controller.commands.CastlingCommand;
import chess.controller.commands.GetAllowedCastlingsCommand;
import chess.controller.commands.GetAllowedMovesPieceCommand;
import chess.controller.commands.GetPieceAtCommand;
import chess.controller.commands.MoveCommand;
import chess.controller.commands.PromoteCommand;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.controller.commands.UndoCommand;
import chess.controller.commands.GetAllowedCastlingsCommand.CastlingResult;
import chess.controller.event.GameListener;
import chess.model.Coordinate;
import chess.model.Move;
import chess.model.Piece;
public class Window extends JFrame implements GameListener {
private final CommandExecutor commandExecutor;
private Coordinate lastClick = null;
private final JLabel cells[][];
private final JLabel displayText;
private final JButton castlingButton = new JButton("Roque");
private final JButton bigCastlingButton = new JButton("Grand Roque");
private final JButton undoButton = new JButton("Annuler le coup précédent");
private final boolean showPopups;
public Window(final CommandExecutor commandExecutor, boolean showPopups) {
this.cells = new JLabel[8][8];
this.displayText = new JLabel();
this.commandExecutor = commandExecutor;
this.showPopups = showPopups;
setSize(800, 910);
setVisible(true);
setLocationRelativeTo(null);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent e) {
commandExecutor.close();
}
});
}
private CommandResult sendCommand(Command command) {
return this.commandExecutor.executeCommand(command);
}
private Color getCellColor(int x, int y) {
return ((x + y) % 2 == 1) ? Color.DARK_GRAY : Color.LIGHT_GRAY;
}
private void buildButtons(JPanel bottom) {
castlingButton.addActionListener((event) -> {
sendCommand(new CastlingCommand(false));
});
bigCastlingButton.addActionListener((event) -> {
sendCommand(new CastlingCommand(true));
});
undoButton.addActionListener((event) -> {
sendCommand(new UndoCommand());
});
bottom.add(castlingButton);
bottom.add(bigCastlingButton);
bottom.add(undoButton);
}
private void buildBoard() {
JPanel content = new JPanel();
JPanel grid = new JPanel(new GridLayout(8, 8));
JPanel bottom = new JPanel();
buildButtons(bottom);
content.add(this.displayText);
content.add(grid);
content.add(bottom);
setContentPane(content);
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
JLabel label = new JLabel();
label.setOpaque(true);
label.setBackground(getCellColor(x, y));
this.cells[x][y] = label;
final int xx = x;
final int yy = y;
label.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
onCellClicked(xx, yy);
}
});
grid.add(label);
}
}
updateBoard();
}
private boolean isCellEmpty(int x, int y) {
return pieceAt(x, y) == null;
}
private Piece pieceAt(int x, int y) {
GetPieceAtCommand command = new GetPieceAtCommand(new Coordinate(x, y));
sendCommand(command);
return command.getPiece();
}
private void updateBoard() {
PieceIcon pieceIcon = new PieceIcon();
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
JLabel cell = this.cells[x][y];
try {
cell.setIcon(pieceIcon.getIcon(pieceAt(x, y)));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private boolean previewMoves(int x, int y) {
GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(new Coordinate(x, y));
if (sendCommand(movesCommand) == CommandResult.NotAllowed)
return false;
List<Coordinate> allowedMoves = movesCommand.getDestinations();
if (allowedMoves.isEmpty())
return false;
for (Coordinate destCoord : allowedMoves) {
JLabel cell = this.cells[destCoord.getX()][destCoord.getY()];
Graphics g = cell.getGraphics();
g.setColor(new Color(128, 128, 128, 128));
g.fillOval(25, 25, 50, 50);
}
return true;
}
private void drawInvalid(Move move) {
JLabel from = this.cells[move.getStart().getX()][move.getStart().getY()];
JLabel to = this.cells[move.getFinish().getX()][move.getFinish().getY()];
from.setBackground(Color.RED);
to.setBackground(Color.RED);
}
private void clearMoves() {
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
JLabel cell = this.cells[x][y];
cell.setBackground(getCellColor(x, y));
cell.paint(cell.getGraphics());
}
}
}
private void onCellClicked(int x, int y) {
clearMoves();
if (this.lastClick == null) {
if (isCellEmpty(x, y))
return;
if (!previewMoves(x, y))
return;
this.lastClick = new Coordinate(x, y);
return;
}
if (!this.lastClick.equals(new Coordinate(x, y))) {
Move move = new Move(lastClick, new Coordinate(x, y));
sendCommand(new MoveCommand(move));
}
this.lastClick = null;
}
private void updateButtons() {
GetAllowedCastlingsCommand cmd = new GetAllowedCastlingsCommand();
sendCommand(cmd);
this.castlingButton.setEnabled(
cmd.getCastlingResult() == CastlingResult.Small || cmd.getCastlingResult() == CastlingResult.Both);
this.bigCastlingButton.setEnabled(
cmd.getCastlingResult() == CastlingResult.Big || cmd.getCastlingResult() == CastlingResult.Both);
}
@Override
public void onPlayerTurn(chess.model.Color color, boolean undone) {
this.displayText.setText("Current turn: " + color);
updateButtons();
}
@Override
public void onWin(chess.model.Color color) {
JOptionPane.showMessageDialog(this, "Victory of " + color);
}
@Override
public void onKingInCheck() {
if (!showPopups)
return;
SwingUtilities.invokeLater(() -> {
JOptionPane.showMessageDialog(this, "Check!");
});
}
@Override
public void onKingInMat() {
SwingUtilities.invokeLater(() -> {
JOptionPane.showMessageDialog(this, "Checkmate!");
});
}
@Override
public void onPatSituation() {
JOptionPane.showMessageDialog(this, "Pat. It's a draw!");
}
@Override
public void onSurrender(chess.model.Color color) {
JOptionPane.showMessageDialog(this, color + " has surrendered.");
}
@Override
public void onGameEnd() {
JOptionPane.showMessageDialog(this, "End of the game");
this.dispose();
this.commandExecutor.close();
}
@Override
public void onGameStart() {
buildBoard();
}
@Override
public void onPromotePawn(Coordinate pieceCoords) {
if (!showPopups)
return;
SwingUtilities.invokeLater(() -> {
String result = null;
Object[] possibilities = new Object[PromoteType.values().length];
int i = 0;
for (PromoteType type : PromoteType.values()) {
possibilities[i] = type.name();
i++;
}
while (result == null || result.isEmpty()) {
result = (String) JOptionPane.showInputDialog(
this,
"Choose the type of piece to upgrade the pawn",
"Promote Dialog",
JOptionPane.PLAIN_MESSAGE,
null,
possibilities,
possibilities[0]);
}
PromoteType choosedType = null;
for (PromoteType type : PromoteType.values()) {
if (type.name().equals(result)) {
choosedType = type;
break;
}
}
if (choosedType != null)
sendCommand(new PromoteCommand(choosedType));
});
}
@Override
public void onBoardUpdate() {
updateBoard();
}
@Override
public void onMove(Move move, boolean captured) {}
@Override
public void onMoveNotAllowed(Move move) {
drawInvalid(move);
}
@Override
public void onDraw() {
JOptionPane.showMessageDialog(this, "Same position was repeated three times. It's a draw!");
}
@Override
public void onCastling(boolean bigCastling) {}
@Override
public void onPawnPromoted(PromoteType promotion) {}
}

View File

@@ -0,0 +1,26 @@
package common;
import java.util.ArrayList;
import java.util.List;
public class Signal0 {
private final List<Runnable> handlers;
public Signal0() {
this.handlers = new ArrayList<>();
}
public void connect(Runnable handler) {
this.handlers.add(handler);
}
public void disconnect(Runnable handler) {
this.handlers.remove(handler);
}
public void emit() {
for (Runnable handler : this.handlers) {
handler.run();
}
}
}

View File

@@ -0,0 +1,27 @@
package common;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class Signal1<T> {
private final List<Consumer<T>> handlers;
public Signal1() {
this.handlers = new ArrayList<>();
}
public void connect(Consumer<T> handler) {
this.handlers.add(handler);
}
public void disconnect(Consumer<T> handler) {
this.handlers.remove(handler);
}
public void emit(T arg) {
for (Consumer<T> handler : this.handlers) {
handler.accept(arg);
}
}
}

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e9de4cc336a3758a7285ca5dcc5c01409f1ff5d50e764f38857cdf5900590751
size 1862504

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3fba82d694608f48ba85224f9d1099ae6d884b4588e1e9b355b136824ba12993
size 38924264

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e2b10052a3e58ff14020f737c4493266a9fae7bd8318527ebf32cddfb97b21fe
size 40833600

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a6782c2627d0de0de695baaeaca286da3c77f443cc5788d8c3a0057406296776
size 3503068

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b714c3884dbcb0c8863ae8583c9dd61b5a3c4531e38bb5af8a403d7b69f52183
size 2249344

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b3ebb87642f2f7f205dbfac65ed2bdef1ab4f4b67cf9929619d2e681004655cc
size 15926128

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c58aaab69205d56e6f41f94dc2c4e759d2d7cd43fa4fdd7cf615d21a6f18c30f
size 5458016

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f2929607904bde363cbd4fc3766ba2f9a55b7d37f5e6d0c8ad6995dc311775a7
size 10249428

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:81da4beec7bf500fea31b5626c58edc2d9f575b3d5ef743b16854ca40e9d86c8
size 2110080

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1d812a5ddd17844e71fa8216722413065b46b12ce37f9d5591d6120e29393686
size 25214944

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:56f9ca7da9758b8f4801a829cf92b03c4ee3fbf4d306e55f1526236a12a8931d
size 9602776

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0cd5999920bf1919a6c37fbb3405272fe8721e915f3fd1dac9114a154cf23898
size 19819784

View File

@@ -0,0 +1 @@
1.g4 Na6 2.Bh3 b6 3.Nf3 c6

View File

@@ -0,0 +1 @@
1.e4 e6 2.e5 d5

View File

@@ -0,0 +1 @@
1.f3 e5 2.g4 Qh4# 0-1

View File

@@ -0,0 +1 @@
1.f4 e5 2.fxe5 Ke7 3.e6 Kf6 4.e7 Kg6

View File

@@ -0,0 +1,10 @@
1. e4 e5 2. Nc3
Bc5 3. Nf3
Qf6 4. Nd5 Qd6 5. d3 c6 6. Nc3 h6 7. a3 Qg6
8. Nxe5 Qd6 9. Nc4 Qe6 10. d4 Be7
11. Ne3 b5 12. Nf5 d5 13. Nxg7+ Kd7 14. Nxe6
fxe6 15. exd5 cxd5 16. Bf4 Nf6 17.
Bxb5+ Kd8 18. Qf3 Bd7 19. Be5 a6 20. Bxf6 Re8 21. Bxe7+ Kxe7 22. Nxd5+ exd5 23. Qxd5
Kf8+ 24. Be2 Bc6 25. Qd6+ Re7 26. Kf1 Ba4 27. b3
Nc6 28. bxa4 a5 29. Qxc6 Rd8 30. Qxh6+ Kg8 31. Bc4+ Rf7 32. Qg5+ Kh8 33. Bxf7
Kh7 34. Qg6+ Kh8 35. Ra2 Rxd4 36. Qg8# 1-0

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Some files were not shown because too many files have changed in this diff Show More