basic model

This commit is contained in:
2025-03-25 22:36:28 +01:00
parent dc2ea660ff
commit 0bef89c46f
16 changed files with 688 additions and 0 deletions

View File

@@ -0,0 +1,107 @@
package chess.model;
import chess.model.visitor.KingIdentifier;
import chess.model.visitor.PiecePathChecker;
public class ChessBoard {
public static class Cell {
private Piece piece;
public Cell() {
this.piece = null;
}
public Piece getPiece() {
return piece;
}
public void setPiece(Piece piece) {
this.piece = piece;
}
}
private final Cell[][] cells;
public ChessBoard() {
this.cells = new Cell[Coordinate.VALUE_MAX][Coordinate.VALUE_MAX];
}
public void applyMove(Move move) {
assert (move.isValid());
Piece deadPiece = pieceAt(move.getFinish());
if (deadPiece != null) {
deadPiece.eject();
}
Piece movingPiece = pieceAt(move.getStart());
pieceComes(movingPiece, move.getFinish());
pieceLeaves(move.getStart());
}
public void undoMove(Move move) {
// for later
}
public boolean isCellEmpty(Coordinate coordinate) {
return pieceAt(coordinate) == null;
}
public Piece pieceAt(Coordinate coordinate) {
assert (coordinate.isValid());
return cellAt(coordinate).getPiece();
}
public void clearBoard() {
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
pieceLeaves(new Coordinate(i, j));
}
}
}
private Cell cellAt(Coordinate coordinate) {
return this.cells[coordinate.getX()][coordinate.getY()];
}
private void pieceComes(Piece piece, Coordinate coordinate) {
cellAt(coordinate).setPiece(piece);
}
private void pieceLeaves(Coordinate coordinate) {
cellAt(coordinate).setPiece(null);
}
public Coordinate findKing(Color color) {
KingIdentifier kingIdentifier = new KingIdentifier(color);
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
Coordinate coordinate = new Coordinate(i, j);
Piece piece = pieceAt(coordinate);
if (piece != null && kingIdentifier.visit(piece)) {
return coordinate;
}
}
}
assert false : "No king found ?!";
return null;
}
public boolean isKingInCheck(Color color) {
Coordinate kingPos = findKing(color);
assert kingPos.isValid() : "King position is invalid!";
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
Coordinate attackCoords = new Coordinate(i, j);
Piece attackPiece = pieceAt(attackCoords);
if (attackPiece == null)
continue;
PiecePathChecker checker = new PiecePathChecker(this, new Move(attackCoords, kingPos));
if (checker.isValidForPiece(attackPiece))
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,6 @@
package chess.model;
public enum Color {
White,
Black
}

View File

@@ -0,0 +1,41 @@
package chess.model;
public class Coordinate {
private final int x;
private final int y;
public static int VALUE_MAX = 8;
public Coordinate(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public boolean isValid() {
return 0 <= this.x && this.x < VALUE_MAX && 0 <= this.y && this.y < VALUE_MAX;
}
public static Coordinate fromIndex(int index) {
return new Coordinate(index % VALUE_MAX, index / VALUE_MAX);
}
public int toIndex() {
return this.y * VALUE_MAX + this.x;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Coordinate coo) {
return this.x == coo.x && this.y == coo.y;
}
return false;
}
}

View File

@@ -0,0 +1,46 @@
package chess.model;
public enum Direction {
Unset(65),
Front(8), Back(-8), Left(-1), Right(1),
FrontLeft(7), FrontRight(9), BackLeft(-9), BackRight(-7);
private final int indexOffset;
Direction(int indexOffset) {
this.indexOffset = indexOffset;
}
public int getIndexOffset() {
return indexOffset;
}
public static Direction findDirection(Move move) {
assert move.isValid() : "Move is invalid!";
int diffX = move.getFinish().getX() - move.getStart().getX();
int diffY = move.getFinish().getY() - move.getStart().getY();
if (diffX == 0 && diffY < 0)
return Direction.Back;
if (diffX == 0 && diffY > 0)
return Direction.Front;
if (diffX < 0 && diffY == 0)
return Direction.Left;
if (diffX > 0 && diffY == 0)
return Direction.Right;
if (diffX < 0 && -diffX == diffY)
return Direction.FrontLeft;
if (diffX > 0 && diffX == diffY)
return Direction.FrontRight;
if (diffY < 0 && diffX == diffY)
return Direction.BackLeft;
if (diffY > 0 && diffX == -diffY)
return Direction.BackRight;
return Direction.Unset;
}
}

View File

@@ -0,0 +1,42 @@
package chess.model;
public class Move {
private final Coordinate start;
private final Coordinate finish;
public Move(Coordinate start, Coordinate finish) {
this.start = start;
this.finish = finish;
}
public boolean isValid() {
return this.start.isValid() && this.finish.isValid() && !this.start.equals(this.finish);
}
public Coordinate getStart() {
return start;
}
public Coordinate getFinish() {
return finish;
}
public int traversedCells() {
assert isValid() : "Move is invalid!";
int diffX = getFinish().getX() - getStart().getX();
int diffY = getFinish().getY() - getStart().getY();
assert Math.abs(diffX) < Coordinate.VALUE_MAX : "Move is too big!";
assert Math.abs(diffX) < Coordinate.VALUE_MAX : "Move is too big!";
if (diffX == 0)
return Math.abs(diffY);
if (diffY == 0)
return Math.abs(diffX);
if (Math.abs(diffX) == Math.abs(diffY))
return Math.abs(diffX);
return 0;
}
}

View File

@@ -0,0 +1,31 @@
package chess.model;
public abstract class Piece {
private final Color color;
private boolean moved;
public Piece(Color color) {
this.color = color;
this.moved = false;
}
public void move() {
this.moved = true;
}
public Color getColor() {
return color;
}
public boolean hasMoved() {
return moved;
}
public void eject() {
// for later
}
public abstract <T> T accept(PieceVisitor<T> visitor);
}

View File

@@ -0,0 +1,28 @@
package chess.model;
import chess.model.pieces.Bishop;
import chess.model.pieces.King;
import chess.model.pieces.Knight;
import chess.model.pieces.Pawn;
import chess.model.pieces.Queen;
import chess.model.pieces.Rook;
public interface PieceVisitor<T> {
default T visit(Piece piece) {
return piece.accept(this);
}
T visitPiece(Bishop bishop);
T visitPiece(King king);
T visitPiece(Knight knight);
T visitPiece(Pawn pawn);
T visitPiece(Queen queen);
T visitPiece(Rook rook);
}

View File

@@ -0,0 +1,18 @@
package chess.model.pieces;
import chess.model.Color;
import chess.model.Piece;
import chess.model.PieceVisitor;
public class Bishop extends Piece {
public Bishop(Color color) {
super(color);
}
@Override
public <T> T accept(PieceVisitor<T> visitor) {
return visitor.visitPiece(this);
}
}

View File

@@ -0,0 +1,17 @@
package chess.model.pieces;
import chess.model.Color;
import chess.model.Piece;
import chess.model.PieceVisitor;
public class King extends Piece {
public King(Color color) {
super(color);
}
@Override
public <T> T accept(PieceVisitor<T> visitor) {
return visitor.visitPiece(this);
}
}

View File

@@ -0,0 +1,17 @@
package chess.model.pieces;
import chess.model.Color;
import chess.model.Piece;
import chess.model.PieceVisitor;
public class Knight extends Piece {
public Knight(Color color) {
super(color);
}
@Override
public <T> T accept(PieceVisitor<T> visitor) {
return visitor.visitPiece(this);
}
}

View File

@@ -0,0 +1,21 @@
package chess.model.pieces;
import chess.model.Color;
import chess.model.Piece;
import chess.model.PieceVisitor;
public class Pawn extends Piece {
public Pawn(Color color) {
super(color);
}
@Override
public <T> T accept(PieceVisitor<T> visitor) {
return visitor.visitPiece(this);
}
public int multiplier() {
return getColor() == Color.White ? 1 : -1;
}
}

View File

@@ -0,0 +1,17 @@
package chess.model.pieces;
import chess.model.Color;
import chess.model.Piece;
import chess.model.PieceVisitor;
public class Queen extends Piece {
public Queen(Color color) {
super(color);
}
@Override
public <T> T accept(PieceVisitor<T> visitor) {
return visitor.visitPiece(this);
}
}

View File

@@ -0,0 +1,17 @@
package chess.model.pieces;
import chess.model.Color;
import chess.model.Piece;
import chess.model.PieceVisitor;
public class Rook extends Piece {
public Rook(Color color) {
super(color);
}
@Override
public <T> T accept(PieceVisitor<T> visitor) {
return visitor.visitPiece(this);
}
}

View File

@@ -0,0 +1,45 @@
package chess.model.visitor;
import chess.model.Color;
import chess.model.PieceVisitor;
import chess.model.pieces.*;
public class KingIdentifier implements PieceVisitor<Boolean> {
private final Color color;
public KingIdentifier(Color color) {
this.color = color;
}
@Override
public Boolean visitPiece(Bishop bishop) {
return false;
}
@Override
public Boolean visitPiece(King king) {
return king.getColor() == color;
}
@Override
public Boolean visitPiece(Knight knight) {
return false;
}
@Override
public Boolean visitPiece(Pawn pawn) {
return false;
}
@Override
public Boolean visitPiece(Queen queen) {
return false;
}
@Override
public Boolean visitPiece(Rook rook) {
return false;
}
}

View File

@@ -0,0 +1,119 @@
package chess.model.visitor;
import chess.model.Coordinate;
import chess.model.Direction;
import chess.model.Move;
import chess.model.Piece;
import chess.model.PieceVisitor;
import chess.model.pieces.Bishop;
import chess.model.pieces.King;
import chess.model.pieces.Knight;
import chess.model.pieces.Pawn;
import chess.model.pieces.Queen;
import chess.model.pieces.Rook;
public class PermissiveRuleChecker implements PieceVisitor<Boolean> {
private final Move move;
public PermissiveRuleChecker(Move move) {
this.move = move;
assert move.isValid() : "Move is invalid!";
}
public boolean isValidFor(Piece piece) {
return visit(piece);
}
@Override
public Boolean visitPiece(Bishop bishop) {
Direction moveDirection = Direction.findDirection(this.move);
switch (moveDirection) {
case FrontLeft:
case BackLeft:
case FrontRight:
case BackRight:
return true;
default:
return false;
}
}
@Override
public Boolean visitPiece(King king) {
return this.move.traversedCells() == 1;
}
@Override
public Boolean visitPiece(Knight knight) {
Coordinate piecePos = move.getStart();
final Coordinate[] positions = {
new Coordinate(piecePos.getX() - 1, piecePos.getY() - 2),
new Coordinate(piecePos.getX() - 1, piecePos.getY() + 2),
new Coordinate(piecePos.getX() + 1, piecePos.getY() - 2),
new Coordinate(piecePos.getX() + 1, piecePos.getY() + 2),
new Coordinate(piecePos.getX() + 2, piecePos.getY() - 1),
new Coordinate(piecePos.getX() + 2, piecePos.getY() + 1),
new Coordinate(piecePos.getX() - 2, piecePos.getY() - 1),
new Coordinate(piecePos.getX() - 2, piecePos.getY() + 1),
};
for (int i = 0; i < positions.length; i++) {
if (this.move.getFinish().equals(positions[i]))
return true;
}
return false;
}
@Override
public Boolean visitPiece(Pawn pawn) {
Direction moveDirection = Direction.findDirection(this.move);
int directionIndexOffset = moveDirection.getIndexOffset();
int distance = this.move.traversedCells();
// Revoke moving backwards
if (directionIndexOffset * pawn.multiplier() < 0)
return false;
// Allowing straight moves
if (Math.abs(directionIndexOffset) == Direction.Front.getIndexOffset()) {
if (pawn.hasMoved())
return distance == 1;
return distance == 1 || distance == 2;
}
// Allowing small diagonal moves
if (Math.abs(directionIndexOffset) == Direction.FrontLeft.getIndexOffset()
|| Math.abs(directionIndexOffset) == Direction.FrontRight.getIndexOffset()) {
return distance == 1;
}
return false;
}
@Override
public Boolean visitPiece(Queen queen) {
Direction moveDirection = Direction.findDirection(this.move);
return moveDirection != Direction.Unset;
}
@Override
public Boolean visitPiece(Rook rook) {
Direction moveDirection = Direction.findDirection(this.move);
switch (moveDirection) {
case Front:
case Back:
case Left:
case Right:
return true;
default:
return false;
}
}
}

View File

@@ -0,0 +1,116 @@
package chess.model.visitor;
import chess.model.ChessBoard;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Direction;
import chess.model.Move;
import chess.model.Piece;
import chess.model.PieceVisitor;
import chess.model.pieces.Bishop;
import chess.model.pieces.King;
import chess.model.pieces.Knight;
import chess.model.pieces.Pawn;
import chess.model.pieces.Queen;
import chess.model.pieces.Rook;
public class PiecePathChecker implements PieceVisitor<Boolean> {
private final ChessBoard board;
private final Move move;
public PiecePathChecker(ChessBoard board, Move move) {
this.move = move;
this.board = board;
}
public boolean isValidForPiece(Piece piece) {
return visit(piece);
}
@Override
public Boolean visitPiece(Bishop bishop) {
if (!new PermissiveRuleChecker(this.move).isValidFor(bishop))
return false;
return testPath(bishop.getColor());
}
@Override
public Boolean visitPiece(King king) {
if (!new PermissiveRuleChecker(this.move).isValidFor(king))
return false;
Piece destPiece = board.pieceAt(this.move.getFinish());
if (destPiece == null)
return true;
return destPiece.getColor() != king.getColor();
}
@Override
public Boolean visitPiece(Knight knight) {
if (!new PermissiveRuleChecker(this.move).isValidFor(knight))
return false;
Piece destPiece = board.pieceAt(this.move.getFinish());
if (destPiece == null)
return true;
return destPiece.getColor() != knight.getColor();
}
@Override
public Boolean visitPiece(Pawn pawn) {
if (!new PermissiveRuleChecker(this.move).isValidFor(pawn))
return false;
Direction moveDirection = Direction.findDirection(this.move);
// ...
// moveDirection = Directions(int(findDirection(move)) * pawn.multiplier())
if (moveDirection == Direction.Front)
return testPath(pawn.getColor());
assert moveDirection == Direction.FrontLeft || moveDirection == Direction.FrontRight;
Piece destPiece = this.board.pieceAt(this.move.getFinish());
if (destPiece == null)
return true;
return destPiece.getColor() != pawn.getColor();
}
@Override
public Boolean visitPiece(Queen queen) {
if (!new PermissiveRuleChecker(this.move).isValidFor(queen))
return false;
return testPath(queen.getColor());
}
@Override
public Boolean visitPiece(Rook rook) {
if (!new PermissiveRuleChecker(this.move).isValidFor(rook))
return false;
return testPath(rook.getColor());
}
private boolean testPath(Color color) {
Direction moveDirection = Direction.findDirection(this.move);
int distance = this.move.traversedCells();
int stepIndex = move.getStart().toIndex();
for (int step = 0; step < distance; step++) {
stepIndex += moveDirection.getIndexOffset();
if (Coordinate.fromIndex(stepIndex).equals(move.getFinish())) {
Piece pieceDest = this.board.pieceAt(move.getFinish());
if (pieceDest == null)
return true;
return pieceDest.getColor() != color;
}
if (!this.board.isCellEmpty(Coordinate.fromIndex(stepIndex)))
return false;
}
return false;
}
}