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>
</classpathentry>
<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"/>
</classpath>

View File

@ -17,7 +17,7 @@ import dev.kske.chess.event.MoveEvent;
public class Board {
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();
/**

View File

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

View File

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

View File

@ -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<Color, Player> players = new HashMap<>(2);
private Map<Color, Player> 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);

View File

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

View File

@ -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
*/

View File

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

View File

@ -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<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);
List<MoveProcessor> 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<ProcessingResult> results = new ArrayList<>(numThreads);
try {
List<Future<ProcessingResult>> futures = executor.invokeAll(processors);
for (Future<ProcessingResult> 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));

View File

@ -37,7 +37,7 @@ public class TextureUtil {
* @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);
}

View File

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