This commit is contained in:
Jacob Signorovitch
2025-05-27 00:45:16 -04:00
parent 5574f330e9
commit 9e450561f2
5 changed files with 159 additions and 25 deletions

View File

@@ -1,5 +1,7 @@
package chess;
import java.util.ArrayList;
// An actor is an entity capable of making descisions on the board.
abstract class Actor {
Util.Col col; // The color the Actor is acting as.

View File

@@ -41,6 +41,11 @@ class Board {
Util.scale, Util.scale, OutlineMode.SOLID,
new Color(0, 150, 200, 150)
);
else if (info == Info.PROMOTION)
infoImage = new RectangleImage(
Util.scale, Util.scale, OutlineMode.SOLID,
new Color(200, 50, 200, 150)
);
scene.placeImageXY(
new OverlayImage(
@@ -64,6 +69,22 @@ class Board {
);
}
// Check for a pawn eligible for promotion.
boolean isPawnPromotionPosition(Coord coord, Piece piece) {
if (piece == null || piece.type != PieceType.PAWN) return false;
boolean isPromotionPosition =
(piece.col == Util.Col.WHITE && coord.y == 0) ||
(piece.col == Util.Col.BLACK && coord.y == 7);
return isPromotionPosition;
}
// Display promotion dialog.
void displayPromotionOptions(Coord coord) {
this.info.put(coord, Info.PROMOTION);
}
WorldImage drawPiece(Piece piece) {
return (piece == null) ? new EmptyImage() : piece.draw();
}
@@ -105,6 +126,15 @@ class Board {
this.drop(targ);
this.set(dest, piece);
// Check if a pawn needs promotion.
if (piece.type == PieceType.PAWN &&
isPawnPromotionPosition(dest, piece)) {
// Hold turn til promotion complete.
this.game.setAwaitingPromotion(dest);
return;
}
this.game.changeActive();
}
@@ -115,6 +145,31 @@ class Board {
return piece.moves(coord, this);
}
// Promote pawn to type.
void promotePawn(Coord pawnPos, PieceType promotionType) {
Piece pawn = this.get(pawnPos);
if (pawn == null || pawn.type != PieceType.PAWN) return;
Util.Col color = pawn.col;
Piece newPiece = null;
switch (promotionType) {
case QUEEN: newPiece = new Queen(color); break;
case ROOK: newPiece = new Rook(color); break;
case BISHOP: newPiece = new Bishop(color); break;
case KNIGHT: newPiece = new Knight(color); break;
default: newPiece = new Queen(color); // Default queen because best.
}
// Replace pawn with new piece.
this.set(pawnPos, newPiece);
// Clear promotion indicator.
this.info.remove(pawnPos);
this.game.promotionComplete();
}
// Is the selected space free? (either nothing there, or an enemy piece).
boolean isFree(Coord coord, Util.Col col) {
return this.get(coord) == null || this.get(coord).col != col;
@@ -234,7 +289,7 @@ class Board {
}
// Information layered over the board.
enum Info { CANMOVE, WARN, SELECTED }
enum Info { CANMOVE, WARN, SELECTED, PROMOTION }
// Measured from top left.
class Coord {

View File

@@ -11,6 +11,8 @@ class Game extends World {
WorldScene scene;
Actor activeActor;
Actor winner;
Coord awaitingPromotion; // Tracks pawn awaiting promotion.
boolean promotionMode; // Whether game is in special promotion mode.
Game(Actor white, Actor black, Board board) {
this.white = white;
@@ -18,6 +20,8 @@ class Game extends World {
this.board = board;
this.activeActor = this.white;
this.winner = null;
this.awaitingPromotion = null;
this.promotionMode = false;
}
Game() {
@@ -26,6 +30,8 @@ class Game extends World {
this.black = new Player(Util.Col.BLACK, this.board);
this.activeActor = this.white;
this.winner = null;
this.awaitingPromotion = null;
this.promotionMode = false;
this.setup();
}
@@ -53,14 +59,30 @@ class Game extends World {
Util.scale * 4, 3 * Util.scale
);
} else {
this.scene.placeImageXY(
new TextImage(
(this.activeActor.equals(this.white) ? "white" : "black") +
"'s turn",
Util.scale / 2, Color.red
),
Util.scale * 2, 3 * Util.scale
);
if (this.promotionMode) {
this.scene.placeImageXY(
new TextImage(
"PROMOTE: [Q]ueen; [R]ook; [B]ishop; "
+ "K[N]ight",
Util.scale / 3, Color.magenta
),
Util.scale * 4, 3 * Util.scale
);
// Highlight pawn being promoted.
if (this.awaitingPromotion != null) {
this.board.displayPromotionOptions(this.awaitingPromotion);
}
} else {
this.scene.placeImageXY(
new TextImage(
(this.activeActor.equals(this.white) ? "white" : "black"
) + "'s turn",
Util.scale / 2, Color.red
),
Util.scale * 2, 3 * Util.scale
);
}
}
}
@@ -139,12 +161,16 @@ class Game extends World {
public void onMouseClicked(Posn posn) {
// Process clicks if game not over.
if (this.winner == null) this.activeActor.click(this.where(posn));
if (this.winner == null && !this.promotionMode)
this.activeActor.click(this.where(posn));
}
public void onKeyEvent(String key) {
// Process keys if game is not over.
if (this.winner == null) this.activeActor.key(key);
if (this.winner == null) {
if (this.promotionMode) handlePromotionKey(key);
else this.activeActor.key(key);
}
}
void changeActive() {
@@ -152,12 +178,41 @@ class Game extends World {
else this.activeActor = this.white;
}
void setWinner(Actor winner) {
this.winner = winner;
System.out.println(
(winner.equals(this.white) ? "WHITE" : "BLACK") + " WINS!"
);
// Enter promotion mode.
void setAwaitingPromotion(Coord pawnPos) {
this.awaitingPromotion = pawnPos;
this.promotionMode = true;
this.board.displayPromotionOptions(pawnPos);
}
// Promotion selection key.
void handlePromotionKey(String key) {
if (this.awaitingPromotion == null) return;
PieceType promotionType = null;
switch (key.toLowerCase()) {
case "q": promotionType = PieceType.QUEEN; break;
case "r": promotionType = PieceType.ROOK; break;
case "b": promotionType = PieceType.BISHOP; break;
case "n":
promotionType = PieceType.KNIGHT;
break; // N for knight because K already taken for vim keys :P.
default: return;
}
if (promotionType != null)
this.board.promotePawn(this.awaitingPromotion, promotionType);
}
// Called when promotion is complete.
void promotionComplete() {
this.awaitingPromotion = null;
this.promotionMode = false;
this.changeActive();
}
void setWinner(Actor winner) { this.winner = winner; }
boolean isGameOver() { return this.winner != null; }
}

View File

@@ -56,6 +56,12 @@ class Pawn extends Piece {
return moves;
}
// Check if pawn in promotion position.
boolean canPromote(Coord coord) {
return (this.col == Util.Col.WHITE && coord.y == 0) ||
(this.col == Util.Col.BLACK && coord.y == Util.boardW - 1);
}
}
class Rook extends Piece {
@@ -121,12 +127,6 @@ class Bishop extends Piece {
ArrayList<Coord> moves(Coord coord, Board board) {
ArrayList<Coord> moves = new ArrayList<Coord>();
/*
for (int i = -7; i < 8; i++) {
if (i == 0) continue; // Can't move to own position.
moves.add(new Coord(coord.x + i, coord.y + i));
moves.add(new Coord(coord.x + i, coord.y - i));
}*/
ArrayList<Coord> ne = board.getLineDiagNE(coord, this.col);
ArrayList<Coord> nw = board.getLineDiagNW(coord, this.col);
ArrayList<Coord> se = board.getLineDiagSE(coord, this.col);

View File

@@ -2,11 +2,15 @@ package chess;
import java.util.ArrayList;
// A human-controlled Actor."
// A human-controlled Actor.
class Player extends Actor {
Coord selected; // The currently selected piece.
Coord selected; // The currently selected piece.
Coord promotionPending; // Tracks if in a promotion.
Player(Util.Col col, Board board) { super(col, board); }
Player(Util.Col col, Board board) {
super(col, board);
this.promotionPending = null;
}
void click(Coord coord) {
Piece piece = this.board.get(coord);
@@ -34,6 +38,17 @@ class Player extends Actor {
System.out.println(
"clicked on moveable space with something selected"
);
// Check if this is a pawn that'll be promoted.
Piece movingPiece = this.board.get(this.selected);
boolean isPawnPromotion = false;
if (movingPiece != null && movingPiece.type == PieceType.PAWN) {
isPawnPromotion =
(movingPiece.col == Util.Col.WHITE && coord.y % 7 == 0
); // Check if on end of board; modulo is nice here.
if (isPawnPromotion) this.promotionPending = coord;
}
this.board.move(this.selected, coord);
this.board.unselect(this.selected);
this.board.undisplayMoves(moves);
@@ -58,9 +73,16 @@ class Player extends Actor {
}
void key(String key) {
// Not my problem.
if (this.promotionPending != null) return;
if (this.selected == null) return;
this.board.undisplayMoves(this.board.getMoves(this.selected));
this.board.unselect(this.selected);
this.selected = null;
}
// Call when promotion complete.
void clearPromotionPending() { this.promotionPending = null; }
}