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:
parent
2333021bba
commit
929fd9381e
@ -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>
|
||||
|
@ -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();
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
|
||||
|
Reference in New Issue
Block a user