Fixed checkmate detection, simplified event handling

This commit is contained in:
Kai S. K. Engelbart 2019-07-07 21:07:58 +02:00
parent f4b399ae43
commit 31c393150b
Signed by: kske
GPG Key ID: 8BEB13EC5DF7EF13
8 changed files with 68 additions and 100 deletions

View File

@ -7,9 +7,6 @@ import java.util.Map;
import dev.kske.chess.board.Piece.Color; import dev.kske.chess.board.Piece.Color;
import dev.kske.chess.board.Piece.Type; import dev.kske.chess.board.Piece.Type;
import dev.kske.chess.event.GameEvent;
import dev.kske.chess.event.GameEvent.GameEventType;
import dev.kske.chess.event.GameEventListener;
/** /**
* Project: <strong>Chess</strong><br> * Project: <strong>Chess</strong><br>
@ -22,12 +19,9 @@ public class Board {
private Piece[][] boardArr; private Piece[][] boardArr;
private Map<Color, Position> kingPos; private Map<Color, Position> kingPos;
private List<GameEventListener> gameEventListeners;
public Board() { public Board() {
boardArr = new Piece[8][8]; boardArr = new Piece[8][8];
kingPos = new HashMap<>(); kingPos = new HashMap<>();
gameEventListeners = new ArrayList<>();
initializeDefaultPositions(); initializeDefaultPositions();
} }
@ -53,11 +47,6 @@ public class Board {
return false; return false;
} }
// Detect check and stalemate on the opposite team
Color oppositeColor = piece.getColor().opposite();
GameEventType eventType = getGameEventType(oppositeColor);
if (eventType != GameEventType.NONE) notifyListeners(new GameEvent(this, eventType, oppositeColor));
return true; return true;
} }
} }
@ -114,6 +103,12 @@ public class Board {
return get(pos).getMoves(pos); return get(pos).getMoves(pos);
} }
/**
* Checks, if the king is in check.
*
* @param color The color of the king to check
* @return {@code true}, if the king is in check
*/
public boolean checkCheck(Color color) { public boolean checkCheck(Color color) {
for (int i = 0; i < 8; i++) for (int i = 0; i < 8; i++)
for (int j = 0; j < 8; j++) { for (int j = 0; j < 8; j++) {
@ -125,10 +120,31 @@ public class Board {
return false; return false;
} }
public GameEventType getGameEventType(Color color) { /**
* Checks, if the king is in checkmate.
* This requires the king to already be in check!
*
* @param color The color of the king to check
* @return {@code true}, if the king is in checkmate
*/
public boolean checkCheckmate(Color color) {
// Return false immediately if the king can move
if (!getMoves(kingPos.get(color)).isEmpty()) return false;
else {
for (Move move : getMoves(color)) {
Piece capturePiece = move(move);
boolean check = checkCheck(color);
revert(move, capturePiece);
if (!check) return false;
}
return true;
}
}
public GameState getGameEventType(Color color) {
return checkCheck(color) return checkCheck(color)
? getMoves(kingPos.get(color)).isEmpty() ? GameEventType.CHECKMATE : GameEventType.CHECK ? checkCheckmate(color) ? GameState.CHECKMATE : GameState.CHECK
: getMoves(color).isEmpty() ? GameEventType.STALEMATE : GameEventType.NONE; : getMoves(color).isEmpty() ? GameState.STALEMATE : GameState.NORMAL;
} }
/** /**
@ -161,14 +177,6 @@ public class Board {
return score; return score;
} }
public void registerGameEventListener(GameEventListener listener) {
gameEventListeners.add(listener);
}
private void notifyListeners(GameEvent evt) {
gameEventListeners.forEach(listener -> listener.onGameEvent(evt));
}
/** /**
* Initialized the board array with the default chess pieces and positions. * Initialized the board array with the default chess pieces and positions.
*/ */

View File

@ -0,0 +1,11 @@
package dev.kske.chess.board;
/**
* Project: <strong>Chess</strong><br>
* File: <strong>GameState.java</strong><br>
* Created: <strong>07.07.2019</strong><br>
* Author: <strong>Kai S. K. Engelbart</strong>
*/
public enum GameState {
CHECK, CHECKMATE, STALEMATE, NORMAL;
}

View File

@ -1,38 +0,0 @@
package dev.kske.chess.event;
import java.util.EventObject;
import dev.kske.chess.board.Board;
import dev.kske.chess.board.Piece.Color;
/**
* Project: <strong>Chess</strong><br>
* File: <strong>GameEvent.java</strong><br>
* Created: <strong>03.07.2019</strong><br>
* Author: <strong>Kai S. K. Engelbart</strong>
*/
public class GameEvent extends EventObject {
private static final long serialVersionUID = -6783035746521826589L;
private final Board source;
private final GameEventType eventType;
private final Color color;
public GameEvent(Board source, GameEventType eventType, Color color) {
super(source);
this.source = source;
this.eventType = eventType;
this.color = color;
}
public Board getSource() { return source; }
public GameEventType getEventType() { return eventType; }
public Color getColor() { return color; }
public static enum GameEventType {
CHECK, CHECKMATE, STALEMATE, NONE;
}
}

View File

@ -1,12 +0,0 @@
package dev.kske.chess.event;
/**
* Project: <strong>Chess</strong><br>
* File: <strong>GameEventListener.java</strong><br>
* Created: <strong>03.07.2019</strong><br>
* Author: <strong>Kai S. K. Engelbart</strong>
*/
public interface GameEventListener {
void onGameEvent(GameEvent evt);
}

View File

@ -14,6 +14,7 @@ import dev.kske.chess.board.Piece.Color;
public class AIPlayer extends Player { public class AIPlayer extends Player {
private Move bestMove; private Move bestMove;
private int count;
public AIPlayer(Board board, Color color) { public AIPlayer(Board board, Color color) {
super(board, color); super(board, color);
@ -21,19 +22,22 @@ public class AIPlayer extends Player {
@Override @Override
public void requestMove() { public void requestMove() {
count = 0;
findBestMove(board, color, 0); findBestMove(board, color, 0);
System.out.println("Moved processes: " + count);
game.onMove(this, bestMove); game.onMove(this, bestMove);
} }
private int findBestMove(Board board, Color color, int depth) { private int findBestMove(Board board, Color color, int depth) {
int bestValue = Integer.MIN_VALUE; int bestValue = Integer.MIN_VALUE;
for (Move move : board.getMoves(color)) { for (Move move : board.getMoves(color)) {
++count;
Piece capturePiece = board.move(move); Piece capturePiece = board.move(move);
int teamValue = board.evaluate(color); int teamValue = board.evaluate(color);
int enemyValue = board.evaluate(color.opposite()); int enemyValue = board.evaluate(color.opposite());
int valueChange = teamValue - enemyValue; int valueChange = teamValue - enemyValue;
if (depth < 4) valueChange -= findBestMove(board, color.opposite(), depth + 1); if (depth <= 3 && valueChange >= 0) valueChange -= findBestMove(board, color.opposite(), depth + 1);
if (valueChange > bestValue) { if (valueChange > bestValue) {
bestValue = valueChange; bestValue = valueChange;

View File

@ -3,6 +3,7 @@ package dev.kske.chess.game;
import java.util.Map; import java.util.Map;
import dev.kske.chess.board.Board; import dev.kske.chess.board.Board;
import dev.kske.chess.board.GameState;
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.ui.BoardPanel; import dev.kske.chess.ui.BoardPanel;
@ -36,7 +37,18 @@ public class Game {
if (board.getPos(move).getColor() == player.color && board.attemptMove(move)) { if (board.getPos(move).getColor() == player.color && board.attemptMove(move)) {
System.out.printf("%s: %s%n", player.color, move); System.out.printf("%s: %s%n", player.color, move);
boardPanel.repaint(); boardPanel.repaint();
players.get(player.color.opposite()).requestMove();
GameState eventType = board.getGameEventType(board.getDest(move).getColor().opposite());
switch (eventType) {
case CHECKMATE:
case STALEMATE:
System.out.printf("%s in %s!%n", player.color, eventType);
break;
case CHECK:
System.out.printf("%s in check!%n", player.color);
default:
players.get(player.color.opposite()).requestMove();
}
} else { } else {
System.out.printf("%s: Illegal move!%n", player.getColor()); System.out.printf("%s: Illegal move!%n", player.getColor());
player.requestMove(); player.requestMove();

View File

@ -8,13 +8,10 @@ import java.awt.event.ComponentEvent;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.swing.JOptionPane;
import javax.swing.JPanel; import javax.swing.JPanel;
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.event.GameEvent;
import dev.kske.chess.event.GameEventListener;
/** /**
* Project: <strong>Chess</strong><br> * Project: <strong>Chess</strong><br>
@ -26,7 +23,7 @@ import dev.kske.chess.event.GameEventListener;
* this must be added to a parent component that allows the child to decide the * this must be added to a parent component that allows the child to decide the
* size. * size.
*/ */
public class BoardPanel extends JPanel implements GameEventListener { public class BoardPanel extends JPanel {
private static final long serialVersionUID = 6771148331334310216L; private static final long serialVersionUID = 6771148331334310216L;
@ -90,18 +87,6 @@ public class BoardPanel extends JPanel implements GameEventListener {
} }
} }
@Override
public void onGameEvent(GameEvent evt) {
switch (evt.getEventType()) {
case CHECK:
JOptionPane.showMessageDialog(this, evt.getColor().toString() + " in check!");
break;
case CHECKMATE:
JOptionPane.showMessageDialog(this, evt.getColor().toString() + " in checkmate!");
break;
}
}
/** /**
* Displays move destinations on the board. * Displays move destinations on the board.
* *
@ -142,10 +127,5 @@ public class BoardPanel extends JPanel implements GameEventListener {
public Board getBoard() { return board; } public Board getBoard() { return board; }
public void setBoard(Board board) { public void setBoard(Board board) { this.board = board; }
this.board = board;
// Register this BoardPanel as a GameEventListener to the board
board.registerGameEventListener(this);
}
} }

View File

@ -49,8 +49,7 @@ public class GameModeDialog extends JDialog {
Map<Color, Player> players = new HashMap<>(); Map<Color, Player> players = new HashMap<>();
players.put(Color.WHITE, new NaturalPlayer(boardPanel.getBoard(), Color.WHITE, boardPanel)); players.put(Color.WHITE, new NaturalPlayer(boardPanel.getBoard(), Color.WHITE, boardPanel));
players.put(Color.BLACK, new AIPlayer(boardPanel.getBoard(), Color.BLACK)); players.put(Color.BLACK, new AIPlayer(boardPanel.getBoard(), Color.BLACK));
new Game(players, boardPanel.getBoard(), boardPanel).start(); startGame(players, boardPanel);
dispose();
}); });
getContentPane().add(btnAI); getContentPane().add(btnAI);
@ -59,9 +58,13 @@ public class GameModeDialog extends JDialog {
Map<Color, Player> players = new HashMap<>(); Map<Color, Player> players = new HashMap<>();
players.put(Color.WHITE, new AIPlayer(boardPanel.getBoard(), Color.WHITE)); players.put(Color.WHITE, new AIPlayer(boardPanel.getBoard(), Color.WHITE));
players.put(Color.BLACK, new AIPlayer(boardPanel.getBoard(), Color.BLACK)); players.put(Color.BLACK, new AIPlayer(boardPanel.getBoard(), Color.BLACK));
new Game(players, boardPanel.getBoard(), boardPanel).start(); startGame(players, boardPanel);
dispose();
}); });
getContentPane().add(btnAI2); getContentPane().add(btnAI2);
} }
private void startGame(Map<Color, Player> players, BoardPanel boardPanel) {
new Game(players, boardPanel.getBoard(), boardPanel).start();
dispose();
}
} }