From 929fd9381ee2703abac9baa6aa07780b1baba425 Mon Sep 17 00:00:00 2001 From: kske Date: Sun, 23 Feb 2020 21:21:19 +0100 Subject: [PATCH] Refactor for efficiency and concise code * Change player map implentation in Game to EnumMap * Add proper toString methods to Piece and MoveNode * Compact code in MainWindow * Synchronize methods in Game --- .classpath | 40 ++++++++++++---------- src/dev/kske/chess/board/Board.java | 2 +- src/dev/kske/chess/board/MoveNode.java | 12 +++++++ src/dev/kske/chess/board/Piece.java | 4 +-- src/dev/kske/chess/game/Game.java | 23 ++++++------- src/dev/kske/chess/game/NaturalPlayer.java | 5 +-- src/dev/kske/chess/game/Player.java | 25 ++++++-------- src/dev/kske/chess/game/UCIPlayer.java | 5 +-- src/dev/kske/chess/game/ai/AIPlayer.java | 33 ++++++++---------- src/dev/kske/chess/io/TextureUtil.java | 10 +++--- src/dev/kske/chess/ui/MainWindow.java | 27 +++------------ 11 files changed, 87 insertions(+), 99 deletions(-) diff --git a/.classpath b/.classpath index 8469573..afa4751 100644 --- a/.classpath +++ b/.classpath @@ -1,18 +1,22 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/dev/kske/chess/board/Board.java b/src/dev/kske/chess/board/Board.java index 51bf632..d7c161d 100644 --- a/src/dev/kske/chess/board/Board.java +++ b/src/dev/kske/chess/board/Board.java @@ -17,7 +17,7 @@ import dev.kske.chess.event.MoveEvent; public class Board { private Piece[][] boardArr = new Piece[8][8]; - private Map kingPos = new HashMap<>(); + private Map kingPos = new EnumMap<>(Color.class); private Log log = new Log(); /** diff --git a/src/dev/kske/chess/board/MoveNode.java b/src/dev/kske/chess/board/MoveNode.java index 608f2ff..162612f 100644 --- a/src/dev/kske/chess/board/MoveNode.java +++ b/src/dev/kske/chess/board/MoveNode.java @@ -158,6 +158,18 @@ public class MoveNode { */ public boolean hasParent() { return parent != null; } + @Override + public String toString() { + return String.format("MoveNode[move=%s,capturedPiece=%s,castlingRights=%s,enPassant=%s,activeColor=%s,fullmoveCounter=%d,halfmoveClock=%d]", + move, + capturedPiece, + Arrays.toString(castlingRights), + enPassant, + activeColor, + fullmoveCounter, + halfmoveClock); + } + @Override public int hashCode() { final int prime = 31; diff --git a/src/dev/kske/chess/board/Piece.java b/src/dev/kske/chess/board/Piece.java index a0ea314..6bbf6f2 100644 --- a/src/dev/kske/chess/board/Piece.java +++ b/src/dev/kske/chess/board/Piece.java @@ -98,7 +98,7 @@ public abstract class Piece implements Cloneable { } @Override - public String toString() { return getClass().getSimpleName(); } + public String toString() { return String.format("%s[color=%s]", getClass().getSimpleName(), color); } @Override public int hashCode() { return Objects.hash(color); } @@ -122,7 +122,7 @@ public abstract class Piece implements Cloneable { * @return The first character of this {@link Piece} in algebraic notation and * lower case */ - public char firstChar() { return Character.toLowerCase(toString().charAt(0)); } + public char firstChar() { return Character.toLowerCase(getClass().getSimpleName().charAt(0)); } /** * @param firstChar the first character of a piece's name diff --git a/src/dev/kske/chess/game/Game.java b/src/dev/kske/chess/game/Game.java index 8905c1c..6c42cf3 100644 --- a/src/dev/kske/chess/game/Game.java +++ b/src/dev/kske/chess/game/Game.java @@ -1,6 +1,6 @@ package dev.kske.chess.game; -import java.util.HashMap; +import java.util.EnumMap; import java.util.Map; import javax.swing.JOptionPane; @@ -29,7 +29,7 @@ import dev.kske.chess.ui.OverlayComponent; */ public class Game { - private Map players = new HashMap<>(2); + private Map players = new EnumMap<>(Color.class); private Board board; private OverlayComponent overlayComponent; private BoardComponent boardComponent; @@ -69,9 +69,6 @@ public class Game { // Initialize players players.put(Color.WHITE, getPlayer(whiteName, Color.WHITE)); players.put(Color.BLACK, getPlayer(blackName, Color.BLACK)); - - // Initialize the game variable in each player - players.values().forEach(player -> player.setGame(this)); } /** @@ -88,12 +85,12 @@ public class Game { private Player getPlayer(String name, Color color) { switch (name) { case "Natural Player": - return new NaturalPlayer(color, overlayComponent); + return new NaturalPlayer(this, color, overlayComponent); case "AI Player": - return new AIPlayer(color, 4, -10); + return new AIPlayer(this, color, 4, -10); default: for (EngineInfo info : EngineUtil.getEngineInfos()) - if (info.name.equals(name)) return new UCIPlayer(color, info.path); + if (info.name.equals(name)) return new UCIPlayer(this, color, info.path); System.err.println("Invalid player name: " + name); return null; } @@ -107,7 +104,7 @@ public class Game { * @param player the player who generated the move * @param move the generated move */ - public void onMove(Player player, Move move) { + public synchronized void onMove(Player player, Move move) { if (board.getPos(move).getColor() == player.color && board.attemptMove(move)) { // Redraw @@ -138,7 +135,7 @@ public class Game { * Starts the game by requesting a move from the player of the currently active * color. */ - public void start() { + public synchronized void start() { EventBus.getInstance().dispatch(new GameStartEvent(this)); players.get(board.getLog().getActiveColor()).requestMove(); } @@ -147,7 +144,7 @@ public class Game { * Cancels move calculations, initializes the default position and clears the * {@link OverlayComponent}. */ - public void reset() { + public synchronized void reset() { players.values().forEach(Player::cancelMove); board.initDefaultPositions(); boardComponent.repaint(); @@ -158,12 +155,12 @@ public class Game { /** * Stops the game by disconnecting its players from the UI. */ - public void stop() { players.values().forEach(Player::disconnect); } + public synchronized void stop() { players.values().forEach(Player::disconnect); } /** * Assigns the players their opposite colors. */ - public void swapColors() { + public synchronized void swapColors() { players.values().forEach(Player::cancelMove); Player white = players.get(Color.WHITE); Player black = players.get(Color.BLACK); diff --git a/src/dev/kske/chess/game/NaturalPlayer.java b/src/dev/kske/chess/game/NaturalPlayer.java index 563b1a0..4ed1dc8 100644 --- a/src/dev/kske/chess/game/NaturalPlayer.java +++ b/src/dev/kske/chess/game/NaturalPlayer.java @@ -37,12 +37,13 @@ public class NaturalPlayer extends Player implements MouseListener { /** * Creates an instance of {@link NaturalPlayer}. * + * @param game the game in which this player will be used * @param color the piece color this player will control * @param overlayComponent the overlay component that will be used to display * possible moves to the user */ - public NaturalPlayer(Color color, OverlayComponent overlayComponent) { - super(color); + public NaturalPlayer(Game game, Color color, OverlayComponent overlayComponent) { + super(game, color); this.overlayComponent = overlayComponent; name = "Player"; moveRequested = false; diff --git a/src/dev/kske/chess/game/Player.java b/src/dev/kske/chess/game/Player.java index 4c323b2..7a9f6de 100644 --- a/src/dev/kske/chess/game/Player.java +++ b/src/dev/kske/chess/game/Player.java @@ -17,17 +17,23 @@ import dev.kske.chess.board.Piece.Color; */ public abstract class Player { - protected Game game; - protected Board board; - protected Color color; + protected final Game game; + protected final Board board; + protected String name; + protected Color color; /** * Initializes the color of this player. * + * @param game the game in which this player will be used * @param color the piece color that this player will control */ - public Player(Color color) { this.color = color; } + public Player(Game game, Color color) { + this.game = game; + board = game.getBoard(); + this.color = color; + } /** * Initiates a move generation and reports the result to the game by calling @@ -50,17 +56,6 @@ public abstract class Player { */ public Game getGame() { return game; } - /** - * Sets the game in which this player is used. The board of that game will be - * assigned as well. - * - * @param game the game to set - */ - public void setGame(Game game) { - this.game = game; - board = game.getBoard(); - } - /** * @return the board on which this player is used */ diff --git a/src/dev/kske/chess/game/UCIPlayer.java b/src/dev/kske/chess/game/UCIPlayer.java index 0a06645..d73cb75 100644 --- a/src/dev/kske/chess/game/UCIPlayer.java +++ b/src/dev/kske/chess/game/UCIPlayer.java @@ -27,11 +27,12 @@ public class UCIPlayer extends Player implements UCIListener { /** * Creates an instance of {@link UCIPlayer}. * + * @param game the game in which this player will be used * @param color the piece color that this player will control * @param enginePath the path to the engine executable */ - public UCIPlayer(Color color, String enginePath) { - super(color); + public UCIPlayer(Game game, Color color, String enginePath) { + super(game, color); try { handle = new UCIHandle(enginePath); handle.registerListener(this); diff --git a/src/dev/kske/chess/game/ai/AIPlayer.java b/src/dev/kske/chess/game/ai/AIPlayer.java index ee68765..6dc030a 100644 --- a/src/dev/kske/chess/game/ai/AIPlayer.java +++ b/src/dev/kske/chess/game/ai/AIPlayer.java @@ -9,6 +9,7 @@ import javax.swing.SwingUtilities; import dev.kske.chess.board.Board; import dev.kske.chess.board.Move; import dev.kske.chess.board.Piece.Color; +import dev.kske.chess.game.Game; import dev.kske.chess.game.Player; /** @@ -31,38 +32,34 @@ public class AIPlayer extends Player { /** * Creates an instance of {@link AIPlayer}. * + * @param game the game in which this player will be used * @param color the piece color this player will control * @param maxDepth the maximum search depth * @param alphaBetaThreshold the board evaluation threshold that has to be * reached to continue searching the children of a * move */ - public AIPlayer(Color color, int maxDepth, int alphaBetaThreshold) { - super(color); + public AIPlayer(Game game, Color color, int maxDepth, int alphaBetaThreshold) { + super(game, color); name = "AIPlayer"; availableProcessors = Runtime.getRuntime().availableProcessors(); this.maxDepth = maxDepth; this.alphaBetaThreshold = alphaBetaThreshold; - exitRequested = false; } @Override public void requestMove() { exitRequested = false; - /* - * Define some processing threads, split the available moves between them and - * retrieve the result after their execution. - */ + + // Define some processing threads, split the available moves between them and + // retrieve the result after their execution. new Thread(() -> { - /* - * Get a copy of the board and the available moves. - */ + + // Get a copy of the board and the available moves. Board board = new Board(this.board, false); List moves = board.getMoves(color); - /* - * Define move processors and split the available moves between them. - */ + // Define move processors and split the available moves between them. int numThreads = Math.min(moves.size(), availableProcessors); List processors = new ArrayList<>(numThreads); final int step = moves.size() / numThreads; @@ -71,23 +68,21 @@ public class AIPlayer extends Player { for (int i = 0; i < numThreads; i++) { if (rem-- > 0) ++endIndex; endIndex += step; - processors.add(new MoveProcessor(new Board(board, false), moves.subList(beginIndex, endIndex), color, - maxDepth, alphaBetaThreshold)); + processors.add(new MoveProcessor(new Board(board, false), moves.subList(beginIndex, endIndex), color, maxDepth, alphaBetaThreshold)); beginIndex = endIndex; } - /* - * Execute processors, get the best result and pass it back to the Game class - */ + // Execute processors, get the best result and pass it back to the Game class executor = Executors.newFixedThreadPool(numThreads); List results = new ArrayList<>(numThreads); try { List> futures = executor.invokeAll(processors); for (Future f : futures) results.add(f.get()); - executor.shutdown(); } catch (InterruptedException | ExecutionException ex) { ex.printStackTrace(); + } finally { + executor.shutdown(); } results.sort((r1, r2) -> Integer.compare(r2.score, r1.score)); if (!exitRequested) SwingUtilities.invokeLater(() -> game.onMove(this, results.get(0).move)); diff --git a/src/dev/kske/chess/io/TextureUtil.java b/src/dev/kske/chess/io/TextureUtil.java index 79d251b..d3240e5 100644 --- a/src/dev/kske/chess/io/TextureUtil.java +++ b/src/dev/kske/chess/io/TextureUtil.java @@ -15,7 +15,7 @@ import dev.kske.chess.board.Piece; * Project: Chess
* File: TextureUtil.java
* Created: 01.07.2019
- * + * * @since Chess v0.1-alpha * @author Kai S. K. Engelbart */ @@ -32,18 +32,18 @@ public class TextureUtil { /** * Loads a piece texture fitting to a piece object. - * + * * @param piece The piece from which the texture properties are taken * @return The fitting texture */ public static Image getPieceTexture(Piece piece) { - String key = piece.toString().toLowerCase() + "_" + piece.getColor().toString().toLowerCase(); + String key = piece.getClass().getSimpleName().toLowerCase() + "_" + piece.getColor().toString().toLowerCase(); return scaledTextures.get(key); } /** * Scales all piece textures to fit the current tile size. - * + * * @param tileSize the new width and height of the piece textures */ public static void scalePieceTextures(int tileSize) { @@ -53,7 +53,7 @@ public class TextureUtil { /** * Loads an image from a file in the resource folder. - * + * * @param fileName The name of the image resource * @return The loaded image */ diff --git a/src/dev/kske/chess/ui/MainWindow.java b/src/dev/kske/chess/ui/MainWindow.java index fee7aa4..86ae49f 100644 --- a/src/dev/kske/chess/ui/MainWindow.java +++ b/src/dev/kske/chess/ui/MainWindow.java @@ -1,7 +1,6 @@ package dev.kske.chess.ui; import java.awt.Desktop; -import java.awt.EventQueue; import java.awt.Toolkit; import java.awt.dnd.DropTarget; import java.io.File; @@ -29,47 +28,31 @@ import dev.kske.chess.pgn.PGNGame; */ public class MainWindow extends JFrame { - private static final long serialVersionUID = -3100939302567978977L; + private JTabbedPane tabbedPane = new JTabbedPane(); - private JTabbedPane tabbedPane; + private static final long serialVersionUID = -3100939302567978977L; /** * Launch the application. * * @param args command line arguments are ignored */ - public static void main(String[] args) { - EventQueue.invokeLater(() -> { - try { - new MainWindow(); - } catch (Exception ex) { - ex.printStackTrace(); - } - }); - } + public static void main(String[] args) { SwingUtilities.invokeLater(() -> new MainWindow()); } /** * Create the application. */ - public MainWindow() { + private MainWindow() { super("Chess by Kai S. K. Engelbart"); - initialize(); - } - /** - * Initialize the contents of the frame. - */ - private void initialize() { // Configure frame 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(); + // Add tabbed pane, menu bar and drop target getContentPane().add(tabbedPane); - setJMenuBar(new MenuBar(this)); new DropTarget(this, new GameDropTarget(this));