200 lines
5.8 KiB
Java
200 lines
5.8 KiB
Java
package dev.kske.chess.game;
|
|
|
|
import java.util.*;
|
|
|
|
import javax.swing.JOptionPane;
|
|
|
|
import dev.kske.chess.board.*;
|
|
import dev.kske.chess.board.Piece.Color;
|
|
import dev.kske.chess.event.*;
|
|
import dev.kske.chess.game.ai.AIPlayer;
|
|
import dev.kske.chess.io.EngineUtil;
|
|
import dev.kske.chess.io.EngineUtil.EngineInfo;
|
|
import dev.kske.chess.ui.*;
|
|
import dev.kske.eventbus.EventBus;
|
|
|
|
/**
|
|
* Project: <strong>Chess</strong><br>
|
|
* File: <strong>Game.java</strong><br>
|
|
* Created: <strong>06.07.2019</strong><br>
|
|
*
|
|
* @since Chess v0.1-alpha
|
|
* @author Kai S. K. Engelbart
|
|
*/
|
|
public class Game {
|
|
|
|
private Map<Color, Player> players = new EnumMap<>(Color.class);
|
|
private Board board;
|
|
private OverlayComponent overlayComponent;
|
|
private BoardComponent boardComponent;
|
|
|
|
/**
|
|
* Initializes game with a new {@link Board}.
|
|
*
|
|
* @param boardPane the board pane which will display the newly created
|
|
* board
|
|
* @param whiteName the name of the player controlling the white pieces
|
|
* @param blackName the name of the player controlling the black pieces
|
|
*/
|
|
public Game(BoardPane boardPane, String whiteName, String blackName) {
|
|
board = new Board();
|
|
init(boardPane, whiteName, blackName);
|
|
}
|
|
|
|
/**
|
|
* Initializes game with an existing {@link Board}.
|
|
*
|
|
* @param boardPane the board pane which will display the newly created
|
|
* board
|
|
* @param whiteName the name of the player controlling the white pieces
|
|
* @param blackName the name of the player controlling the black pieces
|
|
* @param board the board on which the game will be played
|
|
*/
|
|
public Game(
|
|
BoardPane boardPane, String whiteName, String blackName, Board board
|
|
) {
|
|
this.board = board;
|
|
init(boardPane, whiteName, blackName);
|
|
}
|
|
|
|
private void init(BoardPane boardPane, String whiteName, String blackName) {
|
|
|
|
// Initialize / synchronize UI
|
|
overlayComponent = boardPane.getOverlayComponent();
|
|
boardComponent = boardPane.getBoardComponent();
|
|
boardComponent.setBoard(board);
|
|
|
|
// Initialize players
|
|
players.put(Color.WHITE, getPlayer(whiteName, Color.WHITE));
|
|
players.put(Color.BLACK, getPlayer(blackName, Color.BLACK));
|
|
}
|
|
|
|
/**
|
|
* Initializes player subclass.
|
|
*
|
|
* @param name the name of the player. {@code Natural Player} will
|
|
* initialize a
|
|
* {@link NaturalPlayer}, {@code AI Player} will initialize an
|
|
* {@link AIPlayer}. Everything else will attempt to load an
|
|
* engine
|
|
* with that name
|
|
* @param color the color of the player
|
|
* @return the instantiated player or {@code null} if the name could not be
|
|
* recognized
|
|
*/
|
|
private Player getPlayer(String name, Color color) {
|
|
switch (name) {
|
|
case "Natural Player":
|
|
return new NaturalPlayer(this, color, overlayComponent);
|
|
case "AI Player":
|
|
return new AIPlayer(this, color, 4, -10);
|
|
default:
|
|
for (EngineInfo info : EngineUtil.getEngineInfos())
|
|
if (info.name.equals(name))
|
|
return new UCIPlayer(this, color, info.path);
|
|
System.err.println("Invalid player name: " + name);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Should be called once a player makes a move. Depending on the legality of
|
|
* that move and the state of the game another move might be requested from
|
|
* one
|
|
* of the players.
|
|
*
|
|
* @param player the player who generated the move
|
|
* @param move the generated move
|
|
*/
|
|
public synchronized void onMove(Player player, Move move) {
|
|
if (
|
|
board.getPos(move).getColor() == player.color
|
|
&& board.attemptMove(move)
|
|
) {
|
|
|
|
// Redraw
|
|
boardComponent.repaint();
|
|
overlayComponent.displayArrow(move);
|
|
|
|
// Run garbage collection
|
|
System.gc();
|
|
|
|
BoardState boardState
|
|
= board.getState(board.getDest(move).getColor().opposite());
|
|
EventBus.getInstance().dispatch(new MoveEvent(move, boardState));
|
|
switch (boardState) {
|
|
case CHECKMATE:
|
|
case STALEMATE:
|
|
String result = String.format(
|
|
"%s in %s!%n",
|
|
player.color.opposite(),
|
|
boardState
|
|
);
|
|
System.out.print(result);
|
|
JOptionPane.showMessageDialog(boardComponent, result);
|
|
break;
|
|
case CHECK:
|
|
System.out
|
|
.printf("%s in check!%n", player.color.opposite());
|
|
default:
|
|
players.get(board.getLog().getActiveColor()).requestMove();
|
|
}
|
|
} else
|
|
player.requestMove();
|
|
}
|
|
|
|
/**
|
|
* Starts the game by requesting a move from the player of the currently
|
|
* active
|
|
* color.
|
|
*/
|
|
public synchronized void start() {
|
|
EventBus.getInstance().dispatch(new GameStartEvent(this));
|
|
players.get(board.getLog().getActiveColor()).requestMove();
|
|
}
|
|
|
|
/**
|
|
* Cancels move calculations, initializes the default position and clears
|
|
* the
|
|
* {@link OverlayComponent}.
|
|
*/
|
|
public synchronized void reset() {
|
|
players.values().forEach(Player::cancelMove);
|
|
board.initDefaultPositions();
|
|
boardComponent.repaint();
|
|
overlayComponent.clearDots();
|
|
overlayComponent.clearArrow();
|
|
}
|
|
|
|
/**
|
|
* Stops the game by disconnecting its players from the UI.
|
|
*/
|
|
public synchronized void stop() {
|
|
players.values().forEach(Player::disconnect);
|
|
}
|
|
|
|
/**
|
|
* Assigns the players their opposite colors.
|
|
*/
|
|
public synchronized void swapColors() {
|
|
players.values().forEach(Player::cancelMove);
|
|
Player white = players.get(Color.WHITE);
|
|
Player black = players.get(Color.BLACK);
|
|
white.setColor(Color.BLACK);
|
|
black.setColor(Color.WHITE);
|
|
players.put(Color.WHITE, black);
|
|
players.put(Color.BLACK, white);
|
|
players.get(board.getLog().getActiveColor()).requestMove();
|
|
}
|
|
|
|
/**
|
|
* @return The board on which this game's moves are made
|
|
*/
|
|
public Board getBoard() { return board; }
|
|
|
|
/**
|
|
* @return The players participating in this game
|
|
*/
|
|
public Map<Color, Player> getPlayers() { return players; }
|
|
}
|