diff --git a/app/src/main/java/chess/SwingMain.java b/app/src/main/java/chess/SwingMain.java index ab5ce8b..531997c 100644 --- a/app/src/main/java/chess/SwingMain.java +++ b/app/src/main/java/chess/SwingMain.java @@ -1,5 +1,6 @@ package chess; +import chess.ai.AlphaBetaAI; import chess.ai.DumbAI; import chess.ai.HungryAI; import chess.controller.CommandExecutor; @@ -18,12 +19,15 @@ public class SwingMain { Window window = new Window(commandExecutor, false); commandExecutor.addListener(window); - DumbAI ai = new DumbAI(commandExecutor, Color.Black); + DumbAI ai = new DumbAI(commandExecutor, Color.White); commandExecutor.addListener(ai); - HungryAI ai2 = new HungryAI(commandExecutor, Color.White); + AlphaBetaAI ai2 = new AlphaBetaAI(commandExecutor, Color.Black, 3); commandExecutor.addListener(ai2); + // Window window2 = new Window(ai2.getSimulation(), false); + // ai2.getSimulation().addListener(window2); + commandExecutor.addListener(new GameAdaptator(){ @Override public void onGameEnd() { 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/AlphaBetaAI.java b/app/src/main/java/chess/ai/AlphaBetaAI.java new file mode 100644 index 0000000..6b9313d --- /dev/null +++ b/app/src/main/java/chess/ai/AlphaBetaAI.java @@ -0,0 +1,141 @@ +package chess.ai; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +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.model.ChessBoard; +import chess.model.Color; +import chess.model.Coordinate; +import chess.model.Game; +import chess.model.Move; + +public class AlphaBetaAI extends AI { + + private final int searchDepth; + private final PieceCost pieceCost; + private final CommandExecutor simulation; + private final Game gameSimulation; + + public AlphaBetaAI(CommandExecutor commandExecutor, Color color, int searchDepth) { + super(commandExecutor, color); + this.searchDepth = 3; + this.pieceCost = new PieceCost(color); + this.gameSimulation = new Game(); + this.simulation = new CommandExecutor(this.gameSimulation); + } + + public CommandExecutor getSimulation() { + return simulation; + } + + private int getBoardEvaluation() { + final ChessBoard board = this.gameSimulation.getBoard(); + int result = 0; + for (int i = 0; i < Coordinate.VALUE_MAX; i++) { + for (int j = 0; j < Coordinate.VALUE_MAX; j++) { + result += pieceCost.getCost(board.pieceAt(new Coordinate(i, j))); + } + } + return result; + } + + private int negaMax(int depth, int alpha, int beta) { + if (depth == 0) + return getBoardEvaluation(); + + int value = Integer.MIN_VALUE; + + List moves = getAllowedMoves(this.simulation); + + if (moves.isEmpty()) { + System.out.println("oaaaaaaaaa"); + int board = getBoardEvaluation(); + int result = board + (this.gameSimulation.getPlayerTurn() == this.color ? -100 : 100); + return result; + } + + for (Move move : moves) { + sendCommand(new MoveCommand(move), this.simulation); + if (this.gameSimulation.getBoard().pawnShouldBePromoted()) + sendCommand(new PromoteCommand(PromoteType.Queen), this.simulation); + int leaveValue = -negaMax(depth - 1, -beta, -alpha); + value = Integer.max(value, leaveValue); + sendCommand(new UndoCommand(), this.simulation); + alpha = Integer.max(alpha, value); + if (alpha >= beta) + return value; + } + + return value; + } + + private Move getBestMove() { + List moves = getAllowedMoves(this.simulation); + int bestMove = Integer.MIN_VALUE; + List bestMoves = new ArrayList<>(20); + + for (Move move : moves) { + sendCommand(new MoveCommand(move), this.simulation); + if (this.gameSimulation.getBoard().pawnShouldBePromoted()) + sendCommand(new PromoteCommand(PromoteType.Queen), this.simulation); + int value = -negaMax(this.searchDepth - 1, Integer.MIN_VALUE, Integer.MAX_VALUE); + sendCommand(new UndoCommand(), this.simulation); + if (value > bestMove) { + bestMove = value; + bestMoves.clear(); + bestMoves.add(move); + } else if (value == bestMove) { + bestMoves.add(move); + } + } + + System.out.println("Best move : " + bestMove + " count : " + bestMoves.size()); + + return bestMoves.get(new Random().nextInt(bestMoves.size())); + } + + @Override + public void onGameStart() { + sendCommand(new NewGameCommand(), this.simulation); + } + + @Override + public void onPawnPromoted(PromoteType promotion, Color player) { + // if (player == this.color) + // return; + sendCommand(new PromoteCommand(promotion), this.simulation); + } + + @Override + public void onCastling(boolean bigCastling) { + sendCommand(new CastlingCommand(bigCastling), this.simulation); + } + + @Override + public void onMove(Move move) { + sendCommand(new MoveCommand(move), this.simulation); + } + + @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/controller/CommandExecutor.java b/app/src/main/java/chess/controller/CommandExecutor.java index 76eae78..607a5ca 100644 --- a/app/src/main/java/chess/controller/CommandExecutor.java +++ b/app/src/main/java/chess/controller/CommandExecutor.java @@ -55,7 +55,6 @@ public class CommandExecutor { this.dispatcher.onBoardUpdate(); if (checkGameStatus()) { this.dispatcher.onGameEnd(); - return; } switchPlayerTurn(command instanceof UndoCommand); return; diff --git a/app/src/main/java/chess/controller/commands/CastlingCommand.java b/app/src/main/java/chess/controller/commands/CastlingCommand.java index 36af108..04a02ca 100644 --- a/app/src/main/java/chess/controller/commands/CastlingCommand.java +++ b/app/src/main/java/chess/controller/commands/CastlingCommand.java @@ -49,6 +49,8 @@ public class CastlingCommand extends PlayerCommand { board.applyMove(this.kingMove); board.applyMove(this.rookMove); + 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..7116690 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; @@ -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..a735ab8 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, game.getPlayerTurn()); + return CommandResult.Moved; } diff --git a/app/src/main/java/chess/controller/event/GameAdaptator.java b/app/src/main/java/chess/controller/event/GameAdaptator.java index 9ef52c3..33ed535 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, Color player) {} + } diff --git a/app/src/main/java/chess/controller/event/GameDispatcher.java b/app/src/main/java/chess/controller/event/GameDispatcher.java index e0a6fe0..c311f9f 100644 --- a/app/src/main/java/chess/controller/event/GameDispatcher.java +++ b/app/src/main/java/chess/controller/event/GameDispatcher.java @@ -6,6 +6,7 @@ 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; @@ -93,6 +94,16 @@ public class GameDispatcher implements GameListener { asyncForEachCall((l) -> l.onDraw()); } + @Override + public void onCastling(boolean bigCastling) { + asyncForEachCall((l) -> l.onCastling(bigCastling)); + } + + @Override + public void onPawnPromoted(PromoteType promotion, Color player) { + asyncForEachCall((l) -> l.onPawnPromoted(promotion, player)); + } + public void stopService() { this.executor.shutdown(); } diff --git a/app/src/main/java/chess/controller/event/GameListener.java b/app/src/main/java/chess/controller/event/GameListener.java index a530d9a..74e4cfe 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, Color player); + } diff --git a/app/src/main/java/chess/view/consolerender/Console.java b/app/src/main/java/chess/view/consolerender/Console.java index 3d2406b..1479d1e 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, Color player) {} + + } diff --git a/app/src/main/java/chess/view/simplerender/Window.java b/app/src/main/java/chess/view/simplerender/Window.java index d09d4a3..275dd8a 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, chess.model.Color player) {} + }