Added pawn promotion selection

* Letting a NaturalPlayer select the promotion piece with a combo box
* Optimized reflection use in PawnPromotion
* Changed toString method of Move to use LAN

Closes #9
This commit is contained in:
Kai S. K. Engelbart 2019-11-05 05:41:26 +01:00
parent 5b99370884
commit 86a1c70eda
5 changed files with 84 additions and 33 deletions

View File

@ -1,6 +1,5 @@
package dev.kske.chess.board; package dev.kske.chess.board;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
@ -143,8 +142,7 @@ public class Board {
if (m.group("promotedTo") != null) { if (m.group("promotedTo") != null) {
try { try {
move = new PawnPromotion(pos, dest, Piece.fromFirstChar(m.group("promotedTo").charAt(0))); move = new PawnPromotion(pos, dest, Piece.fromFirstChar(m.group("promotedTo").charAt(0)));
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException } catch (NoSuchMethodException | SecurityException | IllegalArgumentException e) {
| InvocationTargetException | InstantiationException e) {
e.printStackTrace(); e.printStackTrace();
} }
} else move = new Move(pos, dest); } else move = new Move(pos, dest);
@ -162,8 +160,7 @@ public class Board {
if (m.group("promotedTo") != null) { if (m.group("promotedTo") != null) {
try { try {
move = new PawnPromotion(pos, dest, Piece.fromFirstChar(m.group("promotedTo").charAt(0))); move = new PawnPromotion(pos, dest, Piece.fromFirstChar(m.group("promotedTo").charAt(0)));
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException } catch (NoSuchMethodException | SecurityException | IllegalArgumentException e) {
| InvocationTargetException | InstantiationException e) {
e.printStackTrace(); e.printStackTrace();
} }
} else move = new Move(pos, dest); } else move = new Move(pos, dest);

View File

@ -1,6 +1,5 @@
package dev.kske.chess.board; package dev.kske.chess.board;
import java.lang.reflect.InvocationTargetException;
import java.util.Objects; import java.util.Objects;
/** /**
@ -45,8 +44,7 @@ public class Move {
if (move.length() == 5) { if (move.length() == 5) {
try { try {
return new PawnPromotion(pos, dest, Piece.fromFirstChar(move.charAt(4))); return new PawnPromotion(pos, dest, Piece.fromFirstChar(move.charAt(4)));
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException } catch (NoSuchMethodException | SecurityException | IllegalArgumentException e) {
| InstantiationException e) {
e.printStackTrace(); e.printStackTrace();
return null; return null;
} }
@ -62,7 +60,7 @@ public class Move {
public boolean isDiagonal() { return getxDist() == getyDist(); } public boolean isDiagonal() { return getxDist() == getyDist(); }
@Override @Override
public String toString() { return String.format("%s -> %s", getPos(), getDest()); } public String toString() { return toLAN(); }
@Override @Override
public int hashCode() { return Objects.hash(getDest(), getPos(), getxDist(), getxSign(), getyDist(), getySign()); } public int hashCode() { return Objects.hash(getDest(), getPos(), getxDist(), getxSign(), getyDist(), getySign()); }

View File

@ -1,6 +1,5 @@
package dev.kske.chess.board; package dev.kske.chess.board;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -75,8 +74,7 @@ public class Pawn extends Piece {
moves.add(new PawnPromotion(pos, dest, Rook.class)); moves.add(new PawnPromotion(pos, dest, Rook.class));
moves.add(new PawnPromotion(pos, dest, Knight.class)); moves.add(new PawnPromotion(pos, dest, Knight.class));
moves.add(new PawnPromotion(pos, dest, Bishop.class)); moves.add(new PawnPromotion(pos, dest, Bishop.class));
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException } catch (NoSuchMethodException | SecurityException | IllegalArgumentException e) {
| InstantiationException e) {
e.printStackTrace(); e.printStackTrace();
} }
} else moves.add(move); } else moves.add(move);

View File

@ -2,6 +2,7 @@ package dev.kske.chess.board;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.Objects;
import dev.kske.chess.board.Piece.Color; import dev.kske.chess.board.Piece.Color;
@ -15,19 +16,16 @@ import dev.kske.chess.board.Piece.Color;
*/ */
public class PawnPromotion extends Move { public class PawnPromotion extends Move {
private final Class<? extends Piece> promotionPieceClass;
private final Constructor<? extends Piece> promotionPieceConstructor; private final Constructor<? extends Piece> promotionPieceConstructor;
private final char promotionPieceChar;
public PawnPromotion(Position pos, Position dest, Class<? extends Piece> promotionPiece) throws NoSuchMethodException, SecurityException, public PawnPromotion(Position pos, Position dest, Class<? extends Piece> promotionPieceClass) throws NoSuchMethodException, SecurityException {
IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
super(pos, dest); super(pos, dest);
this.promotionPieceClass = promotionPieceClass;
// Cache piece constructor // Cache piece constructor
promotionPieceConstructor = promotionPiece.getDeclaredConstructor(Color.class, Board.class); promotionPieceConstructor = promotionPieceClass.getDeclaredConstructor(Color.class, Board.class);
promotionPieceConstructor.setAccessible(true); promotionPieceConstructor.setAccessible(true);
// Cache piece first char
promotionPieceChar = (char) promotionPiece.getMethod("firstChar").invoke(promotionPieceConstructor.newInstance(null, null));
} }
public PawnPromotion(int xPos, int yPos, int xDest, int yDest, Class<? extends Piece> promotionPiece) public PawnPromotion(int xPos, int yPos, int xDest, int yDest, Class<? extends Piece> promotionPiece)
@ -53,5 +51,31 @@ public class PawnPromotion extends Move {
} }
@Override @Override
public String toLAN() { return pos.toLAN() + dest.toLAN() + promotionPieceChar; } public String toLAN() {
char promotionPieceChar = '-';
try {
promotionPieceChar = (char) promotionPieceClass.getMethod("firstChar").invoke(promotionPieceConstructor.newInstance(null, null));
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException
| InstantiationException e) {
e.printStackTrace();
}
return pos.toLAN() + dest.toLAN() + promotionPieceChar;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + Objects.hash(promotionPieceClass);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!super.equals(obj)) return false;
if (getClass() != obj.getClass()) return false;
PawnPromotion other = (PawnPromotion) obj;
return Objects.equals(promotionPieceClass, other.promotionPieceClass);
}
} }

