package chess.view.DDDrender; import java.io.IOException; import java.util.List; import java.util.function.Consumer; import java.util.function.Function; import chess.controller.commands.*; import imgui.ImGui; import imgui.type.ImBoolean; import org.joml.Vector2f; import org.joml.Vector3f; import chess.controller.Command.CommandResult; import chess.controller.CommandExecutor; import chess.controller.CommandSender; import chess.controller.event.GameAdapter; import chess.model.Color; import chess.model.Coordinate; import chess.model.Move; import chess.model.Piece; import chess.view.DDDrender.world.BoardEntity; import chess.view.DDDrender.world.PieceEntity; import chess.view.DDDrender.world.World; public class DDDView extends GameAdapter implements CommandSender { private static final Vector3f BLACK = new Vector3f(0.3f, 0.3f, 0.3f); private static final Vector3f WHITE = new Vector3f(1.0f, 1.0f, 1.0f); private static final Vector3f RED = new Vector3f(1.0f, 0.0f, 0.0f); private static final Vector3f YELLOW = new Vector3f(1.0f, 1.0f, 0.0f); private final CommandExecutor commandExecutor; private final Window window; private final World world; private BoardEntity boardEntity; private final Camera camera; private Coordinate click = null; private static final float animationTime = 1.5f; // in seconds private static final int animationTurns = 1; private float moveProgress = 0.0f; private String waitingPopup = null; private final ImBoolean popupOpened = new ImBoolean(false); public DDDView(CommandExecutor commandExecutor) { this.commandExecutor = commandExecutor; this.world = new World(); this.camera = new Camera(); this.window = new Window(new Renderer(), this.world, this.camera); } @Override public CommandExecutor getCommandExecutor() { return this.commandExecutor; } private void cancelClick() { this.click = null; } private void setClick(Coordinate coordinate) { this.click = coordinate; } // Invoked when a cell is clicked private void onCellClick(Coordinate coordinate) { if (this.click == null) { // case: first click List allowedMoves = getPieceAllowedMoves(coordinate); if (allowedMoves.isEmpty()) { // case: no movement possible for piece System.out.println("This piece cannot be moved at the moment."); return; } setClick(coordinate); previewMoves(coordinate); System.out.println("First click on " + coordinate); return; } // case: second click GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(this.click); if (sendCommand(movesCommand) == CommandResult.NotAllowed) { // case: invalid piece to move cancelPreview(this.click); System.out.println("Nothing to do here."); cancelClick(); return; } List allowedMoves = movesCommand.getDestinations(); if (allowedMoves.isEmpty()) { // case: no movement possible for piece cancelPreview(this.click); System.out.println("This piece cannot be moved at the moment."); cancelClick(); return; } if (allowedMoves.contains(coordinate)) { // case: valid attempt to move System.out.println("Move on " + coordinate); cancelPreview(this.click); sendMove(new Move(click, coordinate)); cancelClick(); return; } if (!(coordinate == this.click)) { System.out.println("New click on " + coordinate); // cases: invalid move, selecting another piece cancelPreview(this.click); previewMoves(coordinate); setClick(coordinate); return; } System.out.println("Cancelling click."); // case: cancelling previous click cancelClick(); } private void previewMoves(Coordinate coordinate) { List allowedMoves = getPieceAllowedMoves(coordinate); if (allowedMoves.isEmpty()) return; this.boardEntity.setCellColor(coordinate, RED); this.world.getPiece(coordinate).setColor(RED); for (Coordinate destCoord : allowedMoves) { this.boardEntity.setCellColor(destCoord, YELLOW); } } // Invoked when a cell is hovered private void onCellEnter(Coordinate coordinate) { if (this.click == null) { // small test turning a cell red when hovered this.boardEntity.setCellColor(coordinate, RED); Piece p = pieceAt(coordinate); if (p == null) return; this.world.getPiece(coordinate).setColor(RED); List allowedMoves = getPieceAllowedMoves(coordinate); if (allowedMoves.isEmpty()) return; for (Coordinate destCoord : allowedMoves) { this.boardEntity.setCellColor(destCoord, RED); } } } // Invoked when a cell is not hovered anymore private void onCellExit(Coordinate coordinate) { if (this.click == null) { this.boardEntity.resetCellColor(coordinate); Piece p = pieceAt(coordinate); if (p == null) return; PieceEntity pEntity = this.world.getPiece(coordinate); if (pEntity == null) return; pEntity.setColor(p.getColor() == Color.White ? WHITE : BLACK); List allowedMoves = getPieceAllowedMoves(coordinate); if (allowedMoves.isEmpty()) return; for (Coordinate destCoord : allowedMoves) { this.boardEntity.resetCellColor(destCoord); } } } private void cancelPreview(Coordinate coordinate) { this.boardEntity.resetCellColor(coordinate); Piece p = pieceAt(coordinate); if (p == null) return; this.world.getPiece(coordinate).setColor(p.getColor() == Color.White ? WHITE : BLACK); List allowedMoves = getPieceAllowedMoves(coordinate); if (allowedMoves.isEmpty()) return; for (Coordinate destCoord : allowedMoves) { this.boardEntity.resetCellColor(destCoord); } } private Piece pieceAt(Coordinate pos) { GetPieceAtCommand cmd = new GetPieceAtCommand(pos); this.commandExecutor.executeCommand(cmd); return cmd.getPiece(); } @Override public void onGameStart() { this.window.scheduleTask(() -> { try { initBoard(); } catch (IOException e) { e.printStackTrace(); } // start listening to mouse events this.window.OnCellClick.connect(this::onCellClick); this.window.OnCellEnter.connect(this::onCellEnter); this.window.OnCellExit.connect(this::onCellExit); this.window.OnImGuiTopRender.connect(this::onHeaderRender); this.window.OnImGuiBottomRender.connect(this::onFooterRender); }); } private void onHeaderRender() { ImGui.text("FPS : " + (int) ImGui.getIO().getFramerate()); } private void onFooterRender() { if (ImGui.button("Roque")) { sendCastling(); } ImGui.sameLine(); if (ImGui.button("Grand Roque")) { sendBigCastling(); } ImGui.sameLine(); if (ImGui.button("Annuler le coup précédent")) { sendUndo(); } openPopup(); renderPopups(); } private void openPopup() { if (waitingPopup != null) { ImGui.openPopup(waitingPopup); waitingPopup = null; } } private void initBoard() throws IOException { for (int i = 0; i < Coordinate.VALUE_MAX; i++) { for (int j = 0; j < Coordinate.VALUE_MAX; j++) { Coordinate pos = new Coordinate(i, j); Piece piece = pieceAt(pos); if (piece == null) continue; Vector2f pieceBoardPos = DDDPlacement.coordinatesToVector(pos); Vector3f pieceWorldPos = new Vector3f(pieceBoardPos.x(), 0, pieceBoardPos.y()); PieceEntity entity = new PieceEntity(piece, pieceWorldPos, piece.getColor() == Color.White ? WHITE : BLACK, piece.getColor() == Color.White ? 0.0f : (float) Math.PI); this.world.addPiece(entity, pos); } } this.boardEntity = new BoardEntity(); this.world.addEntity(this.boardEntity); } /** * @param begin begin * @param middle control point * @param end end * @param t between 0 and 1 * @return the point */ private Vector3f bezierCurve(Vector3f begin, Vector3f middle, Vector3f end, float t) { return begin.mul((1.0f - t) * (1.0f - t)).add(middle.mul(2.0f * t * (1.0f - t))).add(end.mul(t * t)); } private Function lagrangeInterpolation(Vector3f begin, Vector3f middle, Vector3f end) { return (t) -> { return t + 1.0f; }; } private void pieceTick(float progress, PieceEntity piece, Move move) { float height = 1; // how much the piece is raised Vector2f pieceStartBoard = DDDPlacement.coordinatesToVector(move.getStart()); Vector2f pieceDestinationBoard = DDDPlacement.coordinatesToVector(move.getFinish()); Vector3f start = new Vector3f(pieceStartBoard.x(), 0, pieceStartBoard.y()); Vector3f top = new Vector3f(pieceDestinationBoard.x() - pieceStartBoard.x(), height, pieceDestinationBoard.y() - pieceStartBoard.y()); Vector3f end = new Vector3f(pieceDestinationBoard.x(), 0, pieceDestinationBoard.y()); piece.setPosition(bezierCurve(start, top, end, progress)); } private void move3DPiece(Move move) { Vector2f pieceDestBoardPos = DDDPlacement.coordinatesToVector(move.getFinish()); Vector3f pieceDestWorldPos = new Vector3f(pieceDestBoardPos.x(), 0, pieceDestBoardPos.y()); final PieceEntity pEntity = this.world.getPiece(move.getStart()); final Move pMove = move; this.moveProgress = 0.0f; Consumer moveConsumer = (delta) -> { this.moveProgress += delta; pieceTick(this.moveProgress, pEntity, pMove); }; this.window.addRegularTask(moveConsumer); try { Thread.sleep(1000); } catch (InterruptedException e) { } this.window.removeRegularTask(moveConsumer); if (move.getDeadPieceCoords() != null) { this.world.ejectPiece(move.getDeadPieceCoords()); } pEntity.setPosition(pieceDestWorldPos); this.world.movePiece(pEntity, move); } @Override public void onMove(Move move, boolean captured) { move3DPiece(move); cameraRotate(); } private void cameraTick(float delta) { int oddAnimationTurn = (2 * (animationTurns - 1)) + 1; final float angle = (float) Math.PI; this.camera.setRotateAngle(this.camera.getRotateAngle() + angle * delta * oddAnimationTurn / animationTime); } private void cameraRotate() { float end = this.camera.getRotateAngle() + (float) Math.PI; Consumer rotationConsumer = this::cameraTick; this.window.addRegularTask(rotationConsumer); try { Thread.sleep((long) (animationTime * 1000)); } catch (InterruptedException e) { throw new RuntimeException(e); } this.window.removeRegularTask(rotationConsumer); this.camera.setRotateAngle(end); } public void run() { this.window.run(); // free OpenGL resources try { this.window.close(); this.world.close(); } catch (IOException e) { e.printStackTrace(); } } private void renderPopup(String title, String text) { if (ImGui.beginPopupModal(title, popupOpened)) { ImGui.text(text); ImGui.endPopup(); } } private void renderPopups() { renderPopup("Check", "Your king is in check"); renderPopup("Checkmate", "Checkmate, it's a win!"); renderPopup("Promotion", "Please promote your pawn."); renderPopup("Pat", "It's a pat!"); renderPopup("Tie", "It's a tie!"); renderPopup("White surrender", "The white player has surrendered!"); renderPopup("Black surrender", "The black player has surrendered!"); renderPopup("White victory", "The white player has won !"); renderPopup("Black victory", "The black player has won !"); renderPopup("End", "End of the game, thank you for playing!"); } private void openPopup(String title) { this.popupOpened.set(true); this.waitingPopup = title; } @Override public void onKingInCheck() { openPopup("Check"); } @Override public void onDraw() { openPopup("Tie"); } @Override public void onKingInMat() { openPopup("Checkmate"); } @Override public void onGameEnd() { openPopup("End"); } @Override public void onPatSituation() { openPopup("Pat"); } @Override public void onSurrender(Color color) { openPopup(color == Color.White ? "White surrender" : "Black surrender"); openPopup(color == Color.White ? "Black victory" : "White victory"); } }