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
f7c1be3404
commit
9fb60ab0ae
@ -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>
|
||||||
|
@ -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();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
@ -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);
|
||||||
|
@ -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));
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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));
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user