From 0bef89c46f126f590061558f03929fa6b4acd26f Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Tue, 25 Mar 2025 22:36:28 +0100 Subject: [PATCH] basic model --- app/src/main/java/chess/model/ChessBoard.java | 107 ++++++++++++++++ app/src/main/java/chess/model/Color.java | 6 + app/src/main/java/chess/model/Coordinate.java | 41 ++++++ app/src/main/java/chess/model/Direction.java | 46 +++++++ app/src/main/java/chess/model/Move.java | 42 +++++++ app/src/main/java/chess/model/Piece.java | 31 +++++ .../main/java/chess/model/PieceVisitor.java | 28 +++++ .../main/java/chess/model/pieces/Bishop.java | 18 +++ .../main/java/chess/model/pieces/King.java | 17 +++ .../main/java/chess/model/pieces/Knight.java | 17 +++ .../main/java/chess/model/pieces/Pawn.java | 21 ++++ .../main/java/chess/model/pieces/Queen.java | 17 +++ .../main/java/chess/model/pieces/Rook.java | 17 +++ .../chess/model/visitor/KingIdentifier.java | 45 +++++++ .../model/visitor/PermissiveRuleChecker.java | 119 ++++++++++++++++++ .../chess/model/visitor/PiecePathChecker.java | 116 +++++++++++++++++ 16 files changed, 688 insertions(+) create mode 100644 app/src/main/java/chess/model/ChessBoard.java create mode 100644 app/src/main/java/chess/model/Color.java create mode 100644 app/src/main/java/chess/model/Coordinate.java create mode 100644 app/src/main/java/chess/model/Direction.java create mode 100644 app/src/main/java/chess/model/Move.java create mode 100644 app/src/main/java/chess/model/Piece.java create mode 100644 app/src/main/java/chess/model/PieceVisitor.java create mode 100644 app/src/main/java/chess/model/pieces/Bishop.java create mode 100644 app/src/main/java/chess/model/pieces/King.java create mode 100644 app/src/main/java/chess/model/pieces/Knight.java create mode 100644 app/src/main/java/chess/model/pieces/Pawn.java create mode 100644 app/src/main/java/chess/model/pieces/Queen.java create mode 100644 app/src/main/java/chess/model/pieces/Rook.java create mode 100644 app/src/main/java/chess/model/visitor/KingIdentifier.java create mode 100644 app/src/main/java/chess/model/visitor/PermissiveRuleChecker.java create mode 100644 app/src/main/java/chess/model/visitor/PiecePathChecker.java diff --git a/app/src/main/java/chess/model/ChessBoard.java b/app/src/main/java/chess/model/ChessBoard.java new file mode 100644 index 0000000..9bed54e --- /dev/null +++ b/app/src/main/java/chess/model/ChessBoard.java @@ -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; + } +} diff --git a/app/src/main/java/chess/model/Color.java b/app/src/main/java/chess/model/Color.java new file mode 100644 index 0000000..9ac4903 --- /dev/null +++ b/app/src/main/java/chess/model/Color.java @@ -0,0 +1,6 @@ +package chess.model; + +public enum Color { + White, + Black +} diff --git a/app/src/main/java/chess/model/Coordinate.java b/app/src/main/java/chess/model/Coordinate.java new file mode 100644 index 0000000..3461e3a --- /dev/null +++ b/app/src/main/java/chess/model/Coordinate.java @@ -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; + } +} diff --git a/app/src/main/java/chess/model/Direction.java b/app/src/main/java/chess/model/Direction.java new file mode 100644 index 0000000..a8ab8e6 --- /dev/null +++ b/app/src/main/java/chess/model/Direction.java @@ -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; + } +} diff --git a/app/src/main/java/chess/model/Move.java b/app/src/main/java/chess/model/Move.java new file mode 100644 index 0000000..d4db5dd --- /dev/null +++ b/app/src/main/java/chess/model/Move.java @@ -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; + } + +} diff --git a/app/src/main/java/chess/model/Piece.java b/app/src/main/java/chess/model/Piece.java new file mode 100644 index 0000000..47d17c8 --- /dev/null +++ b/app/src/main/java/chess/model/Piece.java @@ -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 accept(PieceVisitor visitor); + +} diff --git a/app/src/main/java/chess/model/PieceVisitor.java b/app/src/main/java/chess/model/PieceVisitor.java new file mode 100644 index 0000000..fffbfaf --- /dev/null +++ b/app/src/main/java/chess/model/PieceVisitor.java @@ -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 { + + 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); + +} diff --git a/app/src/main/java/chess/model/pieces/Bishop.java b/app/src/main/java/chess/model/pieces/Bishop.java new file mode 100644 index 0000000..301baea --- /dev/null +++ b/app/src/main/java/chess/model/pieces/Bishop.java @@ -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 accept(PieceVisitor visitor) { + return visitor.visitPiece(this); + } + +} diff --git a/app/src/main/java/chess/model/pieces/King.java b/app/src/main/java/chess/model/pieces/King.java new file mode 100644 index 0000000..8234b7b --- /dev/null +++ b/app/src/main/java/chess/model/pieces/King.java @@ -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 accept(PieceVisitor visitor) { + return visitor.visitPiece(this); + } +} diff --git a/app/src/main/java/chess/model/pieces/Knight.java b/app/src/main/java/chess/model/pieces/Knight.java new file mode 100644 index 0000000..9f3f31f --- /dev/null +++ b/app/src/main/java/chess/model/pieces/Knight.java @@ -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 accept(PieceVisitor visitor) { + return visitor.visitPiece(this); + } +} diff --git a/app/src/main/java/chess/model/pieces/Pawn.java b/app/src/main/java/chess/model/pieces/Pawn.java new file mode 100644 index 0000000..2573df4 --- /dev/null +++ b/app/src/main/java/chess/model/pieces/Pawn.java @@ -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 accept(PieceVisitor visitor) { + return visitor.visitPiece(this); + } + + public int multiplier() { + return getColor() == Color.White ? 1 : -1; + } +} diff --git a/app/src/main/java/chess/model/pieces/Queen.java b/app/src/main/java/chess/model/pieces/Queen.java new file mode 100644 index 0000000..9549256 --- /dev/null +++ b/app/src/main/java/chess/model/pieces/Queen.java @@ -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 accept(PieceVisitor visitor) { + return visitor.visitPiece(this); + } +} diff --git a/app/src/main/java/chess/model/pieces/Rook.java b/app/src/main/java/chess/model/pieces/Rook.java new file mode 100644 index 0000000..d484bc3 --- /dev/null +++ b/app/src/main/java/chess/model/pieces/Rook.java @@ -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 accept(PieceVisitor visitor) { + return visitor.visitPiece(this); + } +} diff --git a/app/src/main/java/chess/model/visitor/KingIdentifier.java b/app/src/main/java/chess/model/visitor/KingIdentifier.java new file mode 100644 index 0000000..fdd29ad --- /dev/null +++ b/app/src/main/java/chess/model/visitor/KingIdentifier.java @@ -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 { + + 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; + } + +} diff --git a/app/src/main/java/chess/model/visitor/PermissiveRuleChecker.java b/app/src/main/java/chess/model/visitor/PermissiveRuleChecker.java new file mode 100644 index 0000000..637f8bb --- /dev/null +++ b/app/src/main/java/chess/model/visitor/PermissiveRuleChecker.java @@ -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 { + + 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; + } + } + +} diff --git a/app/src/main/java/chess/model/visitor/PiecePathChecker.java b/app/src/main/java/chess/model/visitor/PiecePathChecker.java new file mode 100644 index 0000000..cf285d1 --- /dev/null +++ b/app/src/main/java/chess/model/visitor/PiecePathChecker.java @@ -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 { + + 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; + } +}