diff --git a/app/src/main/java/chess/io/Command.java b/app/src/main/java/chess/io/Command.java new file mode 100644 index 0000000..717e3c0 --- /dev/null +++ b/app/src/main/java/chess/io/Command.java @@ -0,0 +1,7 @@ +package chess.io; + +import chess.model.Game; + +public abstract class Command { + public abstract CommandResult execute(Game game, OutputSystem outputSystem); +} diff --git a/app/src/main/java/chess/io/CommandExecutor.java b/app/src/main/java/chess/io/CommandExecutor.java new file mode 100644 index 0000000..0732a1e --- /dev/null +++ b/app/src/main/java/chess/io/CommandExecutor.java @@ -0,0 +1,37 @@ +package chess.io; + +import chess.model.Game; + +public class CommandExecutor { + + private final Game game; + private final OutputSystem outputSystem; + + public CommandExecutor(Game game, OutputSystem outputSystem) { + this.game = game; + this.outputSystem = outputSystem; + } + + public CommandResult executeCommand(Command command) { + CommandResult result = command.execute(this.game, this.outputSystem); + if (result == CommandResult.Moved) + alternatePlayers(); + return result; + } + + private void alternatePlayers() { + this.game.switchPlayerTurn(); + this.outputSystem.playerTurn(this.game.getPlayerTurn()); + } + + public Game getGame() { + return game; + } + + public OutputSystem getOutputSystem() { + return outputSystem; + } + + + +} diff --git a/app/src/main/java/chess/io/CommandResult.java b/app/src/main/java/chess/io/CommandResult.java new file mode 100644 index 0000000..8eb6b85 --- /dev/null +++ b/app/src/main/java/chess/io/CommandResult.java @@ -0,0 +1,5 @@ +package chess.io; + +public enum CommandResult { + Moved, NotMoved, NotAllowed; +} diff --git a/app/src/main/java/chess/io/OutputSystem.java b/app/src/main/java/chess/io/OutputSystem.java new file mode 100644 index 0000000..7138368 --- /dev/null +++ b/app/src/main/java/chess/io/OutputSystem.java @@ -0,0 +1,8 @@ +package chess.io; + +public abstract class OutputSystem implements OutputSystemInterface { + + public OutputSystem() { + } + +} diff --git a/app/src/main/java/chess/io/OutputSystemInterface.java b/app/src/main/java/chess/io/OutputSystemInterface.java new file mode 100644 index 0000000..2d2c023 --- /dev/null +++ b/app/src/main/java/chess/io/OutputSystemInterface.java @@ -0,0 +1,18 @@ +package chess.io; + +import chess.model.Color; + +public interface OutputSystemInterface { + + void playerTurn(Color color); + + void winnerIs(Color color); + + void kingIsInCheck(); + + void kingIsInMat(); + + void patSituation(); + + void hasSurrendered(Color color); +} diff --git a/app/src/main/java/chess/io/commands/CastlingCommand.java b/app/src/main/java/chess/io/commands/CastlingCommand.java new file mode 100644 index 0000000..38ce968 --- /dev/null +++ b/app/src/main/java/chess/io/commands/CastlingCommand.java @@ -0,0 +1,15 @@ +package chess.io.commands; + +import chess.io.Command; +import chess.io.CommandResult; +import chess.io.OutputSystem; +import chess.model.Game; + +public class CastlingCommand extends Command{ + + @Override + public CommandResult execute(Game game, OutputSystem outputSystem) { + return CommandResult.NotAllowed; + } + +} diff --git a/app/src/main/java/chess/io/commands/GrandCastlingCommand.java b/app/src/main/java/chess/io/commands/GrandCastlingCommand.java new file mode 100644 index 0000000..f02eb44 --- /dev/null +++ b/app/src/main/java/chess/io/commands/GrandCastlingCommand.java @@ -0,0 +1,15 @@ +package chess.io.commands; + +import chess.io.Command; +import chess.io.CommandResult; +import chess.io.OutputSystem; +import chess.model.Game; + +public class GrandCastlingCommand extends Command{ + + @Override + public CommandResult execute(Game game, OutputSystem outputSystem) { + return CommandResult.NotAllowed; + } + +} diff --git a/app/src/main/java/chess/io/commands/MoveCommand.java b/app/src/main/java/chess/io/commands/MoveCommand.java new file mode 100644 index 0000000..5d9bc9d --- /dev/null +++ b/app/src/main/java/chess/io/commands/MoveCommand.java @@ -0,0 +1,63 @@ +package chess.io.commands; + +import chess.io.Command; +import chess.io.CommandResult; +import chess.io.OutputSystem; +import chess.model.ChessBoard; +import chess.model.Color; +import chess.model.Game; +import chess.model.Move; +import chess.model.Piece; +import chess.model.visitor.PiecePathChecker; + +public class MoveCommand extends Command { + private final Move move; + + public MoveCommand(Move move) { + this.move = move; + } + + @Override + public CommandResult execute(Game game, OutputSystem outputSystem) { + final ChessBoard board = game.getBoard(); + + Piece piece = board.pieceAt(move.getStart()); + if (piece == null) + return CommandResult.NotAllowed; + + if (piece.getColor() != game.getPlayerTurn()) + return CommandResult.NotAllowed; + + boolean valid = new PiecePathChecker(board, move).isValid(); + if (!valid) + return CommandResult.NotAllowed; + + board.applyMove(move); + + if (board.isKingInCheck(game.getPlayerTurn())) { + board.undoLastMove(); + return CommandResult.NotAllowed; + } + + checkGameStatus(game, outputSystem); + + return CommandResult.Moved; + } + + private void checkGameStatus(Game game, OutputSystem outputSystem) { + final ChessBoard board = game.getBoard(); + + final Color enemy = Color.getEnemy(game.getPlayerTurn()); + + if (board.isKingInCheck(enemy)) { + if (board.hasAllowedMoves(enemy)) { + outputSystem.kingIsInCheck(); + } else { + outputSystem.kingIsInMat(); + outputSystem.winnerIs(game.getPlayerTurn()); + } + } else if(!board.hasAllowedMoves(enemy)) { + outputSystem.patSituation(); + } + } +} diff --git a/app/src/main/java/chess/io/commands/NewGameCommand.java b/app/src/main/java/chess/io/commands/NewGameCommand.java new file mode 100644 index 0000000..74ebe9b --- /dev/null +++ b/app/src/main/java/chess/io/commands/NewGameCommand.java @@ -0,0 +1,54 @@ +package chess.io.commands; + +import chess.io.Command; +import chess.io.CommandResult; +import chess.io.OutputSystem; +import chess.model.ChessBoard; +import chess.model.Color; +import chess.model.Coordinate; +import chess.model.Game; +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 NewGameCommand extends Command { + public CommandResult execute(Game game, OutputSystem outputSystem) { + ChessBoard board = game.getBoard(); + + for (int i = 0; i < 8; i++) { + board.pieceComes(new Pawn(Color.Black), new Coordinate(i, 0)); + board.pieceComes(new Pawn(Color.White), new Coordinate(i, Coordinate.VALUE_MAX - 1)); + } + + board.pieceComes(new Rook(Color.Black), new Coordinate(0, 1)); + board.pieceComes(new Rook(Color.Black), new Coordinate(Coordinate.VALUE_MAX - 1, 1)); + + board.pieceComes(new Rook(Color.White), new Coordinate(0, Coordinate.VALUE_MAX - 2)); + board.pieceComes(new Rook(Color.White), new Coordinate(Coordinate.VALUE_MAX - 1, Coordinate.VALUE_MAX - 2)); + + board.pieceComes(new Knight(Color.Black), new Coordinate(1, 1)); + board.pieceComes(new Knight(Color.Black), new Coordinate(Coordinate.VALUE_MAX - 2, 1)); + + board.pieceComes(new Knight(Color.White), new Coordinate(1, Coordinate.VALUE_MAX - 2)); + board.pieceComes(new Knight(Color.White), new Coordinate(Coordinate.VALUE_MAX - 2, Coordinate.VALUE_MAX - 2)); + + board.pieceComes(new Bishop(Color.Black), new Coordinate(2, 1)); + board.pieceComes(new Bishop(Color.Black), new Coordinate(Coordinate.VALUE_MAX - 3, 1)); + + board.pieceComes(new Bishop(Color.White), new Coordinate(2, Coordinate.VALUE_MAX - 2)); + board.pieceComes(new Bishop(Color.White), new Coordinate(Coordinate.VALUE_MAX - 3, Coordinate.VALUE_MAX - 2)); + + board.pieceComes(new Queen(Color.Black), new Coordinate(4, 1)); + board.pieceComes(new King(Color.Black), new Coordinate(3, 1)); + + board.pieceComes(new Queen(Color.White), new Coordinate(4, Coordinate.VALUE_MAX - 2)); + board.pieceComes(new King(Color.White), new Coordinate(3, Coordinate.VALUE_MAX - 2)); + + game.resetPlayerTurn(); + + return CommandResult.NotMoved; + } +} diff --git a/app/src/main/java/chess/io/commands/SurrenderCommand.java b/app/src/main/java/chess/io/commands/SurrenderCommand.java new file mode 100644 index 0000000..3aa73b4 --- /dev/null +++ b/app/src/main/java/chess/io/commands/SurrenderCommand.java @@ -0,0 +1,24 @@ +package chess.io.commands; + +import chess.io.Command; +import chess.io.CommandResult; +import chess.io.OutputSystem; +import chess.model.Color; +import chess.model.Game; + +public class SurrenderCommand extends Command { + + private final Color player; + + public SurrenderCommand(Color player) { + this.player = player; + } + + @Override + public CommandResult execute(Game game, OutputSystem outputSystem) { + outputSystem.hasSurrendered(player); + outputSystem.winnerIs(Color.getEnemy(player)); + return CommandResult.NotMoved; + } + +} diff --git a/app/src/main/java/chess/model/ChessBoard.java b/app/src/main/java/chess/model/ChessBoard.java index d0fd9f8..aed435c 100644 --- a/app/src/main/java/chess/model/ChessBoard.java +++ b/app/src/main/java/chess/model/ChessBoard.java @@ -1,11 +1,7 @@ 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 java.util.Stack; + import chess.model.visitor.KingIdentifier; import chess.model.visitor.PiecePathChecker; @@ -28,6 +24,8 @@ public class ChessBoard { } private final Cell[][] cells; + private final Stack moves; + private final Stack ejectedPieces; public ChessBoard() { this.cells = new Cell[Coordinate.VALUE_MAX][Coordinate.VALUE_MAX]; @@ -36,23 +34,43 @@ public class ChessBoard { this.cells[i][j] = new Cell(); } } - initPieces(); + this.moves = new Stack<>(); + this.ejectedPieces = new Stack<>(); } public void applyMove(Move move) { assert (move.isValid()); Piece deadPiece = pieceAt(move.getFinish()); if (deadPiece != null) { - deadPiece.eject(); + deadPiece.eject(this.moves.size()); + this.ejectedPieces.add(deadPiece); } Piece movingPiece = pieceAt(move.getStart()); pieceComes(movingPiece, move.getFinish()); pieceLeaves(move.getStart()); movingPiece.move(); + this.moves.add(move); } - public void undoMove(Move move) { - // for later + public void undoLastMove() { + assert !this.moves.empty() : "Can't undo at the beginning!"; + + Move move = this.moves.pop(); + + Piece movingPiece = pieceAt(move.getFinish()); + pieceComes(movingPiece, move.getStart()); + pieceLeaves(move.getFinish()); + movingPiece.unMove(); + + if (this.ejectedPieces.empty()) + return; + + Piece piece = this.ejectedPieces.lastElement(); + if (piece.getEjectedMoveNumber() != this.moves.size()) + return; + + pieceComes(piece, move.getFinish()); + piece.eject(-1); } public boolean isCellEmpty(Coordinate coordinate) { @@ -77,11 +95,11 @@ public class ChessBoard { return this.cells[coordinate.getX()][coordinate.getY()]; } - private void pieceComes(Piece piece, Coordinate coordinate) { + public void pieceComes(Piece piece, Coordinate coordinate) { cellAt(coordinate).setPiece(piece); } - private void pieceLeaves(Coordinate coordinate) { + public void pieceLeaves(Coordinate coordinate) { cellAt(coordinate).setPiece(null); } @@ -119,49 +137,55 @@ public class ChessBoard { return false; } + public boolean hasAllowedMoves(Color player) { + 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; + + if (attackPiece.getColor() != player) + continue; + + if (getAllowedMoves(attackCoords) != null) + 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]; + Color player = piece.getColor(); + + 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(); + Move move = new Move(pieceCoords, new Coordinate(i, j)); + PiecePathChecker piecePathChecker = new PiecePathChecker(this, + move); + if (!piecePathChecker.isValid()) + continue; + + applyMove(move); + result[i][j] = !isKingInCheck(player); + undoLastMove(); } } - 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)); + // return null is no valid moves are possible + for (int x = 0; x < 8; x++) { + for (int y = 0; y < 8; y++) { + if (result[x][y]) + return result; + } } - 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)); + return null; } + } diff --git a/app/src/main/java/chess/model/Color.java b/app/src/main/java/chess/model/Color.java index 9ac4903..7432476 100644 --- a/app/src/main/java/chess/model/Color.java +++ b/app/src/main/java/chess/model/Color.java @@ -2,5 +2,9 @@ package chess.model; public enum Color { White, - Black + Black; + + public static Color getEnemy(Color color) { + return color == White ? Black : White; + } } diff --git a/app/src/main/java/chess/model/Game.java b/app/src/main/java/chess/model/Game.java index 67f1f2c..18fafea 100644 --- a/app/src/main/java/chess/model/Game.java +++ b/app/src/main/java/chess/model/Game.java @@ -1,14 +1,8 @@ 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<>(); + private Color playerTurn; public Game(ChessBoard board) { this.board = board; @@ -18,14 +12,28 @@ public class Game { 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(); + public Color getPlayerTurn() { + return playerTurn; + } + + public void resetPlayerTurn() { + this.playerTurn = Color.White; + } + + public void switchPlayerTurn() { + playerTurn = Color.getEnemy(playerTurn); + } + + public boolean[][] getAllowedMoves(Coordinate coordinate) { + Piece piece = this.board.pieceAt(coordinate); + + if (piece == null) + return null; + + if (piece.getColor() != getPlayerTurn()) + return null; + + return this.board.getAllowedMoves(coordinate); } } diff --git a/app/src/main/java/chess/model/Piece.java b/app/src/main/java/chess/model/Piece.java index 47d17c8..d35ff97 100644 --- a/app/src/main/java/chess/model/Piece.java +++ b/app/src/main/java/chess/model/Piece.java @@ -3,15 +3,17 @@ package chess.model; public abstract class Piece { private final Color color; - private boolean moved; + private int moved; + private int ejectedMoveNumber; public Piece(Color color) { this.color = color; - this.moved = false; + this.moved = 0; + this.ejectedMoveNumber = -1; } public void move() { - this.moved = true; + this.moved++; } public Color getColor() { @@ -19,11 +21,23 @@ public abstract class Piece { } public boolean hasMoved() { - return moved; + return moved > 0; } - public void eject() { - // for later + public void unMove() { + this.moved--; + } + + public boolean isEjected() { + return this.ejectedMoveNumber != -1; + } + + public int getEjectedMoveNumber() { + return ejectedMoveNumber; + } + + public void eject(int moveNumber) { + this.ejectedMoveNumber = moveNumber; } public abstract T accept(PieceVisitor visitor); diff --git a/app/src/main/java/chess/simplerender/Window.java b/app/src/main/java/chess/simplerender/Window.java index 80baf2d..895453b 100644 --- a/app/src/main/java/chess/simplerender/Window.java +++ b/app/src/main/java/chess/simplerender/Window.java @@ -9,6 +9,12 @@ import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; +import chess.io.Command; +import chess.io.CommandExecutor; +import chess.io.CommandResult; +import chess.io.OutputSystem; +import chess.io.commands.MoveCommand; +import chess.io.commands.NewGameCommand; import chess.model.ChessBoard; import chess.model.Coordinate; import chess.model.Game; @@ -17,25 +23,66 @@ import chess.model.Move; public class Window extends JFrame { private final JLabel cells[][]; - private final Game game; + private final CommandExecutor commandExecutor; 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(); + this.commandExecutor = new CommandExecutor(game, initSlots()); + this.board = this.commandExecutor.getGame().getBoard(); build(); setSize(800, 800); setVisible(true); setDefaultCloseOperation(EXIT_ON_CLOSE); + sendCommand(new NewGameCommand()); + updateBoard(); } - private void initSlots() { - this.game.OnRenderUpdate.connect(this::updateBoard); - this.game.OnMoveRefused.connect(this::drawInvalid); + private OutputSystem initSlots() { + OutputSystem outputSystem = new OutputSystem() { + + @Override + public void playerTurn(chess.model.Color color) { + System.out.println("Player turn " + color); + } + + @Override + public void winnerIs(chess.model.Color color) { + System.out.println("Winner is " + color); + } + + @Override + public void kingIsInCheck() { + System.out.println("Check"); + } + + @Override + public void kingIsInMat() { + System.out.println("CheckMate"); + } + + @Override + public void patSituation() { + System.out.println("Pat"); + } + + @Override + public void hasSurrendered(chess.model.Color color) { + System.out.println("Surrendered"); + } + + }; + return outputSystem; + } + + private CommandResult sendCommand(Command command) { + CommandResult result = this.commandExecutor.executeCommand(command); + if (result == CommandResult.Moved) { + updateBoard(); + } + return result; } private Color getCellColor(int x, int y) { @@ -77,10 +124,10 @@ public class Window extends JFrame { } } - private void previewMoves(int x, int y) { - boolean[][] allowedMoves = this.board.getAllowedMoves(new Coordinate(x, y)); + private boolean previewMoves(int x, int y) { + boolean[][] allowedMoves = this.commandExecutor.getGame().getAllowedMoves(new Coordinate(x, y)); if (allowedMoves == null) - return; + return false; for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { JLabel cell = this.cells[i][j]; @@ -88,6 +135,7 @@ public class Window extends JFrame { cell.setBackground(Color.CYAN); } } + return true; } private void drawInvalid(Move move) { @@ -111,12 +159,17 @@ public class Window extends JFrame { if (this.lastClick == null) { if (this.board.isCellEmpty(new Coordinate(x, y))) return; + if (!previewMoves(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))); + if (!this.lastClick.equals(new Coordinate(x, y))) { + Move move = new Move(lastClick, new Coordinate(x, y)); + if (sendCommand(new MoveCommand(move)) == CommandResult.NotAllowed) { + drawInvalid(move); + } + } this.lastClick = null; }