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 08ac0ac114
commit 19712a2bb7
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 {
private Piece[][] boardArr;
private Map<Color, Position> kingPos;
private Map<Color, Map<Type, Boolean>> castlingRights;
private Log log;
private Piece[][] boardArr = new Piece[8][8];
private Map<Color, Position> kingPos = new HashMap<>();
private Map<Color, Map<Type, Boolean>> castlingRights = new HashMap<>();
private Log log = new Log();
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 } });
}
/**
* 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<Type, Boolean> 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<Type, Boolean> 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]));

View File

@ -26,18 +26,29 @@ import dev.kske.chess.ui.OverlayComponent;
*/
public class Game {
private Map<Color, Player> players;
private Map<Color, Player> 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<Color, Player> getPlayers() { return players; }
}

View File

@ -11,6 +11,8 @@ import java.io.FileReader;
import java.io.IOException;
import java.util.List;
import dev.kske.chess.game.Game;
/**
* Project: <strong>Chess</strong><br>
* File: <strong>FENDropTarget.java</strong><br>
@ -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<File> droppedFiles = (List<File>) 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<File>) 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();

View File

@ -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<String> 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);
}
}

View File

@ -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);

View File

@ -13,10 +13,11 @@ import javax.swing.JTabbedPane;
* Created: <strong>01.07.2019</strong><br>
* Author: <strong>Kai S. K. Engelbart</strong>
*/
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);
}
}

View File

@ -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();
}
}