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:
		| @@ -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(); | ||||
| 				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(); | ||||
|   | ||||
| @@ -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,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); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -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(); | ||||
| 	} | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user