From 3b38e0da1fe18e2b0804d093720cc277d1172405 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Wed, 30 Apr 2025 18:28:01 +0000 Subject: [PATCH] Super IA (#5) Reviewed-on: https://git.ale-pri.com/Crabs/3DChess/pulls/5 Co-authored-by: Persson-dev Co-committed-by: Persson-dev --- app/build.gradle | 4 + app/src/main/java/chess/SwingMain.java | 13 +- app/src/main/java/chess/ai/AI.java | 19 ++- app/src/main/java/chess/ai/HungryAI.java | 2 +- app/src/main/java/chess/ai/PieceCost.java | 39 +++--- app/src/main/java/chess/ai/PiecePosCost.java | 129 ++++++++++++++++++ .../java/chess/ai/minimax/AlphaBetaAI.java | 89 ++++++++++++ .../chess/ai/minimax/AlphaBetaThread.java | 101 ++++++++++++++ .../ai/minimax/AlphaBetaThreadCreator.java | 36 +++++ .../java/chess/ai/minimax/GameSimulation.java | 103 ++++++++++++++ .../chess/controller/CommandExecutor.java | 19 +-- .../controller/commands/CastlingCommand.java | 4 + .../controller/commands/MoveCommand.java | 6 +- .../controller/commands/PromoteCommand.java | 2 + .../controller/event/AsyncGameDispatcher.java | 113 +++++++++++++++ .../controller/event/EmptyGameDispatcher.java | 19 ++- .../chess/controller/event/GameAdaptator.java | 7 + .../controller/event/GameDispatcher.java | 99 +------------- .../chess/controller/event/GameListener.java | 40 ++++-- app/src/main/java/chess/model/ChessBoard.java | 49 +++++-- app/src/main/java/chess/model/Game.java | 19 ++- .../main/java/chess/model/PermissiveGame.java | 20 +++ app/src/main/java/chess/pgn/PgnExport.java | 36 ++--- .../chess/view/consolerender/Console.java | 7 + .../java/chess/view/simplerender/Window.java | 6 + app/src/main/resources/games/wendy.pgn | 12 ++ 26 files changed, 814 insertions(+), 179 deletions(-) create mode 100644 app/src/main/java/chess/ai/PiecePosCost.java create mode 100644 app/src/main/java/chess/ai/minimax/AlphaBetaAI.java create mode 100644 app/src/main/java/chess/ai/minimax/AlphaBetaThread.java create mode 100644 app/src/main/java/chess/ai/minimax/AlphaBetaThreadCreator.java create mode 100644 app/src/main/java/chess/ai/minimax/GameSimulation.java create mode 100644 app/src/main/java/chess/controller/event/AsyncGameDispatcher.java create mode 100644 app/src/main/java/chess/model/PermissiveGame.java create mode 100644 app/src/main/resources/games/wendy.pgn diff --git a/app/build.gradle b/app/build.gradle index e3a8f95..2db934f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -39,6 +39,10 @@ application { applicationName = "3DChess" } +run { + standardInput = System.in +} + jar { manifest { attributes 'Main-Class': application.mainClass diff --git a/app/src/main/java/chess/SwingMain.java b/app/src/main/java/chess/SwingMain.java index 8080711..27d6805 100644 --- a/app/src/main/java/chess/SwingMain.java +++ b/app/src/main/java/chess/SwingMain.java @@ -1,7 +1,7 @@ package chess; -import chess.ai.DumbAI; -import chess.ai.HungryAI; +import chess.ai.AI; +import chess.ai.minimax.AlphaBetaAI; import chess.controller.CommandExecutor; import chess.controller.commands.NewGameCommand; import chess.controller.event.GameAdaptator; @@ -19,11 +19,14 @@ public class SwingMain { Window window = new Window(commandExecutor, false); commandExecutor.addListener(window); - DumbAI ai = new DumbAI(commandExecutor, Color.Black); + AI ai = new AlphaBetaAI(commandExecutor, Color.Black, 5); commandExecutor.addListener(ai); - HungryAI ai2 = new HungryAI(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 GameAdaptator(){ @Override diff --git a/app/src/main/java/chess/ai/AI.java b/app/src/main/java/chess/ai/AI.java index 677b340..c53709a 100644 --- a/app/src/main/java/chess/ai/AI.java +++ b/app/src/main/java/chess/ai/AI.java @@ -4,6 +4,7 @@ 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; @@ -50,8 +51,12 @@ public abstract class AI extends GameAdaptator{ } protected List getAllowedMoves() { + return getAllowedMoves(this.commandExecutor); + } + + protected List getAllowedMoves(CommandExecutor commandExecutor) { GetPlayerMovesCommand cmd = new GetPlayerMovesCommand(); - sendCommand(cmd); + sendCommand(cmd, commandExecutor); return cmd.getMoves(); } @@ -61,8 +66,16 @@ public abstract class AI extends GameAdaptator{ return cmd2.getCastlingResult(); } - protected void sendCommand(Command command) { - this.commandExecutor.executeCommand(command); + 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; } } diff --git a/app/src/main/java/chess/ai/HungryAI.java b/app/src/main/java/chess/ai/HungryAI.java index 7c1cb54..435c465 100644 --- a/app/src/main/java/chess/ai/HungryAI.java +++ b/app/src/main/java/chess/ai/HungryAI.java @@ -26,7 +26,7 @@ public class HungryAI extends AI { private int getMoveCost(Move move) { Piece piece = pieceAt(move.getDeadPieceCoords()); - return pieceCost.getCost(piece); + return - (int) pieceCost.getCost(piece); } private List getBestMoves() { diff --git a/app/src/main/java/chess/ai/PieceCost.java b/app/src/main/java/chess/ai/PieceCost.java index 61a9c4f..8654688 100644 --- a/app/src/main/java/chess/ai/PieceCost.java +++ b/app/src/main/java/chess/ai/PieceCost.java @@ -10,51 +10,58 @@ import chess.model.pieces.Pawn; import chess.model.pieces.Queen; import chess.model.pieces.Rook; -public class PieceCost implements PieceVisitor { +public class PieceCost implements PieceVisitor { 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 int getCost(Piece piece) { + public float getCost(Piece piece) { if (piece == null) return 0; - int cost = visit(piece); - if (piece.getColor() == player) + float cost = visit(piece); + if (piece.getColor() != player) cost = -cost; return cost; } @Override - public Integer visitPiece(Bishop bishop) { - return 3; + public Float visitPiece(Bishop bishop) { + return BISHOP; } @Override - public Integer visitPiece(King king) { - return 90; + public Float visitPiece(King king) { + return KING; } @Override - public Integer visitPiece(Knight knight) { - return 3; + public Float visitPiece(Knight knight) { + return KNIGHT; } @Override - public Integer visitPiece(Pawn pawn) { - return 1; + public Float visitPiece(Pawn pawn) { + return PAWN; } @Override - public Integer visitPiece(Queen queen) { - return 9; + public Float visitPiece(Queen queen) { + return QUEEN; } @Override - public Integer visitPiece(Rook rook) { - return 5; + public Float visitPiece(Rook rook) { + return ROOK; } } diff --git a/app/src/main/java/chess/ai/PiecePosCost.java b/app/src/main/java/chess/ai/PiecePosCost.java new file mode 100644 index 0000000..68af25e --- /dev/null +++ b/app/src/main/java/chess/ai/PiecePosCost.java @@ -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> { + + private final Color color; + + private static final List 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 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 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 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 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 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 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 visitPiece(Bishop bishop) { + return BISHOP; + } + + @Override + public List visitPiece(King king) { + return KING; + } + + @Override + public List visitPiece(Knight knight) { + return KNIGHT; + } + + @Override + public List visitPiece(Pawn pawn) { + return PAWN; + } + + @Override + public List visitPiece(Queen queen) { + return QUEEN; + } + + @Override + public List visitPiece(Rook rook) { + return ROOK; + } + +} diff --git a/app/src/main/java/chess/ai/minimax/AlphaBetaAI.java b/app/src/main/java/chess/ai/minimax/AlphaBetaAI.java new file mode 100644 index 0000000..98cef39 --- /dev/null +++ b/app/src/main/java/chess/ai/minimax/AlphaBetaAI.java @@ -0,0 +1,89 @@ +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; + +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 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 moves = getAllowedMoves(); + List> moveEvaluations = new ArrayList<>(50); + float bestMoveValue = MIN_FLOAT; + Move bestMove = null; + + System.out.println("Evaluating " + moves.size() + " moves ..."); + + for (Move move : moves) { + moveEvaluations.add(this.threadPool.submit(() -> { + return AlphaBetaThreadCreator.getMoveValue(move, this.searchDepth); + })); + } + + for (int i = 0; i < moves.size(); i++) { + System.out.printf("Progress : %.2f %% ", (float) (i) / moves.size() * 100.0f); + Move move = moves.get(i); + System.out.print("\r"); + float value = MIN_FLOAT; + try { + value = moveEvaluations.get(i).get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + if (value > bestMoveValue) { + bestMoveValue = value; + bestMove = move; + } + } + + System.out.println("Best move : " + bestMoveValue + " "); + + return bestMove; + } + + @Override + public void onGameEnd() { + this.threadPool.close(); + } + + @Override + protected void play() { + long current = System.currentTimeMillis(); + Move move = getBestMove(); + long elapsed = System.currentTimeMillis() - current; + System.out.println("Took " + elapsed + "ms"); + sendCommand(new MoveCommand(move)); + } + + @Override + protected void promote(Coordinate pawnCoords) { + sendCommand(new PromoteCommand(PromoteType.Queen)); + } + +} diff --git a/app/src/main/java/chess/ai/minimax/AlphaBetaThread.java b/app/src/main/java/chess/ai/minimax/AlphaBetaThread.java new file mode 100644 index 0000000..7657037 --- /dev/null +++ b/app/src/main/java/chess/ai/minimax/AlphaBetaThread.java @@ -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 moves = this.simulation.getAllowedMoves(); + + if (moves.isEmpty()) + return -getEndGameEvaluation(); + + List> 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; + } +} diff --git a/app/src/main/java/chess/ai/minimax/AlphaBetaThreadCreator.java b/app/src/main/java/chess/ai/minimax/AlphaBetaThreadCreator.java new file mode 100644 index 0000000..8bee1d9 --- /dev/null +++ b/app/src/main/java/chess/ai/minimax/AlphaBetaThreadCreator.java @@ -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; + } + +} diff --git a/app/src/main/java/chess/ai/minimax/GameSimulation.java b/app/src/main/java/chess/ai/minimax/GameSimulation.java new file mode 100644 index 0000000..772e463 --- /dev/null +++ b/app/src/main/java/chess/ai/minimax/GameSimulation.java @@ -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) { + 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 getAllowedMoves() { + GetPlayerMovesCommand cmd = new GetPlayerMovesCommand(); + sendCommand(cmd); + return cmd.getMoves(); + } + + public void close() { + this.simulation.close(); + } + +} diff --git a/app/src/main/java/chess/controller/CommandExecutor.java b/app/src/main/java/chess/controller/CommandExecutor.java index 76eae78..2b11eaa 100644 --- a/app/src/main/java/chess/controller/CommandExecutor.java +++ b/app/src/main/java/chess/controller/CommandExecutor.java @@ -2,6 +2,7 @@ package chess.controller; 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; @@ -17,9 +18,9 @@ public class CommandExecutor { } public CommandExecutor(Game game) { - this(game, new GameDispatcher()); + this(game, new AsyncGameDispatcher()); } - + public CommandExecutor(Game game, GameDispatcher dispatcher) { this.game = game; this.dispatcher = dispatcher; @@ -52,19 +53,21 @@ public class CommandExecutor { return; case Moved: + boolean notifyPlayerTurn = true; this.dispatcher.onBoardUpdate(); - if (checkGameStatus()) { + if (!(command instanceof UndoCommand) && checkGameStatus()) { this.dispatcher.onGameEnd(); - return; + notifyPlayerTurn = false; } - switchPlayerTurn(command instanceof UndoCommand); + switchPlayerTurn(command instanceof UndoCommand, notifyPlayerTurn); return; } } - private void switchPlayerTurn(boolean undone) { + private void switchPlayerTurn(boolean undone, boolean notifyPlayerTurn) { this.game.switchPlayerTurn(); - this.dispatcher.onPlayerTurn(this.game.getPlayerTurn(), undone); + if (notifyPlayerTurn) + this.dispatcher.onPlayerTurn(this.game.getPlayerTurn(), undone); } /** @@ -107,6 +110,6 @@ public class CommandExecutor { } public void close() { - this.dispatcher.stopService(); + this.dispatcher.close(); } } diff --git a/app/src/main/java/chess/controller/commands/CastlingCommand.java b/app/src/main/java/chess/controller/commands/CastlingCommand.java index 36af108..b53fd2e 100644 --- a/app/src/main/java/chess/controller/commands/CastlingCommand.java +++ b/app/src/main/java/chess/controller/commands/CastlingCommand.java @@ -49,6 +49,10 @@ public class CastlingCommand extends PlayerCommand { board.applyMove(this.kingMove); board.applyMove(this.rookMove); + board.setLastMove(this.kingMove); + + outputSystem.onCastling(this.bigCastling); + return CommandResult.Moved; } diff --git a/app/src/main/java/chess/controller/commands/MoveCommand.java b/app/src/main/java/chess/controller/commands/MoveCommand.java index 894fbec..08a957b 100644 --- a/app/src/main/java/chess/controller/commands/MoveCommand.java +++ b/app/src/main/java/chess/controller/commands/MoveCommand.java @@ -37,7 +37,6 @@ public class MoveCommand extends PlayerCommand { return result; case Moved: - outputSystem.onMove(this.move); game.saveTraitPiecesPos(); return result; @@ -53,7 +52,7 @@ public class MoveCommand extends PlayerCommand { final ChessBoard board = game.getBoard(); // we must promote the pending pawn before - if (board.pawnShouldBePromoted()) + if (game.pawnShouldBePromoted()) return CommandResult.NotAllowed; Piece piece = board.pieceAt(move.getStart()); @@ -76,11 +75,14 @@ public class MoveCommand extends PlayerCommand { } if (tryPromote(game, outputSystem)) { + outputSystem.onMove(this.move); return CommandResult.ActionNeeded; } board.setLastMove(this.move); + outputSystem.onMove(this.move); + return CommandResult.Moved; } diff --git a/app/src/main/java/chess/controller/commands/PromoteCommand.java b/app/src/main/java/chess/controller/commands/PromoteCommand.java index c012e8d..3adb4c4 100644 --- a/app/src/main/java/chess/controller/commands/PromoteCommand.java +++ b/app/src/main/java/chess/controller/commands/PromoteCommand.java @@ -55,6 +55,8 @@ public class PromoteCommand extends PlayerCommand { this.oldPawn = pawn; board.pieceComes(createPiece(this.promoteType, pawn.getColor()), this.pieceCoords); + outputSystem.onPawnPromoted(this.promoteType); + return CommandResult.Moved; } diff --git a/app/src/main/java/chess/controller/event/AsyncGameDispatcher.java b/app/src/main/java/chess/controller/event/AsyncGameDispatcher.java new file mode 100644 index 0000000..49c9343 --- /dev/null +++ b/app/src/main/java/chess/controller/event/AsyncGameDispatcher.java @@ -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 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 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) { + asyncForEachCall((l) -> l.onMove(move)); + } + + @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(); + } + +} diff --git a/app/src/main/java/chess/controller/event/EmptyGameDispatcher.java b/app/src/main/java/chess/controller/event/EmptyGameDispatcher.java index 1e1ac2e..8ad5084 100644 --- a/app/src/main/java/chess/controller/event/EmptyGameDispatcher.java +++ b/app/src/main/java/chess/controller/event/EmptyGameDispatcher.java @@ -1,5 +1,6 @@ package chess.controller.event; +import chess.controller.commands.PromoteCommand.PromoteType; import chess.model.Color; import chess.model.Coordinate; import chess.model.Move; @@ -57,5 +58,21 @@ public class EmptyGameDispatcher extends GameDispatcher { @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() { + } + } diff --git a/app/src/main/java/chess/controller/event/GameAdaptator.java b/app/src/main/java/chess/controller/event/GameAdaptator.java index 9ef52c3..03a3957 100644 --- a/app/src/main/java/chess/controller/event/GameAdaptator.java +++ b/app/src/main/java/chess/controller/event/GameAdaptator.java @@ -1,5 +1,6 @@ package chess.controller.event; +import chess.controller.commands.PromoteCommand.PromoteType; import chess.model.Color; import chess.model.Coordinate; import chess.model.Move; @@ -45,4 +46,10 @@ public abstract class GameAdaptator implements GameListener { @Override public void onDraw() {} + @Override + public void onCastling(boolean bigCastling) {} + + @Override + public void onPawnPromoted(PromoteType promotion) {} + } diff --git a/app/src/main/java/chess/controller/event/GameDispatcher.java b/app/src/main/java/chess/controller/event/GameDispatcher.java index e0a6fe0..87c3f4d 100644 --- a/app/src/main/java/chess/controller/event/GameDispatcher.java +++ b/app/src/main/java/chess/controller/event/GameDispatcher.java @@ -1,100 +1,9 @@ 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; +public abstract class GameDispatcher extends GameAdaptator { -import chess.model.Color; -import chess.model.Coordinate; -import chess.model.Move; - -public class GameDispatcher implements GameListener { - - private final List 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 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) { - 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(); - } + public abstract void addListener(GameListener listener); + public abstract void close(); + } diff --git a/app/src/main/java/chess/controller/event/GameListener.java b/app/src/main/java/chess/controller/event/GameListener.java index a530d9a..7086e93 100644 --- a/app/src/main/java/chess/controller/event/GameListener.java +++ b/app/src/main/java/chess/controller/event/GameListener.java @@ -1,5 +1,6 @@ package chess.controller.event; +import chess.controller.commands.PromoteCommand.PromoteType; import chess.model.Color; import chess.model.Coordinate; import chess.model.Move; @@ -15,7 +16,7 @@ public interface GameListener { * 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) */ @@ -25,7 +26,7 @@ public interface GameListener { * Invoked when the game has started */ void onGameStart(); - + /** * Invoked when a king is in check */ @@ -35,47 +36,68 @@ public interface GameListener { * Invoked when a checkmate occurs */ void onKingInMat(); - + /** * Invoked when a valid move on the board occurs + * * @param move the move to be processed */ void onMove(Move move); /** * 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 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); + } diff --git a/app/src/main/java/chess/model/ChessBoard.java b/app/src/main/java/chess/model/ChessBoard.java index ebc15e5..adaf385 100644 --- a/app/src/main/java/chess/model/ChessBoard.java +++ b/app/src/main/java/chess/model/ChessBoard.java @@ -4,7 +4,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import chess.model.visitor.KingIdentifier; +import chess.model.pieces.King; import chess.model.visitor.PawnIdentifier; import chess.model.visitor.PiecePathChecker; @@ -31,6 +31,10 @@ public class ChessBoard { private Move lastMove; private Piece lastEjectedPiece; + private List 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++) { @@ -41,6 +45,7 @@ public class ChessBoard { this.lastVirtualMove = null; this.lastMove = null; this.lastEjectedPiece = null; + this.kingPos = new Coordinate[Color.values().length]; } public void applyMove(Move move) { @@ -98,6 +103,8 @@ 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) { @@ -105,18 +112,19 @@ public class ChessBoard { } public Coordinate findKing(Color color) { - KingIdentifier kingIdentifier = new KingIdentifier(color); - 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 = pieceAt(coordinate); - if (kingIdentifier.isKing(piece)) { - return coordinate; - } - } - } - assert false : "No king found ?!"; - return null; + // KingIdentifier kingIdentifier = new KingIdentifier(color); + // 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 = pieceAt(coordinate); + // if (kingIdentifier.isKing(piece)) { + // return coordinate; + // } + // } + // } + // assert false : "No king found ?!"; + // return null; + return kingPos[color.ordinal()]; } public boolean isKingInCheck(Color color) { @@ -127,7 +135,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) + if (attackPiece == null || attackPiece.getColor() == color) continue; PiecePathChecker checker = new PiecePathChecker(this, new Move(attackCoords, kingPos)); @@ -143,6 +151,10 @@ public class ChessBoard { } public List getAllowedMoves(Color player) { + if (this.cachedAllowedMoves != null) { + return this.cachedAllowedMoves; + } + List result = new ArrayList<>(); for (int x = 0; x < Coordinate.VALUE_MAX; x++) { @@ -159,6 +171,11 @@ 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()) @@ -172,6 +189,7 @@ public class ChessBoard { } } } + this.cachedAllowedMoves = result; return result; } @@ -300,10 +318,11 @@ 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; diff --git a/app/src/main/java/chess/model/Game.java b/app/src/main/java/chess/model/Game.java index 8982632..28be020 100644 --- a/app/src/main/java/chess/model/Game.java +++ b/app/src/main/java/chess/model/Game.java @@ -70,24 +70,27 @@ public class Game { playerTurn = Color.getEnemy(playerTurn); } - public GameStatus checkGameStatus() { - final Color enemy = Color.getEnemy(getPlayerTurn()); - + // this is the bottleneck of algorithms using this chess engine + public GameStatus checkGameStatus(Color color) { if (checkDraw()) return GameStatus.Draw; - if (this.board.isKingInCheck(enemy)) - if (this.board.hasAllowedMoves(enemy)) + if (this.board.isKingInCheck(color)) + if (this.board.hasAllowedMoves(color)) return GameStatus.Check; else return GameStatus.CheckMate; - if (!board.hasAllowedMoves(enemy)) + 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); } @@ -119,4 +122,8 @@ public class Game { return this.movesHistory; } + public boolean pawnShouldBePromoted() { + return this.board.pawnShouldBePromoted(); + } + } diff --git a/app/src/main/java/chess/model/PermissiveGame.java b/app/src/main/java/chess/model/PermissiveGame.java new file mode 100644 index 0000000..3554ee4 --- /dev/null +++ b/app/src/main/java/chess/model/PermissiveGame.java @@ -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() { + + } + +} diff --git a/app/src/main/java/chess/pgn/PgnExport.java b/app/src/main/java/chess/pgn/PgnExport.java index 3ad34f5..e4fb6d4 100644 --- a/app/src/main/java/chess/pgn/PgnExport.java +++ b/app/src/main/java/chess/pgn/PgnExport.java @@ -21,24 +21,24 @@ 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); + // final Game game = new Game(); + // final CommandExecutor commandExecutor = new CommandExecutor(game); - // DumbAI ai1 = new DumbAI(commandExecutor, Color.White); - // commandExecutor.addListener(ai1); + // DumbAI ai1 = new DumbAI(commandExecutor, Color.White); + // commandExecutor.addListener(ai1); - // DumbAI ai2 = new DumbAI(commandExecutor, Color.Black); - // commandExecutor.addListener(ai2); + // 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.addListener(new GameAdaptator() { + // @Override + // public void onGameEnd() { + // System.out.println(exportGame(game)); + // commandExecutor.close(); + // } + // }); - // commandExecutor.executeCommand(new NewGameCommand()); + // commandExecutor.executeCommand(new NewGameCommand()); // } private static final PiecePgnName piecePgnName = new PiecePgnName(); @@ -56,15 +56,15 @@ public class PgnExport { } private static String gameEnd(Game game) { - switch (game.checkGameStatus()) { + switch (game.checkGameStatus(game.getPlayerTurn())) { case Draw: case Pat: return "1/2-1/2"; case CheckMate: if (game.getPlayerTurn() == Color.White) - return "1-0"; - return "0-1"; + return "0-1"; + return "1-0"; default: return ""; @@ -137,7 +137,7 @@ public class PgnExport { } private static String checkCheckMate(Game game) { - switch (game.checkGameStatus()) { + switch (game.checkGameStatus(game.getPlayerTurn())) { case CheckMate: return "#"; diff --git a/app/src/main/java/chess/view/consolerender/Console.java b/app/src/main/java/chess/view/consolerender/Console.java index 3d2406b..559ff54 100644 --- a/app/src/main/java/chess/view/consolerender/Console.java +++ b/app/src/main/java/chess/view/consolerender/Console.java @@ -333,4 +333,11 @@ public class Console implements GameListener { this.captureInput = captureInput; } + @Override + public void onCastling(boolean bigCastling) {} + + @Override + public void onPawnPromoted(PromoteType promotion) {} + + } diff --git a/app/src/main/java/chess/view/simplerender/Window.java b/app/src/main/java/chess/view/simplerender/Window.java index d09d4a3..10f6fab 100644 --- a/app/src/main/java/chess/view/simplerender/Window.java +++ b/app/src/main/java/chess/view/simplerender/Window.java @@ -318,4 +318,10 @@ 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) {} + } diff --git a/app/src/main/resources/games/wendy.pgn b/app/src/main/resources/games/wendy.pgn new file mode 100644 index 0000000..50a9e72 --- /dev/null +++ b/app/src/main/resources/games/wendy.pgn @@ -0,0 +1,12 @@ +1. e4 {1.e4 $1 A fiery start $1} 1... e5 {I like how this game is starting $1} 2. Nc3 +Bc5 {J'aime un feu chaud.} 3. Nf3 {It's too cold in here for my liking.} 3... +Qf6 4. Nd5 Qd6 5. d3 c6 {A fiery position is what I seek $1} 6. Nc3 h6 7. a3 Qg6 +8. Nxe5 {Things are beginning to heat up, non $2} 8... Qd6 9. Nc4 Qe6 10. d4 Be7 +11. Ne3 b5 12. Nf5 d5 13. Nxg7+ {That's not very nice.} 13... Kd7 14. Nxe6 +{Brrrrrr. It is getting cold in here.} 14... fxe6 15. exd5 cxd5 16. Bf4 Nf6 17. +Bxb5+ Kd8 18. Qf3 Bd7 19. Be5 {My attack is getting cold, I need to go get some +more firewood $1} 19... a6 20. Bxf6 Re8 21. Bxe7+ Kxe7 22. Nxd5+ exd5 23. Qxd5 +Kf8+ {It's getting toasty in here $1} 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 +{C'est très, très mauvais $1} 33... Kh7 34. Qg6+ Kh8 35. Ra2 Rxd4 36. Qg8# {Good +play $1 I'll have to throw another log on the fire and try again.} 1-0 \ No newline at end of file