diff --git a/.gitignore b/.gitignore index 609e392..0056fd7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ # Ignore Gradle build output directory build -app/bin \ No newline at end of file +app/bin + +.vscode \ No newline at end of file diff --git a/app/src/main/java/chess/App.java b/app/src/main/java/chess/App.java index 716a717..5010ca6 100644 --- a/app/src/main/java/chess/App.java +++ b/app/src/main/java/chess/App.java @@ -3,7 +3,9 @@ */ package chess; -import chess.render.*; +import chess.model.ChessBoard; +import chess.model.Game; +import chess.simplerender.Window; public class App { public String getGreeting() { @@ -11,6 +13,6 @@ public class App { } public static void main(String[] args) { - new Window().run(); + new Window(new Game(new ChessBoard())); } } diff --git a/app/src/main/java/chess/model/ChessBoard.java b/app/src/main/java/chess/model/ChessBoard.java index 7bc1ae2..d0fd9f8 100644 --- a/app/src/main/java/chess/model/ChessBoard.java +++ b/app/src/main/java/chess/model/ChessBoard.java @@ -1,5 +1,11 @@ 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; import chess.model.visitor.KingIdentifier; import chess.model.visitor.PiecePathChecker; @@ -30,6 +36,7 @@ public class ChessBoard { this.cells[i][j] = new Cell(); } } + initPieces(); } public void applyMove(Move move) { @@ -41,6 +48,7 @@ public class ChessBoard { Piece movingPiece = pieceAt(move.getStart()); pieceComes(movingPiece, move.getFinish()); pieceLeaves(move.getStart()); + movingPiece.move(); } public void undoMove(Move move) { @@ -52,7 +60,8 @@ public class ChessBoard { } public Piece pieceAt(Coordinate coordinate) { - assert (coordinate.isValid()); + if (!coordinate.isValid()) + return null; return cellAt(coordinate).getPiece(); } @@ -103,10 +112,56 @@ public class ChessBoard { continue; PiecePathChecker checker = new PiecePathChecker(this, new Move(attackCoords, kingPos)); - if (checker.isValidForPiece(attackPiece)) + if (checker.isValid()) return true; } } return false; } + + public boolean[][] getAllowedMoves(Coordinate pieceCoords) { + Piece piece = pieceAt(pieceCoords); + if (piece == null) + return null; + + boolean[][] result = new boolean[Coordinate.VALUE_MAX][Coordinate.VALUE_MAX]; + for (int i = 0; i < Coordinate.VALUE_MAX; i++) { + for (int j = 0; j < Coordinate.VALUE_MAX; j++) { + PiecePathChecker piecePathChecker = new PiecePathChecker(this, new Move(pieceCoords, new Coordinate(i, j))); + result[i][j] = piecePathChecker.isValid(); + } + } + return result; + } + + public void initPieces() { + for (int i = 0; i < 8; i++) { + pieceComes(new Pawn(Color.Black), new Coordinate(i, 0)); + pieceComes(new Pawn(Color.White), new Coordinate(i, Coordinate.VALUE_MAX - 1)); + } + + pieceComes(new Rook(Color.Black), new Coordinate(0, 1)); + pieceComes(new Rook(Color.Black), new Coordinate(Coordinate.VALUE_MAX - 1, 1)); + + pieceComes(new Rook(Color.White), new Coordinate(0, Coordinate.VALUE_MAX - 2)); + pieceComes(new Rook(Color.White), new Coordinate(Coordinate.VALUE_MAX - 1, Coordinate.VALUE_MAX - 2)); + + pieceComes(new Knight(Color.Black), new Coordinate(1, 1)); + pieceComes(new Knight(Color.Black), new Coordinate(Coordinate.VALUE_MAX - 2, 1)); + + pieceComes(new Knight(Color.White), new Coordinate(1, Coordinate.VALUE_MAX - 2)); + pieceComes(new Knight(Color.White), new Coordinate(Coordinate.VALUE_MAX - 2, Coordinate.VALUE_MAX - 2)); + + pieceComes(new Bishop(Color.Black), new Coordinate(2, 1)); + pieceComes(new Bishop(Color.Black), new Coordinate(Coordinate.VALUE_MAX - 3, 1)); + + pieceComes(new Bishop(Color.White), new Coordinate(2, Coordinate.VALUE_MAX - 2)); + pieceComes(new Bishop(Color.White), new Coordinate(Coordinate.VALUE_MAX - 3, Coordinate.VALUE_MAX - 2)); + + pieceComes(new Queen(Color.Black), new Coordinate(4, 1)); + pieceComes(new King(Color.Black), new Coordinate(3, 1)); + + pieceComes(new Queen(Color.White), new Coordinate(4, Coordinate.VALUE_MAX - 2)); + pieceComes(new King(Color.White), new Coordinate(3, Coordinate.VALUE_MAX - 2)); + } } diff --git a/app/src/main/java/chess/model/Direction.java b/app/src/main/java/chess/model/Direction.java index a8ab8e6..4c673b7 100644 --- a/app/src/main/java/chess/model/Direction.java +++ b/app/src/main/java/chess/model/Direction.java @@ -3,8 +3,8 @@ package chess.model; public enum Direction { Unset(65), - Front(8), Back(-8), Left(-1), Right(1), - FrontLeft(7), FrontRight(9), BackLeft(-9), BackRight(-7); + Front(-8), Back(8), Left(-1), Right(1), + FrontLeft(-9), FrontRight(-7), BackLeft(7), BackRight(9); private final int indexOffset; @@ -16,15 +16,23 @@ public enum Direction { return indexOffset; } + public static Direction fromInt(int direction) { + for (Direction dir : Direction.values()) { + if (dir.getIndexOffset() == direction) + return dir; + } + return null; + } + 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.Back; if (diffX < 0 && diffY == 0) return Direction.Left; @@ -32,14 +40,14 @@ public enum Direction { return Direction.Right; if (diffX < 0 && -diffX == diffY) - return Direction.FrontLeft; + return Direction.BackLeft; if (diffX > 0 && diffX == diffY) - return Direction.FrontRight; + return Direction.BackRight; if (diffY < 0 && diffX == diffY) - return Direction.BackLeft; - if (diffY > 0 && diffX == -diffY) - return Direction.BackRight; + return Direction.FrontLeft; + if (diffX > 0 && diffX == -diffY) + return Direction.FrontRight; return Direction.Unset; } diff --git a/app/src/main/java/chess/model/Game.java b/app/src/main/java/chess/model/Game.java new file mode 100644 index 0000000..67f1f2c --- /dev/null +++ b/app/src/main/java/chess/model/Game.java @@ -0,0 +1,31 @@ +package chess.model; + +import chess.model.visitor.PiecePathChecker; +import common.Signal0; +import common.Signal1; + +public class Game { + private final ChessBoard board; + + public final Signal0 OnRenderUpdate = new Signal0(); + public final Signal1 OnMoveRefused = new Signal1<>(); + + public Game(ChessBoard board) { + this.board = board; + } + + public ChessBoard getBoard() { + return board; + } + + public void tryMove(Move move) { + boolean valid = new PiecePathChecker(board, move).isValid(); + if (!valid) { + this.OnMoveRefused.emit(move); + return; + } + this.board.applyMove(move); + this.OnRenderUpdate.emit(); + } + +} diff --git a/app/src/main/java/chess/model/visitor/PermissiveRuleChecker.java b/app/src/main/java/chess/model/visitor/PermissiveRuleChecker.java index 637f8bb..8af813e 100644 --- a/app/src/main/java/chess/model/visitor/PermissiveRuleChecker.java +++ b/app/src/main/java/chess/model/visitor/PermissiveRuleChecker.java @@ -75,19 +75,19 @@ public class PermissiveRuleChecker implements PieceVisitor { int distance = this.move.traversedCells(); // Revoke moving backwards - if (directionIndexOffset * pawn.multiplier() < 0) + if (directionIndexOffset * pawn.multiplier() > 0) return false; // Allowing straight moves - if (Math.abs(directionIndexOffset) == Direction.Front.getIndexOffset()) { + if (Math.abs(directionIndexOffset) == Math.abs(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()) { + if (directionIndexOffset == Direction.FrontLeft.getIndexOffset() + || directionIndexOffset == Direction.FrontRight.getIndexOffset()) { return distance == 1; } diff --git a/app/src/main/java/chess/model/visitor/PiecePathChecker.java b/app/src/main/java/chess/model/visitor/PiecePathChecker.java index cf285d1..02f5036 100644 --- a/app/src/main/java/chess/model/visitor/PiecePathChecker.java +++ b/app/src/main/java/chess/model/visitor/PiecePathChecker.java @@ -24,7 +24,12 @@ public class PiecePathChecker implements PieceVisitor { this.board = board; } - public boolean isValidForPiece(Piece piece) { + public boolean isValid() { + if (this.move.getStart().equals(this.move.getFinish())) + return false; + Piece piece = this.board.pieceAt(move.getStart()); + if (piece == null) + return false; return visit(piece); } @@ -62,18 +67,16 @@ public class PiecePathChecker implements PieceVisitor { if (!new PermissiveRuleChecker(this.move).isValidFor(pawn)) return false; - Direction moveDirection = Direction.findDirection(this.move); - // ... - // moveDirection = Directions(int(findDirection(move)) * pawn.multiplier()) + Direction moveDirection = Direction.fromInt(Direction.findDirection(move).getIndexOffset() * pawn.multiplier()); if (moveDirection == Direction.Front) - return testPath(pawn.getColor()); + return testPath(pawn.getColor()) && this.board.pieceAt(this.move.getFinish()) == null; assert moveDirection == Direction.FrontLeft || moveDirection == Direction.FrontRight; Piece destPiece = this.board.pieceAt(this.move.getFinish()); if (destPiece == null) - return true; + return false; return destPiece.getColor() != pawn.getColor(); } diff --git a/app/src/main/java/chess/simplerender/PieceIcon.java b/app/src/main/java/chess/simplerender/PieceIcon.java new file mode 100644 index 0000000..f47f509 --- /dev/null +++ b/app/src/main/java/chess/simplerender/PieceIcon.java @@ -0,0 +1,62 @@ +package chess.simplerender; + +import java.awt.Image; + +import javax.swing.Icon; +import javax.swing.ImageIcon; + +import chess.model.Color; +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 PieceIcon implements PieceVisitor { + + private static final String basePath = "app/src/main/resources/pieces2D/"; + + public Icon getIcon(Piece piece) { + if (piece == null) + return null; + return new ImageIcon(new ImageIcon(basePath + colorToString(piece.getColor()) + "-" + visit(piece) + ".png").getImage().getScaledInstance(100,100, Image.SCALE_SMOOTH)); + } + + private String colorToString(Color color) { + return color == Color.Black ? "black" : "white"; + } + + @Override + public String visitPiece(Bishop bishop) { + return "bishop"; + } + + @Override + public String visitPiece(King king) { + return "king"; + } + + @Override + public String visitPiece(Knight knight) { + return "knight"; + } + + @Override + public String visitPiece(Pawn pawn) { + return "pawn"; + } + + @Override + public String visitPiece(Queen queen) { + return "queen"; + } + + @Override + public String visitPiece(Rook rook) { + return "rook"; + } + +} diff --git a/app/src/main/java/chess/simplerender/Window.java b/app/src/main/java/chess/simplerender/Window.java new file mode 100644 index 0000000..80baf2d --- /dev/null +++ b/app/src/main/java/chess/simplerender/Window.java @@ -0,0 +1,123 @@ +package chess.simplerender; + +import java.awt.Color; +import java.awt.GridLayout; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import chess.model.ChessBoard; +import chess.model.Coordinate; +import chess.model.Game; +import chess.model.Move; + +public class Window extends JFrame { + + private final JLabel cells[][]; + private final Game game; + private final ChessBoard board; + + private Coordinate lastClick = null; + + public Window(Game game) { + this.cells = new JLabel[8][8]; + this.game = game; + this.board = game.getBoard(); + initSlots(); + build(); + setSize(800, 800); + setVisible(true); + setDefaultCloseOperation(EXIT_ON_CLOSE); + } + + private void initSlots() { + this.game.OnRenderUpdate.connect(this::updateBoard); + this.game.OnMoveRefused.connect(this::drawInvalid); + } + + private Color getCellColor(int x, int y) { + return ((x + y) % 2 == 1) ? Color.BLACK : Color.WHITE; + } + + private void build() { + JPanel content = new JPanel(new GridLayout(8, 8)); + setContentPane(content); + for (int y = 0; y < 8; y++) { + for (int x = 0; x < 8; x++) { + JLabel label = new JLabel(); + label.setOpaque(true); + label.setBackground(getCellColor(x, y)); + this.cells[x][y] = label; + + final int xx = x; + final int yy = y; + + label.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + onCellClicked(xx, yy); + } + }); + content.add(label); + } + } + updateBoard(); + } + + private void updateBoard() { + PieceIcon pieceIcon = new PieceIcon(); + for (int y = 0; y < 8; y++) { + for (int x = 0; x < 8; x++) { + JLabel cell = this.cells[x][y]; + cell.setIcon(pieceIcon.getIcon(this.board.pieceAt(new Coordinate(x, y)))); + } + } + } + + private void previewMoves(int x, int y) { + boolean[][] allowedMoves = this.board.getAllowedMoves(new Coordinate(x, y)); + if (allowedMoves == null) + return; + for (int i = 0; i < 8; i++) { + for (int j = 0; j < 8; j++) { + JLabel cell = this.cells[i][j]; + if (allowedMoves[i][j]) + cell.setBackground(Color.CYAN); + } + } + } + + private void drawInvalid(Move move) { + JLabel from = this.cells[move.getStart().getX()][move.getStart().getY()]; + JLabel to = this.cells[move.getFinish().getX()][move.getFinish().getY()]; + from.setBackground(Color.RED); + to.setBackground(Color.RED); + } + + private void clearMoves() { + for (int y = 0; y < 8; y++) { + for (int x = 0; x < 8; x++) { + JLabel cell = this.cells[x][y]; + cell.setBackground(getCellColor(x, y)); + } + } + } + + private void onCellClicked(int x, int y) { + clearMoves(); + if (this.lastClick == null) { + if (this.board.isCellEmpty(new Coordinate(x, y))) + return; + this.lastClick = new Coordinate(x, y); + previewMoves(x, y); + return; + } + if (!this.lastClick.equals(new Coordinate(x, y))) + this.game.tryMove(new Move(lastClick, new Coordinate(x, y))); + this.lastClick = null; + } + +} diff --git a/app/src/main/java/common/Signal0.java b/app/src/main/java/common/Signal0.java new file mode 100644 index 0000000..ab3af2c --- /dev/null +++ b/app/src/main/java/common/Signal0.java @@ -0,0 +1,26 @@ +package common; + +import java.util.ArrayList; +import java.util.List; + +public class Signal0 { + private final List handlers; + + public Signal0() { + this.handlers = new ArrayList<>(); + } + + public void connect(Runnable handler) { + this.handlers.add(handler); + } + + public void disconnect(Runnable handler) { + this.handlers.remove(handler); + } + + public void emit() { + for (Runnable handler : this.handlers) { + handler.run(); + } + } +} diff --git a/app/src/main/java/common/Signal1.java b/app/src/main/java/common/Signal1.java new file mode 100644 index 0000000..d491fba --- /dev/null +++ b/app/src/main/java/common/Signal1.java @@ -0,0 +1,27 @@ +package common; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +public class Signal1 { + private final List> handlers; + + public Signal1() { + this.handlers = new ArrayList<>(); + } + + public void connect(Consumer handler) { + this.handlers.add(handler); + } + + public void disconnect(Consumer handler) { + this.handlers.remove(handler); + } + + public void emit(T arg) { + for (Consumer handler : this.handlers) { + handler.accept(arg); + } + } +} diff --git a/app/src/main/resources/pieces2D/black-bishop.png b/app/src/main/resources/pieces2D/black-bishop.png new file mode 100644 index 0000000..0dd4f27 Binary files /dev/null and b/app/src/main/resources/pieces2D/black-bishop.png differ diff --git a/app/src/main/resources/pieces2D/black-king.png b/app/src/main/resources/pieces2D/black-king.png new file mode 100644 index 0000000..d888501 Binary files /dev/null and b/app/src/main/resources/pieces2D/black-king.png differ diff --git a/app/src/main/resources/pieces2D/black-knight.png b/app/src/main/resources/pieces2D/black-knight.png new file mode 100644 index 0000000..03d05f6 Binary files /dev/null and b/app/src/main/resources/pieces2D/black-knight.png differ diff --git a/app/src/main/resources/pieces2D/black-pawn.png b/app/src/main/resources/pieces2D/black-pawn.png new file mode 100644 index 0000000..e8b7481 Binary files /dev/null and b/app/src/main/resources/pieces2D/black-pawn.png differ diff --git a/app/src/main/resources/pieces2D/black-queen.png b/app/src/main/resources/pieces2D/black-queen.png new file mode 100644 index 0000000..d5156fe Binary files /dev/null and b/app/src/main/resources/pieces2D/black-queen.png differ diff --git a/app/src/main/resources/pieces2D/black-rook.png b/app/src/main/resources/pieces2D/black-rook.png new file mode 100644 index 0000000..79a3f57 Binary files /dev/null and b/app/src/main/resources/pieces2D/black-rook.png differ diff --git a/app/src/main/resources/pieces2D/white-bishop.png b/app/src/main/resources/pieces2D/white-bishop.png new file mode 100644 index 0000000..550c0d1 Binary files /dev/null and b/app/src/main/resources/pieces2D/white-bishop.png differ diff --git a/app/src/main/resources/pieces2D/white-king.png b/app/src/main/resources/pieces2D/white-king.png new file mode 100644 index 0000000..479b9cc Binary files /dev/null and b/app/src/main/resources/pieces2D/white-king.png differ diff --git a/app/src/main/resources/pieces2D/white-knight.png b/app/src/main/resources/pieces2D/white-knight.png new file mode 100644 index 0000000..1572474 Binary files /dev/null and b/app/src/main/resources/pieces2D/white-knight.png differ diff --git a/app/src/main/resources/pieces2D/white-pawn.png b/app/src/main/resources/pieces2D/white-pawn.png new file mode 100644 index 0000000..b2ddcf0 Binary files /dev/null and b/app/src/main/resources/pieces2D/white-pawn.png differ diff --git a/app/src/main/resources/pieces2D/white-queen.png b/app/src/main/resources/pieces2D/white-queen.png new file mode 100644 index 0000000..b07b190 Binary files /dev/null and b/app/src/main/resources/pieces2D/white-queen.png differ diff --git a/app/src/main/resources/pieces2D/white-rook.png b/app/src/main/resources/pieces2D/white-rook.png new file mode 100644 index 0000000..359d911 Binary files /dev/null and b/app/src/main/resources/pieces2D/white-rook.png differ