feat: popups

This commit is contained in:
fl.du.pr Grens
2025-05-16 17:53:01 +02:00
parent 6cec5d9e31
commit 459e458028

View File

@@ -3,18 +3,18 @@ package chess.view.DDDrender;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function;
import chess.controller.commands.MoveCommand; import chess.controller.commands.*;
import chess.controller.event.GameListener; import chess.controller.event.GameListener;
import imgui.ImGui; import imgui.ImGui;
import imgui.type.ImBoolean;
import org.joml.Vector2f; import org.joml.Vector2f;
import org.joml.Vector3f; import org.joml.Vector3f;
import chess.controller.Command; import chess.controller.Command;
import chess.controller.Command.CommandResult; import chess.controller.Command.CommandResult;
import chess.controller.CommandExecutor; import chess.controller.CommandExecutor;
import chess.controller.commands.GetAllowedMovesPieceCommand;
import chess.controller.commands.GetPieceAtCommand;
import chess.controller.event.GameAdaptator; import chess.controller.event.GameAdaptator;
import chess.model.Color; import chess.model.Color;
import chess.model.Coordinate; import chess.model.Coordinate;
@@ -26,256 +26,395 @@ import chess.view.DDDrender.world.World;
public class DDDView extends GameAdaptator implements GameListener { public class DDDView extends GameAdaptator implements GameListener {
private static final Vector3f BLACK = new Vector3f(0.3f, 0.3f, 0.3f); 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 WHITE = new Vector3f(1.0f, 1.0f, 1.0f);
private final CommandExecutor commandExecutor; private final CommandExecutor commandExecutor;
private final Window window; private final Window window;
private final World world; private final World world;
private BoardEntity boardEntity; private BoardEntity boardEntity;
private final Camera camera; private final Camera camera;
private Coordinate click = null; private Coordinate click = null;
private static final float animationTime = 1.5f; // in seconds private static final float animationTime = 1.5f; // in seconds
private static final int animationTurns = 1; private static final int animationTurns = 1;
public DDDView(CommandExecutor commandExecutor) { private float moveProgress = 0.0f;
this.commandExecutor = commandExecutor;
this.world = new World();
this.camera = new Camera();
this.window = new Window(new Renderer(), this.world, this.camera);
}
private void cancelClick(){ private String waitingPopup = null;
this.click=null; private final ImBoolean popupOpened = new ImBoolean(false);
}
private void setClick(Coordinate coordinate) {this.click=coordinate;}
private CommandResult sendCommand(Command command) {
return this.commandExecutor.executeCommand(command);
}
// Invoked when a cell is clicked public DDDView(CommandExecutor commandExecutor) {
private void onCellClick(Coordinate coordinate) { this.commandExecutor = commandExecutor;
if (this.click == null){ // case: first click this.world = new World();
GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate); this.camera = new Camera();
if (sendCommand(movesCommand) == CommandResult.NotAllowed) { // case: invalid piece to move this.window = new Window(new Renderer(), this.world, this.camera);
System.out.println("Nothing to do here."); }
return;
}
List<Coordinate> allowedMoves = movesCommand.getDestinations();
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<Coordinate> 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);
Command.CommandResult result = sendCommand(new MoveCommand(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){ private void cancelClick() {
Piece p = pieceAt(coordinate); this.click = null;
if (p == null) { }
return;
}
GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate);
if (sendCommand(movesCommand) == CommandResult.NotAllowed) {
return;
}
this.boardEntity.setCellColor(coordinate, new Vector3f(1, 0, 0));
this.world.getPiece(coordinate).setColor(new Vector3f(1, 0, 0));
List<Coordinate> allowedMoves = movesCommand.getDestinations();
if (allowedMoves.isEmpty())
return;
for (Coordinate destCoord : allowedMoves) {
this.boardEntity.setCellColor(destCoord, new Vector3f(1, 1, 0));
}
}
// Invoked when a cell is hovered private void setClick(Coordinate coordinate) {
private void onCellEnter(Coordinate coordinate) { this.click = coordinate;
if (this.click == null){ }
// small test turning a cell red when hovered
this.boardEntity.setCellColor(coordinate, new Vector3f(1, 0, 0));
Piece p = pieceAt(coordinate);
if (p == null)
return;
this.world.getPiece(coordinate).setColor(new Vector3f(1, 0, 0));
GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate);
if (sendCommand(movesCommand) == CommandResult.NotAllowed)
return ;
List<Coordinate> allowedMoves = movesCommand.getDestinations();
if (allowedMoves.isEmpty())
return ;
for (Coordinate destCoord : allowedMoves) {
this.boardEntity.setCellColor(destCoord, new Vector3f(1, 0, 0));
}
}
}
// Invoked when a cell is not hovered anymore private CommandResult sendCommand(Command command) {
private void onCellExit(Coordinate coordinate) { return this.commandExecutor.executeCommand(command);
if (this.click == null){ }
this.boardEntity.resetCellColor(coordinate);
Piece p = pieceAt(coordinate);
if (p == null)
return;
this.world.getPiece(coordinate).setColor(p.getColor() == Color.White ? WHITE : BLACK);
GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate);
if (sendCommand(movesCommand) == CommandResult.NotAllowed)
return ;
List<Coordinate> allowedMoves = movesCommand.getDestinations();
if (allowedMoves.isEmpty())
return ;
for (Coordinate destCoord : allowedMoves) {
this.boardEntity.resetCellColor(destCoord);
}
}
}
private void cancelPreview(Coordinate coordinate){ // Invoked when a cell is clicked
this.boardEntity.resetCellColor(coordinate); private void onCellClick(Coordinate coordinate) {
Piece p = pieceAt(coordinate); if (this.click == null) { // case: first click
if (p == null) GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate);
return; if (sendCommand(movesCommand) == CommandResult.NotAllowed) { // case: invalid piece to move
this.world.getPiece(coordinate).setColor(p.getColor() == Color.White ? WHITE : BLACK); System.out.println("Nothing to do here.");
GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate); return;
if (sendCommand(movesCommand) == CommandResult.NotAllowed) }
return ; List<Coordinate> allowedMoves = movesCommand.getDestinations();
List<Coordinate> allowedMoves = movesCommand.getDestinations(); if (allowedMoves.isEmpty()) { // case: no movement possible for piece
if (allowedMoves.isEmpty()) System.out.println("This piece cannot be moved at the moment.");
return ; return;
for (Coordinate destCoord : allowedMoves) { }
this.boardEntity.resetCellColor(destCoord); 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<Coordinate> 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);
Command.CommandResult result = sendCommand(new MoveCommand(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 Piece pieceAt(Coordinate pos) { private void previewMoves(Coordinate coordinate) {
GetPieceAtCommand cmd = new GetPieceAtCommand(pos); Piece p = pieceAt(coordinate);
this.commandExecutor.executeCommand(cmd); if (p == null) {
return cmd.getPiece(); return;
} }
GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate);
if (sendCommand(movesCommand) == CommandResult.NotAllowed) {
return;
}
this.boardEntity.setCellColor(coordinate, new Vector3f(1, 0, 0));
this.world.getPiece(coordinate).setColor(new Vector3f(1, 0, 0));
List<Coordinate> allowedMoves = movesCommand.getDestinations();
if (allowedMoves.isEmpty())
return;
for (Coordinate destCoord : allowedMoves) {
this.boardEntity.setCellColor(destCoord, new Vector3f(1, 1, 0));
}
}
@Override // Invoked when a cell is hovered
public void onGameStart() { private void onCellEnter(Coordinate coordinate) {
this.window.scheduleTask(() -> { if (this.click == null) {
try { // small test turning a cell red when hovered
initBoard(); this.boardEntity.setCellColor(coordinate, new Vector3f(1, 0, 0));
} catch (IOException e) { Piece p = pieceAt(coordinate);
e.printStackTrace(); if (p == null)
} return;
// start listening to mouse events this.world.getPiece(coordinate).setColor(new Vector3f(1, 0, 0));
this.window.OnCellClick.connect(this::onCellClick); GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate);
this.window.OnCellEnter.connect(this::onCellEnter); if (sendCommand(movesCommand) == CommandResult.NotAllowed)
this.window.OnCellExit.connect(this::onCellExit); return;
this.window.OnImGuiTopRender.connect(this::onHeaderRender); List<Coordinate> allowedMoves = movesCommand.getDestinations();
this.window.OnImGuiBottomRender.connect(this::onFooterRender); if (allowedMoves.isEmpty())
}); return;
} for (Coordinate destCoord : allowedMoves) {
this.boardEntity.setCellColor(destCoord, new Vector3f(1, 0, 0));
}
}
}
private void onHeaderRender() { // Invoked when a cell is not hovered anymore
ImGui.text("FPS : " + ImGui.getIO().getFramerate()); 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;
private void onFooterRender() { pEntity.setColor(p.getColor() == Color.White ? WHITE : BLACK);
ImGui.button("Coucou"); GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate);
} if (sendCommand(movesCommand) == CommandResult.NotAllowed)
return;
List<Coordinate> allowedMoves = movesCommand.getDestinations();
if (allowedMoves.isEmpty())
return;
for (Coordinate destCoord : allowedMoves) {
this.boardEntity.resetCellColor(destCoord);
}
}
}
private void initBoard() throws IOException { private void cancelPreview(Coordinate coordinate) {
for (int i = 0; i < Coordinate.VALUE_MAX; i++) { this.boardEntity.resetCellColor(coordinate);
for (int j = 0; j < Coordinate.VALUE_MAX; j++) { Piece p = pieceAt(coordinate);
Coordinate pos = new Coordinate(i, j); if (p == null)
Piece piece = pieceAt(pos); return;
if (piece == null) this.world.getPiece(coordinate).setColor(p.getColor() == Color.White ? WHITE : BLACK);
continue; GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate);
if (sendCommand(movesCommand) == CommandResult.NotAllowed)
return;
List<Coordinate> allowedMoves = movesCommand.getDestinations();
if (allowedMoves.isEmpty())
return;
for (Coordinate destCoord : allowedMoves) {
this.boardEntity.resetCellColor(destCoord);
}
}
Vector2f pieceBoardPos = DDDPlacement.coordinatesToVector(pos); private Piece pieceAt(Coordinate pos) {
Vector3f pieceWorldPos = new Vector3f(pieceBoardPos.x(), 0, pieceBoardPos.y()); GetPieceAtCommand cmd = new GetPieceAtCommand(pos);
this.commandExecutor.executeCommand(cmd);
return cmd.getPiece();
}
PieceEntity entity = new PieceEntity(piece, pieceWorldPos, @Override
piece.getColor() == Color.White ? WHITE : BLACK, public void onGameStart() {
piece.getColor() == Color.White ? 0.0f : (float) Math.PI); 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);
});
}
this.world.addPiece(entity, pos); private void onHeaderRender() {
} ImGui.text("FPS : " + ImGui.getIO().getFramerate());
} }
this.boardEntity = new BoardEntity();
this.world.addEntity(this.boardEntity);
}
@Override private void onFooterRender() {
public void onMove(Move move) { if (ImGui.button("Roque")) {
if(move.getDeadPieceCoords() != null) { sendCommand(new CastlingCommand(false));
this.world.ejectPiece(move.getDeadPieceCoords()); }
} ImGui.sameLine();
Vector2f pieceBoardPos = DDDPlacement.coordinatesToVector(move.getFinish()); if (ImGui.button("Grand Roque")) {
Vector3f pieceWorldPos = new Vector3f(pieceBoardPos.x(), 0, pieceBoardPos.y()); sendCommand(new CastlingCommand(true));
this.world.getPiece(move.getStart()).setPosition(pieceWorldPos); }
this.world.movePiece(this.world.getPiece(move.getStart()), move); ImGui.sameLine();
cameraRotate(); if (ImGui.button("Annuler le coup précédent")) {
} sendCommand(new UndoCommand());
}
openPopup();
renderPopups();
}
private void cameraTick(float delta) { private void openPopup() {
int oddAnimationTurn = (2 * (animationTurns-1)) + 1; if (waitingPopup != null) {
final float angle = (float) Math.PI; ImGui.openPopup(waitingPopup);
this.camera.setRotateAngle(this.camera.getRotateAngle() + angle * delta * oddAnimationTurn / animationTime); waitingPopup = null;
} }
}
public void cameraRotate() { private void initBoard() throws IOException {
float end = this.camera.getRotateAngle() + (float) Math.PI; for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
Consumer<Float> rotationConsumer = this::cameraTick; for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
this.window.addRegularTask(rotationConsumer); 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<Float, Float> 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<Float> 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) {
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<Float> rotationConsumer = this::cameraTick;
this.window.addRegularTask(rotationConsumer);
try { try {
Thread.sleep((long) (animationTime * 1000)); Thread.sleep((long) (animationTime * 1000));
} catch (InterruptedException e) { } catch (InterruptedException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
this.window.removeRegularTask(rotationConsumer); this.window.removeRegularTask(rotationConsumer);
this.camera.setRotateAngle(end); this.camera.setRotateAngle(end);
} }
public void run() { public void run() {
this.window.run(); this.window.run();
// free OpenGL resources // free OpenGL resources
try { try {
this.window.close(); this.window.close();
this.world.close(); this.world.close();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); 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");
}
} }