Refactoring and documentation, improved FEN integration

+ Adding a new game when loading a FEN string
+ Loading multiple FEN files with drag and drop
This commit is contained in:
Kai S. K. Engelbart 2019-09-08 20:18:45 +02:00
parent 897353efeb
commit 37cf7dcab2
7 changed files with 117 additions and 93 deletions

View File

@ -17,10 +17,10 @@ import dev.kske.chess.board.Piece.Type;
*/ */
public class Board implements Cloneable { public class Board implements Cloneable {
private Piece[][] boardArr; private Piece[][] boardArr = new Piece[8][8];
private Map<Color, Position> kingPos; private Map<Color, Position> kingPos = new HashMap<>();
private Map<Color, Map<Type, Boolean>> castlingRights; private Map<Color, Map<Type, Boolean>> castlingRights = new HashMap<>();
private Log log; private Log log = new Log();
private static final Map<Type, int[][]> positionScores; private static final Map<Type, int[][]> 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 } }); 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() { public Board() {
boardArr = new Piece[8][8];
kingPos = new HashMap<>();
castlingRights = new HashMap<>();
log = new Log();
initDefaultPositions(); 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. * Moves a piece across the board if the move is legal.
* *
@ -369,7 +377,6 @@ public class Board implements Cloneable {
boardArr[i][j] = null; boardArr[i][j] = null;
// Initialize castling rights // Initialize castling rights
castlingRights.clear();
Map<Type, Boolean> whiteCastling = new HashMap<>(), blackCastling = new HashMap<>(); Map<Type, Boolean> whiteCastling = new HashMap<>(), blackCastling = new HashMap<>();
whiteCastling.put(Type.KING, true); whiteCastling.put(Type.KING, true);
whiteCastling.put(Type.QUEEN, true); whiteCastling.put(Type.QUEEN, true);
@ -434,22 +441,25 @@ public class Board implements Cloneable {
log.setActiveColor(Color.fromFirstChar(parts[1].charAt(0))); log.setActiveColor(Color.fromFirstChar(parts[1].charAt(0)));
// Castling rights // Castling rights
Map<Type, Boolean> whiteCastling = new HashMap<>(), blackCastling = new HashMap<>();
for (char c : parts[2].toCharArray()) for (char c : parts[2].toCharArray())
switch (c) { switch (c) {
case 'K': case 'K':
castlingRights.get(Color.WHITE).put(Type.KING, true); whiteCastling.put(Type.KING, true);
case 'Q': case 'Q':
castlingRights.get(Color.WHITE).put(Type.QUEEN, true); whiteCastling.put(Type.QUEEN, true);
case 'k': case 'k':
castlingRights.get(Color.BLACK).put(Type.KING, true); blackCastling.put(Type.KING, true);
case 'q': case 'q':
castlingRights.get(Color.BLACK).put(Type.QUEEN, true); blackCastling.put(Type.QUEEN, true);
case '-': case '-':
break; break;
default: default:
System.err System.err
.printf("Unknown character '%c' in castling rights declaration of FEN string '%s'", c, fen); .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 // En passant availability
if (!parts[3].equals("-")) log.setEnPassant(Position.fromSAN(parts[3])); if (!parts[3].equals("-")) log.setEnPassant(Position.fromSAN(parts[3]));

View File

@ -26,18 +26,29 @@ import dev.kske.chess.ui.OverlayComponent;
*/ */
public class Game { public class Game {
private Map<Color, Player> players; private Map<Color, Player> players = new HashMap<>();
private Board board; private Board board;
private OverlayComponent overlayComponent; private OverlayComponent overlayComponent;
private BoardComponent boardComponent; private BoardComponent boardComponent;
public Game(BoardPane boardPane, String whiteName, String blackName) { public Game(BoardPane boardPane, String whiteName, String blackName) {
players = new HashMap<>();
board = new Board(); 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(); overlayComponent = boardPane.getOverlayComponent();
boardComponent = boardPane.getBoardComponent(); boardComponent = boardPane.getBoardComponent();
boardComponent.setBoard(board); boardComponent.setBoard(board);
// Initialize players
players.put(Color.WHITE, getPlayer(whiteName, Color.WHITE)); players.put(Color.WHITE, getPlayer(whiteName, Color.WHITE));
players.put(Color.BLACK, getPlayer(blackName, Color.BLACK)); players.put(Color.BLACK, getPlayer(blackName, Color.BLACK));
@ -61,6 +72,7 @@ public class Game {
public void onMove(Player player, Move move) { public void onMove(Player player, Move move) {
if (board.getPos(move).getColor() == player.color && board.attemptMove(move)) { if (board.getPos(move).getColor() == player.color && board.attemptMove(move)) {
// Redraw // Redraw
boardComponent.repaint(); boardComponent.repaint();
overlayComponent.displayArrow(move); overlayComponent.displayArrow(move);
@ -85,7 +97,7 @@ public class Game {
} }
public void start() { public void start() {
players.get(Color.WHITE).requestMove(); players.get(board.getLog().getActiveColor()).requestMove();
} }
public void reset() { 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); players.values().forEach(Player::disconnect);
} }
/**
* Assigns the players their opposite colors.
*/
public void swapColors() { public void swapColors() {
players.values().forEach(Player::cancelMove); players.values().forEach(Player::cancelMove);
Player white = players.get(Color.WHITE); Player white = players.get(Color.WHITE);
@ -114,7 +129,13 @@ public class Game {
players.get(board.getLog().getActiveColor()).requestMove(); players.get(board.getLog().getActiveColor()).requestMove();
} }
/**
* @return The board on which this game's moves are made
*/
public Board getBoard() { return board; } public Board getBoard() { return board; }
/**
* @return The players participating in this game
*/
public Map<Color, Player> getPlayers() { return players; } public Map<Color, Player> getPlayers() { return players; }
} }

View File

@ -11,6 +11,8 @@ import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import dev.kske.chess.game.Game;
/** /**
* Project: <strong>Chess</strong><br> * Project: <strong>Chess</strong><br>
* File: <strong>FENDropTarget.java</strong><br> * File: <strong>FENDropTarget.java</strong><br>
@ -25,23 +27,26 @@ public class FENDropTarget extends DropTargetAdapter {
this.mainWindow = mainWindow; this.mainWindow = mainWindow;
} }
@SuppressWarnings("unchecked")
@Override @Override
public void drop(DropTargetDropEvent evt) { public void drop(DropTargetDropEvent evt) {
try { try {
evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE); evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
@SuppressWarnings("unchecked") ((List<File>) evt.getTransferable().getTransferData(DataFlavor.javaFileListFlavor)).forEach(file -> {
List<File> droppedFiles = (List<File>) evt.getTransferable().getTransferData(DataFlavor.javaFileListFlavor); try (BufferedReader br = new BufferedReader(new FileReader(file))) {
try (BufferedReader br = new BufferedReader(new FileReader(droppedFiles.get(0)))) { final GamePane gamePane = mainWindow.addGamePane();
mainWindow.getSelectedGamePane().getGame().reset(); final String fen = br.readLine();
mainWindow.getSelectedGamePane().getGame().getBoard().initFromFEN(br.readLine()); new GameConfigurationDialog((whiteName, blackName) -> {
mainWindow.getSelectedGamePane().getBoardPane().getBoardComponent().repaint(); final Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, fen);
mainWindow.getSelectedGamePane() gamePane.setGame(game);
.getGame() game.start();
.getPlayers() });
.get(mainWindow.getSelectedGamePane().getGame().getBoard().getLog().getActiveColor())
.requestMove();
evt.dropComplete(true); evt.dropComplete(true);
} catch (IOException e) {
e.printStackTrace();
evt.rejectDrop();
} }
});
} catch (UnsupportedFlavorException | IOException ex) { } catch (UnsupportedFlavorException | IOException ex) {
ex.printStackTrace(); ex.printStackTrace();
evt.rejectDrop(); evt.rejectDrop();

View File

@ -21,20 +21,16 @@ public class GameConfigurationDialog extends JDialog {
private static final long serialVersionUID = 9080577278529876972L; private static final long serialVersionUID = 9080577278529876972L;
private String whiteName, blackName;
private boolean startGame;
/** /**
* Create the dialog. * Create the dialog.
*/ */
public GameConfigurationDialog() { public GameConfigurationDialog(ActionWithConfiguration action) {
setTitle("Game Configuration"); setTitle("Game Configuration");
setBounds(100, 100, 281, 142); setBounds(100, 100, 281, 142);
setModal(true); setModal(true);
setLocationRelativeTo(null); setLocationRelativeTo(null);
getContentPane().setLayout(null); getContentPane().setLayout(null);
startGame = false;
List<String> options = new ArrayList<>(Arrays.asList("Natural Player", "AI Player")); List<String> options = new ArrayList<>(Arrays.asList("Natural Player", "AI Player"));
EngineUtil.getEngineInfos().forEach(info -> options.add(info.name)); EngineUtil.getEngineInfos().forEach(info -> options.add(info.name));
@ -60,10 +56,8 @@ public class GameConfigurationDialog extends JDialog {
JButton btnStart = new JButton("Start"); JButton btnStart = new JButton("Start");
btnStart.addActionListener((evt) -> { btnStart.addActionListener((evt) -> {
startGame = true;
whiteName = options.get(cbWhite.getSelectedIndex());
blackName = options.get(cbBlack.getSelectedIndex());
dispose(); dispose();
action.onConfigurationSet(options.get(cbWhite.getSelectedIndex()), options.get(cbBlack.getSelectedIndex()));
}); });
btnStart.setBounds(20, 73, 89, 23); btnStart.setBounds(20, 73, 89, 23);
getContentPane().add(btnStart); getContentPane().add(btnStart);
@ -72,11 +66,12 @@ public class GameConfigurationDialog extends JDialog {
btnCancel.addActionListener((evt) -> dispose()); btnCancel.addActionListener((evt) -> dispose());
btnCancel.setBounds(157, 73, 89, 23); btnCancel.setBounds(157, 73, 89, 23);
getContentPane().add(btnCancel); getContentPane().add(btnCancel);
setVisible(true);
} }
public String getWhiteName() { return whiteName; } public static interface ActionWithConfiguration {
public String getBlackName() { return blackName; } void onConfigurationSet(String whiteName, String blackName);
}
public boolean isStartGame() { return startGame; }
} }

View File

@ -89,6 +89,7 @@ public class GamePane extends JComponent {
letterPanel.add(letterLabel); letterPanel.add(letterLabel);
} }
// TODO: LogPanel
// LogPanel logPanel = new LogPanel(game.getBoard().getLog()); // LogPanel logPanel = new LogPanel(game.getBoard().getLog());
// GridBagConstraints gbc_logPanel = new GridBagConstraints(); // 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. * @param game The {@link Game} to assign to this game pane.
*/ */
public void setGame(Game game) { public void setGame(Game game) {
if (this.game != null) this.game.disconnect(); if (this.game != null) this.game.stop();
this.game = game; this.game = game;
btnSwapColors.setEnabled(game.getPlayers().get(Color.WHITE) instanceof NaturalPlayer btnSwapColors.setEnabled(game.getPlayers().get(Color.WHITE) instanceof NaturalPlayer
^ game.getPlayers().get(Color.BLACK) instanceof NaturalPlayer); ^ game.getPlayers().get(Color.BLACK) instanceof NaturalPlayer);

View File

@ -13,9 +13,10 @@ import javax.swing.JTabbedPane;
* Created: <strong>01.07.2019</strong><br> * Created: <strong>01.07.2019</strong><br>
* Author: <strong>Kai S. K. Engelbart</strong> * Author: <strong>Kai S. K. Engelbart</strong>
*/ */
public class MainWindow { public class MainWindow extends JFrame {
private static final long serialVersionUID = -3100939302567978977L;
private JFrame frame;
private JTabbedPane tabbedPane; private JTabbedPane tabbedPane;
/** /**
@ -35,6 +36,7 @@ public class MainWindow {
* Create the application. * Create the application.
*/ */
public MainWindow() { public MainWindow() {
super("Chess by Kai S. K. Engelbart");
initialize(); initialize();
} }
@ -43,24 +45,22 @@ public class MainWindow {
*/ */
private void initialize() { private void initialize() {
// Configure frame // Configure frame
frame = new JFrame("Chess by Kai S. K. Engelbart"); setResizable(false);
frame.setResizable(false); setBounds(100, 100, 494, 565);
frame.setBounds(100, 100, 494, 565); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("/pieces/queen_white.png")));
frame.setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("/pieces/queen_white.png")));
// Add frame content // Add frame content
tabbedPane = new JTabbedPane(); tabbedPane = new JTabbedPane();
addGamePane(); // Game is initialized inside MenuBar getContentPane().add(tabbedPane);
frame.getContentPane().add(tabbedPane);
frame.setJMenuBar(new MenuBar(this)); setJMenuBar(new MenuBar(this));
new DropTarget(frame, new FENDropTarget(this)); new DropTarget(this, new FENDropTarget(this));
// Update position and dimensions // Update position and dimensions
frame.pack(); pack();
frame.setLocationRelativeTo(null); setLocationRelativeTo(null);
frame.setVisible(true); setVisible(true);
} }
/** /**
@ -79,4 +79,13 @@ public class MainWindow {
tabbedPane.setSelectedIndex(tabbedPane.getComponentCount() - 1); tabbedPane.setSelectedIndex(tabbedPane.getComponentCount() - 1);
return gamePane; 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);
}
} }

View File

@ -34,22 +34,16 @@ public class MenuBar extends JMenuBar {
JMenu gameMenu = new JMenu("Game"); JMenu gameMenu = new JMenu("Game");
JMenuItem newGameMenuItem = new JMenuItem("New Game"); JMenuItem newGameMenuItem = new JMenuItem("New Game");
newGameMenuItem.addActionListener((evt) -> { newGameMenuItem.addActionListener((evt) -> {
GameConfigurationDialog dialog = new GameConfigurationDialog(); new GameConfigurationDialog((whiteName, blackName) -> {
dialog.setVisible(true);
if (dialog.isStartGame()) {
GamePane gamePane = mainWindow.addGamePane(); GamePane gamePane = mainWindow.addGamePane();
Game game = new Game(gamePane.getBoardPane(), dialog.getWhiteName(), dialog.getBlackName()); Game game = new Game(gamePane.getBoardPane(), whiteName, blackName);
gamePane.setGame(game); gamePane.setGame(game);
game.start(); game.start();
} });
}); });
gameMenu.add(newGameMenuItem); gameMenu.add(newGameMenuItem);
add(gameMenu); add(gameMenu);
newGameMenuItem.doClick();
// TODO: Game initialization
// Start a game
startGame(new Game(mainWindow.getSelectedGamePane().getBoardPane(), "Natural Player", "Natural Player"));
} }
private void initEngineMenu() { private void initEngineMenu() {
@ -75,36 +69,25 @@ public class MenuBar extends JMenuBar {
JMenu toolsMenu = new JMenu("Tools"); JMenu toolsMenu = new JMenu("Tools");
JMenuItem exportFENMenuItem = new JMenuItem("Export board to FEN"); JMenuItem exportFENMenuItem = new JMenuItem("Export board to FEN");
exportFENMenuItem.addActionListener((evt) -> Toolkit.getDefaultToolkit() exportFENMenuItem.addActionListener((evt) -> {
.getSystemClipboard() final String fen = mainWindow.getSelectedGamePane().getGame().getBoard().toFEN();
.setContents(new StringSelection(mainWindow.getSelectedGamePane().getGame().getBoard().toFEN()), null)); Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(fen), null);
JOptionPane.showMessageDialog(mainWindow, String.format("FEN-string copied to clipboard!%n%s", fen));
});
toolsMenu.add(exportFENMenuItem); toolsMenu.add(exportFENMenuItem);
// TODO: Synchronize with game
JMenuItem loadFromFENMenuItem = new JMenuItem("Load board from FEN"); JMenuItem loadFromFENMenuItem = new JMenuItem("Load board from FEN");
loadFromFENMenuItem.addActionListener((evt) -> { loadFromFENMenuItem.addActionListener((evt) -> {
mainWindow.getSelectedGamePane().getGame().reset(); final GamePane gamePane = mainWindow.addGamePane();
mainWindow.getSelectedGamePane() final String fen = JOptionPane.showInputDialog("Enter a FEN string: ");
.getGame() new GameConfigurationDialog((whiteName, blackName) -> {
.getBoard() final Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, fen);
.initFromFEN(JOptionPane.showInputDialog("Enter a FEN string: ")); gamePane.setGame(game);
mainWindow.getSelectedGamePane().getBoardPane().getBoardComponent().repaint(); game.start();
mainWindow.getSelectedGamePane() });
.getGame()
.getPlayers()
.get(mainWindow.getSelectedGamePane().getGame().getBoard().getLog().getActiveColor())
.requestMove();
}); });
toolsMenu.add(loadFromFENMenuItem); toolsMenu.add(loadFromFENMenuItem);
add(toolsMenu); add(toolsMenu);
} }
private void startGame(Game game) {
mainWindow.getSelectedGamePane().setGame(game);
// Update board and board component
game.reset();
game.start();
}
} }