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:
parent
00eea6d863
commit
3f2e7183dd
@ -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]));
|
||||
|
@ -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; }
|
||||
}
|
||||
|
@ -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();
|
||||
((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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -13,9 +13,10 @@ 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 static final long serialVersionUID = -3100939302567978977L;
|
||||
|
||||
private JFrame frame;
|
||||
private JTabbedPane tabbedPane;
|
||||
|
||||
/**
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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()) {
|
||||
new GameConfigurationDialog((whiteName, blackName) -> {
|
||||
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);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user