View File

@ -5,8 +5,11 @@ import java.awt.event.MouseListener;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import dev.kske.chess.board.Board; import javax.swing.JComboBox;
import javax.swing.JOptionPane;
import dev.kske.chess.board.Move; import dev.kske.chess.board.Move;
import dev.kske.chess.board.Piece;
import dev.kske.chess.board.Piece.Color; import dev.kske.chess.board.Piece.Color;
import dev.kske.chess.board.Position; import dev.kske.chess.board.Position;
import dev.kske.chess.ui.OverlayComponent; import dev.kske.chess.ui.OverlayComponent;
@ -24,7 +27,8 @@ public class NaturalPlayer extends Player implements MouseListener {
private final OverlayComponent overlayComponent; private final OverlayComponent overlayComponent;
private boolean moveRequested; private boolean moveRequested;
private Position pos; private Piece selectedPiece;
private List<Move> possibleMoves;
public NaturalPlayer(Color color, OverlayComponent overlayComponent) { public NaturalPlayer(Color color, OverlayComponent overlayComponent) {
super(color); super(color);
@ -47,22 +51,52 @@ public class NaturalPlayer extends Player implements MouseListener {
@Override @Override
public void mousePressed(MouseEvent evt) { public void mousePressed(MouseEvent evt) {
if (!moveRequested) return; if (!moveRequested) return;
if (pos == null) { if (selectedPiece == null) {
pos = new Position(evt.getPoint().x / overlayComponent.getTileSize(), evt.getPoint().y / overlayComponent.getTileSize());
Board board = new Board(this.board); // Get selected Piece
if (board.get(pos) != null && board.get(pos).getColor() == color) { final Position pos = new Position(evt.getPoint().x / overlayComponent.getTileSize(), evt.getPoint().y / overlayComponent.getTileSize());
List<Position> positions = board.getMoves(pos).stream().map(move -> move.getDest()).collect(Collectors.toList()); selectedPiece = board.get(pos);
overlayComponent.displayDots(positions);
} else pos = null; // Check if a piece was selected
if (selectedPiece != null) {
// Discard selection if the piece has the wrong color
if (selectedPiece.getColor() == color.opposite()) selectedPiece = null;
else {
// Generate all moves possible with the selected piece and display their
// destinations
possibleMoves = selectedPiece.getMoves(pos);
overlayComponent.displayDots(possibleMoves.stream().map(move -> move.getDest()).collect(Collectors.toList()));
}
}
} else { } else {
Position dest = new Position(evt.getPoint().x / overlayComponent.getTileSize(), evt.getPoint().y / overlayComponent.getTileSize()); Position dest = new Position(evt.getPoint().x / overlayComponent.getTileSize(), evt.getPoint().y / overlayComponent.getTileSize());
overlayComponent.clearDots(); // Get all moves leading to the specified destination
List<Move> selectedMoves = possibleMoves.stream().filter(m -> m.getDest().equals(dest)).collect(Collectors.toList());
if (!selectedMoves.isEmpty()) {
Move move;
// Process pawn promotion if necessary
if (selectedMoves.size() > 1) {
// Let the user select a promotion piece
JComboBox<Move> comboBox = new JComboBox<Move>(selectedMoves.toArray(new Move[0]));
JOptionPane.showMessageDialog(overlayComponent, comboBox, "Select a promotion", JOptionPane.QUESTION_MESSAGE);
move = selectedMoves.get(comboBox.getSelectedIndex());
} else move = selectedMoves.get(0);
// Tell the game to execute the move
moveRequested = false; moveRequested = false;
// TODO: Special moves game.onMove(NaturalPlayer.this, move);
game.onMove(NaturalPlayer.this, new Move(pos, dest)); }
pos = null;
// Discard the selection
overlayComponent.clearDots();
selectedPiece = null;
possibleMoves = null;
} }
} }