Added stockfish + random move, position conversion & more

This commit is contained in:
Filip Znachor 2023-05-02 14:47:21 +02:00
parent 2af3448a51
commit 2bc9b79ad8
7 changed files with 288 additions and 27 deletions

View file

@ -50,14 +50,8 @@ public class Chess {
public static void setLookAndFeel() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (UnsupportedLookAndFeelException e) {
throw new RuntimeException(e);
} catch (Exception e) {
System.out.println("Unable to load system theme!");
}
}
@ -65,6 +59,7 @@ public class Chess {
menuBar = new JMenuBar();
JMenu menuGame = new JMenu("Game");
JMenu menuCPU = new JMenu("CPU");
JMenu menuExport = new JMenu("Export");
JMenuItem startNewGame = new JMenuItem("Start new game");
@ -88,6 +83,18 @@ public class Chess {
});
menuGame.add(toggleBlindMode);
JMenuItem toggleAutomaticRandomOpponent = new JMenuItem("Toggle automatic random opponent");
toggleAutomaticRandomOpponent.addActionListener(l -> {
chessboard.getOtherPlayer().toggleAutomaticRandom();
});
menuCPU.add(toggleAutomaticRandomOpponent);
JMenuItem toggleAutomaticSmartOpponent = new JMenuItem("Toggle automatic smart opponent");
toggleAutomaticSmartOpponent.addActionListener(l -> {
chessboard.getOtherPlayer().toggleAutomaticSmart();
});
menuCPU.add(toggleAutomaticSmartOpponent);
JMenuItem exportAsSvg = new JMenuItem("Export as SVG");
exportAsSvg.addActionListener(l -> exportSVG());
menuExport.add(exportAsSvg);
@ -97,6 +104,7 @@ public class Chess {
menuExport.add(exportAsPng);
menuBar.add(menuGame);
menuBar.add(menuCPU);
menuBar.add(menuExport);
return menuBar;
@ -106,8 +114,8 @@ public class Chess {
// Hack to force repaint of the chessboard (repaint is not working)
int winWidth = window.getWidth();
int winHeight = window.getHeight();
window.resize(winWidth+1, winHeight+1);
window.resize(winWidth, winHeight);
window.setSize(winWidth+1, winHeight+1);
window.setSize(winWidth, winHeight);
}
static void newGame(Chessboard newChessboard) {

View file

@ -1,10 +1,9 @@
import org.jfree.svg.SVGGraphics2D;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
/**
* Chessboard class
@ -59,7 +58,9 @@ public class Chessboard extends JPanel {
private Player activePlayer;
private Player otherPlayer;
private Player player1;
private Player player2;
public boolean blindMode = false;
@ -113,12 +114,11 @@ public class Chessboard extends JPanel {
y++;
}
if(parts[1].equals("w")) {
c.setPlayer1(white);
c.setPlayer2(black);
} else {
c.setPlayer1(black);
c.setPlayer2(white);
c.setPlayer1(white);
c.setPlayer2(black);
if(parts[1].equals("b")) {
c.setActivePlayer(black);
}
for (char item : parts[2].toCharArray()) {
@ -132,10 +132,77 @@ public class Chessboard extends JPanel {
}
}
if(!parts[3].equals("-") && parts[3].length() == 2) {
PiecePosition pos = PiecePosition.fromString(parts[3]);
if(pos == null) return null;
boolean[][] lastMove = new boolean[c.SQUARE_COUNT][c.SQUARE_COUNT];
lastMove[pos.y-1][pos.x] = true;
lastMove[pos.y+1][pos.x] = true;
c.showLastMove(lastMove);
}
return c;
}
public String toFEN() {
StringBuilder chessboardSB = new StringBuilder();
for (int y = 0; y < pieces.length; y++) {
int empty = 0;
for (APiece piece : pieces[y]) {
if (piece == null) {
empty++;
continue;
}
Character pieceLetter = null;
if (piece instanceof Pawn) pieceLetter = 'p';
if (piece instanceof Knight) pieceLetter = 'n';
if (piece instanceof King) pieceLetter = 'k';
if (piece instanceof Queen) pieceLetter = 'q';
if (piece instanceof Bishop) pieceLetter = 'b';
if (piece instanceof Rook) pieceLetter = 'r';
if (empty != 0) chessboardSB.append(empty);
chessboardSB.append(piece.getPlayer() == player1 ? Character.toUpperCase(pieceLetter) : pieceLetter);
empty = 0;
}
if (empty != 0) chessboardSB.append(empty);
if(y != 7) chessboardSB.append('/');
}
StringBuilder castlingSB = new StringBuilder();
for (Player player : new Player[]{player1, player2}) {
if(player.getKing().getMoveCount() != 0) continue;
StartPosition sp = player.getStartPosition();
for (int x : new int[]{7, 0}) {
APiece rook = getPiece(x, sp == StartPosition.TOP ? 0 : 7);
if(rook instanceof Rook && rook.getMoveCount() == 0 && rook.getPlayer() == player) {
char castlingType = x == 0 ? 'q' : 'k';
castlingSB.append(sp == StartPosition.TOP ? castlingType : Character.toUpperCase(castlingType));
}
}
}
if(castlingSB.length() == 0) castlingSB.append('-');
char player = player1 == activePlayer ? 'w' : 'b';
String enPassantTargetSquare = "-";
for (int y = 0; y < lastMove.length; y++) {
for (int x = 0; x < lastMove[y].length; x++) {
if(lastMove[y][x] && (y == 1 || y == 4) && lastMove[y+2][x]) {
APiece pawn = pieces[y == 1 ? y+2 : y][x];
if(pawn instanceof Pawn && pawn.getMoveCount() == 1) {
enPassantTargetSquare = (new PiecePosition(x, y+2)).toString();
break;
}
}
}
}
return chessboardSB + " " + player + " " + castlingSB + " " + enPassantTargetSquare + " 0 0";
}
/**
* Add passed piece to the specified coordinates
* @param piece piece
@ -397,20 +464,29 @@ public class Chessboard extends JPanel {
public void setPlayer1(Player player) {
activePlayer = player;
player1 = player;
}
public void setPlayer2(Player player) {
otherPlayer = player;
player2 = player;
}
public Player getActivePlayer() {
return activePlayer;
}
public void setActivePlayer(Player player) {
activePlayer = player;
}
public Player getOtherPlayer() {
return activePlayer == player1 ? player2 : player1;
}
public void changeActivePlayer() {
Player tempPlayer = activePlayer;
activePlayer = otherPlayer;
otherPlayer = tempPlayer;
activePlayer = activePlayer == player1 ? player2 : player1;
getRootPane().repaint();
activePlayer.play();
}
}

View file

@ -52,17 +52,19 @@ public class ChessboardMouseAdapter extends MouseAdapter {
if (piece != null && piece.getPlayer() == c.getActivePlayer() && selectedPiece != piece) {
c.setSelectedPiece(piece);
c.showPossibleMoves(piece.getPossibleMoves());
c.getRootPane().repaint();
} else if(selectedPiece != null) {
if(selectedPiece.getPossibleMoves()[pos.y][pos.x]) {
selectedPiece.move(pos);
if(c.getActivePlayer().inCheck()) System.out.println("Inactive player in check!");
c.changeActivePlayer();
if(c.getActivePlayer().inCheck()) System.out.println("Active player in check!");
} else {
c.getRootPane().repaint();
}
c.showPossibleMoves(null);
c.setSelectedPiece(null);
}
c.getRootPane().repaint();
}
public void onDragStart(MouseEvent me) {
@ -98,12 +100,12 @@ public class ChessboardMouseAdapter extends MouseAdapter {
if(c.getActivePlayer().inCheck()) System.out.println("Inactive player in check!");
c.changeActivePlayer();
if(c.getActivePlayer().inCheck()) System.out.println("Active player in check!");
} else {
c.getRootPane().repaint();
}
c.showPossibleMoves(null);
c.setSelectedPiece(null);
}
c.showPossibleMoves(null);
c.getRootPane().repaint();
}
}

View file

@ -70,7 +70,7 @@ public class Pawn extends APiece {
super.move(pos, animate);
int changeY = color == PieceColor.WHITE ? 0 : chessboard.SQUARE_COUNT-1;
int changeY = player.getStartPosition() == StartPosition.BOTTOM ? 0 : chessboard.SQUARE_COUNT-1;
if(y == changeY) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {

View file

@ -13,6 +13,20 @@ public class PiecePosition {
*/
public int y;
public String toString() {
return String.valueOf((char) (x+97)) + (8-y);
}
public static PiecePosition fromString(String position) {
try {
int x = position.charAt(0) - 97;
int y = 8 - Integer.parseInt(String.valueOf(position.charAt(1)));
return new PiecePosition(x, y);
} catch (Exception e) {
return null;
}
}
/**
* PiecePosition constructor
* @param x piece's X location

View file

@ -1,3 +1,8 @@
import java.util.ArrayList;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
public class Player {
private Chessboard chessboard;
@ -8,6 +13,12 @@ public class Player {
private King king;
private Stockfish stockfish;
private boolean automaticRandom = false;
private boolean automaticSmart = false;
public Player(Chessboard chessboard, StartPosition startPosition, PieceColor color) {
this.chessboard = chessboard;
this.color = color;
@ -18,6 +29,62 @@ public class Player {
return king.isEndangered();
}
public void play() {
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
if(automaticRandom) randomMove();
if(automaticSmart) smartMove();
t.cancel();
}
}, 1000, 1000);
}
public void randomMove() {
ArrayList<APiece> myPieces = getPieces();
while(myPieces.size() != 0) {
Random r = new Random();
APiece selectedPiece = myPieces.get(r.nextInt(myPieces.size()));
ArrayList<PiecePosition> possibleMoves = new ArrayList<>();
myPieces.remove(selectedPiece);
boolean[][] possibleMoveMap = selectedPiece.getPossibleMoves();
for (int y = 0; y < possibleMoveMap.length; y++) {
for (int x = 0; x < possibleMoveMap[y].length; x++) {
if(possibleMoveMap[y][x]) {
possibleMoves.add(new PiecePosition(x, y));
}
}
}
int moveCount = possibleMoves.size();
if(moveCount == 0) {
continue;
}
PiecePosition randomMove = possibleMoves.get(r.nextInt(moveCount));
selectedPiece.move(randomMove, true);
chessboard.changeActivePlayer();
return;
}
System.out.println("No possible move!");
}
public void smartMove() {
if(stockfish == null) {
stockfish = new Stockfish();
stockfish.startEngine();
}
String fen = chessboard.toFEN();
String bestMove = stockfish.getBestMove(fen, 500);
PiecePosition fromPos = PiecePosition.fromString(bestMove.substring(0, 2));
PiecePosition toPos = PiecePosition.fromString(bestMove.substring(2, 4));
APiece piece = chessboard.getPiece(fromPos.x, fromPos.y);
piece.move(toPos);
chessboard.changeActivePlayer();
}
public Chessboard getChessboard() {
return chessboard;
}
@ -38,4 +105,24 @@ public class Player {
this.king = king;
}
public ArrayList<APiece> getPieces() {
ArrayList<APiece> myPieces = new ArrayList<>();
for (APiece[] pieces : chessboard.pieces) {
for (APiece piece : pieces) {
if(piece != null && piece.getPlayer() == this) {
myPieces.add(piece);
}
}
}
return myPieces;
}
public void toggleAutomaticRandom() {
automaticRandom = !automaticRandom;
}
public void toggleAutomaticSmart() {
automaticSmart = !automaticSmart;
}
}

74
src/Stockfish.java Normal file
View file

@ -0,0 +1,74 @@
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class Stockfish {
private Process engineProcess;
private BufferedReader processReader;
private OutputStreamWriter processWriter;
private static final String PATH = "./stockfish";
/**
* Starts Stockfish engine as a process and initializes it
*
* @return True on success. False otherwise
*/
public boolean startEngine() {
try {
engineProcess = Runtime.getRuntime().exec(PATH);
processReader = new BufferedReader(new InputStreamReader(
engineProcess.getInputStream()));
processWriter = new OutputStreamWriter(
engineProcess.getOutputStream());
} catch (Exception e) {
return false;
}
return true;
}
public void sendCommand(String command) {
try {
processWriter.write(command + "\n");
processWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
public String getOutput(int waitTime) {
StringBuffer buffer = new StringBuffer();
try {
Thread.sleep(waitTime);
sendCommand("isready");
while (true) {
String text = processReader.readLine();
if (text.equals("readyok"))
break;
else
buffer.append(text + "\n");
}
} catch (Exception e) {
e.printStackTrace();
}
return buffer.toString();
}
public String getBestMove(String fen, int waitTime) {
sendCommand("position fen " + fen);
sendCommand("go movetime " + waitTime);
return getOutput(waitTime + 100).split("bestmove ")[1].split(" ")[0];
}
public void stopEngine() {
try {
sendCommand("quit");
processReader.close();
processWriter.close();
} catch (IOException e) {
}
}
}