1 Commits

Author SHA1 Message Date
3ac7b4ad65 i failed you 2025-04-16 19:03:55 +02:00
91 changed files with 791 additions and 1896 deletions

3
.gitattributes vendored
View File

@@ -3,6 +3,7 @@
#
# 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

View File

@@ -1,25 +0,0 @@
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

3
.gitignore vendored
View File

@@ -7,6 +7,3 @@ build
app/bin
.vscode
audio/*.wav
app/audio/*.wav

View File

@@ -21,7 +21,7 @@ def lwjgl_natives = "natives-linux"
dependencies {
// Use JUnit Jupiter for testing.
testImplementation "org.junit.jupiter:junit-jupiter:5.11.4"
testImplementation "org.junit.jupiter:junit-jupiter:5.9.1"
implementation "org.lwjgl:lwjgl:$lwjgl_version"
implementation "org.lwjgl:lwjgl-opengl:$lwjgl_version"
@@ -39,18 +39,6 @@ application {
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

View File

@@ -1,30 +1,23 @@
package chess;
import java.util.Scanner;
import chess.view.consolerender.Colors;
import java.util.Scanner;
public class App {
public static void main(String[] args) {
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) {
2 - Window.""");
switch (new Scanner(System.in).nextLine()) {
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

@@ -5,17 +5,26 @@ package chess;
import chess.controller.CommandExecutor;
import chess.controller.commands.NewGameCommand;
import chess.model.ChessBoard;
import chess.model.Game;
import chess.simulator.PromoteTest;
import chess.view.consolerender.Console;
public class ConsoleMain {
public static void main(String[] args) {
Game game = new Game();
Game game = new Game(new ChessBoard());
CommandExecutor commandExecutor = new CommandExecutor(game);
PromoteTest promoteTest = new PromoteTest(commandExecutor);
commandExecutor.addListener(promoteTest);
Console console = new Console(commandExecutor);
commandExecutor.addListener(console);
promoteTest.onComplete.connect(() -> {
console.setCaptureInput(true);
});
commandExecutor.executeCommand(new NewGameCommand());
}
}

View File

@@ -1,9 +1,33 @@
package chess;
import chess.view.DDDrender.Window;
import chess.ai.DumbAI;
import chess.controller.CommandExecutor;
import chess.controller.commands.NewGameCommand;
import chess.controller.event.GameAdaptator;
import chess.model.ChessBoard;
import chess.model.Color;
import chess.model.Game;
import chess.pgn.PgnExport;
import chess.view.render.Window;
public class OpenGLMain {
public static void main(String[] args) {
new Window().run();
Game game = new Game(new ChessBoard());
CommandExecutor commandExecutor = new CommandExecutor(game);
Window window = new Window(commandExecutor);
commandExecutor.addListener(window);
// DumbAI ai = new DumbAI(commandExecutor, Color.Black);
// commandExecutor.addListener(ai);
// DumbAI ai2 = new DumbAI(commandExecutor, Color.White);
// commandExecutor.addListener(ai2);
commandExecutor.executeCommand(new NewGameCommand());
window.run();
}
}

View File

@@ -1,45 +1,36 @@
package chess;
import chess.ai.minimax.AlphaBetaAI;
import chess.ai.minimax.AlphaBetaConsolePrinter;
import chess.ai.DumbAI;
import chess.controller.CommandExecutor;
import chess.controller.commands.NewGameCommand;
import chess.controller.event.GameAdapter;
import chess.controller.event.GameAdaptator;
import chess.model.ChessBoard;
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();
Game game = new Game(new ChessBoard());
CommandExecutor commandExecutor = new CommandExecutor(game);
Window window = new Window(commandExecutor, true);
Window window = new Window(commandExecutor, false);
commandExecutor.addListener(window);
AlphaBetaAI ai = new AlphaBetaAI(commandExecutor, Color.Black, 5);
DumbAI ai = new DumbAI(commandExecutor, Color.Black);
commandExecutor.addListener(ai);
AlphaBetaConsolePrinter aiResults = new AlphaBetaConsolePrinter(ai);
aiResults.connect();
DumbAI ai2 = new DumbAI(commandExecutor, Color.White);
commandExecutor.addListener(ai2);
// 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 GameAdapter(){
commandExecutor.addListener(new GameAdaptator(){
@Override
public void onGameEnd() {
System.out.println(PgnExport.exportGame(game));
}
});
commandExecutor.addListener(new GameAudio());
commandExecutor.executeCommand(new NewGameCommand());
}
}

View File

@@ -1,41 +0,0 @@
package chess.ai;
import java.util.List;
import chess.ai.actions.AIAction;
import chess.ai.actions.AIActions;
import chess.controller.CommandExecutor;
import chess.controller.event.GameAdapter;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Piece;
public abstract class AI extends GameAdapter {
protected final CommandExecutor commandExecutor;
protected final Color color;
public AI(CommandExecutor commandExecutor, Color color) {
this.commandExecutor = commandExecutor;
this.color = color;
}
protected abstract void play();
@Override
public void onPlayerTurn(Color color, boolean undone) {
if (this.color != color || undone)
return;
play();
}
protected List<AIAction> getAllowedActions() {
return AIActions.getAllowedActions(this.commandExecutor);
}
protected Piece pieceAt(Coordinate coordinate) {
return AIActions.pieceAt(coordinate, this.commandExecutor);
}
}

View File

@@ -3,25 +3,100 @@ package chess.ai;
import java.util.List;
import java.util.Random;
import chess.ai.actions.AIAction;
import chess.controller.Command;
import chess.controller.CommandExecutor;
import chess.controller.commands.CastlingCommand;
import chess.controller.commands.GetAllowedCastlingsCommand;
import chess.controller.commands.GetPieceAtCommand;
import chess.controller.commands.GetPlayerMovesCommand;
import chess.controller.commands.MoveCommand;
import chess.controller.commands.PromoteCommand;
import chess.controller.commands.GetAllowedCastlingsCommand.CastlingResult;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.controller.event.GameAdaptator;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
import chess.model.Piece;
public class DumbAI extends AI {
public class DumbAI extends GameAdaptator {
private final Color player;
private final CommandExecutor commandExecutor;
private final Random random = new Random();
public DumbAI(CommandExecutor commandExecutor, Color color) {
super(commandExecutor, color);
this.player = color;
this.commandExecutor = commandExecutor;
}
@Override
protected void play() {
List<AIAction> actions = getAllowedActions();
public void onPlayerTurn(Color color) {
if (color != player)
return;
int randomAction = this.random.nextInt(actions.size());
GetPlayerMovesCommand cmd = new GetPlayerMovesCommand();
sendCommand(cmd);
actions.get(randomAction).applyAction();
GetAllowedCastlingsCommand cmd2 = new GetAllowedCastlingsCommand();
sendCommand(cmd2);
CastlingResult castlings = cmd2.getCastlingResult();
List<Move> moves = cmd.getMoves();
switch (castlings) {
case Both: {
int randomMove = this.random.nextInt(moves.size() + 2);
if (randomMove < moves.size() - 2)
break;
this.commandExecutor.executeCommand(new CastlingCommand(randomMove == moves.size()));
return;
}
case Small: {
int randomMove = this.random.nextInt(moves.size() + 1);
if (randomMove != moves.size())
break;
this.commandExecutor.executeCommand(new CastlingCommand(false));
return;
}
case Big: {
int randomMove = this.random.nextInt(moves.size() + 1);
if (randomMove != moves.size())
break;
this.commandExecutor.executeCommand(new CastlingCommand(true));
return;
}
default:
break;
}
int randomMove = this.random.nextInt(moves.size());
this.commandExecutor.executeCommand(new MoveCommand(moves.get(randomMove)));
}
@Override
public void onPromotePawn(Coordinate pieceCoords) {
Piece pawn = pieceAt(pieceCoords);
if (pawn.getColor() != this.player)
return;
int promote = this.random.nextInt(PromoteType.values().length);
this.commandExecutor.executeCommand(new PromoteCommand(PromoteType.values()[promote]));
}
private Piece pieceAt(Coordinate coordinate) {
GetPieceAtCommand command = new GetPieceAtCommand(coordinate);
sendCommand(command);
return command.getPiece();
}
private void sendCommand(Command command) {
this.commandExecutor.executeCommand(command);
}
}

View File

@@ -1,54 +0,0 @@
package chess.ai;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import chess.ai.actions.AIAction;
import chess.ai.actions.AIActionMove;
import chess.controller.CommandExecutor;
import chess.model.Color;
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<AIAction> getBestMoves() {
List<AIAction> actions = getAllowedActions();
List<AIAction> bestMoves = new ArrayList<>();
int bestCost = 0;
for (AIAction action : actions) {
if (action instanceof AIActionMove move) {
int moveCost = getMoveCost(move.getMove());
if (moveCost == bestCost) {
bestMoves.add(move);
} else if (moveCost > bestCost) {
bestMoves.clear();
bestMoves.add(move);
bestCost = moveCost;
}
}
}
return bestMoves;
}
@Override
protected void play() {
List<AIAction> bestMoves = getBestMoves();
bestMoves.get(this.random.nextInt(bestMoves.size())).applyAction();
}
}

View File

@@ -1,67 +0,0 @@
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

@@ -1,129 +0,0 @@
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

@@ -1,33 +0,0 @@
package chess.ai.actions;
import chess.controller.Command;
import chess.controller.CommandExecutor;
import chess.controller.Command.CommandResult;
import chess.controller.commands.UndoCommand;
public abstract class AIAction {
private final CommandExecutor commandExecutor;
public AIAction(CommandExecutor commandExecutor) {
this.commandExecutor = commandExecutor;
}
protected CommandResult sendCommand(Command cmd, CommandExecutor commandExecutor) {
return commandExecutor.executeCommand(cmd);
}
public void undoAction(CommandExecutor commandExecutor) {
sendCommand(new UndoCommand(), commandExecutor);
}
public void undoAction() {
undoAction(this.commandExecutor);
}
public void applyAction() {
applyAction(this.commandExecutor);
}
public abstract void applyAction(CommandExecutor commandExecutor);
}

View File

@@ -1,20 +0,0 @@
package chess.ai.actions;
import chess.controller.CommandExecutor;
import chess.controller.commands.CastlingCommand;
public class AIActionCastling extends AIAction{
private final boolean bigCastling;
public AIActionCastling(CommandExecutor commandExecutor, boolean bigCastling) {
super(commandExecutor);
this.bigCastling = bigCastling;
}
@Override
public void applyAction(CommandExecutor commandExecutor) {
sendCommand(new CastlingCommand(this.bigCastling), commandExecutor);
}
}

View File

@@ -1,25 +0,0 @@
package chess.ai.actions;
import chess.controller.CommandExecutor;
import chess.controller.commands.MoveCommand;
import chess.model.Move;
public class AIActionMove extends AIAction{
private final Move move;
public AIActionMove(CommandExecutor commandExecutor, Move move) {
super(commandExecutor);
this.move = move;
}
public Move getMove() {
return move;
}
@Override
public void applyAction(CommandExecutor commandExecutor) {
sendCommand(new MoveCommand(move), commandExecutor);
}
}

View File

@@ -1,26 +0,0 @@
package chess.ai.actions;
import chess.controller.CommandExecutor;
import chess.controller.commands.MoveCommand;
import chess.controller.commands.PromoteCommand;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.model.Move;
public class AIActionMoveAndPromote extends AIAction{
private final Move move;
private final PromoteType promoteType;
public AIActionMoveAndPromote(CommandExecutor commandExecutor, Move move, PromoteType promoteType) {
super(commandExecutor);
this.move = move;
this.promoteType = promoteType;
}
@Override
public void applyAction(CommandExecutor commandExecutor) {
sendCommand(new MoveCommand(move), commandExecutor);
sendCommand(new PromoteCommand(promoteType), commandExecutor);
}
}

View File

@@ -1,87 +0,0 @@
package chess.ai.actions;
import java.util.ArrayList;
import java.util.List;
import chess.controller.Command;
import chess.controller.Command.CommandResult;
import chess.controller.CommandExecutor;
import chess.controller.commands.GetAllowedCastlingsCommand;
import chess.controller.commands.GetAllowedCastlingsCommand.CastlingResult;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.controller.commands.GetPieceAtCommand;
import chess.controller.commands.GetPlayerMovesCommand;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
import chess.model.Piece;
import chess.model.pieces.Pawn;
public class AIActions {
public static List<AIAction> getAllowedActions(CommandExecutor commandExecutor) {
List<Move> moves = getAllowedMoves(commandExecutor);
CastlingResult castlingResult = getAllowedCastlings(commandExecutor);
List<AIAction> actions = new ArrayList<>(moves.size() + 10);
for (Move move : moves) {
Piece movingPiece = pieceAt(move.getStart(), commandExecutor);
if (movingPiece instanceof Pawn) {
int enemyLineY = movingPiece.getColor() == Color.White ? 0 : 7;
if (move.getFinish().getY() == enemyLineY) {
PromoteType[] promotes = PromoteType.values();
for (PromoteType promote : promotes) {
actions.add(new AIActionMoveAndPromote(commandExecutor, move, promote));
}
continue;
}
}
actions.add(new AIActionMove(commandExecutor, move));
}
switch (castlingResult) {
case Both:
actions.add(new AIActionCastling(commandExecutor, true));
actions.add(new AIActionCastling(commandExecutor, false));
break;
case Small:
actions.add(new AIActionCastling(commandExecutor, false));
break;
case Big:
actions.add(new AIActionCastling(commandExecutor, true));
break;
case None:
break;
}
return actions;
}
private static CastlingResult getAllowedCastlings(CommandExecutor commandExecutor) {
GetAllowedCastlingsCommand cmd2 = new GetAllowedCastlingsCommand();
sendCommand(cmd2, commandExecutor);
return cmd2.getCastlingResult();
}
private static List<Move> getAllowedMoves(CommandExecutor commandExecutor) {
GetPlayerMovesCommand cmd = new GetPlayerMovesCommand();
sendCommand(cmd, commandExecutor);
return cmd.getMoves();
}
private static CommandResult sendCommand(Command command, CommandExecutor commandExecutor) {
CommandResult result = commandExecutor.executeCommand(command);
assert result != CommandResult.NotAllowed : "Command not allowed!";
return result;
}
public static Piece pieceAt(Coordinate coordinate, CommandExecutor commandExecutor) {
GetPieceAtCommand command = new GetPieceAtCommand(coordinate);
commandExecutor.executeCommand(command);
return command.getPiece();
}
}

View File

@@ -1,83 +0,0 @@
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.ai.actions.AIAction;
import chess.controller.CommandExecutor;
import chess.model.Color;
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 AIAction getBestMove() {
List<AIAction> actions = getAllowedActions();
List<Future<Float>> moveEvaluations = new ArrayList<>(actions.size());
float bestMoveValue = MIN_FLOAT;
AIAction bestMove = null;
this.onStartEval.emit(actions.size());
for (AIAction action : actions) {
moveEvaluations.add(this.threadPool.submit(() -> {
return AlphaBetaThreadCreator.getMoveValue(action, this.searchDepth);
}));
}
for (int i = 0; i < actions.size(); i++) {
this.onProgress.emit((float) i / (float) actions.size());
AIAction action = actions.get(i);
float value = MIN_FLOAT;
try {
value = moveEvaluations.get(i).get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
if (value > bestMoveValue) {
bestMoveValue = value;
bestMove = action;
}
}
this.onCompleteEval.emit(bestMoveValue);
return bestMove;
}
@Override
public void onGameEnd() {
this.threadPool.close();
}
@Override
protected void play() {
AIAction move = getBestMove();
move.applyAction();
}
}

View File

@@ -1,27 +0,0 @@
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

@@ -1,101 +0,0 @@
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.ai.actions.AIAction;
import chess.model.ChessBoard;
import chess.model.Color;
import chess.model.Coordinate;
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(AIAction move, int searchDepth) {
move.applyAction(this.simulation.getCommandExecutor());
float value = -negaMax(searchDepth - 1, MIN_FLOAT, MAX_FLOAT);
move.undoAction(this.simulation.getCommandExecutor());
return value;
}
private float negaMax(int depth, float alpha, float beta) {
float value = MIN_FLOAT;
List<AIAction> moves = this.simulation.getAllowedActions();
if (moves.isEmpty())
return -getEndGameEvaluation();
List<Entry<AIAction, Float>> movesCost = new ArrayList<>(moves.size());
for (AIAction move : moves) {
move.applyAction();
movesCost.add(Map.entry(move, -getBoardEvaluation()));
move.undoAction();
}
Collections.sort(movesCost, (first, second) -> {
return Float.compare(first.getValue(), second.getValue());
});
if (depth == 1)
return -movesCost.getFirst().getValue();
for (var moveEntry : movesCost) {
AIAction move = moveEntry.getKey();
move.applyAction();
value = Float.max(value, -negaMax(depth - 1, -beta, -alpha));
move.undoAction();
alpha = Float.max(alpha, value);
if (alpha >= beta)
return value;
}
return value;
}
public GameSimulation getSimulation() {
return simulation;
}
}

View File

@@ -1,36 +0,0 @@
package chess.ai.minimax;
import java.util.concurrent.ThreadFactory;
import chess.ai.actions.AIAction;
import chess.controller.CommandExecutor;
import chess.model.Color;
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(AIAction 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

@@ -1,102 +0,0 @@
package chess.ai.minimax;
import java.util.List;
import chess.ai.actions.AIAction;
import chess.ai.actions.AIActions;
import chess.controller.Command;
import chess.controller.Command.CommandResult;
import chess.controller.CommandExecutor;
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.commands.UndoCommand;
import chess.controller.event.EmptyGameDispatcher;
import chess.controller.event.GameAdapter;
import chess.model.ChessBoard;
import chess.model.Color;
import chess.model.Game;
import chess.model.Move;
import chess.model.PermissiveGame;
public class GameSimulation extends GameAdapter {
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<AIAction> getAllowedActions() {
return AIActions.getAllowedActions(this.simulation);
}
public void close() {
this.simulation.close();
}
}

View File

@@ -1,10 +1,7 @@
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;
@@ -20,7 +17,7 @@ public class CommandExecutor {
}
public CommandExecutor(Game game) {
this(game, new AsyncGameDispatcher());
this(game, new GameDispatcher());
}
public CommandExecutor(Game game, GameDispatcher dispatcher) {
@@ -44,13 +41,6 @@ public class CommandExecutor {
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:
@@ -62,21 +52,19 @@ public class CommandExecutor {
return;
case Moved:
boolean notifyPlayerTurn = true;
this.dispatcher.onBoardUpdate();
if (!(command instanceof UndoCommand) && checkGameStatus()) {
if (checkGameStatus()) {
this.dispatcher.onGameEnd();
notifyPlayerTurn = false;
return;
}
switchPlayerTurn(command instanceof UndoCommand, notifyPlayerTurn);
switchPlayerTurn();
return;
}
}
private void switchPlayerTurn(boolean undone, boolean notifyPlayerTurn) {
private void switchPlayerTurn() {
this.game.switchPlayerTurn();
if (notifyPlayerTurn)
this.dispatcher.onPlayerTurn(this.game.getPlayerTurn(), undone);
this.dispatcher.onPlayerTurn(this.game.getPlayerTurn());
}
/**
@@ -119,6 +107,6 @@ public class CommandExecutor {
}
public void close() {
this.dispatcher.close();
this.dispatcher.stopService();
}
}

View File

@@ -49,10 +49,6 @@ public class CastlingCommand extends PlayerCommand {
board.applyMove(this.kingMove);
board.applyMove(this.rookMove);
board.setLastMove(this.kingMove);
outputSystem.onCastling(this.bigCastling);
return CommandResult.Moved;
}

View File

@@ -7,7 +7,7 @@ import chess.model.Coordinate;
import chess.model.Game;
import chess.model.Move;
import chess.model.Piece;
import chess.model.rules.PiecePathChecker;
import chess.model.visitor.PiecePathChecker;
public class MoveCommand extends PlayerCommand {
@@ -37,6 +37,7 @@ public class MoveCommand extends PlayerCommand {
return result;
case Moved:
outputSystem.onMove(this.move);
game.saveTraitPiecesPos();
return result;
@@ -52,7 +53,7 @@ public class MoveCommand extends PlayerCommand {
final ChessBoard board = game.getBoard();
// we must promote the pending pawn before
if (game.pawnShouldBePromoted())
if (board.pawnShouldBePromoted())
return CommandResult.NotAllowed;
Piece piece = board.pieceAt(move.getStart());
@@ -75,14 +76,11 @@ public class MoveCommand extends PlayerCommand {
}
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;
}

View File

@@ -51,7 +51,7 @@ public class NewGameCommand extends Command {
game.reset();
outputSystem.onGameStart();
outputSystem.onPlayerTurn(game.getPlayerTurn(), false);
outputSystem.onPlayerTurn(game.getPlayerTurn());
return CommandResult.NotMoved;
}

View File

@@ -9,9 +9,9 @@ import chess.model.Game;
import chess.model.Piece;
import chess.model.pieces.Bishop;
import chess.model.pieces.Knight;
import chess.model.pieces.Pawn;
import chess.model.pieces.Queen;
import chess.model.pieces.Rook;
import chess.model.visitor.PawnIdentifier;
public class PromoteCommand extends PlayerCommand {
@@ -28,7 +28,6 @@ public class PromoteCommand extends PlayerCommand {
public PromoteCommand(PromoteType promoteType) {
this.promoteType = promoteType;
assert this.promoteType != null;
this.pieceCoords = null;
this.oldPawn = null;
}
@@ -43,7 +42,7 @@ public class PromoteCommand extends PlayerCommand {
return CommandResult.NotAllowed;
Piece pawn = board.pieceAt(this.pieceCoords);
if (!(pawn instanceof Pawn))
if (!new PawnIdentifier(game.getPlayerTurn()).isPawn(pawn))
return CommandResult.NotAllowed;
int destY = this.pieceCoords.getY();
@@ -56,11 +55,6 @@ public class PromoteCommand extends PlayerCommand {
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;
}

View File

@@ -1,113 +0,0 @@
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

@@ -1,6 +1,5 @@
package chess.controller.event;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
@@ -32,7 +31,7 @@ public class EmptyGameDispatcher extends GameDispatcher {
}
@Override
public void onMove(Move move, boolean captured) {
public void onMove(Move move) {
}
@Override
@@ -44,7 +43,7 @@ public class EmptyGameDispatcher extends GameDispatcher {
}
@Override
public void onPlayerTurn(Color color, boolean undone) {
public void onPlayerTurn(Color color) {
}
@Override
@@ -59,20 +58,4 @@ public class EmptyGameDispatcher extends GameDispatcher {
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

@@ -1,14 +1,13 @@
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 GameAdapter implements GameListener {
public abstract class GameAdaptator implements GameListener {
@Override
public void onPlayerTurn(Color color, boolean undone) {}
public void onPlayerTurn(Color color) {}
@Override
public void onWin(Color color) {}
@@ -38,7 +37,7 @@ public abstract class GameAdapter implements GameListener {
public void onGameEnd() {}
@Override
public void onMove(Move move, boolean captured) {}
public void onMove(Move move) {}
@Override
public void onMoveNotAllowed(Move move) {}
@@ -46,10 +45,4 @@ public abstract class GameAdapter implements GameListener {
@Override
public void onDraw() {}
@Override
public void onCastling(boolean bigCastling) {}
@Override
public void onPawnPromoted(PromoteType promotion) {}
}

View File

@@ -1,9 +1,100 @@
package chess.controller.event;
public abstract class GameDispatcher extends GameAdapter {
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
public abstract void addListener(GameListener listener);
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
public abstract void close();
public class GameDispatcher implements GameListener {
private final List<GameListener> listeners;
private final ExecutorService executor;
public GameDispatcher() {
this.listeners = new ArrayList<>();
this.executor = Executors.newSingleThreadExecutor();
}
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) {
asyncForEachCall((l) -> l.onPlayerTurn(color));
}
@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) {
asyncForEachCall((l) -> l.onMove(move));
}
@Override
public void onMoveNotAllowed(Move move) {
asyncForEachCall((l) -> l.onMoveNotAllowed(move));
}
@Override
public void onDraw() {
asyncForEachCall((l) -> l.onDraw());
}
public void stopService() {
this.executor.shutdown();
}
}

View File

@@ -1,6 +1,5 @@
package chess.controller.event;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
@@ -39,15 +38,12 @@ public interface GameListener {
/**
* 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);
void onMove(Move move);
/**
* Invoked when a sent move is not allowed
*
* @param move the move to be processed
*/
void onMoveNotAllowed(Move move);
@@ -59,46 +55,26 @@ public interface GameListener {
/**
* 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);
void onPlayerTurn(Color color);
/**
* 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

@@ -4,9 +4,9 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import chess.model.pieces.King;
import chess.model.pieces.Pawn;
import chess.model.rules.PiecePathChecker;
import chess.model.visitor.KingIdentifier;
import chess.model.visitor.PawnIdentifier;
import chess.model.visitor.PiecePathChecker;
public class ChessBoard {
public static class Cell {
@@ -31,10 +31,6 @@ public class ChessBoard {
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++) {
@@ -45,7 +41,6 @@ public class ChessBoard {
this.lastVirtualMove = null;
this.lastMove = null;
this.lastEjectedPiece = null;
this.kingPos = new Coordinate[Color.values().length];
}
public void applyMove(Move move) {
@@ -103,8 +98,6 @@ public class ChessBoard {
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) {
@@ -112,31 +105,18 @@ public class ChessBoard {
}
public Coordinate findKing(Color color) {
return kingPos[color.ordinal()];
}
public List<Coordinate> getAllowedStarts(Coordinate finish, Color color) {
List<Coordinate> starts = new ArrayList<>();
KingIdentifier kingIdentifier = new KingIdentifier(color);
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();
Coordinate coordinate = new Coordinate(i, j);
Piece piece = pieceAt(coordinate);
if (kingIdentifier.isKing(piece)) {
return coordinate;
}
}
return starts;
}
assert false : "No king found ?!";
return null;
}
public boolean isKingInCheck(Color color) {
@@ -147,7 +127,7 @@ public class ChessBoard {
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)
if (attackPiece == null)
continue;
PiecePathChecker checker = new PiecePathChecker(this, new Move(attackCoords, kingPos));
@@ -163,10 +143,6 @@ public class ChessBoard {
}
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++) {
@@ -183,11 +159,6 @@ public class ChessBoard {
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())
@@ -201,7 +172,6 @@ public class ChessBoard {
}
}
}
this.cachedAllowedMoves = result;
return result;
}
@@ -296,12 +266,13 @@ public class ChessBoard {
*/
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 (piece instanceof Pawn)
if (identifier.isPawn(piece))
return pieceCoords;
}
@@ -329,11 +300,10 @@ public class ChessBoard {
public void setLastMove(Move lastMove) {
this.lastMove = lastMove;
this.cachedAllowedMoves = null;
}
@Override
public boolean equals(Object obj) {
public boolean equals(Object obj){
if (obj instanceof ChessBoard board)
return board.hashCode() == this.hashCode();
return false;

View File

@@ -20,8 +20,8 @@ public class Game {
Draw, Check, CheckMate, OnGoing, Pat;
}
public Game() {
this.board = new ChessBoard();
public Game(ChessBoard board) {
this.board = board;
this.movesHistory = new Stack<>();
this.traitsPos = new HashMap<>();
}
@@ -70,27 +70,24 @@ public class Game {
playerTurn = Color.getEnemy(playerTurn);
}
// this is the bottleneck of algorithms using this chess engine
public GameStatus checkGameStatus(Color color) {
public GameStatus checkGameStatus() {
final Color enemy = Color.getEnemy(getPlayerTurn());
if (checkDraw())
return GameStatus.Draw;
if (this.board.isKingInCheck(color))
if (this.board.hasAllowedMoves(color))
if (this.board.isKingInCheck(enemy))
if (this.board.hasAllowedMoves(enemy))
return GameStatus.Check;
else
return GameStatus.CheckMate;
if (!board.hasAllowedMoves(color))
if (!board.hasAllowedMoves(enemy))
return GameStatus.Pat;
return GameStatus.OnGoing;
}
public GameStatus checkGameStatus() {
return checkGameStatus(Color.getEnemy(getPlayerTurn()));
}
public void addAction(PlayerCommand command) {
this.movesHistory.add(command);
}
@@ -122,8 +119,4 @@ public class Game {
return this.movesHistory;
}
public boolean pawnShouldBePromoted() {
return this.board.pawnShouldBePromoted();
}
}

View File

@@ -1,20 +0,0 @@
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

@@ -28,4 +28,7 @@ public abstract class Piece {
public abstract <T> T accept(PieceVisitor<T> visitor);
@Override
public abstract boolean equals(Object other);
}

View File

@@ -15,4 +15,13 @@ public class Bishop extends Piece {
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

@@ -14,4 +14,13 @@ public class King extends Piece {
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

@@ -15,4 +15,12 @@ public class Knight extends Piece {
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

@@ -19,4 +19,12 @@ public class Pawn extends Piece {
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

@@ -15,5 +15,12 @@ public class Queen extends Piece {
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

@@ -15,4 +15,12 @@ public class Rook extends Piece {
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

@@ -1,4 +1,4 @@
package chess.model.rules;
package chess.model.visitor;
import chess.model.Coordinate;
import chess.model.Direction;

View File

@@ -1,4 +1,4 @@
package chess.model.rules;
package chess.model.visitor;
import chess.model.ChessBoard;
import chess.model.Color;
@@ -104,11 +104,10 @@ public class PiecePathChecker implements PieceVisitor<Boolean> {
Coordinate middle = lastMove.getMiddle();
if (middle.equals(this.move.getFinish())
&& pieceToEat instanceof Pawn) {
&& new PawnIdentifier(pieceToEat.getColor()).isPawn(pieceToEat)) {
this.move.setDeadPieceCoords(lastMove.getFinish());
return true;
}
return false;
}

View File

@@ -4,7 +4,6 @@ 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;
@@ -12,6 +11,7 @@ import chess.controller.commands.MoveCommand;
import chess.controller.commands.NewGameCommand;
import chess.controller.commands.PromoteCommand;
import chess.controller.event.EmptyGameDispatcher;
import chess.model.ChessBoard;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Game;
@@ -22,7 +22,7 @@ import chess.model.pieces.Pawn;
public class PgnExport {
// public static void main(String[] args) {
// final Game game = new Game();
// final Game game = new Game(new ChessBoard());
// final CommandExecutor commandExecutor = new CommandExecutor(game);
// DumbAI ai1 = new DumbAI(commandExecutor, Color.White);
@@ -31,7 +31,7 @@ public class PgnExport {
// DumbAI ai2 = new DumbAI(commandExecutor, Color.Black);
// commandExecutor.addListener(ai2);
// commandExecutor.addListener(new GameAdapter() {
// commandExecutor.addListener(new GameAdaptator() {
// @Override
// public void onGameEnd() {
// System.out.println(exportGame(game));
@@ -57,15 +57,15 @@ public class PgnExport {
}
private static String gameEnd(Game game) {
switch (game.checkGameStatus(game.getPlayerTurn())) {
switch (game.checkGameStatus()) {
case Draw:
case Pat:
return "1/2-1/2";
case CheckMate:
if (game.getPlayerTurn() == Color.White)
return "0-1";
return "1-0";
return "0-1";
default:
return "";
@@ -89,7 +89,7 @@ public class PgnExport {
Piece otherPiece = pieceAt(cmdExec, move.getStart());
// checking type of piece
if (!otherPiece.getClass().equals(movingPiece.getClass()))
if (otherPiece.hashCode() != movingPiece.hashCode())
continue;
String startPos = toString(pieceMove.getStart());
@@ -138,7 +138,7 @@ public class PgnExport {
}
private static String checkCheckMate(Game game) {
switch (game.checkGameStatus(game.getPlayerTurn())) {
switch (game.checkGameStatus()) {
case CheckMate:
return "#";
@@ -150,10 +150,7 @@ public class PgnExport {
}
}
private record MoveResult(String move, CommandResult commandResult) {
}
private static MoveResult printMove(PlayerCommand cmd, PlayerCommand nextCommand, Game virtualGame,
private static String printMove(PlayerCommand cmd, PlayerCommand nextCommand, Game virtualGame,
CommandExecutor executor) {
String result = "";
if (cmd instanceof MoveCommand move) {
@@ -181,19 +178,20 @@ public class PgnExport {
result += castling(castlingCommand);
}
CommandResult commandResult = executor.executeCommand(cmd);
executor.executeCommand(cmd);
// check or checkmate
result += checkCheckMate(virtualGame);
result += " ";
return new MoveResult(result, commandResult);
return result;
}
public static String exportGame(Game game) {
Game virtualGame = new Game();
ChessBoard board = new ChessBoard();
Game virtualGame = new Game(board);
CommandExecutor executor = new CommandExecutor(virtualGame, new EmptyGameDispatcher());
executor.executeCommand(new NewGameCommand());
@@ -203,33 +201,17 @@ public class PgnExport {
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) {
if (virtualGame.getPlayerTurn() == Color.White) {
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();
result += printMove(cmd, nextCommand, virtualGame, executor);
}
return result + " " + gameEnd(virtualGame);
}

View File

@@ -1,12 +0,0 @@
package chess.pgn;
import chess.controller.CommandExecutor;
import common.AssetManager;
public class PgnFileSimulator extends PgnSimulator{
public PgnFileSimulator(CommandExecutor commandExecutor, String fileName) {
super(commandExecutor, AssetManager.getResourceAsString(fileName));
}
}

View File

@@ -1,172 +0,0 @@
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

@@ -1,30 +0,0 @@
package chess.pgn;
import java.util.List;
import chess.controller.CommandExecutor;
import chess.controller.PlayerCommand;
import chess.controller.event.GameAdapter;
import common.Signal0;
public class PgnSimulator extends GameAdapter {
private final CommandExecutor commandExecutor;
private final String pgn;
public final Signal0 onComplete = new Signal0();
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);
this.onComplete.emit();
}
}

View File

@@ -0,0 +1,31 @@
package chess.simulator;
import chess.controller.CommandExecutor;
import chess.model.Coordinate;
import chess.model.Move;
import java.util.Arrays;
import java.util.List;
public class CastlingTest extends Simulator {
public CastlingTest(CommandExecutor commandExecutor) {
super(commandExecutor);
}
@Override
protected List<Move> getMoves() {
return Arrays.asList(
// white pawn
new Move(new Coordinate(6, 6), new Coordinate(6, 4)),
// black knight
new Move(new Coordinate(1, 0), new Coordinate(0, 2)),
// white bishop
new Move(new Coordinate(5, 7), new Coordinate(7, 5)),
// black pawn
new Move(new Coordinate(1, 1), new Coordinate(1, 2)),
// white knight
new Move(new Coordinate(6, 7), new Coordinate(5, 5)),
// black pawn, bis
new Move(new Coordinate(2, 1), new Coordinate(2, 2)));
}
}

View File

@@ -0,0 +1,28 @@
package chess.simulator;
import java.util.Arrays;
import java.util.List;
import chess.controller.CommandExecutor;
import chess.model.Coordinate;
import chess.model.Move;
public class FoolCheckMate extends Simulator {
public FoolCheckMate(CommandExecutor commandExecutor) {
super(commandExecutor);
}
@Override
public List<Move> getMoves() {
return Arrays.asList(
// white pawn
new Move(new Coordinate(5, 6), new Coordinate(5, 5)),
// black pawn
new Move(new Coordinate(4, 1), new Coordinate(4, 3)),
// 2nd white pawn
new Move(new Coordinate(6, 6), new Coordinate(6, 4)),
// black queen
new Move(new Coordinate(3, 0), new Coordinate(7, 4)));
}
}

View File

@@ -0,0 +1,40 @@
package chess.simulator;
import java.util.Arrays;
import java.util.List;
import chess.controller.CommandExecutor;
import chess.model.Coordinate;
import chess.model.Move;
public class PromoteTest extends Simulator{
public PromoteTest(CommandExecutor commandExecutor) {
super(commandExecutor);
}
@Override
protected List<Move> getMoves() {
return Arrays.asList(
// white pawn
new Move(new Coordinate(5, 6), new Coordinate(5, 4)),
// black pawn
new Move(new Coordinate(4, 1), new Coordinate(4, 3)),
// white pawn capture
new Move(new Coordinate(5, 4), new Coordinate(4, 3)),
// black king
new Move(new Coordinate(4, 0), new Coordinate(4, 1)),
// white pawn moves
new Move(new Coordinate(4, 3), new Coordinate(4, 2)),
// black king
new Move(new Coordinate(4, 1), new Coordinate(5, 2)),
// white pawn moves
new Move(new Coordinate(4, 2), new Coordinate(4, 1)),
// black king
new Move(new Coordinate(5, 2), new Coordinate(6, 2))
// white pawn moves
// new Move(new Coordinate(4, 1), new Coordinate(4, 0))
);
}
}

View File

@@ -0,0 +1,39 @@
package chess.simulator;
import java.util.List;
import chess.controller.CommandExecutor;
import chess.controller.commands.MoveCommand;
import chess.controller.event.GameAdaptator;
import chess.model.Move;
import common.Signal0;
public abstract class Simulator extends GameAdaptator {
protected final CommandExecutor commandExecutor;
public final Signal0 onComplete = new Signal0();
private int currentMove = 0;
public Simulator(CommandExecutor commandExecutor) {
this.commandExecutor = commandExecutor;
}
@Override
public void onGameStart() {
for (Move move : getMoves()) {
this.commandExecutor.executeCommand(new MoveCommand(move));
}
}
@Override
public void onBoardUpdate() {
currentMove++;
if (currentMove == getMoves().size()) {
onComplete.emit();
}
}
protected abstract List<Move> getMoves();
}

View File

@@ -1,11 +1,9 @@
package common;
package chess.view;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class AssetManager {
@@ -24,13 +22,6 @@ public class AssetManager {
return ClassLoader.getSystemResourceAsStream(name);
}
public static String getResourceAsString(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();
}
private static InputStream getFileInputStream(String path) {
File f = new File(path);
if (f.exists()) {

View File

@@ -1,63 +0,0 @@
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 common.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

@@ -1,44 +0,0 @@
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

@@ -1,63 +0,0 @@
package chess.view.audio;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.controller.event.GameAdapter;
import chess.model.Move;
public class GameAudio extends GameAdapter {
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

@@ -20,17 +20,12 @@ 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 boolean captureInput = false;
private final ExecutorService executor;
public Console(CommandExecutor commandExecutor, boolean captureInput) {
public Console(CommandExecutor commandExecutor) {
this.commandExecutor = commandExecutor;
this.executor = Executors.newSingleThreadExecutor();
this.captureInput = captureInput;
}
public Console(CommandExecutor commandExecutor) {
this(commandExecutor, true);
}
private Piece pieceAt(int x, int y) {
@@ -66,7 +61,7 @@ public class Console implements GameListener {
}
@Override
public void onPlayerTurn(Color color, boolean undone) {
public void onPlayerTurn(Color color) {
if (!captureInput)
return;
System.out.println(Colors.RED + "Player turn: " + color + Colors.RESET);
@@ -270,7 +265,7 @@ public class Console implements GameListener {
}
@Override
public void onMove(Move move, boolean captured) {
public void onMove(Move move) {
}
@Override
@@ -333,11 +328,4 @@ public class Console implements GameListener {
this.captureInput = captureInput;
}
@Override
public void onCastling(boolean bigCastling) {}
@Override
public void onPawnPromoted(PromoteType promotion) {}
}

View File

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

View File

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

View File

@@ -1,11 +1,12 @@
package chess.view.DDDrender;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT;
package chess.view.render;
import org.joml.Vector3f;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.*;
import chess.view.DDDrender.shader.BoardShader;
import chess.model.Coordinate;
import chess.view.render.shader.BoardShader;
import static org.lwjgl.opengl.GL30.*;
public class Renderer {
private BoardShader shader;
@@ -59,16 +60,42 @@ public class Renderer {
return positions;
}
private Coordinate GetCellFromColor(Vector3f color) {
int offset = 1;
if (color.x > 0.5) {
color = new Vector3f(1.0f, 1.0f, 1.0f).sub(color);
offset = 0;
}
int r = (int) (color.x * 255.0f);
int g = (int) (color.y * 255.0f);
int b = (int) (color.z * 255.0f);
int index = (r + g + b) * 2 + offset;
return Coordinate.fromIndex(index);
}
private Vector3f GetCellColor(int x, int y) {
float index = (y * BOARD_WIDTH + x) / 2.0f;
float r = (int) (index / 3) / 255.0f;
float g = (int) ((index + 1) / 3) / 255.0f;
float b = (int) ((index + 2) / 3) / 255.0f;
if ((x + y) % 2 != 0) {
System.out.println(GetCellFromColor(new Vector3f(1.0f - r - 1.0f / 255.0f, 1.0f - g - 1.0f / 255.0f, 1.0f - b - 1.0f / 255.0f)));
return new Vector3f(1.0f - r - 1.0f / 255.0f, 1.0f - g - 1.0f / 255.0f, 1.0f - b - 1.0f / 255.0f);
} else {
System.out.println(GetCellFromColor(new Vector3f(r, g, b)));
return new Vector3f(r, g, b);
}
}
private float[] GetBoardColors() {
float[] colors = new float[BOARD_SIZE * SQUARE_VERTEX_COUNT * 3];
for (int i = 0; i < BOARD_WIDTH; i++) {
for (int j = 0; j < BOARD_HEIGHT; j++) {
Vector3f color;
if ((i + j) % 2 != 0) {
color = new Vector3f(1.0f, 1.0f, 1.0f);
} else {
color = new Vector3f(0.0f, 0.0f, 0.0f);
}
for (int i = 0; i < BOARD_WIDTH; i++) {
Vector3f color = GetCellColor(i, j);
int squareIndex = i * BOARD_WIDTH + j;
for (int k = 0; k < SQUARE_VERTEX_COUNT; k++) {
colors[squareIndex * SQUARE_VERTEX_COUNT * 3 + k * 3] = color.x;
@@ -109,6 +136,12 @@ public class Renderer {
this.vao.Unbind();
}
public Coordinate GetSelectedCell() {
float pixels[] = new float[3];
GL30.glReadPixels(500, 500, 1, 1, GL_RGB, GL_FLOAT, pixels);
return GetCellFromColor(new Vector3f(pixels[0], pixels[1], pixels[2]));
}
public void Render(Camera cam) {
this.shader.Start();
this.shader.SetCamMatrix(cam.getMatrix());

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,16 @@
package chess.view.DDDrender;
package chess.view.render;
import org.lwjgl.*;
import org.lwjgl.glfw.*;
import org.lwjgl.opengl.*;
import org.lwjgl.system.*;
import chess.controller.CommandExecutor;
import chess.controller.event.GameListener;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
import java.nio.*;
import static org.lwjgl.glfw.Callbacks.*;
@@ -13,21 +19,20 @@ import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.system.MemoryStack.*;
import static org.lwjgl.system.MemoryUtil.*;
public class Window {
public class Window implements GameListener{
// The window handle
private long window;
private final CommandExecutor commandExecutor;
private Renderer renderer;
private Camera cam;
public Window() {
public Window(CommandExecutor commandExecutor) {
this.renderer = new Renderer();
this.cam = new Camera();
}
public static void main(String[] args) {
new Window().run();
this.commandExecutor = new CommandExecutor();
}
public void run() {
@@ -118,6 +123,8 @@ public class Window {
render();
System.out.println(this.renderer.GetSelectedCell());
glfwSwapBuffers(window); // swap the color buffers
// Poll for window events. The key callback above will only be
@@ -136,4 +143,82 @@ public class Window {
}
}
@Override
public void onBoardUpdate() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onBoardUpdate'");
}
@Override
public void onDraw() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onDraw'");
}
@Override
public void onGameEnd() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onGameEnd'");
}
@Override
public void onGameStart() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onGameStart'");
}
@Override
public void onKingInCheck() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onKingInCheck'");
}
@Override
public void onKingInMat() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onKingInMat'");
}
@Override
public void onMove(Move move) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onMove'");
}
@Override
public void onMoveNotAllowed(Move move) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onMoveNotAllowed'");
}
@Override
public void onPatSituation() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onPatSituation'");
}
@Override
public void onPlayerTurn(Color color) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onPlayerTurn'");
}
@Override
public void onPromotePawn(Coordinate pieceCoords) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onPromotePawn'");
}
@Override
public void onSurrender(Color coward) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onSurrender'");
}
@Override
public void onWin(Color winner) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onWin'");
}
}

View File

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

View File

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

View File

@@ -17,7 +17,7 @@ import chess.model.pieces.Knight;
import chess.model.pieces.Pawn;
import chess.model.pieces.Queen;
import chess.model.pieces.Rook;
import common.AssetManager;
import chess.view.AssetManager;
public class PieceIcon implements PieceVisitor<String> {

View File

@@ -213,7 +213,7 @@ public class Window extends JFrame implements GameListener {
}
@Override
public void onPlayerTurn(chess.model.Color color, boolean undone) {
public void onPlayerTurn(chess.model.Color color) {
this.displayText.setText("Current turn: " + color);
updateButtons();
}
@@ -306,7 +306,7 @@ public class Window extends JFrame implements GameListener {
}
@Override
public void onMove(Move move, boolean captured) {}
public void onMove(Move move) {}
@Override
public void onMoveNotAllowed(Move move) {
@@ -318,10 +318,4 @@ public class Window extends JFrame implements GameListener {
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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,15 @@
[Event "URS-chT"]
[Site "Moscow"]
[Date "1963.??.??"]
[Round "?"]
[White "Listergarten, Leonid B"]
[Black "Akopian, Vladimir"]
[Result "1-0"]
[WhiteElo ""]
[BlackElo ""]
[ECO "B48"]
1.e4 c5 2.Nf3 e6 3.d4 cxd4 4.Nxd4 a6 5.Nc3 Qc7 6.Bd3 Nc6 7.Be3 b5 8.a3 Bb7
9.O-O Rc8 10.Nxc6 Qxc6 11.Qg4 Nf6 12.Qg3 h5 13.e5 Nd5 14.Ne4 h4 15.Qh3 Qc7
16.f4 Nxe3 17.Qxe3 h3 18.gxh3 f5 19.exf6 d5 20.Nf2 Kf7 21.Rae1 Re8 22.Qg3 g5
23.fxg5 Qxg3+ 24.hxg3 e5 25.g6+ Kxf6 26.Ng4+ Kg5 27.Rf5+ 1-0

View File

@@ -1,10 +0,0 @@
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

View File

@@ -3,20 +3,24 @@
*/
package chess;
import org.junit.jupiter.api.Test;
import chess.ai.DumbAI;
import chess.controller.Command;
import chess.controller.CommandExecutor;
import chess.controller.commands.NewGameCommand;
import chess.controller.commands.UndoCommand;
import chess.controller.event.GameAdapter;
import chess.model.Color;
import chess.model.Game;
import chess.controller.event.GameAdaptator;
import chess.model.*;
import chess.model.pieces.*;
import chess.simulator.Simulator;
import chess.view.simplerender.Window;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Objects;
class AppTest {
private void functionalRollback(){
Game game = new Game();
@Test void functionalRollback(){
Game game = new Game(new ChessBoard());
CommandExecutor commandExecutor = new CommandExecutor(game);
DumbAI ai = new DumbAI(commandExecutor, Color.Black);
@@ -25,7 +29,7 @@ class AppTest {
DumbAI ai2 = new DumbAI(commandExecutor, Color.White);
commandExecutor.addListener(ai2);
commandExecutor.addListener(new GameAdapter() {
commandExecutor.addListener(new GameAdaptator() {
@Override
public void onGameEnd() {
@@ -34,12 +38,9 @@ class AppTest {
result = commandExecutor.executeCommand(new UndoCommand());
} while (result != Command.CommandResult.NotAllowed);
Game initialGame = new Game();
Game initialGame = new Game(new ChessBoard());
CommandExecutor initialCommandExecutor = new CommandExecutor(initialGame);
initialCommandExecutor.executeCommand(new NewGameCommand());
initialCommandExecutor.close();
commandExecutor.close();
commandExecutor.executeCommand(new NewGameCommand());
assert(game.getBoard().equals(initialGame.getBoard()));

View File

@@ -1,124 +0,0 @@
package chess;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import org.junit.jupiter.api.Test;
import chess.ai.DumbAI;
import chess.controller.CommandExecutor;
import chess.controller.PlayerCommand;
import chess.controller.commands.NewGameCommand;
import chess.controller.event.GameAdapter;
import chess.model.Color;
import chess.model.Game;
import chess.pgn.PgnExport;
import chess.pgn.PgnFileSimulator;
import chess.pgn.PgnImport;
import common.AssetManager;
public class PgnTest {
private Game getRandomGame() {
Game game = new Game();
CommandExecutor commandExecutor = new CommandExecutor(game);
commandExecutor.addListener(new DumbAI(commandExecutor, Color.White));
commandExecutor.addListener(new DumbAI(commandExecutor, Color.Black));
commandExecutor.addListener(new GameAdapter() {
@Override
public void onGameEnd() {
synchronized (game) {
game.notifyAll();
}
}
});
commandExecutor.executeCommand(new NewGameCommand());
synchronized (game) {
try {
game.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
commandExecutor.close();
return game;
}
private void importExportRandom() {
Game game = getRandomGame();
String pgnContent = PgnExport.exportGame(game);
List<PlayerCommand> moves = PgnImport.importGame(pgnContent);
Game game2 = new Game();
CommandExecutor commandExecutor = new CommandExecutor(game2);
commandExecutor.executeCommand(new NewGameCommand());
commandExecutor.executeCommands(moves);
String pgnContent2 = PgnExport.exportGame(game2);
commandExecutor.close();
assertEquals(pgnContent, pgnContent2);
}
private void importExportFile(String name) {
Game game = new Game();
CommandExecutor commandExecutor = new CommandExecutor(game);
PgnFileSimulator fileSimulator = new PgnFileSimulator(commandExecutor, name);
commandExecutor.addListener(fileSimulator);
final StringBuilder pgnContent = new StringBuilder();
fileSimulator.onComplete.connect(() -> {
pgnContent.append(PgnExport.exportGame(game));
commandExecutor.close();
synchronized (game) {
game.notifyAll();
}
});
commandExecutor.executeCommand(new NewGameCommand());
synchronized (game) {
try {
game.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String content1 = pgnContent.toString().trim();
String content2 = AssetManager.getResourceAsString(name).replaceAll("\n", " ").trim();
assertEquals(content1, content2);
}
@Test
void importExportFiles() {
importExportFile("games/CastlingTest.pgn");
importExportFile("games/EnPassantTest.pgn");
importExportFile("games/FoolCheckmate.pgn");
importExportFile("games/PromoteTest.pgn");
}
@Test
void importExports() {
for (int i = 0; i < 50; i++) {
importExportRandom();
}
}
}