From 86ea62614be28fbb362acdf71cd11369b765fef1 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Mon, 26 May 2025 21:33:02 +0200 Subject: [PATCH] feat: add app chooser --- app/src/main/java/chess/App.java | 256 ++++++++++++++++-- app/src/main/java/chess/SwingMain.java | 2 +- app/src/main/java/chess/ai/AI.java | 4 + .../chess/controller/CommandExecutor.java | 4 + .../controller/event/AsyncGameDispatcher.java | 5 + .../controller/event/EmptyGameDispatcher.java | 7 + .../controller/event/GameDispatcher.java | 4 + .../java/chess/view/DDDrender/DDDView.java | 7 +- app/src/main/java/chess/view/GameView.java | 21 ++ .../chess/view/consolerender/Console.java | 7 +- .../java/chess/view/simplerender/Window.java | 64 ++--- 11 files changed, 319 insertions(+), 62 deletions(-) create mode 100644 app/src/main/java/chess/view/GameView.java diff --git a/app/src/main/java/chess/App.java b/app/src/main/java/chess/App.java index e29d761..9545d1f 100644 --- a/app/src/main/java/chess/App.java +++ b/app/src/main/java/chess/App.java @@ -1,42 +1,244 @@ package chess; -import java.util.Scanner; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; -import chess.view.consolerender.Colors; +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; + +import chess.ai.AI; +import chess.ai.ais.AlphaBetaAI; +import chess.ai.ais.DumbAI; +import chess.ai.ais.HungryAI; +import chess.ai.alphabeta.AlphaBetaConsolePrinter; +import chess.controller.CommandExecutor; +import chess.controller.commands.NewGameCommand; +import chess.controller.event.GameAdapter; +import chess.model.Color; +import chess.model.Game; +import chess.pgn.PgnExport; +import chess.view.GameView; +import chess.view.DDDrender.DDDView; +import chess.view.audio.GameAudio; +import chess.view.consolerender.Console; +import chess.view.simplerender.Window; /** - * Main class of the chess game. Asks the user for the version to use and runs the according main. + * Main class of the chess game. Asks the user for the version to use and runs + * the according main. + * * @author Grenier Lilas * @author Pribylski Simon * @see ConsoleMain * @see SwingMain - * @see OpenGLMain + * @see OpenGLMain */ 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) { - 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; + + private static final String version = "v1.1"; + + private final JFrame frame; + private final JPanel content; + + private ButtonGroup viewSelection; + private JRadioButton consoleButton; + private JRadioButton swingButton; + private JRadioButton openglButton; + + private final JRadioButton[] manualButtons = new JRadioButton[2]; + private final JRadioButton[] dumbButtons = new JRadioButton[2]; + private final JRadioButton[] hungryButtons = new JRadioButton[2]; + private final JRadioButton[] alphaButtons = new JRadioButton[2]; + + private JCheckBox audioCheck; + private JCheckBox pgnCheck; + + private void drawViewSelection() { + JPanel viewPanel = new JPanel(); + + JLabel text = new JLabel("Display mode: "); + viewPanel.add(text); + + viewSelection = new ButtonGroup(); + + consoleButton = new JRadioButton("Console"); + viewPanel.add(consoleButton); + viewSelection.add(consoleButton); + + swingButton = new JRadioButton("2D Window"); + viewPanel.add(swingButton); + viewSelection.add(swingButton); + + openglButton = new JRadioButton("3D Window"); + viewPanel.add(openglButton); + viewSelection.add(openglButton); + openglButton.setSelected(true); + + content.add(viewPanel); + } + + private void drawPlayer(Color color) { + JPanel playerContent = new JPanel(); + + JLabel text = new JLabel(color + ": "); + playerContent.add(text); + + ButtonGroup playerSelection = new ButtonGroup(); + + int playerIndex = color.ordinal(); + + JRadioButton manualButton = new JRadioButton("Manual"); + playerContent.add(manualButton); + playerSelection.add(manualButton); + manualButton.setSelected(true); + + JRadioButton dumbButton = new JRadioButton("DumbAI"); + playerContent.add(dumbButton); + playerSelection.add(dumbButton); + + JRadioButton hungryButton = new JRadioButton("HungryAI"); + playerContent.add(hungryButton); + playerSelection.add(hungryButton); + + JRadioButton alphaButton = new JRadioButton("AlphaBetaAI"); + playerContent.add(alphaButton); + playerSelection.add(alphaButton); + + content.add(playerContent); + + manualButtons[playerIndex] = manualButton; + dumbButtons[playerIndex] = dumbButton; + hungryButtons[playerIndex] = hungryButton; + alphaButtons[playerIndex] = alphaButton; + } + + private void drawFooter() { + JPanel footerArea = new JPanel(); + + this.audioCheck = new JCheckBox("Audio"); + footerArea.add(this.audioCheck); + + this.pgnCheck = new JCheckBox("PGN export"); + footerArea.add(this.pgnCheck); + + JButton startButton = new JButton("Start"); + startButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + startGame(); + } + + }); + footerArea.add(startButton); + content.add(footerArea); + } + + public App() { + frame = new JFrame(); + frame.setTitle("3DChess " + version); + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + content = new JPanel(); + content.setLayout(new BoxLayout(content, BoxLayout.PAGE_AXIS)); + frame.setContentPane(content); + frame.setLocationRelativeTo(null); + frame.setSize(500, 500); + + drawViewSelection(); + drawPlayer(Color.White); + drawPlayer(Color.Black); + drawFooter(); + + frame.setVisible(true); + } + + private GameView getViewSelection(CommandExecutor commandExecutor) { + if (consoleButton.isSelected()) + return new Console(commandExecutor); + if (swingButton.isSelected()) + return new Window(commandExecutor); + return new DDDView(commandExecutor); + } + + private AI getAISelection(CommandExecutor commandExecutor, Color color) { + int playerIndex = color.ordinal(); + + if (dumbButtons[playerIndex].isSelected()) + return new DumbAI(commandExecutor, color); + + if (hungryButtons[playerIndex].isSelected()) + return new HungryAI(commandExecutor, color); + + if (alphaButtons[playerIndex].isSelected()) { + AlphaBetaAI ai = new AlphaBetaAI(commandExecutor, color, 5); + AlphaBetaConsolePrinter printer = new AlphaBetaConsolePrinter(ai); + printer.connect(); + return ai; } + + return null; + } + + private void addAI(CommandExecutor commandExecutor, Color color) { + AI ai = getAISelection(commandExecutor, color); + + if (ai == null) + return; + + commandExecutor.addListener(ai); + } + + private void addOptionalAudio(CommandExecutor commandExecutor) { + if (this.audioCheck.isSelected()) + commandExecutor.addListener(new GameAudio()); + } + + private void addOptionalPgnExport(CommandExecutor commandExecutor, Game game) { + if (!this.pgnCheck.isSelected()) + return; + + commandExecutor.addListener(new GameAdapter() { + @Override + public void onGameEnd() { + System.out.println(PgnExport.exportGame(game)); + } + }); + } + + private void addAIs(CommandExecutor commandExecutor) { + addAI(commandExecutor, Color.White); + addAI(commandExecutor, Color.Black); + } + + private GameView addView(CommandExecutor commandExecutor) { + GameView view = getViewSelection(commandExecutor); + commandExecutor.addListener(view); + return view; + } + + private void startGame() { + Game game = new Game(); + CommandExecutor commandExecutor = new CommandExecutor(game); + + GameView view = addView(commandExecutor); + addAIs(commandExecutor); + addOptionalAudio(commandExecutor); + addOptionalPgnExport(commandExecutor, game); + + commandExecutor.executeCommand(new NewGameCommand()); + + frame.dispose(); + view.run(); + } + + public static void main(String[] args) { + new App(); } } diff --git a/app/src/main/java/chess/SwingMain.java b/app/src/main/java/chess/SwingMain.java index c285a91..8d684ee 100644 --- a/app/src/main/java/chess/SwingMain.java +++ b/app/src/main/java/chess/SwingMain.java @@ -19,7 +19,7 @@ public class SwingMain { Game game = new Game(); CommandExecutor commandExecutor = new CommandExecutor(game); - Window window = new Window(commandExecutor, true); + Window window = new Window(commandExecutor); commandExecutor.addListener(window); AlphaBetaAI ai = new AlphaBetaAI(commandExecutor, Color.Black, 5); diff --git a/app/src/main/java/chess/ai/AI.java b/app/src/main/java/chess/ai/AI.java index 13a16cd..03377fc 100644 --- a/app/src/main/java/chess/ai/AI.java +++ b/app/src/main/java/chess/ai/AI.java @@ -38,4 +38,8 @@ public abstract class AI extends GameAdapter implements AIActions { return this.commandExecutor; } + public Color getColor() { + return color; + } + } diff --git a/app/src/main/java/chess/controller/CommandExecutor.java b/app/src/main/java/chess/controller/CommandExecutor.java index 2f0fb42..fb8a5c9 100644 --- a/app/src/main/java/chess/controller/CommandExecutor.java +++ b/app/src/main/java/chess/controller/CommandExecutor.java @@ -124,4 +124,8 @@ public class CommandExecutor { public void close() { this.dispatcher.close(); } + + public List getListeners() { + return this.dispatcher.getListeners(); + } } diff --git a/app/src/main/java/chess/controller/event/AsyncGameDispatcher.java b/app/src/main/java/chess/controller/event/AsyncGameDispatcher.java index d192e20..396f11f 100644 --- a/app/src/main/java/chess/controller/event/AsyncGameDispatcher.java +++ b/app/src/main/java/chess/controller/event/AsyncGameDispatcher.java @@ -113,4 +113,9 @@ public class AsyncGameDispatcher extends GameDispatcher { this.executor.shutdown(); } + @Override + public List getListeners() { + return this.listeners; + } + } diff --git a/app/src/main/java/chess/controller/event/EmptyGameDispatcher.java b/app/src/main/java/chess/controller/event/EmptyGameDispatcher.java index cf95d26..e135177 100644 --- a/app/src/main/java/chess/controller/event/EmptyGameDispatcher.java +++ b/app/src/main/java/chess/controller/event/EmptyGameDispatcher.java @@ -1,5 +1,7 @@ package chess.controller.event; +import java.util.List; + import chess.controller.commands.PromoteCommand.PromoteType; import chess.model.Color; import chess.model.Coordinate; @@ -78,4 +80,9 @@ public class EmptyGameDispatcher extends GameDispatcher { public void close() { } + @Override + public List getListeners() { + return List.of(); + } + } diff --git a/app/src/main/java/chess/controller/event/GameDispatcher.java b/app/src/main/java/chess/controller/event/GameDispatcher.java index 4152eb3..c983194 100644 --- a/app/src/main/java/chess/controller/event/GameDispatcher.java +++ b/app/src/main/java/chess/controller/event/GameDispatcher.java @@ -1,5 +1,7 @@ package chess.controller.event; +import java.util.List; + /** * Abstract class, provides a dispatcher for game events. */ @@ -7,6 +9,8 @@ public abstract class GameDispatcher extends GameAdapter { public abstract void addListener(GameListener listener); + public abstract List getListeners(); + public abstract void close(); } diff --git a/app/src/main/java/chess/view/DDDrender/DDDView.java b/app/src/main/java/chess/view/DDDrender/DDDView.java index 1d10fbf..b649b91 100644 --- a/app/src/main/java/chess/view/DDDrender/DDDView.java +++ b/app/src/main/java/chess/view/DDDrender/DDDView.java @@ -22,11 +22,12 @@ import chess.model.Color; import chess.model.Coordinate; import chess.model.Move; import chess.model.Piece; +import chess.view.GameView; import chess.view.DDDrender.world.BoardEntity; import chess.view.DDDrender.world.PieceEntity; import chess.view.DDDrender.world.World; -public class DDDView extends GameAdapter implements CommandSender { +public class DDDView extends GameView { private static final Vector3f BLACK = new Vector3f(0.3f, 0.3f, 0.3f); private static final Vector3f WHITE = new Vector3f(1.0f, 1.0f, 1.0f); @@ -409,8 +410,10 @@ public class DDDView extends GameAdapter implements CommandSender { /** * Run the game. */ + @Override public void run() { this.window.run(); + this.commandExecutor.close(); // free OpenGL resources try { @@ -541,6 +544,8 @@ public class DDDView extends GameAdapter implements CommandSender { @Override public void onPromotePawn(Coordinate pieceCoords) { + if (hasAIAttached(getPieceAt(pieceCoords).getColor())) + return; openPopup("Promotion"); } diff --git a/app/src/main/java/chess/view/GameView.java b/app/src/main/java/chess/view/GameView.java new file mode 100644 index 0000000..94ba44c --- /dev/null +++ b/app/src/main/java/chess/view/GameView.java @@ -0,0 +1,21 @@ +package chess.view; + +import chess.ai.AI; +import chess.controller.CommandSender; +import chess.controller.event.GameAdapter; +import chess.controller.event.GameListener; +import chess.model.Color; + +public abstract class GameView extends GameAdapter implements CommandSender{ + public abstract void run(); + + public boolean hasAIAttached(Color color) { + for (GameListener listener : getCommandExecutor().getListeners()) { + if (listener instanceof AI ai) { + if (ai.getColor() == color) + return true; + } + } + return false; + } +} diff --git a/app/src/main/java/chess/view/consolerender/Console.java b/app/src/main/java/chess/view/consolerender/Console.java index 4858d00..541e0e5 100644 --- a/app/src/main/java/chess/view/consolerender/Console.java +++ b/app/src/main/java/chess/view/consolerender/Console.java @@ -10,6 +10,7 @@ import chess.model.Color; import chess.model.Coordinate; import chess.model.Move; import chess.model.Piece; +import chess.view.GameView; import java.util.List; import java.util.Objects; @@ -21,7 +22,7 @@ import java.util.concurrent.Executors; * Console renderer. */ -public class Console extends GameAdapter implements CommandSender { +public class Console extends GameView { private final Scanner scanner = new Scanner(System.in); private final CommandExecutor commandExecutor; private final ConsolePieceName consolePieceName = new ConsolePieceName(); @@ -364,6 +365,10 @@ public class Console extends GameAdapter implements CommandSender { public CommandExecutor getCommandExecutor() { return this.commandExecutor; } + + @Override + public void run() { + } } diff --git a/app/src/main/java/chess/view/simplerender/Window.java b/app/src/main/java/chess/view/simplerender/Window.java index 2503cad..f088e9d 100644 --- a/app/src/main/java/chess/view/simplerender/Window.java +++ b/app/src/main/java/chess/view/simplerender/Window.java @@ -23,34 +23,36 @@ import chess.controller.commands.PromoteCommand.PromoteType; import chess.controller.event.GameListener; import chess.model.Coordinate; import chess.model.Move; +import chess.model.Piece; +import chess.view.GameView; /** * Window for the 2D chess game. */ -public class Window extends JFrame implements GameListener, CommandSender { +public class Window extends GameView { private final CommandExecutor commandExecutor; private Coordinate lastClick = null; + private final JFrame frame; + private final JLabel cells[][]; private final JLabel displayText; private final JButton castlingButton = new JButton("Roque"); private final JButton bigCastlingButton = new JButton("Grand Roque"); private final JButton undoButton = new JButton("Annuler le coup précédent"); - private final boolean showPopups; - - public Window(final CommandExecutor commandExecutor, boolean showPopups) { + public Window(final CommandExecutor commandExecutor) { + this.frame = new JFrame(); this.cells = new JLabel[8][8]; this.displayText = new JLabel(); this.commandExecutor = commandExecutor; - this.showPopups = showPopups; - setSize(800, 910); - setVisible(true); - setLocationRelativeTo(null); - setDefaultCloseOperation(DISPOSE_ON_CLOSE); - addWindowListener(new WindowAdapter() { + this.frame.setSize(800, 910); + this.frame.setVisible(true); + this.frame.setLocationRelativeTo(null); + this.frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + this.frame.addWindowListener(new WindowAdapter() { @Override public void windowClosed(WindowEvent e) { commandExecutor.close(); @@ -99,7 +101,7 @@ public class Window extends JFrame implements GameListener, CommandSender { content.add(grid); content.add(bottom); - setContentPane(content); + this.frame.setContentPane(content); for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x++) { @@ -213,39 +215,37 @@ public class Window extends JFrame implements GameListener, CommandSender { @Override public void onWin(chess.model.Color color) { - JOptionPane.showMessageDialog(this, "Victory of " + color); + JOptionPane.showMessageDialog(this.frame, "Victory of " + color); } @Override public void onKingInCheck() { - if (!showPopups) - return; SwingUtilities.invokeLater(() -> { - JOptionPane.showMessageDialog(this, "Check!"); + JOptionPane.showMessageDialog(this.frame, "Check!"); }); } @Override public void onKingInMat() { SwingUtilities.invokeLater(() -> { - JOptionPane.showMessageDialog(this, "Checkmate!"); + JOptionPane.showMessageDialog(this.frame, "Checkmate!"); }); } @Override public void onPatSituation() { - JOptionPane.showMessageDialog(this, "Pat. It's a draw!"); + JOptionPane.showMessageDialog(this.frame, "Pat. It's a draw!"); } @Override public void onSurrender(chess.model.Color color) { - JOptionPane.showMessageDialog(this, color + " has surrendered."); + JOptionPane.showMessageDialog(this.frame, color + " has surrendered."); } @Override public void onGameEnd() { - JOptionPane.showMessageDialog(this, "End of the game"); - this.dispose(); + JOptionPane.showMessageDialog(this.frame, "End of the game"); + this.frame.dispose(); this.commandExecutor.close(); } @@ -259,8 +259,13 @@ public class Window extends JFrame implements GameListener, CommandSender { */ @Override public void onPromotePawn(Coordinate pieceCoords) { - if (!showPopups) + + Piece pawn = getPieceAt(pieceCoords); + chess.model.Color player = pawn.getColor(); + + if (hasAIAttached(player)) return; + SwingUtilities.invokeLater(() -> { String result = null; @@ -273,7 +278,7 @@ public class Window extends JFrame implements GameListener, CommandSender { while (result == null || result.isEmpty()) { result = (String) JOptionPane.showInputDialog( - this, + this.frame, "Choose the type of piece to upgrade the pawn", "Promote Dialog", JOptionPane.PLAIN_MESSAGE, @@ -301,9 +306,6 @@ public class Window extends JFrame implements GameListener, CommandSender { updateBoard(); } - @Override - public void onMove(Move move, boolean captured) {} - @Override public void onMoveNotAllowed(Move move) { drawInvalid(move); @@ -311,18 +313,16 @@ public class Window extends JFrame implements GameListener, CommandSender { @Override public void onDraw() { - JOptionPane.showMessageDialog(this, "Same position was repeated three times. It's a draw!"); + JOptionPane.showMessageDialog(this.frame, "Same position was repeated three times. It's a draw!"); } - @Override - public void onCastling(boolean bigCastling, Move kingMove, Move rookMove) {} - - @Override - public void onPawnPromoted(PromoteType promotion, Coordinate coordinate) {} - @Override public CommandExecutor getCommandExecutor() { return this.commandExecutor; } + @Override + public void run() { + } + }