From 19712a2bb753997b8e11dd861523fd015684b2a4 Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Sun, 8 Sep 2019 20:18:45 +0200 Subject: [PATCH] Refactoring and documentation, improved FEN integration + Adding a new game when loading a FEN string + Loading multiple FEN files with drag and drop --- src/dev/kske/chess/board/Board.java | 36 ++++++++----- src/dev/kske/chess/game/Game.java | 31 +++++++++-- src/dev/kske/chess/ui/FENDropTarget.java | 31 ++++++----- .../chess/ui/GameConfigurationDialog.java | 19 +++---- src/dev/kske/chess/ui/GamePane.java | 3 +- src/dev/kske/chess/ui/MainWindow.java | 39 ++++++++------ src/dev/kske/chess/ui/MenuBar.java | 51 +++++++------------ 7 files changed, 117 insertions(+), 93 deletions(-) diff --git a/src/dev/kske/chess/board/Board.java b/src/dev/kske/chess/board/Board.java index 234da25..2b114ab 100644 --- a/src/dev/kske/chess/board/Board.java +++ b/src/dev/kske/chess/board/Board.java @@ -17,10 +17,10 @@ import dev.kske.chess.board.Piece.Type; */ public class Board implements Cloneable { - private Piece[][] boardArr; - private Map kingPos; - private Map> castlingRights; - private Log log; + private Piece[][] boardArr = new Piece[8][8]; + private Map kingPos = new HashMap<>(); + private Map> castlingRights = new HashMap<>(); + private Log log = new Log(); private static final Map positionScores; @@ -59,14 +59,22 @@ public class Board implements Cloneable { new int[] { 0, 1, 1, -2, -2, 1, 1, 0 }, new int[] { 0, 0, 0, 0, 0, 0, 0, 0 } }); } + /** + * Initializes the board with the default chess starting position. + */ public Board() { - boardArr = new Piece[8][8]; - kingPos = new HashMap<>(); - castlingRights = new HashMap<>(); - log = new Log(); initDefaultPositions(); } + /** + * Initializes the board with data from a FEN-string. + * + * @param fen The FEN-string to initialize the board from + */ + public Board(String fen) { + initFromFEN(fen); + } + /** * Moves a piece across the board if the move is legal. * @@ -369,7 +377,6 @@ public class Board implements Cloneable { boardArr[i][j] = null; // Initialize castling rights - castlingRights.clear(); Map whiteCastling = new HashMap<>(), blackCastling = new HashMap<>(); whiteCastling.put(Type.KING, true); whiteCastling.put(Type.QUEEN, true); @@ -434,22 +441,25 @@ public class Board implements Cloneable { log.setActiveColor(Color.fromFirstChar(parts[1].charAt(0))); // Castling rights + Map whiteCastling = new HashMap<>(), blackCastling = new HashMap<>(); for (char c : parts[2].toCharArray()) switch (c) { case 'K': - castlingRights.get(Color.WHITE).put(Type.KING, true); + whiteCastling.put(Type.KING, true); case 'Q': - castlingRights.get(Color.WHITE).put(Type.QUEEN, true); + whiteCastling.put(Type.QUEEN, true); case 'k': - castlingRights.get(Color.BLACK).put(Type.KING, true); + blackCastling.put(Type.KING, true); case 'q': - castlingRights.get(Color.BLACK).put(Type.QUEEN, true); + blackCastling.put(Type.QUEEN, true); case '-': break; default: System.err .printf("Unknown character '%c' in castling rights declaration of FEN string '%s'", c, fen); } + castlingRights.put(Color.WHITE, whiteCastling); + castlingRights.put(Color.BLACK, blackCastling); // En passant availability if (!parts[3].equals("-")) log.setEnPassant(Position.fromSAN(parts[3])); diff --git a/src/dev/kske/chess/game/Game.java b/src/dev/kske/chess/game/Game.java index 16ce291..f2c8b66 100644 --- a/src/dev/kske/chess/game/Game.java +++ b/src/dev/kske/chess/game/Game.java @@ -26,18 +26,29 @@ import dev.kske.chess.ui.OverlayComponent; */ public class Game { - private Map players; + private Map players = new HashMap<>(); private Board board; private OverlayComponent overlayComponent; private BoardComponent boardComponent; public Game(BoardPane boardPane, String whiteName, String blackName) { - players = new HashMap<>(); board = new Board(); + init(boardPane, whiteName, blackName); + } + + public Game(BoardPane boardPane, String whiteName, String blackName, String fen) { + board = new Board(fen); + init(boardPane, whiteName, blackName); + } + + private void init(BoardPane boardPane, String whiteName, String blackName) { + + // Initialize / synchronize UI overlayComponent = boardPane.getOverlayComponent(); boardComponent = boardPane.getBoardComponent(); boardComponent.setBoard(board); + // Initialize players players.put(Color.WHITE, getPlayer(whiteName, Color.WHITE)); players.put(Color.BLACK, getPlayer(blackName, Color.BLACK)); @@ -61,6 +72,7 @@ public class Game { public void onMove(Player player, Move move) { if (board.getPos(move).getColor() == player.color && board.attemptMove(move)) { + // Redraw boardComponent.repaint(); overlayComponent.displayArrow(move); @@ -85,7 +97,7 @@ public class Game { } public void start() { - players.get(Color.WHITE).requestMove(); + players.get(board.getLog().getActiveColor()).requestMove(); } public void reset() { @@ -97,12 +109,15 @@ public class Game { } /** - * Removed all connections between the game and the UI. + * Stops the game by disconnecting its players form the UI. */ - public void disconnect() { + public void stop() { players.values().forEach(Player::disconnect); } + /** + * Assigns the players their opposite colors. + */ public void swapColors() { players.values().forEach(Player::cancelMove); Player white = players.get(Color.WHITE); @@ -114,7 +129,13 @@ public class Game { players.get(board.getLog().getActiveColor()).requestMove(); } + /** + * @return The board on which this game's moves are made + */ public Board getBoard() { return board; } + /** + * @return The players participating in this game + */ public Map getPlayers() { return players; } } diff --git a/src/dev/kske/chess/ui/FENDropTarget.java b/src/dev/kske/chess/ui/FENDropTarget.java index dfbd39a..9d913ea 100644 --- a/src/dev/kske/chess/ui/FENDropTarget.java +++ b/src/dev/kske/chess/ui/FENDropTarget.java @@ -11,6 +11,8 @@ import java.io.FileReader; import java.io.IOException; import java.util.List; +import dev.kske.chess.game.Game; + /** * Project: Chess
* File: FENDropTarget.java
@@ -25,23 +27,26 @@ public class FENDropTarget extends DropTargetAdapter { this.mainWindow = mainWindow; } + @SuppressWarnings("unchecked") @Override public void drop(DropTargetDropEvent evt) { try { evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE); - @SuppressWarnings("unchecked") - List droppedFiles = (List) evt.getTransferable().getTransferData(DataFlavor.javaFileListFlavor); - try (BufferedReader br = new BufferedReader(new FileReader(droppedFiles.get(0)))) { - mainWindow.getSelectedGamePane().getGame().reset(); - mainWindow.getSelectedGamePane().getGame().getBoard().initFromFEN(br.readLine()); - mainWindow.getSelectedGamePane().getBoardPane().getBoardComponent().repaint(); - mainWindow.getSelectedGamePane() - .getGame() - .getPlayers() - .get(mainWindow.getSelectedGamePane().getGame().getBoard().getLog().getActiveColor()) - .requestMove(); - evt.dropComplete(true); - } + ((List) evt.getTransferable().getTransferData(DataFlavor.javaFileListFlavor)).forEach(file -> { + try (BufferedReader br = new BufferedReader(new FileReader(file))) { + final GamePane gamePane = mainWindow.addGamePane(); + final String fen = br.readLine(); + new GameConfigurationDialog((whiteName, blackName) -> { + final Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, fen); + gamePane.setGame(game); + game.start(); + }); + evt.dropComplete(true); + } catch (IOException e) { + e.printStackTrace(); + evt.rejectDrop(); + } + }); } catch (UnsupportedFlavorException | IOException ex) { ex.printStackTrace(); evt.rejectDrop(); diff --git a/src/dev/kske/chess/ui/GameConfigurationDialog.java b/src/dev/kske/chess/ui/GameConfigurationDialog.java index 8214fd6..45494dc 100644 --- a/src/dev/kske/chess/ui/GameConfigurationDialog.java +++ b/src/dev/kske/chess/ui/GameConfigurationDialog.java @@ -21,20 +21,16 @@ public class GameConfigurationDialog extends JDialog { private static final long serialVersionUID = 9080577278529876972L; - private String whiteName, blackName; - private boolean startGame; - /** * Create the dialog. */ - public GameConfigurationDialog() { + public GameConfigurationDialog(ActionWithConfiguration action) { setTitle("Game Configuration"); setBounds(100, 100, 281, 142); setModal(true); setLocationRelativeTo(null); getContentPane().setLayout(null); - startGame = false; List options = new ArrayList<>(Arrays.asList("Natural Player", "AI Player")); EngineUtil.getEngineInfos().forEach(info -> options.add(info.name)); @@ -60,10 +56,8 @@ public class GameConfigurationDialog extends JDialog { JButton btnStart = new JButton("Start"); btnStart.addActionListener((evt) -> { - startGame = true; - whiteName = options.get(cbWhite.getSelectedIndex()); - blackName = options.get(cbBlack.getSelectedIndex()); dispose(); + action.onConfigurationSet(options.get(cbWhite.getSelectedIndex()), options.get(cbBlack.getSelectedIndex())); }); btnStart.setBounds(20, 73, 89, 23); getContentPane().add(btnStart); @@ -72,11 +66,12 @@ public class GameConfigurationDialog extends JDialog { btnCancel.addActionListener((evt) -> dispose()); btnCancel.setBounds(157, 73, 89, 23); getContentPane().add(btnCancel); + + setVisible(true); } - public String getWhiteName() { return whiteName; } + public static interface ActionWithConfiguration { - public String getBlackName() { return blackName; } - - public boolean isStartGame() { return startGame; } + void onConfigurationSet(String whiteName, String blackName); + } } diff --git a/src/dev/kske/chess/ui/GamePane.java b/src/dev/kske/chess/ui/GamePane.java index 3c1101f..338ee9a 100644 --- a/src/dev/kske/chess/ui/GamePane.java +++ b/src/dev/kske/chess/ui/GamePane.java @@ -89,6 +89,7 @@ public class GamePane extends JComponent { letterPanel.add(letterLabel); } + // TODO: LogPanel // LogPanel logPanel = new LogPanel(game.getBoard().getLog()); // GridBagConstraints gbc_logPanel = new GridBagConstraints(); // @@ -112,7 +113,7 @@ public class GamePane extends JComponent { * @param game The {@link Game} to assign to this game pane. */ public void setGame(Game game) { - if (this.game != null) this.game.disconnect(); + if (this.game != null) this.game.stop(); this.game = game; btnSwapColors.setEnabled(game.getPlayers().get(Color.WHITE) instanceof NaturalPlayer ^ game.getPlayers().get(Color.BLACK) instanceof NaturalPlayer); diff --git a/src/dev/kske/chess/ui/MainWindow.java b/src/dev/kske/chess/ui/MainWindow.java index c5c2965..68497c3 100644 --- a/src/dev/kske/chess/ui/MainWindow.java +++ b/src/dev/kske/chess/ui/MainWindow.java @@ -13,10 +13,11 @@ import javax.swing.JTabbedPane; * Created: 01.07.2019
* Author: Kai S. K. Engelbart */ -public class MainWindow { +public class MainWindow extends JFrame { - private JFrame frame; - private JTabbedPane tabbedPane; + private static final long serialVersionUID = -3100939302567978977L; + + private JTabbedPane tabbedPane; /** * Launch the application. @@ -35,6 +36,7 @@ public class MainWindow { * Create the application. */ public MainWindow() { + super("Chess by Kai S. K. Engelbart"); initialize(); } @@ -43,24 +45,22 @@ public class MainWindow { */ private void initialize() { // Configure frame - frame = new JFrame("Chess by Kai S. K. Engelbart"); - frame.setResizable(false); - frame.setBounds(100, 100, 494, 565); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - frame.setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("/pieces/queen_white.png"))); + setResizable(false); + setBounds(100, 100, 494, 565); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("/pieces/queen_white.png"))); // Add frame content tabbedPane = new JTabbedPane(); - addGamePane(); // Game is initialized inside MenuBar - frame.getContentPane().add(tabbedPane); + getContentPane().add(tabbedPane); - frame.setJMenuBar(new MenuBar(this)); - new DropTarget(frame, new FENDropTarget(this)); + setJMenuBar(new MenuBar(this)); + new DropTarget(this, new FENDropTarget(this)); // Update position and dimensions - frame.pack(); - frame.setLocationRelativeTo(null); - frame.setVisible(true); + pack(); + setLocationRelativeTo(null); + setVisible(true); } /** @@ -79,4 +79,13 @@ public class MainWindow { tabbedPane.setSelectedIndex(tabbedPane.getComponentCount() - 1); return gamePane; } + + /** + * Removes a {@link GamePane} form the tabbed pane. + * + * @param index The index of the {@link GamePane} to remove + */ + public void removeGamePane(int index) { + tabbedPane.remove(index); + } } diff --git a/src/dev/kske/chess/ui/MenuBar.java b/src/dev/kske/chess/ui/MenuBar.java index 84f1b94..b339c96 100644 --- a/src/dev/kske/chess/ui/MenuBar.java +++ b/src/dev/kske/chess/ui/MenuBar.java @@ -34,22 +34,16 @@ public class MenuBar extends JMenuBar { JMenu gameMenu = new JMenu("Game"); JMenuItem newGameMenuItem = new JMenuItem("New Game"); newGameMenuItem.addActionListener((evt) -> { - GameConfigurationDialog dialog = new GameConfigurationDialog(); - dialog.setVisible(true); - if (dialog.isStartGame()) { - GamePane gamePane = mainWindow.addGamePane(); - Game game = new Game(gamePane.getBoardPane(), dialog.getWhiteName(), dialog.getBlackName()); + new GameConfigurationDialog((whiteName, blackName) -> { + GamePane gamePane = mainWindow.addGamePane(); + Game game = new Game(gamePane.getBoardPane(), whiteName, blackName); gamePane.setGame(game); game.start(); - } + }); }); gameMenu.add(newGameMenuItem); - add(gameMenu); - - // TODO: Game initialization - // Start a game - startGame(new Game(mainWindow.getSelectedGamePane().getBoardPane(), "Natural Player", "Natural Player")); + newGameMenuItem.doClick(); } private void initEngineMenu() { @@ -75,36 +69,25 @@ public class MenuBar extends JMenuBar { JMenu toolsMenu = new JMenu("Tools"); JMenuItem exportFENMenuItem = new JMenuItem("Export board to FEN"); - exportFENMenuItem.addActionListener((evt) -> Toolkit.getDefaultToolkit() - .getSystemClipboard() - .setContents(new StringSelection(mainWindow.getSelectedGamePane().getGame().getBoard().toFEN()), null)); + exportFENMenuItem.addActionListener((evt) -> { + final String fen = mainWindow.getSelectedGamePane().getGame().getBoard().toFEN(); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(fen), null); + JOptionPane.showMessageDialog(mainWindow, String.format("FEN-string copied to clipboard!%n%s", fen)); + }); toolsMenu.add(exportFENMenuItem); - // TODO: Synchronize with game JMenuItem loadFromFENMenuItem = new JMenuItem("Load board from FEN"); loadFromFENMenuItem.addActionListener((evt) -> { - mainWindow.getSelectedGamePane().getGame().reset(); - mainWindow.getSelectedGamePane() - .getGame() - .getBoard() - .initFromFEN(JOptionPane.showInputDialog("Enter a FEN string: ")); - mainWindow.getSelectedGamePane().getBoardPane().getBoardComponent().repaint(); - mainWindow.getSelectedGamePane() - .getGame() - .getPlayers() - .get(mainWindow.getSelectedGamePane().getGame().getBoard().getLog().getActiveColor()) - .requestMove(); + final GamePane gamePane = mainWindow.addGamePane(); + final String fen = JOptionPane.showInputDialog("Enter a FEN string: "); + new GameConfigurationDialog((whiteName, blackName) -> { + final Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, fen); + gamePane.setGame(game); + game.start(); + }); }); toolsMenu.add(loadFromFENMenuItem); add(toolsMenu); } - - private void startGame(Game game) { - mainWindow.getSelectedGamePane().setGame(game); - - // Update board and board component - game.reset(); - game.start(); - } }