From 689677c94d9bb028b78f115ae7cee0634bede1a5 Mon Sep 17 00:00:00 2001 From: ToonSimoens Date: Sun, 3 Aug 2025 22:30:14 +0200 Subject: [PATCH] split game logic from cli + simple chess ui --- README.md | 5 +- src/Game/ChessCLI.java | 124 +++++++++++ src/Game/ChessGUI.java | 284 ++++++++++++++++++++++++++ src/Game/Game.java | 362 +++++++++++++++------------------ src/Game/GameParts/Player.java | 72 +++---- 5 files changed, 609 insertions(+), 238 deletions(-) create mode 100644 src/Game/ChessCLI.java create mode 100644 src/Game/ChessGUI.java diff --git a/README.md b/README.md index 7985497..48eff86 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,10 @@ A basic chess engine written in Java. It also includes a basic min-max algorithm with alpha-beta pruning and quiescence search. ### How to play: - - Run Game.java + - Run ChessCLI.java to run command line interface + ```java -cp src Game.ChessCLI``` + - Run ChessGUI.java to run GUI + ```java -cp src Game.ChessGUI``` - In- and outputs are text-based - Whether white and black are player-controlled can be inputted with "True" or "False". The latter results in white or black being controlled by the min-max algorithm. - Computer difficulty can be set with integers (difficulty refers to default depth in min-max algorithm) diff --git a/src/Game/ChessCLI.java b/src/Game/ChessCLI.java new file mode 100644 index 0000000..7f1ce41 --- /dev/null +++ b/src/Game/ChessCLI.java @@ -0,0 +1,124 @@ +package Game; + +import java.util.Scanner; +import java.util.concurrent.TimeUnit; + +/** + * Command-line interface for the chess game. + * Handles all user interaction and delegates game logic to the Game class. + * + */ +public class ChessCLI { + + public static void main(String[] args) { + ChessCLI cli = new ChessCLI(); + cli.run(); + } + + private final Scanner scanner = new Scanner(System.in); + + /** + * Main entry point for the CLI chess application. + */ + public void run() { + System.out.print(System.lineSeparator()); + Game game = createGame(); + long startTime = System.nanoTime(); + playGame(game); + long duration = System.nanoTime() - startTime; + handleGameEnd(game, duration); + } + + /** + * Create a new game with user-specified settings. + */ + private Game createGame() { + System.out.print("White controlled? "); + boolean whiteControlled = scanner.nextBoolean(); + System.out.print("Black controlled? "); + boolean blackControlled = scanner.nextBoolean(); + + int whiteDifficulty = 3; + int blackDifficulty = 3; + + if (!whiteControlled) { + System.out.print("White difficulty? "); + whiteDifficulty = scanner.nextInt(); + } + if (!blackControlled) { + System.out.print("Black difficulty? "); + blackDifficulty = scanner.nextInt(); + } + + return new Game(whiteControlled, blackControlled, whiteDifficulty, blackDifficulty); + } + + /** + * Play the game until completion. + */ + private void playGame(Game game) { + while (!game.isGameOver()) { + if (game.getPlayerAtPlay().isControlled()) { + handleHumanTurn(game); + } else { + game.playATurn(); + } + if (game.getLastPlayedOperation() != null) { + System.out.println(game.getLastPlayedOperation()); + } + } + } + + private void handleHumanTurn(Game game) { + boolean validMove = false; + while (!validMove) { + System.out.print("Enter starting position: "); + String start = scanner.next().toUpperCase(); + + if (start.equals("UNDO")) { + if (game.undoMove()) { + validMove = true; + } else { + System.out.println("Cannot undo move."); + } + } else if (start.equals("RESIGN")) { + if (game.resignPlayer()) { + validMove = true; + } else { + System.out.println("Cannot resign."); + } + } else { + System.out.print("Enter ending position: "); + String end = scanner.next(); + if (game.makeMove(start, end)) { + validMove = true; + } else { + System.out.println("Wrong input, try again!"); + } + } + } + } + + /** + * Handle game end - display results and offer to save. + */ + private void handleGameEnd(Game game, long durationNanos) { + long minutes = TimeUnit.NANOSECONDS.toMinutes(durationNanos); + long seconds = TimeUnit.NANOSECONDS.toSeconds(durationNanos) - minutes * 60; + String duration = minutes + "m" + seconds + "s"; + + System.out.println(duration); + if (game.getGameEndMessage() != null) { + System.out.println(game.getGameEndMessage()); + } + + System.out.print("Do you want to save this match? (y/n): "); + String response = scanner.next().toLowerCase(); + if (response.equals("y") || response.equals("yes")) { + GameSaver.saveGame(game, duration); + } + } + + + +} \ No newline at end of file diff --git a/src/Game/ChessGUI.java b/src/Game/ChessGUI.java new file mode 100644 index 0000000..ab56b55 --- /dev/null +++ b/src/Game/ChessGUI.java @@ -0,0 +1,284 @@ +package Game; + +import Game.GameParts.Pieces.*; +import Game.GameParts.Position; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class ChessGUI extends JFrame { + private Game game; + private JButton[][] boardButtons; + private JLabel statusLabel; + private JButton newGameButton; + private JButton undoButton; + private JButton resignButton; + + private String selectedSquare = null; + + public ChessGUI() { + initializeGUI(); + startNewGame(true, false, 0, 3); // Default: Human vs AI + } + + private void initializeGUI() { + setTitle("Chess Game"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setResizable(false); + + // Main panel + JPanel mainPanel = new JPanel(new BorderLayout()); + + // Chess board panel + JPanel boardPanel = new JPanel(new GridLayout(8, 8)); + boardButtons = new JButton[8][8]; + + // Create board buttons + for (int row = 0; row < 8; row++) { + for (int col = 0; col < 8; col++) { + JButton button = new JButton(); + button.setPreferredSize(new Dimension(60, 60)); + button.setFont(new Font("Serif", Font.PLAIN, 36)); + + // Set square colors (alternating pattern) + if ((row + col) % 2 == 0) { + button.setBackground(new Color(240, 217, 181)); // Light squares + } else { + button.setBackground(new Color(181, 136, 99)); // Dark squares + } + + final int finalRow = row; + final int finalCol = col; + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + handleSquareClick(finalRow, finalCol); + } + }); + + boardButtons[row][col] = button; + boardPanel.add(button); + } + } + + // Control panel + JPanel controlPanel = new JPanel(new FlowLayout()); + + newGameButton = new JButton("New Game"); + undoButton = new JButton("Undo"); + resignButton = new JButton("Resign"); + + newGameButton.addActionListener(e -> showGameSetupDialog()); + undoButton.addActionListener(e -> handleUndo()); + resignButton.addActionListener(e -> handleResign()); + + controlPanel.add(newGameButton); + controlPanel.add(undoButton); + controlPanel.add(resignButton); + + // Status panel + statusLabel = new JLabel("White to move", JLabel.CENTER); + statusLabel.setFont(new Font("Arial", Font.BOLD, 14)); + + // Add components to main panel + mainPanel.add(boardPanel, BorderLayout.CENTER); + mainPanel.add(controlPanel, BorderLayout.SOUTH); + mainPanel.add(statusLabel, BorderLayout.NORTH); + + add(mainPanel); + pack(); + setLocationRelativeTo(null); + } + + private void startNewGame(boolean whiteControlled, boolean blackControlled, int whiteDifficulty, int blackDifficulty) { + game = new Game(whiteControlled, blackControlled, whiteDifficulty, blackDifficulty); + selectedSquare = null; + updateBoard(); + updateStatus(); + + // If current player is AI, make AI move + if (!game.getPlayerAtPlay().isControlled()) { + makeAIMove(); + } + } + + private void updateBoard() { + for (int row = 0; row < 8; row++) { + for (int col = 0; col < 8; col++) { + JButton button = boardButtons[row][col]; + Position position = game.getPosition(col, 7 - row); // Convert GUI coordinates to game coordinates + Piece piece = position.getOccupyingPiece(); + + if (piece != null) { + button.setText(getPieceSymbol(piece)); + } else { + button.setText(""); + } + + // Reset button colors + if ((row + col) % 2 == 0) { + button.setBackground(new Color(240, 217, 181)); // Light squares + } else { + button.setBackground(new Color(181, 136, 99)); // Dark squares + } + + // Highlight selected square + if (selectedSquare != null) { + String squareName = getSquareName(col, 7 - row); + if (squareName.equals(selectedSquare)) { + button.setBackground(Color.YELLOW); + } + } + } + } + } + + private String getPieceSymbol(Piece piece) { + boolean isWhite = piece.getPlayer().isWhite(); + + if (piece instanceof King) { + return isWhite ? "♔" : "♚"; + } else if (piece instanceof Queen) { + return isWhite ? "♕" : "♛"; + } else if (piece instanceof Rook) { + return isWhite ? "♖" : "♜"; + } else if (piece instanceof Bishop) { + return isWhite ? "♗" : "♝"; + } else if (piece instanceof Knight) { + return isWhite ? "♘" : "♞"; + } else if (piece instanceof Pawn) { + return isWhite ? "♙" : "♟"; + } + + return ""; + } + + private void handleSquareClick(int row, int col) { + if (game.isGameOver()) { + return; + } + + // Only allow human players to click + if (!game.getPlayerAtPlay().isControlled()) { + return; + } + + String squareName = getSquareName(col, 7 - row); // Convert GUI coordinates to chess notation + + if (selectedSquare == null) { + // First click - select piece + Position position = game.getPosition(col, 7 - row); + if (position.getOccupyingPiece() != null && + position.getOccupyingPiece().getPlayer() == game.getPlayerAtPlay()) { + selectedSquare = squareName; + updateBoard(); + } + } else { + // Second click - attempt move + if (squareName.equals(selectedSquare)) { + // Clicked same square - deselect + selectedSquare = null; + updateBoard(); + } else { + // Attempt move + boolean success = game.makeMove(selectedSquare, squareName); + selectedSquare = null; + updateBoard(); + updateStatus(); + + if (success && !game.isGameOver() && !game.getPlayerAtPlay().isControlled()) { + // AI's turn + Timer timer = new Timer(500, e -> makeAIMove()); + timer.setRepeats(false); + timer.start(); + } + } + } + } + + private void makeAIMove() { + if (!game.isGameOver() && !game.getPlayerAtPlay().isControlled()) { + game.playATurn(); + updateBoard(); + updateStatus(); + + // Check if next player is also AI + if (!game.isGameOver() && !game.getPlayerAtPlay().isControlled()) { + Timer timer = new Timer(500, e -> makeAIMove()); + timer.setRepeats(false); + timer.start(); + } + } + } + + private String getSquareName(int x, int y) { + char file = (char) ('A' + x); + int rank = y + 1; + return file + String.valueOf(rank); + } + + private void updateStatus() { + if (game.isGameOver()) { + statusLabel.setText(game.getGameEndMessage()); + } else { + String currentPlayer = game.getPlayerAtPlay().isWhite() ? "White" : "Black"; + String playerType = game.getPlayerAtPlay().isControlled() ? " (Human)" : " (AI)"; + statusLabel.setText(currentPlayer + playerType + " to move"); + } + } + + private void handleUndo() { + if (game.undoMove()) { + selectedSquare = null; + updateBoard(); + updateStatus(); + } + } + + private void handleResign() { + if (game.resignPlayer()) { + updateBoard(); + updateStatus(); + } + } + + private void showGameSetupDialog() { + String[] options = {"Human vs Human", "Human vs AI (Easy)", "Human vs AI (Medium)", "Human vs AI (Hard)", "AI vs AI"}; + int choice = JOptionPane.showOptionDialog(this, + "Choose game type:", + "New Game", + JOptionPane.DEFAULT_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + options, + options[1]); + + switch (choice) { + case 0: // Human vs Human + startNewGame(true, true, 0, 0); + break; + case 1: // Human vs AI (Easy) + startNewGame(true, false, 0, 1); + break; + case 2: // Human vs AI (Medium) + startNewGame(true, false, 0, 3); + break; + case 3: // Human vs AI (Hard) + startNewGame(true, false, 0, 7); + break; + case 4: // AI vs AI + startNewGame(false, false, 3, 7); + break; + } + } + + public static void main(String[] args) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + new ChessGUI().setVisible(true); + } + }); + } +} \ No newline at end of file diff --git a/src/Game/Game.java b/src/Game/Game.java index e39f864..969a012 100644 --- a/src/Game/Game.java +++ b/src/Game/Game.java @@ -16,124 +16,52 @@ */ public class Game { - public Game() { - for (int i = 0; i < 8; ++i) - for (int j = 0; j < 8; ++j) + /** + * Create a new chess game. + * + * @param whiteControlled Whether white player is human-controlled (true) or AI (false) + * @param blackControlled Whether black player is human-controlled (true) or AI (false) + * @param whiteDifficulty AI difficulty for white player (ignored if whiteControlled=true) + * @param blackDifficulty AI difficulty for black player (ignored if blackControlled=true) + */ + public Game(boolean whiteControlled, boolean blackControlled, int whiteDifficulty, int blackDifficulty) { + // Initialize board positions + for (int i = 0; i < 8; ++i) { + for (int j = 0; j < 8; ++j) { this.positions[i + 8*j] = new Position(new int[] {i, j}); - System.out.print("White controlled? "); - boolean whiteControlled = (new Scanner(System.in)).nextBoolean(); - this.white = new Player(whiteControlled, true, this); - System.out.print("Black controlled? "); - boolean blackControlled = (new Scanner(System.in)).nextBoolean(); - this.black = new Player(blackControlled, false, this); + } + } + + // Create players + this.white = new Player(whiteControlled, true, this, whiteDifficulty); + this.black = new Player(blackControlled, false, this, blackDifficulty); + + // Set up the board this.initialiseBoard(); } - public Game(int difficulties) { - for (int i = 0; i < 8; ++i) - for (int j = 0; j < 8; ++j) - this.positions[i + 8*j] = new Position(new int[] {i, j}); - this.white = new Player(true, this, difficulties); - this.black = new Player(false, this, difficulties); - this.initialiseBoard(); + /** + * Convenience constructor with default difficulty (3) for AI players. + */ + public Game(boolean whiteControlled, boolean blackControlled) { + this(whiteControlled, blackControlled, 3, 3); } - public static void main(String[] args) { -//// Game.testGame(); -// System.out.print("Autoplay? "); -// boolean autoPlay = (new Scanner(System.in)).nextBoolean(); -// if (autoPlay) { -// Game.autoPlayer(); -// } -// else { - System.out.print(System.lineSeparator()); - Game game = new Game(); - long startTime = System.nanoTime(); - Game.playGame(game); - long minutes = TimeUnit.NANOSECONDS.toMinutes(System.nanoTime() - startTime); - long seconds = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - startTime) - TimeUnit.NANOSECONDS.toMinutes(System.nanoTime() - startTime) * 60; - String duration = minutes + "m" + seconds + "s"; - System.out.println(duration); - System.out.print("Do you want to save this match? "); - if ((new Scanner(System.in)).nextBoolean()) { - GameSaver.saveGame(game, duration); - } -// } + /** + * Convenience constructor for AI vs AI games with same difficulty. + */ + public Game(int difficulty) { + this(false, false, difficulty, difficulty); } - private static void autoPlayer() { - System.out.println("Runtime: "); - int max = (new Scanner(System.in)).nextInt(); - System.out.println("Player levels: "); - int difficulties = (new Scanner(System.in)).nextInt(); - long begin = TimeUnit.NANOSECONDS.toHours(System.nanoTime()); - int number = 1; - while (TimeUnit.NANOSECONDS.toHours(System.nanoTime()) - begin < max) { - System.out.print(System.lineSeparator() + "------------------------------------------------------" + System.lineSeparator() + - System.lineSeparator() + "Game " + number + System.lineSeparator() - + System.lineSeparator() + "------------------------------------------------------" + System.lineSeparator()); - number++; - System.out.println(System.lineSeparator() + "### NEW GAME ###" + System.lineSeparator()); - Game game = new Game(difficulties); - long startTime = System.nanoTime(); - Game.playGame(game); - long minutes = TimeUnit.NANOSECONDS.toMinutes(System.nanoTime() - startTime); - long seconds = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - startTime) - TimeUnit.NANOSECONDS.toMinutes(System.nanoTime() - startTime) * 60; - String duration = minutes + "m" + seconds + "s"; - System.out.println(duration); - GameSaver.saveGame(game, duration); - System.out.println(System.lineSeparator() + "### GAME OVER ###" + System.lineSeparator()); - } - } -// private static void testGame() { -// Game game = new Game(true); -// Pawn testPawnB = new Pawn(game.black, game.getPosition(7, 6)); -// Pawn testPawnW = new Pawn(game.white, game.getPosition(5, 4)); -// game.update(); -// while (!game.isGameOver()) { -// System.out.println("testPawnB positions: "); -// testPawnB.getPossibleActions().forEach(positions1 -> System.out.println(positions1[1])); -// System.out.println("testPawnW positions: "); -// testPawnW.getPossibleActions().forEach(positions1 -> System.out.println(positions1[1])); -//// System.out.println("testPawnW reach: "); -//// testPawnW.getReach().forEach(System.out::println); -// System.out.println("Possible en passant: "); -// System.out.println(game.getPossibleEnPassant()); -// game.playATurn(); -// } -// } -// -// public Game(boolean test) { -// for (int i = 0; i < 8; ++i) -// for (int j = 0; j < 8; ++j) -// this.positions[i + 8*j] = new Position(new int[] {i, j}); -// this.white = new Player(true, true, this); -// this.black = new Player(true, false, this); -//// SETTING UP THE PAWNS -// for (int i = 1; i < 4; ++i) { -// new Pawn(this.white, this.getPosition(i, 1)); -// new Pawn(this.black, this.getPosition(i+3, 6)); -// } -//// SETTING UP THE ROOKS -// Rook whiteRookA = new Rook(this.white, this.getPosition(0, 0)); -// Rook whiteRookH = new Rook(this.white, this.getPosition(7, 0)); -// Rook blackRookA = new Rook(this.black, this.getPosition(0, 7)); -// Rook blackRookH = new Rook(this.black, this.getPosition(7, 7)); -//// SETTING UP THE KINGS -// new King(this.white, this.getPosition(4, 0), whiteRookA, whiteRookH); -// new King(this.black, this.getPosition(4, 7), blackRookA, blackRookH); -//// INITIALISING MOVES -//// System.out.println(testPawnW.getReach()); -// } - - private static void playGame(Game game) { - while (!game.isGameOver()) { - game.playATurn(); + public void playGame() { + while (!this.isGameOver()) { + this.playATurn(); } } - private boolean isGameOver() { + public boolean isGameOver() { return gameOver; } @@ -219,14 +147,88 @@ private void initialiseBoard(){ } /** - * Play a turn. + * Play a turn for AI players only. */ - private void playATurn() { - TurnAction(this.getPlayerAtPlay()); + public void playATurn() { + if (this.getPlayerAtPlay().isControlled()) { + throw new IllegalStateException("Current player is human-controlled. Use makeMove() instead."); + } + Operation operation = this.getAIPlayerAction(this.getPlayerAtPlay()); + operation.execute(); + this.lastPlayedOperation = operation; + if (operation instanceof Move) { + this.playedActions.add((Move) operation); + } this.endTurn(); + } + + /** + * Make a move for a human player. + * @param startPos Starting position (e.g., "A1") + * @param endPos Ending position (e.g., "B2") + * @return true if move was successful, false if invalid + */ + public boolean makeMove(String startPos, String endPos) { + if (!this.getPlayerAtPlay().isControlled()) { + throw new IllegalStateException("Current player is AI-controlled. Use playATurn() instead."); + } + try { + Operation operation = createMoveFromPositions(startPos, endPos); + if (operation.isValidActionForPlayer(this.getPlayerAtPlay())) { + operation.execute(); + this.lastPlayedOperation = operation; + if (operation instanceof Move) { + this.playedActions.add((Move) operation); + } + this.endTurn(); + return true; + } + } catch (Exception e) { + // Invalid move + } + return false; + } -// this.getPlayers().forEach(player -> System.out.println(player.toString() + " " + player.evaluate())); + /** + * Perform undo operation. + * @return true if undo was successful + */ + public boolean undoMove() { + if (!this.getPlayerAtPlay().isControlled()) { + return false; + } + try { + Operation undo = new Undo(this); + if (undo.isValidActionForPlayer(this.getPlayerAtPlay())) { + undo.execute(); + this.lastPlayedOperation = undo; + this.endTurn(); + return true; + } + } catch (Exception e) { + // Undo failed + } + return false; + } + /** + * Resign current player. + * @return true if resignation was successful + */ + public boolean resignPlayer() { + if (!this.getPlayerAtPlay().isControlled()) { + return false; + } + try { + Operation resign = new Resign(this.getPlayerAtPlay()); + resign.execute(); + this.lastPlayedOperation = resign; + this.endTurn(); + return true; + } catch (Exception e) { + // Resignation failed + } + return false; } /** @@ -274,7 +276,7 @@ private void gameOverChecker() { if (this.winner != null){ this.setGameOver(); this.setTurnsToBeRewound(0); - System.out.println(this.winner.getName() + " wins after " + this.getTurn() + " turns"); + this.gameEndMessage = this.winner.getName() + " wins after " + this.getTurn() + " turns"; return; } // Check for remise @@ -287,17 +289,22 @@ private void gameOverChecker() { return; } } - System.out.println("Game goes in remise after " + this.getTurn() + " turns"); + this.gameEndMessage = "Game goes in remise after " + this.getTurn() + " turns"; this.setGameOver(); this.setTurnsToBeRewound(8); } - Player getWinner() { + public Player getWinner() { return winner; } + public String getGameEndMessage() { + return gameEndMessage; + } + private Player winner = null; + private String gameEndMessage = null; public Position getPosition(int x, int y) { @@ -476,91 +483,58 @@ public Player getPlayerAtPlay(int turn) { } - /** - * Determines the action the active player makes - * - * @param playingPlayer - * The player who's next action is determined. - */ - private void TurnAction(Player playingPlayer){ - Operation operation = null; - while (operation == null || !operation.isValidActionForPlayer(playingPlayer)) { - if (playingPlayer.isControlled()) { - operation = this.inputConverter(playingPlayer); - } - else { - int depth = playingPlayer.getDifficulty() - + Math.max(0, 8 - (this.getPlayers().stream().mapToInt(player -> (int) player.getPieces().stream() - .filter(piece -> !(piece instanceof Pawn)).count()).sum() - + this.getPlayers().stream().mapToInt(player -> (int) player.getPieces().stream() - .filter(piece -> piece instanceof Pawn).count()).sum() / 3)); - NegaMaxReturn output = playingPlayer.alphaBeta(Double.NEGATIVE_INFINITY, - Double.POSITIVE_INFINITY, depth); - Position[] result = output.getAction(); - if (result[0].getOccupyingPiece() instanceof King && - Math.abs(result[1].getCoordinates()[0] - - result[0].getCoordinates()[0]) > 1.1) { // Check if output is castle move - operation = new Castle(result); - } else if (result[0].getOccupyingPiece() instanceof Pawn // Check if output is en passant move - && result[1].getCoordinates()[1] == result[0].getCoordinates()[1] + ((Pawn) result[0].getOccupyingPiece()).getDirection() - && Math.abs(result[1].getCoordinates()[0] - result[0].getCoordinates()[0]) > 0.5 - && Piece.nooneOnTile(result[1])) { - operation = new EnPassant(result); - } else { - operation = new Move(result); - } - - } + private Operation createMoveFromPositions(String startPos, String endPos) { + startPos = startPos.toUpperCase(); + endPos = endPos.toUpperCase(); + + Position startingPosition = this.getPosition((startPos.charAt(0) - 'A'), startPos.charAt(1) - '1'); + Position endPosition = this.getPosition((endPos.charAt(0) - 'A'), endPos.charAt(1) - '1'); + + if (startingPosition.getOccupyingPiece() instanceof King && + Math.abs(endPosition.getCoordinates()[0] + - startingPosition.getCoordinates()[0]) > 1.1) { + return new Castle(startingPosition, endPosition); } -// try { - operation.execute(); - System.out.println(operation); - if (operation instanceof Move) { - this.playedActions.add((Move) operation); - } -// } catch (Exception e) { -// System.out.println(Arrays.toString(e.getStackTrace())); -// this.ArnoldStyle(playingPlayer); -// } - } - - private Operation inputConverter(Player playingPlayer) { - System.out.print("Enter starting position: "); - String start = (new Scanner(System.in)).next().toUpperCase(); - if (start.equals("UNDO")) { - return new Undo(this); + else if (startingPosition.getOccupyingPiece() instanceof Pawn + && endPosition.getCoordinates()[1] == startingPosition.getCoordinates()[1] + ((Pawn) startingPosition.getOccupyingPiece()).getDirection() + && Math.abs(endPosition.getCoordinates()[0] - startingPosition.getCoordinates()[0]) > 0.5 + && Piece.nooneOnTile(endPosition)) { + return new EnPassant(startingPosition, endPosition); } - else if (start.equals("RESIGN")) { - return new Resign(this.getPlayerAtPlay()); + else { + return new Move(startingPosition, endPosition); } - System.out.print("Enter ending position: "); - String end = (new Scanner(System.in)).next(); - try { - Move move; - Position startingPosition = this.getPosition((start.charAt(0) - 'A'), start.charAt(1) - '1'); - Position endPosition = this.getPosition((end.charAt(0) - 'A' ), end.charAt(1) - '1'); - if (startingPosition.getOccupyingPiece() instanceof King && - Math.abs(endPosition.getCoordinates()[0] - - startingPosition.getCoordinates()[0]) > 1.1) { - move = new Castle(startingPosition, endPosition); - } - else if (startingPosition.getOccupyingPiece() instanceof Pawn - && endPosition.getCoordinates()[1] == startingPosition.getCoordinates()[1] + ((Pawn) startingPosition.getOccupyingPiece()).getDirection() - && Math.abs(endPosition.getCoordinates()[0] - startingPosition.getCoordinates()[0]) > 0.5 - && Piece.nooneOnTile(endPosition)) { - move = new EnPassant(startingPosition, endPosition); - } - else { - move = new Move(startingPosition, endPosition); - } - if (!move.isValidActionForPlayer(playingPlayer)) { - throw new IllegalArgumentException(); - } - return move; - } catch (Exception e) { - System.out.println("Wrong input, try again!"); - return inputConverter(playingPlayer); + } + + private Operation getAIPlayerAction(Player playingPlayer) { + int depth = playingPlayer.getDifficulty() + + Math.max(0, 8 - (this.getPlayers().stream().mapToInt(player -> (int) player.getPieces().stream() + .filter(piece -> !(piece instanceof Pawn)).count()).sum() + + this.getPlayers().stream().mapToInt(player -> (int) player.getPieces().stream() + .filter(piece -> piece instanceof Pawn).count()).sum() / 3)); + NegaMaxReturn output = playingPlayer.alphaBeta(Double.NEGATIVE_INFINITY, + Double.POSITIVE_INFINITY, depth); + Position[] result = output.getAction(); + if (result[0].getOccupyingPiece() instanceof King && + Math.abs(result[1].getCoordinates()[0] + - result[0].getCoordinates()[0]) > 1.1) { // Check if output is castle move + return new Castle(result); + } else if (result[0].getOccupyingPiece() instanceof Pawn // Check if output is en passant move + && result[1].getCoordinates()[1] == result[0].getCoordinates()[1] + ((Pawn) result[0].getOccupyingPiece()).getDirection() + && Math.abs(result[1].getCoordinates()[0] - result[0].getCoordinates()[0]) > 0.5 + && Piece.nooneOnTile(result[1])) { + return new EnPassant(result); + } else { + return new Move(result); } } + + public Operation getLastPlayedOperation() { + return lastPlayedOperation; + } + + private Operation lastPlayedOperation = null; + + } \ No newline at end of file diff --git a/src/Game/GameParts/Player.java b/src/Game/GameParts/Player.java index c087df8..a92cdbb 100644 --- a/src/Game/GameParts/Player.java +++ b/src/Game/GameParts/Player.java @@ -15,61 +15,47 @@ public class Player { /** - * Initiate a new Player. + * Create a new Player. * - * @param controlled - * Whether or not this new player will be controlled, or be operated by a computer algorithm. - * @param white - * Whether or not this player is the white player. - * @param game - * The game in which this player plays. + * @param controlled Whether this player is human-controlled (true) or AI (false) + * @param white Whether this player plays white (true) or black (false) + * @param game The game this player belongs to + * @param difficulty AI difficulty level (ignored if controlled=true) */ - public Player(boolean controlled, boolean white, Game game) { + public Player(boolean controlled, boolean white, Game game, int difficulty) { this.controlled = controlled; this.white = white; this.game = game; - this.opponent = this.getGame().getPlayers().stream().filter(player -> player != this).collect(Collectors.toList()).get(0); - if (this.opponent != null) + + // Set up opponent relationship + this.opponent = this.getGame().getPlayers().stream() + .filter(player -> player != this) + .collect(Collectors.toList()) + .get(0); + if (this.opponent != null) { this.opponent.opponent = this; - if (this.isWhite()) { - this.colour = "White"; - } - else { - this.colour = "Black"; } + + // Set color + this.colour = this.isWhite() ? "White" : "Black"; + + // Set name and difficulty if (this.isControlled()) { - System.out.print("Name of player: "); - this.name = (new Scanner(System.in)).nextLine(); - } - else { - this.name = this.getColour(); - while (!this.isValidDifficulty(this.getDifficulty())) { - System.out.print(this.getName() + " difficulty? "); - try { - this.setDifficulty((new Scanner(System.in)).nextInt()); - } catch (IllegalArgumentException ignored) {} + this.name = this.getColour() + " Player"; + } else { + if (!this.isValidDifficulty(difficulty)) { + throw new IllegalArgumentException("Invalid difficulty: " + difficulty); } + this.name = this.getColour(); + this.difficulty = difficulty; } } - public Player(boolean white, Game game, int difficulty) { - if (!this.isValidDifficulty(difficulty)) { - throw new IllegalArgumentException(); - } - this.controlled = false; - this.white = white; - this.game = game; - this.opponent = this.getGame().getPlayers().stream().filter(player -> player != this).collect(Collectors.toList()).get(0); - if (this.opponent != null) - this.opponent.opponent = this; - this.difficulty = difficulty; - if (this.isWhite()) { - this.colour = "White"; - } - else { - this.colour = "Black"; - } - this.name = this.getColour(); + /** + * Convenience constructor for human players (uses default difficulty for AI) + */ + public Player(boolean controlled, boolean white, Game game) { + this(controlled, white, game, 3); // Default difficulty } /**