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
This commit is contained in:
Kai S. K. Engelbart 2020-02-23 21:21:19 +01:00
parent 2333021bba
commit 929fd9381e
Signed by: kske
GPG Key ID: 8BEB13EC5DF7EF13
11 changed files with 87 additions and 99 deletions

View File

@ -13,6 +13,10 @@
</attributes> </attributes>
</classpathentry> </classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/5"/> <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/5"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="module" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="bin"/> <classpathentry kind="output" path="bin"/>
</classpath> </classpath>

View File

@ -17,7 +17,7 @@ import dev.kske.chess.event.MoveEvent;
public class Board { public class Board {
private Piece[][] boardArr = new Piece[8][8]; private Piece[][] boardArr = new Piece[8][8];
private Map<Color, Position> kingPos = new HashMap<>(); private Map<Color, Position> kingPos = new EnumMap<>(Color.class);
private Log log = new Log(); private Log log = new Log();
/** /**

View File

@ -158,6 +158,18 @@ public class MoveNode {
*/ */
public boolean hasParent() { return parent != null; } 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 @Override
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;

View File

@ -98,7 +98,7 @@ public abstract class Piece implements Cloneable {
} }
@Override @Override
public String toString() { return getClass().getSimpleName(); } public String toString() { return String.format("%s[color=%s]", getClass().getSimpleName(), color); }
@Override @Override
public int hashCode() { return Objects.hash(color); } 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 * @return The first character of this {@link Piece} in algebraic notation and
* lower case * 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 * @param firstChar the first character of a piece's name

View File

@ -1,6 +1,6 @@
package dev.kske.chess.game; package dev.kske.chess.game;
import java.util.HashMap; import java.util.EnumMap;
import java.util.Map; import java.util.Map;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
@ -29,7 +29,7 @@ import dev.kske.chess.ui.OverlayComponent;
*/ */
public class Game { public class Game {
private Map<Color, Player> players = new HashMap<>(2); private Map<Color, Player> players = new EnumMap<>(Color.class);
private Board board; private Board board;
private OverlayComponent overlayComponent; private OverlayComponent overlayComponent;
private BoardComponent boardComponent; private BoardComponent boardComponent;
@ -69,9 +69,6 @@ public class Game {
// Initialize players // 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));
// 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) { private Player getPlayer(String name, Color color) {
switch (name) { switch (name) {
case "Natural Player": case "Natural Player":
return new NaturalPlayer(color, overlayComponent); return new NaturalPlayer(this, color, overlayComponent);
case "AI Player": case "AI Player":
return new AIPlayer(color, 4, -10); return new AIPlayer(this, color, 4, -10);
default: default:
for (EngineInfo info : EngineUtil.getEngineInfos()) 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); System.err.println("Invalid player name: " + name);
return null; return null;
} }
@ -107,7 +104,7 @@ public class Game {
* @param player the player who generated the move * @param player the player who generated the move
* @param move the generated 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)) { if (board.getPos(move).getColor() == player.color && board.attemptMove(move)) {
// Redraw // Redraw
@ -138,7 +135,7 @@ public class Game {
* Starts the game by requesting a move from the player of the currently active * Starts the game by requesting a move from the player of the currently active
* color. * color.
*/ */
public void start() { public synchronized void start() {
EventBus.getInstance().dispatch(new GameStartEvent(this)); EventBus.getInstance().dispatch(new GameStartEvent(this));
players.get(board.getLog().getActiveColor()).requestMove(); players.get(board.getLog().getActiveColor()).requestMove();
} }
@ -147,7 +144,7 @@ public class Game {
* Cancels move calculations, initializes the default position and clears the * Cancels move calculations, initializes the default position and clears the
* {@link OverlayComponent}. * {@link OverlayComponent}.
*/ */
public void reset() { public synchronized void reset() {
players.values().forEach(Player::cancelMove); players.values().forEach(Player::cancelMove);
board.initDefaultPositions(); board.initDefaultPositions();
boardComponent.repaint(); boardComponent.repaint();
@ -158,12 +155,12 @@ public class Game {
/** /**
* Stops the game by disconnecting its players from the UI. * 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. * Assigns the players their opposite colors.
*/ */
public void swapColors() { public synchronized void swapColors() {
players.values().forEach(Player::cancelMove); players.values().forEach(Player::cancelMove);
Player white = players.get(Color.WHITE); Player white = players.get(Color.WHITE);
Player black = players.get(Color.BLACK); Player black = players.get(Color.BLACK);

View File

@ -37,12 +37,13 @@ public class NaturalPlayer extends Player implements MouseListener {
/** /**
* Creates an instance of {@link NaturalPlayer}. * 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 color the piece color this player will control
* @param overlayComponent the overlay component that will be used to display * @param overlayComponent the overlay component that will be used to display
* possible moves to the user * possible moves to the user
*/ */
public NaturalPlayer(Color color, OverlayComponent overlayComponent) { public NaturalPlayer(Game game, Color color, OverlayComponent overlayComponent) {
super(color); super(game, color);
this.overlayComponent = overlayComponent; this.overlayComponent = overlayComponent;
name = "Player"; name = "Player";
moveRequested = false; moveRequested = false;

View File

@ -17,17 +17,23 @@ import dev.kske.chess.board.Piece.Color;
*/ */
public abstract class Player { public abstract class Player {
protected Game game; protected final Game game;
protected Board board; protected final Board board;
protected Color color;
protected String name; protected String name;
protected Color color;
/** /**
* Initializes the color of this player. * 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 * @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 * 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; } 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 * @return the board on which this player is used
*/ */

View File

@ -27,11 +27,12 @@ public class UCIPlayer extends Player implements UCIListener {
/** /**
* Creates an instance of {@link UCIPlayer}. * 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 color the piece color that this player will control
* @param enginePath the path to the engine executable * @param enginePath the path to the engine executable
*/ */
public UCIPlayer(Color color, String enginePath) { public UCIPlayer(Game game, Color color, String enginePath) {
super(color); super(game, color);
try { try {
handle = new UCIHandle(enginePath); handle = new UCIHandle(enginePath);
handle.registerListener(this); handle.registerListener(this);

View File

@ -9,6 +9,7 @@ import javax.swing.SwingUtilities;
import dev.kske.chess.board.Board; import dev.kske.chess.board.Board;
import dev.kske.chess.board.Move; import dev.kske.chess.board.Move;
import dev.kske.chess.board.Piece.Color; import dev.kske.chess.board.Piece.Color;
import dev.kske.chess.game.Game;
import dev.kske.chess.game.Player; import dev.kske.chess.game.Player;
/** /**
@ -31,38 +32,34 @@ public class AIPlayer extends Player {
/** /**
* Creates an instance of {@link AIPlayer}. * 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 color the piece color this player will control
* @param maxDepth the maximum search depth * @param maxDepth the maximum search depth
* @param alphaBetaThreshold the board evaluation threshold that has to be * @param alphaBetaThreshold the board evaluation threshold that has to be
* reached to continue searching the children of a * reached to continue searching the children of a
* move * move
*/ */
public AIPlayer(Color color, int maxDepth, int alphaBetaThreshold) { public AIPlayer(Game game, Color color, int maxDepth, int alphaBetaThreshold) {
super(color); super(game, color);
name = "AIPlayer"; name = "AIPlayer";
availableProcessors = Runtime.getRuntime().availableProcessors(); availableProcessors = Runtime.getRuntime().availableProcessors();
this.maxDepth = maxDepth; this.maxDepth = maxDepth;
this.alphaBetaThreshold = alphaBetaThreshold; this.alphaBetaThreshold = alphaBetaThreshold;
exitRequested = false;
} }
@Override @Override
public void requestMove() { public void requestMove() {
exitRequested = false; exitRequested = false;
/*
* Define some processing threads, split the available moves between them and // Define some processing threads, split the available moves between them and
* retrieve the result after their execution. // retrieve the result after their execution.
*/
new Thread(() -> { 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); Board board = new Board(this.board, false);
List<Move> moves = board.getMoves(color); List<Move> 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); int numThreads = Math.min(moves.size(), availableProcessors);
List<MoveProcessor> processors = new ArrayList<>(numThreads); List<MoveProcessor> processors = new ArrayList<>(numThreads);
final int step = moves.size() / numThreads; final int step = moves.size() / numThreads;
@ -71,23 +68,21 @@ public class AIPlayer extends Player {
for (int i = 0; i < numThreads; i++) { for (int i = 0; i < numThreads; i++) {
if (rem-- > 0) ++endIndex; if (rem-- > 0) ++endIndex;
endIndex += step; endIndex += step;
processors.add(new MoveProcessor(new Board(board, false), moves.subList(beginIndex, endIndex), color, processors.add(new MoveProcessor(new Board(board, false), moves.subList(beginIndex, endIndex), color, maxDepth, alphaBetaThreshold));
maxDepth, alphaBetaThreshold));
beginIndex = endIndex; 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); executor = Executors.newFixedThreadPool(numThreads);
List<ProcessingResult> results = new ArrayList<>(numThreads); List<ProcessingResult> results = new ArrayList<>(numThreads);
try { try {
List<Future<ProcessingResult>> futures = executor.invokeAll(processors); List<Future<ProcessingResult>> futures = executor.invokeAll(processors);
for (Future<ProcessingResult> f : futures) for (Future<ProcessingResult> f : futures)
results.add(f.get()); results.add(f.get());
executor.shutdown();
} catch (InterruptedException | ExecutionException ex) { } catch (InterruptedException | ExecutionException ex) {
ex.printStackTrace(); ex.printStackTrace();
} finally {
executor.shutdown();
} }
results.sort((r1, r2) -> Integer.compare(r2.score, r1.score)); results.sort((r1, r2) -> Integer.compare(r2.score, r1.score));
if (!exitRequested) SwingUtilities.invokeLater(() -> game.onMove(this, results.get(0).move)); if (!exitRequested) SwingUtilities.invokeLater(() -> game.onMove(this, results.get(0).move));

View File

@ -37,7 +37,7 @@ public class TextureUtil {
* @return The fitting texture * @return The fitting texture
*/ */
public static Image getPieceTexture(Piece piece) { 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); return scaledTextures.get(key);
} }

View File

@ -1,7 +1,6 @@
package dev.kske.chess.ui; package dev.kske.chess.ui;
import java.awt.Desktop; import java.awt.Desktop;
import java.awt.EventQueue;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.awt.dnd.DropTarget; import java.awt.dnd.DropTarget;
import java.io.File; import java.io.File;
@ -29,47 +28,31 @@ import dev.kske.chess.pgn.PGNGame;
*/ */
public class MainWindow extends JFrame { 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. * Launch the application.
* *
* @param args command line arguments are ignored * @param args command line arguments are ignored
*/ */
public static void main(String[] args) { public static void main(String[] args) { SwingUtilities.invokeLater(() -> new MainWindow()); }
EventQueue.invokeLater(() -> {
try {
new MainWindow();
} catch (Exception ex) {
ex.printStackTrace();
}
});
}
/** /**
* Create the application. * Create the application.
*/ */
public MainWindow() { private MainWindow() {
super("Chess by Kai S. K. Engelbart"); super("Chess by Kai S. K. Engelbart");
initialize();
}
/**
* Initialize the contents of the frame.
*/
private void initialize() {
// Configure frame // Configure frame
setResizable(false); setResizable(false);
setBounds(100, 100, 494, 565); setBounds(100, 100, 494, 565);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("/pieces/queen_white.png"))); setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("/pieces/queen_white.png")));
// Add frame content // Add tabbed pane, menu bar and drop target
tabbedPane = new JTabbedPane();
getContentPane().add(tabbedPane); getContentPane().add(tabbedPane);
setJMenuBar(new MenuBar(this)); setJMenuBar(new MenuBar(this));
new DropTarget(this, new GameDropTarget(this)); new DropTarget(this, new GameDropTarget(this));