From edfffaf0618538bdd151ca1529fa0ccba7be2326 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Thu, 30 Jan 2025 18:35:41 +0100 Subject: [PATCH 01/13] feat: multi select sudoku --- app/src/main/java/gui/SudokuSelector.java | 12 +++++----- .../main/java/gui/menu/MultiPlayerView.java | 24 +++++++++++++------ app/src/main/java/gui/menu/SoloMenu.java | 2 +- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/gui/SudokuSelector.java b/app/src/main/java/gui/SudokuSelector.java index 0d46f51..715d7fc 100644 --- a/app/src/main/java/gui/SudokuSelector.java +++ b/app/src/main/java/gui/SudokuSelector.java @@ -26,16 +26,16 @@ public class SudokuSelector { private final ImInt difficulty = new ImInt(Difficulty.Medium.ordinal()); private final List contraints = new ArrayList<>(); - private static final String[] sudokuTypes = { "Carré", "Rectangle", "Multidoku" }; - private static final int SQUARE = 0, RECTANGLE = 1, MULTIDOKU = 2; - private final ImInt sudokuSize = new ImInt(3); private final ImInt sudokuWidth = new ImInt(3); private final ImInt sudokuHeight = new ImInt(3); - public SudokuSelector(boolean canGenEmptyGrid) { + private final String confirmMessage; + + public SudokuSelector(boolean canGenEmptyGrid, String confirmMessage) { this.canGenEmptyGrid = canGenEmptyGrid; + this.confirmMessage = confirmMessage; initConstraints(); } @@ -98,7 +98,7 @@ public class SudokuSelector { switch (currentType.getMakerParamCount()) { case 1: ImGui.inputInt("Taille", sudokuSize); - if (ImGui.button("Résoudre un sudoku")) { + if (ImGui.button(confirmMessage)) { selectSudoku(currentType.createDoku(getConstraints(), sudokuSize.get()), false); } if (canGenEmptyGrid && ImGui.button("Générer une grille vide")) { @@ -109,7 +109,7 @@ public class SudokuSelector { case 2: ImGui.inputInt("Largeur", sudokuHeight); ImGui.inputInt("Longueur", sudokuWidth); - if (ImGui.button("Résoudre un sudoku")) { + if (ImGui.button(confirmMessage)) { selectSudoku(currentType.createDoku(getConstraints(), sudokuWidth.get(), sudokuHeight.get()), false); } diff --git a/app/src/main/java/gui/menu/MultiPlayerView.java b/app/src/main/java/gui/menu/MultiPlayerView.java index e58f24d..822dd37 100644 --- a/app/src/main/java/gui/menu/MultiPlayerView.java +++ b/app/src/main/java/gui/menu/MultiPlayerView.java @@ -1,24 +1,27 @@ package gui.menu; -import java.util.Arrays; - import game.Player; +import gui.SudokuSelector; import imgui.ImGui; import network.client.Client; import network.server.Server; -import sudoku.constraint.Constraint; import sudoku.structure.MultiDoku; -import sudoku.structure.SudokuFactory; public class MultiPlayerView extends BaseView { private final Client client; private final Server server; + private final SudokuSelector selector; + + private MultiDoku doku = null; + public MultiPlayerView(StateMachine stateMachine, Client client, Server server) { super(stateMachine); this.client = client; this.server = server; + this.selector = new SudokuSelector(false, "Sélectionner le sudoku"); + this.selector.onSelect.connect(this::onSelected); this.client.onDisconnect.connect(this::onDisconnect); this.client.onGameStarted .connect(() -> this.stateMachine.pushState(new MultiPlayerDokuView(stateMachine, client, server))); @@ -34,15 +37,22 @@ public class MultiPlayerView extends BaseView { this.stateMachine.popState(); } + private void onSelected() { + this.doku = this.selector.getDoku(); + } + public void renderGameStatus() { if (this.server == null) { ImGui.text("En attente de l'administrateur du serveur ..."); } else { + if (this.doku == null) + ImGui.beginDisabled(); if (ImGui.button("Démarrer")) { - // temp - MultiDoku doku = SudokuFactory.createBasicXShapedMultidoku(3, Arrays.asList(Constraint.Diagonal)); - this.server.startGame(doku); + this.server.startGame(this.doku); } + if (this.doku == null) + ImGui.endDisabled(); + selector.render(); } } diff --git a/app/src/main/java/gui/menu/SoloMenu.java b/app/src/main/java/gui/menu/SoloMenu.java index c3b714e..d72ef7c 100644 --- a/app/src/main/java/gui/menu/SoloMenu.java +++ b/app/src/main/java/gui/menu/SoloMenu.java @@ -9,7 +9,7 @@ public class SoloMenu extends BaseView { public SoloMenu(StateMachine stateMachine) { super(stateMachine); - this.sudokuSelector = new SudokuSelector(true); + this.sudokuSelector = new SudokuSelector(true, "Résoudre le sudoku"); this.sudokuSelector.onSelect.connect(this::pushSudokuState); } From bcded60fbec4de100952b12f1f12b5e5d6f68079 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Thu, 30 Jan 2025 21:46:55 +0100 Subject: [PATCH 02/13] small fix --- app/src/main/java/network/server/ServerConnexion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/network/server/ServerConnexion.java b/app/src/main/java/network/server/ServerConnexion.java index 20879a7..8f3307d 100644 --- a/app/src/main/java/network/server/ServerConnexion.java +++ b/app/src/main/java/network/server/ServerConnexion.java @@ -29,7 +29,7 @@ public class ServerConnexion extends Connexion { } public boolean update() { - if (shouldClose | isClosed()) + if (shouldClose || isClosed()) return false; return this.keepAliveHandler.update(); } From 25c2270a37c2eb8cf7602dc3b5b3fe052dbb0400 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Thu, 30 Jan 2025 22:16:29 +0100 Subject: [PATCH 03/13] feat: multi synced player scores --- app/src/main/java/common/ConsumerSignal.java | 27 +++++++++ app/src/main/java/game/Player.java | 10 ++++ app/src/main/java/gui/SudokuRenderer.java | 6 +- app/src/main/java/gui/SudokuSelector.java | 12 ++-- .../java/gui/menu/MultiPlayerDokuView.java | 6 ++ .../main/java/gui/menu/MultiPlayerView.java | 4 +- app/src/main/java/gui/menu/SoloMenu.java | 5 +- .../main/java/network/ConnexionThread.java | 2 +- app/src/main/java/network/client/Client.java | 16 ++++++ .../java/network/client/ClientConnexion.java | 22 +++++++ .../java/network/protocol/PacketVisitor.java | 8 ++- .../main/java/network/protocol/Packets.java | 2 +- .../protocol/packets/ChangeCellPacket.java | 38 +++++++++++++ .../protocol/packets/EndGamePacket.java | 26 +++++++++ .../packets/UpdatePlayerScorePacket.java | 32 +++++++++++ app/src/main/java/network/server/Server.java | 3 + .../java/network/server/ServerConnexion.java | 57 ++++++++++++++++++- app/src/main/java/sudoku/structure/Cell.java | 2 + .../main/java/sudoku/structure/MultiDoku.java | 8 ++- 19 files changed, 264 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/common/ConsumerSignal.java create mode 100644 app/src/main/java/network/protocol/packets/ChangeCellPacket.java create mode 100644 app/src/main/java/network/protocol/packets/EndGamePacket.java create mode 100644 app/src/main/java/network/protocol/packets/UpdatePlayerScorePacket.java diff --git a/app/src/main/java/common/ConsumerSignal.java b/app/src/main/java/common/ConsumerSignal.java new file mode 100644 index 0000000..ba4b046 --- /dev/null +++ b/app/src/main/java/common/ConsumerSignal.java @@ -0,0 +1,27 @@ +package common; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; + +public class ConsumerSignal { + private final Set> listeners; + + public ConsumerSignal() { + this.listeners = new HashSet<>(); + } + + public void connect(Consumer listener) { + this.listeners.add(listener); + } + + public void clear() { + this.listeners.clear(); + } + + public void emit(T arg) { + for (Consumer listener : this.listeners) { + listener.accept(arg); + } + } +} diff --git a/app/src/main/java/game/Player.java b/app/src/main/java/game/Player.java index 2336152..9c76a5f 100644 --- a/app/src/main/java/game/Player.java +++ b/app/src/main/java/game/Player.java @@ -8,10 +8,20 @@ public class Player implements Serializable { private final String pseudo; private final int id; + private int score; public Player(int id, String pseudo) { this.pseudo = pseudo; this.id = id; + this.score = 0; + } + + public int getScore() { + return score; + } + + public void setScore(int score) { + this.score = score; } public String getPseudo() { diff --git a/app/src/main/java/gui/SudokuRenderer.java b/app/src/main/java/gui/SudokuRenderer.java index 7b66580..e40e697 100644 --- a/app/src/main/java/gui/SudokuRenderer.java +++ b/app/src/main/java/gui/SudokuRenderer.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import common.ConsumerSignal; import common.Signal; import gui.ColorGenerator.Color; import imgui.ImGui; @@ -33,6 +34,7 @@ public class SudokuRenderer { private final Set diagonals = new HashSet<>(); public final Signal onResolve = new Signal(); + public final ConsumerSignal onCellChange = new ConsumerSignal<>(); public SudokuRenderer(MultiDoku doku) { this.doku = RenderableMultidoku.fromMultidoku(doku); @@ -72,11 +74,13 @@ public class SudokuRenderer { if (currentCell.getSymbolIndex() == i) { if (ImGui.button("X", cellSize)) { currentCell.setSymbolIndex(Cell.NOSYMBOL); + this.onCellChange.emit(currentCell); ImGui.closeCurrentPopup(); } } else { if (ImGui.button(Options.Symboles.getSymbols().get(i), cellSize)) { - currentCell.trySetValue(i); + if (currentCell.trySetValue(i)) + this.onCellChange.emit(currentCell); if (this.doku.getDoku().isSolved()) this.onResolve.emit(); ImGui.closeCurrentPopup(); diff --git a/app/src/main/java/gui/SudokuSelector.java b/app/src/main/java/gui/SudokuSelector.java index 715d7fc..1833f72 100644 --- a/app/src/main/java/gui/SudokuSelector.java +++ b/app/src/main/java/gui/SudokuSelector.java @@ -3,7 +3,7 @@ package gui; import java.util.ArrayList; import java.util.List; -import common.Signal; +import common.ConsumerSignal; import imgui.ImGui; import imgui.extension.imguifiledialog.ImGuiFileDialog; import imgui.extension.imguifiledialog.flag.ImGuiFileDialogFlags; @@ -16,7 +16,7 @@ import sudoku.structure.SudokuFactory; public class SudokuSelector { - public final Signal onSelect = new Signal(); + public final ConsumerSignal onSelect = new ConsumerSignal<>(); private MultiDoku doku; private final boolean canGenEmptyGrid; @@ -63,7 +63,7 @@ public class SudokuSelector { e.printStackTrace(); } } - this.onSelect.emit(); + this.onSelect.emit(this.doku); } public void renderFileDialog() { @@ -75,7 +75,7 @@ public class SudokuSelector { String filePath = entry.getValue(); this.doku = SudokuFactory.fromfile(filePath); if (this.doku != null) - this.onSelect.emit(); + this.onSelect.emit(this.doku); } catch (Exception e) { e.printStackTrace(); } @@ -129,8 +129,4 @@ public class SudokuSelector { renderFileDialog(); } - public MultiDoku getDoku() { - return doku; - } - } diff --git a/app/src/main/java/gui/menu/MultiPlayerDokuView.java b/app/src/main/java/gui/menu/MultiPlayerDokuView.java index 8974cd0..2721f49 100644 --- a/app/src/main/java/gui/menu/MultiPlayerDokuView.java +++ b/app/src/main/java/gui/menu/MultiPlayerDokuView.java @@ -4,6 +4,7 @@ import gui.SudokuRenderer; import imgui.ImGui; import network.client.Client; import network.server.Server; +import sudoku.structure.Cell; public class MultiPlayerDokuView extends BaseView{ @@ -16,9 +17,14 @@ public class MultiPlayerDokuView extends BaseView{ this.client = client; this.server = server; this.sudokuRenderer = new SudokuRenderer(this.client.getGame().getDoku()); + this.sudokuRenderer.onCellChange.connect(this::onCellChange); this.client.onDisconnect.connect(this::onDisconnect); } + private void onCellChange(Cell cell) { + this.client.sendCellChange(cell); + } + public void onDisconnect() { if (server == null) { closeMenu(); diff --git a/app/src/main/java/gui/menu/MultiPlayerView.java b/app/src/main/java/gui/menu/MultiPlayerView.java index 822dd37..0a373e5 100644 --- a/app/src/main/java/gui/menu/MultiPlayerView.java +++ b/app/src/main/java/gui/menu/MultiPlayerView.java @@ -37,8 +37,8 @@ public class MultiPlayerView extends BaseView { this.stateMachine.popState(); } - private void onSelected() { - this.doku = this.selector.getDoku(); + private void onSelected(MultiDoku doku) { + this.doku = doku; } public void renderGameStatus() { diff --git a/app/src/main/java/gui/menu/SoloMenu.java b/app/src/main/java/gui/menu/SoloMenu.java index d72ef7c..1d50124 100644 --- a/app/src/main/java/gui/menu/SoloMenu.java +++ b/app/src/main/java/gui/menu/SoloMenu.java @@ -2,6 +2,7 @@ package gui.menu; import gui.SudokuSelector; import imgui.ImGui; +import sudoku.structure.MultiDoku; public class SoloMenu extends BaseView { @@ -13,8 +14,8 @@ public class SoloMenu extends BaseView { this.sudokuSelector.onSelect.connect(this::pushSudokuState); } - private void pushSudokuState() { - this.stateMachine.pushState(new SudokuView(stateMachine, this.sudokuSelector.getDoku())); + private void pushSudokuState(MultiDoku doku) { + this.stateMachine.pushState(new SudokuView(stateMachine, doku)); } @Override diff --git a/app/src/main/java/network/ConnexionThread.java b/app/src/main/java/network/ConnexionThread.java index 6752d92..17b0584 100644 --- a/app/src/main/java/network/ConnexionThread.java +++ b/app/src/main/java/network/ConnexionThread.java @@ -22,7 +22,7 @@ public class ConnexionThread extends Thread { // System.out.println(objectInputStream.available()); Object o = objectInputStream.readObject(); if (o instanceof Packet packet) { - connexion.visitPacket(packet); + connexion.visit(packet); } } catch (ClassNotFoundException | IOException e) { e.printStackTrace(); diff --git a/app/src/main/java/network/client/Client.java b/app/src/main/java/network/client/Client.java index 10f5ec8..069b105 100644 --- a/app/src/main/java/network/client/Client.java +++ b/app/src/main/java/network/client/Client.java @@ -7,7 +7,11 @@ import java.util.Random; import common.Signal; import game.Game; import game.Player; +import network.protocol.packets.ChangeCellPacket; import network.protocol.packets.LoginPacket; +import sudoku.structure.Cell; +import sudoku.structure.MultiDoku; +import sudoku.structure.Sudoku; public class Client { private final ClientConnexion clientConnection; @@ -54,4 +58,16 @@ public class Client { stop(); } + public void sendCellChange(Cell cell) { + MultiDoku doku = getGame().getDoku(); + for (int sudokuIndex = 0; sudokuIndex < doku.getNbSubGrids(); sudokuIndex++) { + Sudoku sudoku = doku.getSubGrid(sudokuIndex); + int cellIndex = sudoku.getCells().indexOf(cell); + if (cellIndex != -1) { + this.clientConnection.sendPacket(new ChangeCellPacket(sudokuIndex, cellIndex, cell.getSymbolIndex())); + return; + } + } + } + } diff --git a/app/src/main/java/network/client/ClientConnexion.java b/app/src/main/java/network/client/ClientConnexion.java index 6e6fe87..94e87f9 100644 --- a/app/src/main/java/network/client/ClientConnexion.java +++ b/app/src/main/java/network/client/ClientConnexion.java @@ -6,13 +6,16 @@ import java.net.UnknownHostException; import game.Player; import network.Connexion; +import network.protocol.packets.ChangeCellPacket; import network.protocol.packets.ConnexionInfoPacket; import network.protocol.packets.DisconnectPacket; +import network.protocol.packets.EndGamePacket; import network.protocol.packets.KeepAlivePacket; import network.protocol.packets.LoginPacket; import network.protocol.packets.PlayerJoinPacket; import network.protocol.packets.PlayerLeavePacket; import network.protocol.packets.StartGamePacket; +import network.protocol.packets.UpdatePlayerScorePacket; import sudoku.io.SudokuSerializer; public class ClientConnexion extends Connexion { @@ -73,4 +76,23 @@ public class ClientConnexion extends Connexion { this.client.onGameStarted.emit(); } + @Override + public void visitPacket(EndGamePacket packet) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'visitPacket'"); + } + + @Override + public void visitPacket(UpdatePlayerScorePacket packet) { + Player player = this.client.getGame().getPlayerById(packet.getPlayerId()); + assert(player != null); + player.setScore(packet.getCellsLeft()); + System.out.println("Score for " + player.getPseudo() + " : " + packet.getCellsLeft()); + } + + @Override + public void visitPacket(ChangeCellPacket packet) { + throw new UnsupportedOperationException("Unimplemented method 'visitPacketChangeCell'"); + } + } diff --git a/app/src/main/java/network/protocol/PacketVisitor.java b/app/src/main/java/network/protocol/PacketVisitor.java index 582a4f3..9639fd5 100644 --- a/app/src/main/java/network/protocol/PacketVisitor.java +++ b/app/src/main/java/network/protocol/PacketVisitor.java @@ -1,16 +1,19 @@ package network.protocol; +import network.protocol.packets.ChangeCellPacket; import network.protocol.packets.ConnexionInfoPacket; import network.protocol.packets.DisconnectPacket; +import network.protocol.packets.EndGamePacket; import network.protocol.packets.KeepAlivePacket; import network.protocol.packets.LoginPacket; import network.protocol.packets.PlayerJoinPacket; import network.protocol.packets.PlayerLeavePacket; import network.protocol.packets.StartGamePacket; +import network.protocol.packets.UpdatePlayerScorePacket; public interface PacketVisitor { - default void visitPacket(Packet packet) { + default void visit(Packet packet) { packet.accept(this); } @@ -21,5 +24,8 @@ public interface PacketVisitor { void visitPacket(PlayerJoinPacket packet); void visitPacket(PlayerLeavePacket packet); void visitPacket(StartGamePacket packet); + void visitPacket(EndGamePacket packet); + void visitPacket(UpdatePlayerScorePacket packet); + void visitPacket(ChangeCellPacket packet); } diff --git a/app/src/main/java/network/protocol/Packets.java b/app/src/main/java/network/protocol/Packets.java index 9355877..6f056e8 100644 --- a/app/src/main/java/network/protocol/Packets.java +++ b/app/src/main/java/network/protocol/Packets.java @@ -2,6 +2,6 @@ package network.protocol; public enum Packets { - ConnectionInfo, KeepAlive, Disconnect, Login, PlayerJoin, PlayerLeave, StartGame + ConnectionInfo, KeepAlive, Disconnect, Login, PlayerJoin, PlayerLeave, StartGame, ChangeCell, EndGame, UpdatePlayerScore } diff --git a/app/src/main/java/network/protocol/packets/ChangeCellPacket.java b/app/src/main/java/network/protocol/packets/ChangeCellPacket.java new file mode 100644 index 0000000..6d1164e --- /dev/null +++ b/app/src/main/java/network/protocol/packets/ChangeCellPacket.java @@ -0,0 +1,38 @@ +package network.protocol.packets; + +import network.protocol.Packet; +import network.protocol.PacketVisitor; +import network.protocol.Packets; + +public class ChangeCellPacket extends Packet { + + static private final long serialVersionUID = Packets.ChangeCell.ordinal(); + + private final int sudokuIndex; + private final int cellIndex; + private final int newValue; + + public ChangeCellPacket(int sudokuIndex, int cellIndex, int newValue) { + this.sudokuIndex = sudokuIndex; + this.cellIndex = cellIndex; + this.newValue = newValue; + } + + public int getSudokuIndex() { + return sudokuIndex; + } + + public int getCellIndex() { + return cellIndex; + } + + public int getNewValue() { + return newValue; + } + + @Override + public void accept(PacketVisitor packetVisitor) { + packetVisitor.visitPacket(this); + } + +} diff --git a/app/src/main/java/network/protocol/packets/EndGamePacket.java b/app/src/main/java/network/protocol/packets/EndGamePacket.java new file mode 100644 index 0000000..d01a498 --- /dev/null +++ b/app/src/main/java/network/protocol/packets/EndGamePacket.java @@ -0,0 +1,26 @@ +package network.protocol.packets; + +import network.protocol.Packet; +import network.protocol.PacketVisitor; +import network.protocol.Packets; + +public class EndGamePacket extends Packet { + + static private final long serialVersionUID = Packets.EndGame.ordinal(); + + private final int winnerId; + + public EndGamePacket(int winnerId) { + this.winnerId = winnerId; + } + + public int getWinnerId() { + return winnerId; + } + + @Override + public void accept(PacketVisitor packetVisitor) { + packetVisitor.visitPacket(this); + } + +} diff --git a/app/src/main/java/network/protocol/packets/UpdatePlayerScorePacket.java b/app/src/main/java/network/protocol/packets/UpdatePlayerScorePacket.java new file mode 100644 index 0000000..f8011df --- /dev/null +++ b/app/src/main/java/network/protocol/packets/UpdatePlayerScorePacket.java @@ -0,0 +1,32 @@ +package network.protocol.packets; + +import network.protocol.Packet; +import network.protocol.PacketVisitor; +import network.protocol.Packets; + +public class UpdatePlayerScorePacket extends Packet { + + static private final long serialVersionUID = Packets.UpdatePlayerScore.ordinal(); + + private final int playerId; + private final int cellsLeft; + + public UpdatePlayerScorePacket(int playerId, int cellsLeft) { + this.playerId = playerId; + this.cellsLeft = cellsLeft; + } + + public int getPlayerId() { + return playerId; + } + + public int getCellsLeft() { + return cellsLeft; + } + + @Override + public void accept(PacketVisitor packetVisitor) { + packetVisitor.visitPacket(this); + } + +} diff --git a/app/src/main/java/network/server/Server.java b/app/src/main/java/network/server/Server.java index 10ea1f8..79a222d 100644 --- a/app/src/main/java/network/server/Server.java +++ b/app/src/main/java/network/server/Server.java @@ -70,6 +70,9 @@ public class Server { public void startGame(MultiDoku doku) { this.game.startGame(doku); + for (ServerConnexion connexion : this.connexions) { + connexion.setSudoku(doku); + } broadcastPacket(new StartGamePacket(SudokuSerializer.serializeSudoku(doku).toString())); } diff --git a/app/src/main/java/network/server/ServerConnexion.java b/app/src/main/java/network/server/ServerConnexion.java index 8f3307d..d14b2cc 100644 --- a/app/src/main/java/network/server/ServerConnexion.java +++ b/app/src/main/java/network/server/ServerConnexion.java @@ -6,14 +6,19 @@ import java.net.Socket; import game.Player; import game.Game.GameState; import network.Connexion; +import network.protocol.packets.ChangeCellPacket; import network.protocol.packets.ConnexionInfoPacket; import network.protocol.packets.DisconnectPacket; +import network.protocol.packets.EndGamePacket; import network.protocol.packets.KeepAlivePacket; import network.protocol.packets.LoginPacket; import network.protocol.packets.PlayerJoinPacket; import network.protocol.packets.PlayerLeavePacket; import network.protocol.packets.StartGamePacket; +import network.protocol.packets.UpdatePlayerScorePacket; import sudoku.io.SudokuSerializer; +import sudoku.structure.Cell; +import sudoku.structure.MultiDoku; public class ServerConnexion extends Connexion { @@ -21,6 +26,7 @@ public class ServerConnexion extends Connexion { private final KeepAliveHandler keepAliveHandler; private boolean shouldClose = false; private Player player = null; + private MultiDoku doku; public ServerConnexion(Socket socket, Server server) throws IOException { super(socket); @@ -44,7 +50,7 @@ public class ServerConnexion extends Connexion { @Override public synchronized void close() { - if(shouldClose) + if (shouldClose) return; super.close(); shouldClose = true; @@ -54,13 +60,19 @@ public class ServerConnexion extends Connexion { private void finishLogin() { // send players that have already joined (excluding this one) for (Player p : this.server.getGame().getPlayers().values()) { - if (p.getId() != player.getId()) + if (p.getId() != player.getId()) { sendPacket(new PlayerJoinPacket(p)); + sendPacket(new UpdatePlayerScorePacket(p.getId(), p.getScore())); + } } + this.server.broadcastPacket(new PlayerJoinPacket(player)); sendPacket(new ConnexionInfoPacket(player.getId())); + if (this.server.getGame().getGameState() == GameState.GameGoing) { - sendPacket(new StartGamePacket(SudokuSerializer.serializeSudoku(this.server.getGame().getDoku()).toString())); + setSudoku(this.server.getGame().getDoku()); + sendPacket( + new StartGamePacket(SudokuSerializer.serializeSudoku(this.server.getGame().getDoku()).toString())); } } @@ -102,4 +114,43 @@ public class ServerConnexion extends Connexion { throw new UnsupportedOperationException("Unimplemented method 'visitPacketStartGame'"); } + @Override + public void visitPacket(EndGamePacket packet) { + throw new UnsupportedOperationException("Unimplemented method 'visitPacket'"); + } + + @Override + public void visitPacket(UpdatePlayerScorePacket packet) { + throw new UnsupportedOperationException("Unimplemented method 'visitPacket'"); + } + + @Override + public void visitPacket(ChangeCellPacket packet) { + Cell cell = this.doku.getSubGrid(packet.getSudokuIndex()).getCell(packet.getCellIndex()); + if (cell.getSymbolIndex() == Cell.NOSYMBOL && packet.getNewValue() == Cell.NOSYMBOL) + return; + if (cell.getSymbolIndex() != Cell.NOSYMBOL && packet.getNewValue() != Cell.NOSYMBOL) { + cell.trySetValue(packet.getNewValue()); + return; + } + if (cell.getSymbolIndex() != Cell.NOSYMBOL && packet.getNewValue() == Cell.NOSYMBOL) { + cell.trySetValue(Cell.NOSYMBOL); + player.setScore(player.getScore() + 1); + sendPacket(new UpdatePlayerScorePacket(player.getId(), player.getScore())); + return; + } + // on rajoute un chiffre à la grille + if (cell.trySetValue(packet.getNewValue())) { + player.setScore(player.getScore() - 1); + sendPacket(new UpdatePlayerScorePacket(player.getId(), player.getScore())); + } + } + + public void setSudoku(MultiDoku doku) { + this.doku = doku; + assert (player != null); + player.setScore(this.doku.getEmptyCells().size()); + sendPacket(new UpdatePlayerScorePacket(player.getId(), player.getScore())); + } + } diff --git a/app/src/main/java/sudoku/structure/Cell.java b/app/src/main/java/sudoku/structure/Cell.java index eaa4964..b6127ad 100644 --- a/app/src/main/java/sudoku/structure/Cell.java +++ b/app/src/main/java/sudoku/structure/Cell.java @@ -126,6 +126,8 @@ public class Cell { } public boolean trySetValue(int newValue) { + if (!isMutable()) + return false; if (!canHaveValue(newValue)) return false; setSymbolIndex(newValue); diff --git a/app/src/main/java/sudoku/structure/MultiDoku.java b/app/src/main/java/sudoku/structure/MultiDoku.java index eb64d46..1fd9b6e 100644 --- a/app/src/main/java/sudoku/structure/MultiDoku.java +++ b/app/src/main/java/sudoku/structure/MultiDoku.java @@ -1,8 +1,10 @@ package sudoku.structure; -import java.util.*; - -import sudoku.io.SudokuSerializer; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; /** * @class MultiDoku From a160042ef4ddec4a7384c563426dd3e1436c6dd1 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Fri, 31 Jan 2025 13:48:51 +0100 Subject: [PATCH 04/13] feat: uggly leaderboard --- .../java/gui/menu/MultiPlayerDokuView.java | 6 ++++- .../main/java/gui/menu/MultiPlayerView.java | 2 +- app/src/main/java/gui/menu/SoloMenu.java | 2 +- app/src/main/java/gui/menu/SudokuView.java | 2 +- .../java/gui/widget/LeaderboardRenderer.java | 25 +++++++++++++++++++ .../java/gui/{ => widget}/SudokuRenderer.java | 7 +++++- .../java/gui/{ => widget}/SudokuSelector.java | 3 ++- app/src/main/java/network/client/Client.java | 6 +++++ .../java/network/client/ClientConnexion.java | 3 +-- app/src/main/java/network/server/Server.java | 2 +- .../java/network/server/ServerConnexion.java | 10 ++++---- .../main/java/sudoku/structure/MultiDoku.java | 7 ++++++ 12 files changed, 61 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/gui/widget/LeaderboardRenderer.java rename app/src/main/java/gui/{ => widget}/SudokuRenderer.java (97%) rename app/src/main/java/gui/{ => widget}/SudokuSelector.java (98%) diff --git a/app/src/main/java/gui/menu/MultiPlayerDokuView.java b/app/src/main/java/gui/menu/MultiPlayerDokuView.java index 2721f49..2c68cfe 100644 --- a/app/src/main/java/gui/menu/MultiPlayerDokuView.java +++ b/app/src/main/java/gui/menu/MultiPlayerDokuView.java @@ -1,6 +1,7 @@ package gui.menu; -import gui.SudokuRenderer; +import gui.widget.LeaderboardRenderer; +import gui.widget.SudokuRenderer; import imgui.ImGui; import network.client.Client; import network.server.Server; @@ -11,12 +12,14 @@ public class MultiPlayerDokuView extends BaseView{ private final Client client; private final Server server; private final SudokuRenderer sudokuRenderer; + private final LeaderboardRenderer leaderboardRenderer; public MultiPlayerDokuView(StateMachine stateMachine, Client client, Server server) { super(stateMachine); this.client = client; this.server = server; this.sudokuRenderer = new SudokuRenderer(this.client.getGame().getDoku()); + this.leaderboardRenderer = new LeaderboardRenderer(client.getGame(), client.getPlayer()); this.sudokuRenderer.onCellChange.connect(this::onCellChange); this.client.onDisconnect.connect(this::onDisconnect); } @@ -33,6 +36,7 @@ public class MultiPlayerDokuView extends BaseView{ @Override public void render() { + this.leaderboardRenderer.render(); this.sudokuRenderer.render(); if (ImGui.button("Quitter")) { this.client.stop(); diff --git a/app/src/main/java/gui/menu/MultiPlayerView.java b/app/src/main/java/gui/menu/MultiPlayerView.java index 0a373e5..9f45944 100644 --- a/app/src/main/java/gui/menu/MultiPlayerView.java +++ b/app/src/main/java/gui/menu/MultiPlayerView.java @@ -1,7 +1,7 @@ package gui.menu; import game.Player; -import gui.SudokuSelector; +import gui.widget.SudokuSelector; import imgui.ImGui; import network.client.Client; import network.server.Server; diff --git a/app/src/main/java/gui/menu/SoloMenu.java b/app/src/main/java/gui/menu/SoloMenu.java index 1d50124..d823671 100644 --- a/app/src/main/java/gui/menu/SoloMenu.java +++ b/app/src/main/java/gui/menu/SoloMenu.java @@ -1,6 +1,6 @@ package gui.menu; -import gui.SudokuSelector; +import gui.widget.SudokuSelector; import imgui.ImGui; import sudoku.structure.MultiDoku; diff --git a/app/src/main/java/gui/menu/SudokuView.java b/app/src/main/java/gui/menu/SudokuView.java index d75f736..39d01ef 100644 --- a/app/src/main/java/gui/menu/SudokuView.java +++ b/app/src/main/java/gui/menu/SudokuView.java @@ -2,7 +2,7 @@ package gui.menu; import java.util.concurrent.CancellationException; -import gui.SudokuRenderer; +import gui.widget.SudokuRenderer; import imgui.ImGui; import imgui.ImGuiStyle; import sudoku.io.SudokuSerializer; diff --git a/app/src/main/java/gui/widget/LeaderboardRenderer.java b/app/src/main/java/gui/widget/LeaderboardRenderer.java new file mode 100644 index 0000000..d87efc2 --- /dev/null +++ b/app/src/main/java/gui/widget/LeaderboardRenderer.java @@ -0,0 +1,25 @@ +package gui.widget; + +import game.Game; +import game.Player; +import imgui.ImGui; + +public class LeaderboardRenderer { + + private final Game game; + private final Player currentPlayer; + + public LeaderboardRenderer(Game game, Player player) { + this.game = game; + this.currentPlayer = player; + } + + public void render() { + ImGui.text("Leaderboard"); + for (var entry : game.getPlayers().entrySet()) { + Player player = entry.getValue(); + ImGui.text(player.getPseudo() + " : " + player.getScore()); + } + } + +} diff --git a/app/src/main/java/gui/SudokuRenderer.java b/app/src/main/java/gui/widget/SudokuRenderer.java similarity index 97% rename from app/src/main/java/gui/SudokuRenderer.java rename to app/src/main/java/gui/widget/SudokuRenderer.java index e40e697..fb56168 100644 --- a/app/src/main/java/gui/SudokuRenderer.java +++ b/app/src/main/java/gui/widget/SudokuRenderer.java @@ -1,4 +1,4 @@ -package gui; +package gui.widget; import java.util.HashMap; import java.util.HashSet; @@ -8,6 +8,11 @@ import java.util.Set; import common.ConsumerSignal; import common.Signal; +import gui.ColorGenerator; +import gui.Fonts; +import gui.Options; +import gui.RenderableMultidoku; +import gui.Symbols; import gui.ColorGenerator.Color; import imgui.ImGui; import imgui.ImVec2; diff --git a/app/src/main/java/gui/SudokuSelector.java b/app/src/main/java/gui/widget/SudokuSelector.java similarity index 98% rename from app/src/main/java/gui/SudokuSelector.java rename to app/src/main/java/gui/widget/SudokuSelector.java index 1833f72..db1b69e 100644 --- a/app/src/main/java/gui/SudokuSelector.java +++ b/app/src/main/java/gui/widget/SudokuSelector.java @@ -1,9 +1,10 @@ -package gui; +package gui.widget; import java.util.ArrayList; import java.util.List; import common.ConsumerSignal; +import gui.SudokuType; import imgui.ImGui; import imgui.extension.imguifiledialog.ImGuiFileDialog; import imgui.extension.imguifiledialog.flag.ImGuiFileDialogFlags; diff --git a/app/src/main/java/network/client/Client.java b/app/src/main/java/network/client/Client.java index 069b105..20bf94b 100644 --- a/app/src/main/java/network/client/Client.java +++ b/app/src/main/java/network/client/Client.java @@ -22,6 +22,8 @@ public class Client { public final Signal onClosed = new Signal(); public final Signal onGameStarted = new Signal(); + Player player; + String disconnectReason = null; public Client(String address, short port) throws UnknownHostException, IOException { @@ -70,4 +72,8 @@ public class Client { } } + public Player getPlayer() { + return player; + } + } diff --git a/app/src/main/java/network/client/ClientConnexion.java b/app/src/main/java/network/client/ClientConnexion.java index 94e87f9..3f6bfad 100644 --- a/app/src/main/java/network/client/ClientConnexion.java +++ b/app/src/main/java/network/client/ClientConnexion.java @@ -21,7 +21,6 @@ import sudoku.io.SudokuSerializer; public class ClientConnexion extends Connexion { private final Client client; - private Player player = null; public ClientConnexion(String address, short port, Client client) throws UnknownHostException, IOException { super(new Socket(address, port)); @@ -38,7 +37,7 @@ public class ClientConnexion extends Connexion { @Override public void visitPacket(ConnexionInfoPacket packet) { - this.player = this.client.getGame().getPlayerById(packet.getConnectionId()); + this.client.player = this.client.getGame().getPlayerById(packet.getConnectionId()); client.onConnect.emit(); } diff --git a/app/src/main/java/network/server/Server.java b/app/src/main/java/network/server/Server.java index 79a222d..19b2b24 100644 --- a/app/src/main/java/network/server/Server.java +++ b/app/src/main/java/network/server/Server.java @@ -71,7 +71,7 @@ public class Server { public void startGame(MultiDoku doku) { this.game.startGame(doku); for (ServerConnexion connexion : this.connexions) { - connexion.setSudoku(doku); + connexion.setSudoku(doku.clone()); } broadcastPacket(new StartGamePacket(SudokuSerializer.serializeSudoku(doku).toString())); } diff --git a/app/src/main/java/network/server/ServerConnexion.java b/app/src/main/java/network/server/ServerConnexion.java index d14b2cc..4206c17 100644 --- a/app/src/main/java/network/server/ServerConnexion.java +++ b/app/src/main/java/network/server/ServerConnexion.java @@ -70,7 +70,7 @@ public class ServerConnexion extends Connexion { sendPacket(new ConnexionInfoPacket(player.getId())); if (this.server.getGame().getGameState() == GameState.GameGoing) { - setSudoku(this.server.getGame().getDoku()); + setSudoku(this.server.getGame().getDoku().clone()); sendPacket( new StartGamePacket(SudokuSerializer.serializeSudoku(this.server.getGame().getDoku()).toString())); } @@ -134,15 +134,15 @@ public class ServerConnexion extends Connexion { return; } if (cell.getSymbolIndex() != Cell.NOSYMBOL && packet.getNewValue() == Cell.NOSYMBOL) { - cell.trySetValue(Cell.NOSYMBOL); + cell.empty(); player.setScore(player.getScore() + 1); - sendPacket(new UpdatePlayerScorePacket(player.getId(), player.getScore())); + this.server.broadcastPacket(new UpdatePlayerScorePacket(player.getId(), player.getScore())); return; } // on rajoute un chiffre à la grille if (cell.trySetValue(packet.getNewValue())) { player.setScore(player.getScore() - 1); - sendPacket(new UpdatePlayerScorePacket(player.getId(), player.getScore())); + this.server.broadcastPacket(new UpdatePlayerScorePacket(player.getId(), player.getScore())); } } @@ -150,7 +150,7 @@ public class ServerConnexion extends Connexion { this.doku = doku; assert (player != null); player.setScore(this.doku.getEmptyCells().size()); - sendPacket(new UpdatePlayerScorePacket(player.getId(), player.getScore())); + this.server.broadcastPacket(new UpdatePlayerScorePacket(player.getId(), player.getScore())); } } diff --git a/app/src/main/java/sudoku/structure/MultiDoku.java b/app/src/main/java/sudoku/structure/MultiDoku.java index 1fd9b6e..569c163 100644 --- a/app/src/main/java/sudoku/structure/MultiDoku.java +++ b/app/src/main/java/sudoku/structure/MultiDoku.java @@ -6,6 +6,8 @@ import java.util.List; import java.util.Random; import java.util.Set; +import sudoku.io.SudokuSerializer; + /** * @class MultiDoku * @brief Représente une grille de Multidoku. @@ -180,4 +182,9 @@ public class MultiDoku { int randomIndex = rand.nextInt(emptyCells.size()); return emptyCells.get(randomIndex); } + + public MultiDoku clone() { + //TODO: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah + return SudokuSerializer.deserializeSudoku(SudokuSerializer.serializeSudoku(this)); + } } From a5c046f89167a31025af1caa67eea99643f5455e Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 1 Feb 2025 00:09:25 +0100 Subject: [PATCH 05/13] feat: good leaderboard --- app/src/main/java/game/Game.java | 19 ++++++- app/src/main/java/game/Player.java | 2 +- .../java/gui/widget/LeaderboardRenderer.java | 54 +++++++++++++++++-- .../main/java/gui/widget/SudokuRenderer.java | 2 +- .../java/network/client/ClientConnexion.java | 2 +- .../java/network/server/ServerConnexion.java | 6 +-- 6 files changed, 74 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/game/Game.java b/app/src/main/java/game/Game.java index 4daf004..ad4d2e6 100644 --- a/app/src/main/java/game/Game.java +++ b/app/src/main/java/game/Game.java @@ -1,6 +1,9 @@ package game; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import sudoku.structure.MultiDoku; @@ -12,11 +15,13 @@ public class Game { } private final Map players; + private final List leaderboard; private GameState gameState; private MultiDoku doku; public Game() { this.players = new HashMap<>(); + this.leaderboard = new ArrayList<>(); this.gameState = GameState.GameNotStarted; } @@ -26,10 +31,18 @@ public class Game { public void addPlayer(Player player) { players.put(player.getId(), player); + leaderboard.add(player); + } + + public void setPlayerScore(Player player, int newScore) { + player.setScore(newScore); + Collections.sort(this.leaderboard, + (player1, player2) -> Integer.compare(player1.getScore(), player2.getScore())); } public void removePlayer(int id) { - players.remove(id); + this.leaderboard.remove(getPlayerById(id)); + this.players.remove(id); } public Map getPlayers() { @@ -49,4 +62,8 @@ public class Game { return doku; } + public List getLeaderboard() { + return leaderboard; + } + } diff --git a/app/src/main/java/game/Player.java b/app/src/main/java/game/Player.java index 9c76a5f..cfcda50 100644 --- a/app/src/main/java/game/Player.java +++ b/app/src/main/java/game/Player.java @@ -20,7 +20,7 @@ public class Player implements Serializable { return score; } - public void setScore(int score) { + void setScore(int score) { this.score = score; } diff --git a/app/src/main/java/gui/widget/LeaderboardRenderer.java b/app/src/main/java/gui/widget/LeaderboardRenderer.java index d87efc2..461ece4 100644 --- a/app/src/main/java/gui/widget/LeaderboardRenderer.java +++ b/app/src/main/java/gui/widget/LeaderboardRenderer.java @@ -3,23 +3,69 @@ package gui.widget; import game.Game; import game.Player; import imgui.ImGui; +import imgui.ImVec2; +import imgui.ImVec4; +import imgui.flag.ImGuiCol; +import imgui.flag.ImGuiStyleVar; public class LeaderboardRenderer { private final Game game; private final Player currentPlayer; + private final float cellHeight = 75; + private final ImVec2 cellSize = new ImVec2(12 * cellHeight, cellHeight); + private final ImVec2 rankSize = new ImVec2(cellHeight, cellHeight); + private final ImVec2 scoreSize = rankSize; + private final ImVec2 nameSize = new ImVec2(cellSize.x - cellHeight * 2.0f, cellHeight); + private final ImVec4 cellColorPlayer = new ImVec4(0.20f, 0.67f, 1.0f, 0.5f); + private final ImVec4 cellColorEnemy = new ImVec4(1.0f, 0.0f, 0.0f, 0.5f); + private final int maxPlayersShowed = 2; + public LeaderboardRenderer(Game game, Player player) { this.game = game; this.currentPlayer = player; } + private void renderRank(int rank) { + ImGui.button(Integer.toString(rank), rankSize); + } + + private void renderName(String name) { + ImGui.button(name, nameSize); + } + + private void renderScore(int score) { + ImGui.button(Integer.toString(score), scoreSize); + } + + private void renderCell(Player player, int rank, ImVec4 color) { + ImGui.pushStyleColor(ImGuiCol.Button, color); + ImGui.pushStyleColor(ImGuiCol.ButtonHovered, color); + ImGui.pushStyleColor(ImGuiCol.ButtonActive, color); + ImGui.beginChild(player.getPseudo() + "##" + player.getId(), cellSize); + renderRank(rank); + ImGui.sameLine(); + renderName(player.getPseudo()); + ImGui.sameLine(); + renderScore(player.getScore()); + ImGui.endChild(); + ImGui.popStyleColor(3); + } + public void render() { - ImGui.text("Leaderboard"); - for (var entry : game.getPlayers().entrySet()) { - Player player = entry.getValue(); - ImGui.text(player.getPseudo() + " : " + player.getScore()); + ImGui.text("Placeholder for timer"); + var displaySize = ImGui.getIO().getDisplaySize(); + ImGui.setCursorPosX(displaySize.x / 2.0f - cellSize.x / 2.0f); + ImGui.beginChild("Leaderboard", new ImVec2(cellSize.x + 15.0f, cellHeight * maxPlayersShowed)); + ImGui.pushStyleVar(ImGuiStyleVar.ItemSpacing, new ImVec2()); + ImGui.pushStyleVar(ImGuiStyleVar.FrameBorderSize, 3.0f); + for (int i = 0; i < game.getLeaderboard().size(); i++) { + Player player = game.getLeaderboard().get(i); + renderCell(player, i + 1, player == currentPlayer ? cellColorPlayer : cellColorEnemy); } + ImGui.popStyleVar(2); + ImGui.endChild(); } } diff --git a/app/src/main/java/gui/widget/SudokuRenderer.java b/app/src/main/java/gui/widget/SudokuRenderer.java index fb56168..4b38cf1 100644 --- a/app/src/main/java/gui/widget/SudokuRenderer.java +++ b/app/src/main/java/gui/widget/SudokuRenderer.java @@ -107,7 +107,7 @@ public class SudokuRenderer { if (offsetX > 0) { ImGui.setCursorPosX(offsetX); } - ImGui.beginChild(1, new ImVec2(cellSize.x * doku.getWidth(), cellSize.y * doku.getHeight())); + ImGui.beginChild("sudokuChild", new ImVec2(cellSize.x * doku.getWidth(), cellSize.y * doku.getHeight())); ImGui.pushStyleVar(ImGuiStyleVar.FrameBorderSize, 2.0f); ImGui.pushStyleVar(ImGuiStyleVar.ItemSpacing, new ImVec2(0.0f, 0.0f)); diff --git a/app/src/main/java/network/client/ClientConnexion.java b/app/src/main/java/network/client/ClientConnexion.java index 3f6bfad..83eadf2 100644 --- a/app/src/main/java/network/client/ClientConnexion.java +++ b/app/src/main/java/network/client/ClientConnexion.java @@ -85,7 +85,7 @@ public class ClientConnexion extends Connexion { public void visitPacket(UpdatePlayerScorePacket packet) { Player player = this.client.getGame().getPlayerById(packet.getPlayerId()); assert(player != null); - player.setScore(packet.getCellsLeft()); + this.client.getGame().setPlayerScore(player, packet.getCellsLeft()); System.out.println("Score for " + player.getPseudo() + " : " + packet.getCellsLeft()); } diff --git a/app/src/main/java/network/server/ServerConnexion.java b/app/src/main/java/network/server/ServerConnexion.java index 4206c17..61ba7bb 100644 --- a/app/src/main/java/network/server/ServerConnexion.java +++ b/app/src/main/java/network/server/ServerConnexion.java @@ -135,13 +135,13 @@ public class ServerConnexion extends Connexion { } if (cell.getSymbolIndex() != Cell.NOSYMBOL && packet.getNewValue() == Cell.NOSYMBOL) { cell.empty(); - player.setScore(player.getScore() + 1); + this.server.getGame().setPlayerScore(player, player.getScore() + 1); this.server.broadcastPacket(new UpdatePlayerScorePacket(player.getId(), player.getScore())); return; } // on rajoute un chiffre à la grille if (cell.trySetValue(packet.getNewValue())) { - player.setScore(player.getScore() - 1); + this.server.getGame().setPlayerScore(player, player.getScore() - 1); this.server.broadcastPacket(new UpdatePlayerScorePacket(player.getId(), player.getScore())); } } @@ -149,7 +149,7 @@ public class ServerConnexion extends Connexion { public void setSudoku(MultiDoku doku) { this.doku = doku; assert (player != null); - player.setScore(this.doku.getEmptyCells().size()); + this.server.getGame().setPlayerScore(player, this.doku.getEmptyCells().size()); this.server.broadcastPacket(new UpdatePlayerScorePacket(player.getId(), player.getScore())); } From 6d96455ac4120fee74c8ae17e221e801b38e9a70 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 1 Feb 2025 00:19:00 +0100 Subject: [PATCH 06/13] feat: display wrong timer --- .../java/gui/menu/MultiPlayerDokuView.java | 6 +++++ .../java/gui/widget/LeaderboardRenderer.java | 1 - .../main/java/gui/widget/TimerRenderer.java | 27 +++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/gui/widget/TimerRenderer.java diff --git a/app/src/main/java/gui/menu/MultiPlayerDokuView.java b/app/src/main/java/gui/menu/MultiPlayerDokuView.java index 2c68cfe..b18a12a 100644 --- a/app/src/main/java/gui/menu/MultiPlayerDokuView.java +++ b/app/src/main/java/gui/menu/MultiPlayerDokuView.java @@ -2,6 +2,7 @@ package gui.menu; import gui.widget.LeaderboardRenderer; import gui.widget.SudokuRenderer; +import gui.widget.TimerRenderer; import imgui.ImGui; import network.client.Client; import network.server.Server; @@ -13,6 +14,8 @@ public class MultiPlayerDokuView extends BaseView{ private final Server server; private final SudokuRenderer sudokuRenderer; private final LeaderboardRenderer leaderboardRenderer; + private final TimerRenderer timerRenderer; + private static final float GAME_DURATION = 10 * 60; public MultiPlayerDokuView(StateMachine stateMachine, Client client, Server server) { super(stateMachine); @@ -22,6 +25,8 @@ public class MultiPlayerDokuView extends BaseView{ this.leaderboardRenderer = new LeaderboardRenderer(client.getGame(), client.getPlayer()); this.sudokuRenderer.onCellChange.connect(this::onCellChange); this.client.onDisconnect.connect(this::onDisconnect); + // TODO: sync timer + this.timerRenderer = new TimerRenderer(GAME_DURATION); } private void onCellChange(Cell cell) { @@ -36,6 +41,7 @@ public class MultiPlayerDokuView extends BaseView{ @Override public void render() { + this.timerRenderer.render(); this.leaderboardRenderer.render(); this.sudokuRenderer.render(); if (ImGui.button("Quitter")) { diff --git a/app/src/main/java/gui/widget/LeaderboardRenderer.java b/app/src/main/java/gui/widget/LeaderboardRenderer.java index 461ece4..5f47c36 100644 --- a/app/src/main/java/gui/widget/LeaderboardRenderer.java +++ b/app/src/main/java/gui/widget/LeaderboardRenderer.java @@ -54,7 +54,6 @@ public class LeaderboardRenderer { } public void render() { - ImGui.text("Placeholder for timer"); var displaySize = ImGui.getIO().getDisplaySize(); ImGui.setCursorPosX(displaySize.x / 2.0f - cellSize.x / 2.0f); ImGui.beginChild("Leaderboard", new ImVec2(cellSize.x + 15.0f, cellHeight * maxPlayersShowed)); diff --git a/app/src/main/java/gui/widget/TimerRenderer.java b/app/src/main/java/gui/widget/TimerRenderer.java new file mode 100644 index 0000000..2db314c --- /dev/null +++ b/app/src/main/java/gui/widget/TimerRenderer.java @@ -0,0 +1,27 @@ +package gui.widget; + +import imgui.ImGui; + +public class TimerRenderer { + + private float time; + private final float duration; + + public TimerRenderer(float duration) { + this.time = 0; + this.duration = duration; + } + + public void render() { + this.time += ImGui.getIO().getDeltaTime(); + float remainingTime = this.duration - this.time; + int seconds = (int) remainingTime; + int minutes = seconds / 60; + seconds %= 60; + String text = minutes + ":" + seconds; + var textSize = ImGui.calcTextSize(text); + ImGui.setCursorPosX(ImGui.getIO().getDisplaySizeX() / 2.0f - textSize.x / 2.0f); + ImGui.text(text); + } + +} From 3863c812c84e52889c7fd76ee4018df3c63b01de Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 1 Feb 2025 10:56:02 +0100 Subject: [PATCH 07/13] fix: format timer --- app/src/main/java/gui/widget/TimerRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/gui/widget/TimerRenderer.java b/app/src/main/java/gui/widget/TimerRenderer.java index 2db314c..67529b3 100644 --- a/app/src/main/java/gui/widget/TimerRenderer.java +++ b/app/src/main/java/gui/widget/TimerRenderer.java @@ -18,7 +18,7 @@ public class TimerRenderer { int seconds = (int) remainingTime; int minutes = seconds / 60; seconds %= 60; - String text = minutes + ":" + seconds; + String text = String.format("%02d:%02d", minutes, seconds); var textSize = ImGui.calcTextSize(text); ImGui.setCursorPosX(ImGui.getIO().getDisplaySizeX() / 2.0f - textSize.x / 2.0f); ImGui.text(text); From caf65694096d1a477b8f69005429b914bc232195 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 1 Feb 2025 11:22:59 +0100 Subject: [PATCH 08/13] fix: synced timer --- app/src/main/java/game/Game.java | 11 ++++++++- .../java/gui/menu/MultiPlayerDokuView.java | 7 +++--- .../main/java/gui/widget/TimerRenderer.java | 24 ++++++++++--------- .../java/network/client/ClientConnexion.java | 5 ++-- .../protocol/packets/StartGamePacket.java | 11 ++++++++- app/src/main/java/network/server/Server.java | 6 +++-- .../java/network/server/ServerConnexion.java | 10 +++++--- 7 files changed, 50 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/game/Game.java b/app/src/main/java/game/Game.java index ad4d2e6..ed5d78e 100644 --- a/app/src/main/java/game/Game.java +++ b/app/src/main/java/game/Game.java @@ -1,5 +1,6 @@ package game; +import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -14,10 +15,13 @@ public class Game { GameNotStarted, GameGoing, GameEnd } + public static final int GAME_DURATION = 10 * 60; + private final Map players; private final List leaderboard; private GameState gameState; private MultiDoku doku; + private Instant startTime = null; public Game() { this.players = new HashMap<>(); @@ -49,9 +53,10 @@ public class Game { return players; } - public void startGame(MultiDoku doku) { + public void startGame(MultiDoku doku, Instant startTime) { this.doku = doku; this.gameState = GameState.GameGoing; + this.startTime = startTime; } public GameState getGameState() { @@ -66,4 +71,8 @@ public class Game { return leaderboard; } + public Instant getStartTime() { + return startTime; + } + } diff --git a/app/src/main/java/gui/menu/MultiPlayerDokuView.java b/app/src/main/java/gui/menu/MultiPlayerDokuView.java index b18a12a..b6b09e4 100644 --- a/app/src/main/java/gui/menu/MultiPlayerDokuView.java +++ b/app/src/main/java/gui/menu/MultiPlayerDokuView.java @@ -1,5 +1,6 @@ package gui.menu; +import game.Game; import gui.widget.LeaderboardRenderer; import gui.widget.SudokuRenderer; import gui.widget.TimerRenderer; @@ -8,14 +9,13 @@ import network.client.Client; import network.server.Server; import sudoku.structure.Cell; -public class MultiPlayerDokuView extends BaseView{ +public class MultiPlayerDokuView extends BaseView { private final Client client; private final Server server; private final SudokuRenderer sudokuRenderer; private final LeaderboardRenderer leaderboardRenderer; private final TimerRenderer timerRenderer; - private static final float GAME_DURATION = 10 * 60; public MultiPlayerDokuView(StateMachine stateMachine, Client client, Server server) { super(stateMachine); @@ -25,8 +25,7 @@ public class MultiPlayerDokuView extends BaseView{ this.leaderboardRenderer = new LeaderboardRenderer(client.getGame(), client.getPlayer()); this.sudokuRenderer.onCellChange.connect(this::onCellChange); this.client.onDisconnect.connect(this::onDisconnect); - // TODO: sync timer - this.timerRenderer = new TimerRenderer(GAME_DURATION); + this.timerRenderer = new TimerRenderer(this.client.getGame().getStartTime(), Game.GAME_DURATION); } private void onCellChange(Cell cell) { diff --git a/app/src/main/java/gui/widget/TimerRenderer.java b/app/src/main/java/gui/widget/TimerRenderer.java index 67529b3..476036a 100644 --- a/app/src/main/java/gui/widget/TimerRenderer.java +++ b/app/src/main/java/gui/widget/TimerRenderer.java @@ -1,24 +1,26 @@ package gui.widget; +import java.time.Instant; + import imgui.ImGui; public class TimerRenderer { - private float time; - private final float duration; + private final long endTime; - public TimerRenderer(float duration) { - this.time = 0; - this.duration = duration; + public TimerRenderer(Instant startTime, int duration) { + this.endTime = startTime.getEpochSecond() + duration; + } + + private long getTimeRemaining() { + long currentTime = Instant.now().getEpochSecond(); + return endTime - currentTime; } public void render() { - this.time += ImGui.getIO().getDeltaTime(); - float remainingTime = this.duration - this.time; - int seconds = (int) remainingTime; - int minutes = seconds / 60; - seconds %= 60; - String text = String.format("%02d:%02d", minutes, seconds); + long seconds = getTimeRemaining(); + long minutes = seconds / 60; + String text = String.format("%02d:%02d", minutes, seconds % 60); var textSize = ImGui.calcTextSize(text); ImGui.setCursorPosX(ImGui.getIO().getDisplaySizeX() / 2.0f - textSize.x / 2.0f); ImGui.text(text); diff --git a/app/src/main/java/network/client/ClientConnexion.java b/app/src/main/java/network/client/ClientConnexion.java index 83eadf2..faa8970 100644 --- a/app/src/main/java/network/client/ClientConnexion.java +++ b/app/src/main/java/network/client/ClientConnexion.java @@ -71,7 +71,8 @@ public class ClientConnexion extends Connexion { @Override public void visitPacket(StartGamePacket packet) { - this.client.getGame().startGame(SudokuSerializer.deserializeSudoku(packet.getSerializedSudoku())); + this.client.getGame().startGame(SudokuSerializer.deserializeSudoku(packet.getSerializedSudoku()), + packet.getInstant()); this.client.onGameStarted.emit(); } @@ -84,7 +85,7 @@ public class ClientConnexion extends Connexion { @Override public void visitPacket(UpdatePlayerScorePacket packet) { Player player = this.client.getGame().getPlayerById(packet.getPlayerId()); - assert(player != null); + assert (player != null); this.client.getGame().setPlayerScore(player, packet.getCellsLeft()); System.out.println("Score for " + player.getPseudo() + " : " + packet.getCellsLeft()); } diff --git a/app/src/main/java/network/protocol/packets/StartGamePacket.java b/app/src/main/java/network/protocol/packets/StartGamePacket.java index c8e0ae8..e834821 100644 --- a/app/src/main/java/network/protocol/packets/StartGamePacket.java +++ b/app/src/main/java/network/protocol/packets/StartGamePacket.java @@ -1,5 +1,7 @@ package network.protocol.packets; +import java.time.Instant; + import network.protocol.Packet; import network.protocol.PacketVisitor; import network.protocol.Packets; @@ -9,15 +11,22 @@ public class StartGamePacket extends Packet { static private final long serialVersionUID = Packets.StartGame.ordinal(); private final String serializedSudoku; + // used to resume game + private final Instant instant; - public StartGamePacket(String serializedSudoku) { + public StartGamePacket(String serializedSudoku, Instant instant) { this.serializedSudoku = serializedSudoku; + this.instant = instant; } public String getSerializedSudoku() { return serializedSudoku; } + public Instant getInstant() { + return instant; + } + @Override public void accept(PacketVisitor packetVisitor) { packetVisitor.visitPacket(this); diff --git a/app/src/main/java/network/server/Server.java b/app/src/main/java/network/server/Server.java index 19b2b24..006d0d6 100644 --- a/app/src/main/java/network/server/Server.java +++ b/app/src/main/java/network/server/Server.java @@ -2,6 +2,7 @@ package network.server; import java.io.IOException; import java.net.ServerSocket; +import java.time.Instant; import java.util.ArrayList; import java.util.List; @@ -69,11 +70,12 @@ public class Server { } public void startGame(MultiDoku doku) { - this.game.startGame(doku); + Instant now = Instant.now(); + this.game.startGame(doku, now); for (ServerConnexion connexion : this.connexions) { connexion.setSudoku(doku.clone()); } - broadcastPacket(new StartGamePacket(SudokuSerializer.serializeSudoku(doku).toString())); + broadcastPacket(new StartGamePacket(SudokuSerializer.serializeSudoku(doku).toString(), now)); } } diff --git a/app/src/main/java/network/server/ServerConnexion.java b/app/src/main/java/network/server/ServerConnexion.java index 61ba7bb..8b41067 100644 --- a/app/src/main/java/network/server/ServerConnexion.java +++ b/app/src/main/java/network/server/ServerConnexion.java @@ -3,6 +3,7 @@ package network.server; import java.io.IOException; import java.net.Socket; +import game.Game; import game.Player; import game.Game.GameState; import network.Connexion; @@ -69,10 +70,13 @@ public class ServerConnexion extends Connexion { this.server.broadcastPacket(new PlayerJoinPacket(player)); sendPacket(new ConnexionInfoPacket(player.getId())); - if (this.server.getGame().getGameState() == GameState.GameGoing) { - setSudoku(this.server.getGame().getDoku().clone()); + Game game = this.server.getGame(); + + if (game.getGameState() == GameState.GameGoing) { + setSudoku(game.getDoku().clone()); sendPacket( - new StartGamePacket(SudokuSerializer.serializeSudoku(this.server.getGame().getDoku()).toString())); + new StartGamePacket(SudokuSerializer.serializeSudoku(game.getDoku()).toString(), + game.getStartTime())); } } From 438252a8ca298c83e495c497bcfc5872d15ce38a Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 1 Feb 2025 11:58:42 +0100 Subject: [PATCH 09/13] feat: first player progress display --- .../java/gui/menu/MultiPlayerDokuView.java | 4 +++ .../widget/MultiPlayerCompleteProgress.java | 28 +++++++++++++++++++ .../java/gui/widget/SmoothProgressBar.java | 21 ++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 app/src/main/java/gui/widget/MultiPlayerCompleteProgress.java create mode 100644 app/src/main/java/gui/widget/SmoothProgressBar.java diff --git a/app/src/main/java/gui/menu/MultiPlayerDokuView.java b/app/src/main/java/gui/menu/MultiPlayerDokuView.java index b6b09e4..af9bb9b 100644 --- a/app/src/main/java/gui/menu/MultiPlayerDokuView.java +++ b/app/src/main/java/gui/menu/MultiPlayerDokuView.java @@ -2,6 +2,7 @@ package gui.menu; import game.Game; import gui.widget.LeaderboardRenderer; +import gui.widget.MultiPlayerCompleteProgress; import gui.widget.SudokuRenderer; import gui.widget.TimerRenderer; import imgui.ImGui; @@ -16,6 +17,7 @@ public class MultiPlayerDokuView extends BaseView { private final SudokuRenderer sudokuRenderer; private final LeaderboardRenderer leaderboardRenderer; private final TimerRenderer timerRenderer; + private final MultiPlayerCompleteProgress completeProgress; public MultiPlayerDokuView(StateMachine stateMachine, Client client, Server server) { super(stateMachine); @@ -26,6 +28,7 @@ public class MultiPlayerDokuView extends BaseView { this.sudokuRenderer.onCellChange.connect(this::onCellChange); this.client.onDisconnect.connect(this::onDisconnect); this.timerRenderer = new TimerRenderer(this.client.getGame().getStartTime(), Game.GAME_DURATION); + this.completeProgress = new MultiPlayerCompleteProgress(this.client.getGame()); } private void onCellChange(Cell cell) { @@ -42,6 +45,7 @@ public class MultiPlayerDokuView extends BaseView { public void render() { this.timerRenderer.render(); this.leaderboardRenderer.render(); + this.completeProgress.render(); this.sudokuRenderer.render(); if (ImGui.button("Quitter")) { this.client.stop(); diff --git a/app/src/main/java/gui/widget/MultiPlayerCompleteProgress.java b/app/src/main/java/gui/widget/MultiPlayerCompleteProgress.java new file mode 100644 index 0000000..1ea723e --- /dev/null +++ b/app/src/main/java/gui/widget/MultiPlayerCompleteProgress.java @@ -0,0 +1,28 @@ +package gui.widget; + +import game.Game; +import game.Player; +import imgui.ImGui; +import imgui.ImVec2; + +public class MultiPlayerCompleteProgress { + + private final Game game; + private final int emptyCellCount; + private final ImVec2 progressSize = new ImVec2(700, 50); + private final SmoothProgressBar progressBar; + + public MultiPlayerCompleteProgress(Game game) { + this.game = game; + this.emptyCellCount = game.getDoku().getEmptyCells().size(); + this.progressBar = new SmoothProgressBar(); + } + + public void render() { + Player firstPlayer = game.getLeaderboard().get(0); + ImGui.setCursorPosX(ImGui.getIO().getDisplaySizeX() / 2.0f - progressSize.x / 2.0f); + String progressText = firstPlayer.getPseudo() + " - " + firstPlayer.getScore() + "/" + emptyCellCount; + this.progressBar.render(progressText, progressSize, 1.0f - firstPlayer.getScore() / (float) emptyCellCount); + } + +} diff --git a/app/src/main/java/gui/widget/SmoothProgressBar.java b/app/src/main/java/gui/widget/SmoothProgressBar.java new file mode 100644 index 0000000..9db2971 --- /dev/null +++ b/app/src/main/java/gui/widget/SmoothProgressBar.java @@ -0,0 +1,21 @@ +package gui.widget; + +import imgui.ImGui; +import imgui.ImVec2; + +public class SmoothProgressBar { + + private float lastProgress = 0; + private final float speed = 2.0f; + private final float clipConstant = 0.001f; + + public void render(String label, ImVec2 size, float progress) { + float delta = progress - lastProgress; + if (Math.abs(delta) < clipConstant) + lastProgress = progress; + else + lastProgress = lastProgress + delta * ImGui.getIO().getDeltaTime() * speed; + ImGui.progressBar(lastProgress, size, label); + } + +} From e98199e1ec154a38ab532655c7201812fda0846a Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 1 Feb 2025 11:59:05 +0100 Subject: [PATCH 10/13] refactor: remove sysout --- app/src/main/java/network/client/ClientConnexion.java | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/network/client/ClientConnexion.java b/app/src/main/java/network/client/ClientConnexion.java index faa8970..54f8ea0 100644 --- a/app/src/main/java/network/client/ClientConnexion.java +++ b/app/src/main/java/network/client/ClientConnexion.java @@ -87,7 +87,6 @@ public class ClientConnexion extends Connexion { Player player = this.client.getGame().getPlayerById(packet.getPlayerId()); assert (player != null); this.client.getGame().setPlayerScore(player, packet.getCellsLeft()); - System.out.println("Score for " + player.getPseudo() + " : " + packet.getCellsLeft()); } @Override From 02089c649b3a34e580f0dba94ec2276332db2425 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 1 Feb 2025 12:27:50 +0100 Subject: [PATCH 11/13] feat: select game duration --- app/src/main/java/game/Game.java | 14 ++++++++--- .../java/gui/menu/MultiPlayerDokuView.java | 2 +- .../main/java/gui/menu/MultiPlayerView.java | 24 +++++++++++++------ .../main/java/gui/widget/TimerRenderer.java | 2 +- .../java/network/client/ClientConnexion.java | 2 +- .../protocol/packets/EndGamePacket.java | 10 +------- .../protocol/packets/StartGamePacket.java | 8 ++++++- app/src/main/java/network/server/Server.java | 6 ++--- .../java/network/server/ServerConnexion.java | 11 ++++++++- 9 files changed, 52 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/game/Game.java b/app/src/main/java/game/Game.java index ed5d78e..d2fab75 100644 --- a/app/src/main/java/game/Game.java +++ b/app/src/main/java/game/Game.java @@ -15,13 +15,12 @@ public class Game { GameNotStarted, GameGoing, GameEnd } - public static final int GAME_DURATION = 10 * 60; - private final Map players; private final List leaderboard; private GameState gameState; private MultiDoku doku; private Instant startTime = null; + private long gameDuration; public Game() { this.players = new HashMap<>(); @@ -53,10 +52,15 @@ public class Game { return players; } - public void startGame(MultiDoku doku, Instant startTime) { + public void startGame(MultiDoku doku, Instant startTime, long gameDuration) { this.doku = doku; this.gameState = GameState.GameGoing; this.startTime = startTime; + this.gameDuration = gameDuration; + } + + public void stopGame() { + this.gameState = GameState.GameEnd; } public GameState getGameState() { @@ -75,4 +79,8 @@ public class Game { return startTime; } + public long getGameDuration() { + return gameDuration; + } + } diff --git a/app/src/main/java/gui/menu/MultiPlayerDokuView.java b/app/src/main/java/gui/menu/MultiPlayerDokuView.java index af9bb9b..a072c4d 100644 --- a/app/src/main/java/gui/menu/MultiPlayerDokuView.java +++ b/app/src/main/java/gui/menu/MultiPlayerDokuView.java @@ -27,7 +27,7 @@ public class MultiPlayerDokuView extends BaseView { this.leaderboardRenderer = new LeaderboardRenderer(client.getGame(), client.getPlayer()); this.sudokuRenderer.onCellChange.connect(this::onCellChange); this.client.onDisconnect.connect(this::onDisconnect); - this.timerRenderer = new TimerRenderer(this.client.getGame().getStartTime(), Game.GAME_DURATION); + this.timerRenderer = new TimerRenderer(this.client.getGame().getStartTime(), this.client.getGame().getGameDuration()); this.completeProgress = new MultiPlayerCompleteProgress(this.client.getGame()); } diff --git a/app/src/main/java/gui/menu/MultiPlayerView.java b/app/src/main/java/gui/menu/MultiPlayerView.java index 9f45944..c5668ad 100644 --- a/app/src/main/java/gui/menu/MultiPlayerView.java +++ b/app/src/main/java/gui/menu/MultiPlayerView.java @@ -3,6 +3,7 @@ package gui.menu; import game.Player; import gui.widget.SudokuSelector; import imgui.ImGui; +import imgui.type.ImInt; import network.client.Client; import network.server.Server; import sudoku.structure.MultiDoku; @@ -14,6 +15,8 @@ public class MultiPlayerView extends BaseView { private final SudokuSelector selector; + private ImInt gameDurationMinutes = new ImInt(10); + private MultiDoku doku = null; public MultiPlayerView(StateMachine stateMachine, Client client, Server server) { @@ -45,25 +48,32 @@ public class MultiPlayerView extends BaseView { if (this.server == null) { ImGui.text("En attente de l'administrateur du serveur ..."); } else { - if (this.doku == null) - ImGui.beginDisabled(); + renderTimer(); + ImGui.beginDisabled(this.doku == null); if (ImGui.button("Démarrer")) { - this.server.startGame(this.doku); + this.server.startGame(this.doku, this.gameDurationMinutes.get() * 60); } - if (this.doku == null) - ImGui.endDisabled(); + ImGui.endDisabled(); selector.render(); } } - @Override - public void render() { + private void renderPlayers() { ImGui.text("Joueurs :"); { for (Player player : this.client.getGame().getPlayers().values()) { ImGui.bulletText(player.getPseudo()); } } + } + + private void renderTimer() { + ImGui.inputInt("Temps de la partie (minutes)", gameDurationMinutes); + } + + @Override + public void render() { + renderPlayers(); renderGameStatus(); } diff --git a/app/src/main/java/gui/widget/TimerRenderer.java b/app/src/main/java/gui/widget/TimerRenderer.java index 476036a..10548b7 100644 --- a/app/src/main/java/gui/widget/TimerRenderer.java +++ b/app/src/main/java/gui/widget/TimerRenderer.java @@ -8,7 +8,7 @@ public class TimerRenderer { private final long endTime; - public TimerRenderer(Instant startTime, int duration) { + public TimerRenderer(Instant startTime, long duration) { this.endTime = startTime.getEpochSecond() + duration; } diff --git a/app/src/main/java/network/client/ClientConnexion.java b/app/src/main/java/network/client/ClientConnexion.java index 54f8ea0..f9a447b 100644 --- a/app/src/main/java/network/client/ClientConnexion.java +++ b/app/src/main/java/network/client/ClientConnexion.java @@ -72,7 +72,7 @@ public class ClientConnexion extends Connexion { @Override public void visitPacket(StartGamePacket packet) { this.client.getGame().startGame(SudokuSerializer.deserializeSudoku(packet.getSerializedSudoku()), - packet.getInstant()); + packet.getInstant(), packet.getGameDuration()); this.client.onGameStarted.emit(); } diff --git a/app/src/main/java/network/protocol/packets/EndGamePacket.java b/app/src/main/java/network/protocol/packets/EndGamePacket.java index d01a498..42f5066 100644 --- a/app/src/main/java/network/protocol/packets/EndGamePacket.java +++ b/app/src/main/java/network/protocol/packets/EndGamePacket.java @@ -8,15 +8,7 @@ public class EndGamePacket extends Packet { static private final long serialVersionUID = Packets.EndGame.ordinal(); - private final int winnerId; - - public EndGamePacket(int winnerId) { - this.winnerId = winnerId; - } - - public int getWinnerId() { - return winnerId; - } + public EndGamePacket() { } @Override public void accept(PacketVisitor packetVisitor) { diff --git a/app/src/main/java/network/protocol/packets/StartGamePacket.java b/app/src/main/java/network/protocol/packets/StartGamePacket.java index e834821..3832240 100644 --- a/app/src/main/java/network/protocol/packets/StartGamePacket.java +++ b/app/src/main/java/network/protocol/packets/StartGamePacket.java @@ -13,10 +13,12 @@ public class StartGamePacket extends Packet { private final String serializedSudoku; // used to resume game private final Instant instant; + private final long gameDuration; - public StartGamePacket(String serializedSudoku, Instant instant) { + public StartGamePacket(String serializedSudoku, Instant instant, long gameDuration) { this.serializedSudoku = serializedSudoku; this.instant = instant; + this.gameDuration = gameDuration; } public String getSerializedSudoku() { @@ -27,6 +29,10 @@ public class StartGamePacket extends Packet { return instant; } + public long getGameDuration() { + return gameDuration; + } + @Override public void accept(PacketVisitor packetVisitor) { packetVisitor.visitPacket(this); diff --git a/app/src/main/java/network/server/Server.java b/app/src/main/java/network/server/Server.java index 006d0d6..fcf87be 100644 --- a/app/src/main/java/network/server/Server.java +++ b/app/src/main/java/network/server/Server.java @@ -69,13 +69,13 @@ public class Server { return game; } - public void startGame(MultiDoku doku) { + public void startGame(MultiDoku doku, long gameDuration) { Instant now = Instant.now(); - this.game.startGame(doku, now); + this.game.startGame(doku, now, gameDuration); for (ServerConnexion connexion : this.connexions) { connexion.setSudoku(doku.clone()); } - broadcastPacket(new StartGamePacket(SudokuSerializer.serializeSudoku(doku).toString(), now)); + broadcastPacket(new StartGamePacket(SudokuSerializer.serializeSudoku(doku).toString(), now, gameDuration)); } } diff --git a/app/src/main/java/network/server/ServerConnexion.java b/app/src/main/java/network/server/ServerConnexion.java index 8b41067..f84b42b 100644 --- a/app/src/main/java/network/server/ServerConnexion.java +++ b/app/src/main/java/network/server/ServerConnexion.java @@ -76,7 +76,7 @@ public class ServerConnexion extends Connexion { setSudoku(game.getDoku().clone()); sendPacket( new StartGamePacket(SudokuSerializer.serializeSudoku(game.getDoku()).toString(), - game.getStartTime())); + game.getStartTime(), game.getGameDuration())); } } @@ -148,6 +148,15 @@ public class ServerConnexion extends Connexion { this.server.getGame().setPlayerScore(player, player.getScore() - 1); this.server.broadcastPacket(new UpdatePlayerScorePacket(player.getId(), player.getScore())); } + checkWin(); + } + + private void checkWin() { + if (this.player.getScore() == 0) { + // we don't need to specify the winner since it has to be the first + this.server.broadcastPacket(new EndGamePacket()); + this.server.getGame().stopGame(); + } } public void setSudoku(MultiDoku doku) { From f22debdf5f201bd28935326e95e38179665f8181 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 1 Feb 2025 12:54:11 +0100 Subject: [PATCH 12/13] fix: score display --- app/src/main/java/game/Game.java | 6 +++--- app/src/main/java/game/Player.java | 4 ++-- .../java/gui/widget/LeaderboardRenderer.java | 5 ++++- .../gui/widget/MultiPlayerCompleteProgress.java | 4 ++-- .../java/network/client/ClientConnexion.java | 2 +- .../java/network/server/ServerConnexion.java | 16 ++++++++-------- 6 files changed, 20 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/game/Game.java b/app/src/main/java/game/Game.java index d2fab75..9e229e8 100644 --- a/app/src/main/java/game/Game.java +++ b/app/src/main/java/game/Game.java @@ -37,10 +37,10 @@ public class Game { leaderboard.add(player); } - public void setPlayerScore(Player player, int newScore) { - player.setScore(newScore); + public void setPlayerRemainingCells(Player player, int newScore) { + player.setRemainingCells(newScore); Collections.sort(this.leaderboard, - (player1, player2) -> Integer.compare(player1.getScore(), player2.getScore())); + (player1, player2) -> Integer.compare(player1.getRemainingCells(), player2.getRemainingCells())); } public void removePlayer(int id) { diff --git a/app/src/main/java/game/Player.java b/app/src/main/java/game/Player.java index cfcda50..0b40f34 100644 --- a/app/src/main/java/game/Player.java +++ b/app/src/main/java/game/Player.java @@ -16,11 +16,11 @@ public class Player implements Serializable { this.score = 0; } - public int getScore() { + public int getRemainingCells() { return score; } - void setScore(int score) { + void setRemainingCells(int score) { this.score = score; } diff --git a/app/src/main/java/gui/widget/LeaderboardRenderer.java b/app/src/main/java/gui/widget/LeaderboardRenderer.java index 5f47c36..20b4c0d 100644 --- a/app/src/main/java/gui/widget/LeaderboardRenderer.java +++ b/app/src/main/java/gui/widget/LeaderboardRenderer.java @@ -22,9 +22,12 @@ public class LeaderboardRenderer { private final ImVec4 cellColorEnemy = new ImVec4(1.0f, 0.0f, 0.0f, 0.5f); private final int maxPlayersShowed = 2; + private final int emptyCellCount; + public LeaderboardRenderer(Game game, Player player) { this.game = game; this.currentPlayer = player; + this.emptyCellCount = game.getDoku().getEmptyCells().size(); } private void renderRank(int rank) { @@ -48,7 +51,7 @@ public class LeaderboardRenderer { ImGui.sameLine(); renderName(player.getPseudo()); ImGui.sameLine(); - renderScore(player.getScore()); + renderScore(emptyCellCount - player.getRemainingCells()); ImGui.endChild(); ImGui.popStyleColor(3); } diff --git a/app/src/main/java/gui/widget/MultiPlayerCompleteProgress.java b/app/src/main/java/gui/widget/MultiPlayerCompleteProgress.java index 1ea723e..d442459 100644 --- a/app/src/main/java/gui/widget/MultiPlayerCompleteProgress.java +++ b/app/src/main/java/gui/widget/MultiPlayerCompleteProgress.java @@ -21,8 +21,8 @@ public class MultiPlayerCompleteProgress { public void render() { Player firstPlayer = game.getLeaderboard().get(0); ImGui.setCursorPosX(ImGui.getIO().getDisplaySizeX() / 2.0f - progressSize.x / 2.0f); - String progressText = firstPlayer.getPseudo() + " - " + firstPlayer.getScore() + "/" + emptyCellCount; - this.progressBar.render(progressText, progressSize, 1.0f - firstPlayer.getScore() / (float) emptyCellCount); + String progressText = firstPlayer.getPseudo() + " - " + (emptyCellCount - firstPlayer.getRemainingCells()) + "/" + emptyCellCount; + this.progressBar.render(progressText, progressSize, 1.0f - firstPlayer.getRemainingCells() / (float) emptyCellCount); } } diff --git a/app/src/main/java/network/client/ClientConnexion.java b/app/src/main/java/network/client/ClientConnexion.java index f9a447b..0d56dc1 100644 --- a/app/src/main/java/network/client/ClientConnexion.java +++ b/app/src/main/java/network/client/ClientConnexion.java @@ -86,7 +86,7 @@ public class ClientConnexion extends Connexion { public void visitPacket(UpdatePlayerScorePacket packet) { Player player = this.client.getGame().getPlayerById(packet.getPlayerId()); assert (player != null); - this.client.getGame().setPlayerScore(player, packet.getCellsLeft()); + this.client.getGame().setPlayerRemainingCells(player, packet.getCellsLeft()); } @Override diff --git a/app/src/main/java/network/server/ServerConnexion.java b/app/src/main/java/network/server/ServerConnexion.java index f84b42b..5f656dd 100644 --- a/app/src/main/java/network/server/ServerConnexion.java +++ b/app/src/main/java/network/server/ServerConnexion.java @@ -63,7 +63,7 @@ public class ServerConnexion extends Connexion { for (Player p : this.server.getGame().getPlayers().values()) { if (p.getId() != player.getId()) { sendPacket(new PlayerJoinPacket(p)); - sendPacket(new UpdatePlayerScorePacket(p.getId(), p.getScore())); + sendPacket(new UpdatePlayerScorePacket(p.getId(), p.getRemainingCells())); } } @@ -139,20 +139,20 @@ public class ServerConnexion extends Connexion { } if (cell.getSymbolIndex() != Cell.NOSYMBOL && packet.getNewValue() == Cell.NOSYMBOL) { cell.empty(); - this.server.getGame().setPlayerScore(player, player.getScore() + 1); - this.server.broadcastPacket(new UpdatePlayerScorePacket(player.getId(), player.getScore())); + this.server.getGame().setPlayerRemainingCells(player, player.getRemainingCells() + 1); + this.server.broadcastPacket(new UpdatePlayerScorePacket(player.getId(), player.getRemainingCells())); return; } // on rajoute un chiffre à la grille if (cell.trySetValue(packet.getNewValue())) { - this.server.getGame().setPlayerScore(player, player.getScore() - 1); - this.server.broadcastPacket(new UpdatePlayerScorePacket(player.getId(), player.getScore())); + this.server.getGame().setPlayerRemainingCells(player, player.getRemainingCells() - 1); + this.server.broadcastPacket(new UpdatePlayerScorePacket(player.getId(), player.getRemainingCells())); } checkWin(); } private void checkWin() { - if (this.player.getScore() == 0) { + if (this.player.getRemainingCells() == 0) { // we don't need to specify the winner since it has to be the first this.server.broadcastPacket(new EndGamePacket()); this.server.getGame().stopGame(); @@ -162,8 +162,8 @@ public class ServerConnexion extends Connexion { public void setSudoku(MultiDoku doku) { this.doku = doku; assert (player != null); - this.server.getGame().setPlayerScore(player, this.doku.getEmptyCells().size()); - this.server.broadcastPacket(new UpdatePlayerScorePacket(player.getId(), player.getScore())); + this.server.getGame().setPlayerRemainingCells(player, this.doku.getEmptyCells().size()); + this.server.broadcastPacket(new UpdatePlayerScorePacket(player.getId(), player.getRemainingCells())); } } From 352aee49e4cce81217cb105ea07af704a05fb335 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 1 Feb 2025 13:41:13 +0100 Subject: [PATCH 13/13] feat: make timer stop game (Fixes #15) --- app/src/main/java/game/Game.java | 4 +- app/src/main/java/gui/menu/EndGameView.java | 49 +++++++++++++++++++ .../java/gui/menu/MultiPlayerDokuView.java | 14 +++++- app/src/main/java/gui/menu/StateMachine.java | 5 ++ .../widget/MultiPlayerCompleteProgress.java | 2 +- app/src/main/java/network/client/Client.java | 2 + .../java/network/client/ClientConnexion.java | 5 +- app/src/main/java/network/server/Server.java | 24 ++++++++- .../java/network/server/ServerConnexion.java | 4 +- .../network/server/ServerLogicThread.java | 2 +- .../main/java/sudoku/structure/MultiDoku.java | 9 ++++ 11 files changed, 109 insertions(+), 11 deletions(-) create mode 100644 app/src/main/java/gui/menu/EndGameView.java diff --git a/app/src/main/java/game/Game.java b/app/src/main/java/game/Game.java index 9e229e8..6e5d502 100644 --- a/app/src/main/java/game/Game.java +++ b/app/src/main/java/game/Game.java @@ -12,7 +12,7 @@ import sudoku.structure.MultiDoku; public class Game { public static enum GameState { - GameNotStarted, GameGoing, GameEnd + GameNotStarted, GameGoing } private final Map players; @@ -60,7 +60,7 @@ public class Game { } public void stopGame() { - this.gameState = GameState.GameEnd; + this.gameState = GameState.GameNotStarted; } public GameState getGameState() { diff --git a/app/src/main/java/gui/menu/EndGameView.java b/app/src/main/java/gui/menu/EndGameView.java new file mode 100644 index 0000000..409c3f1 --- /dev/null +++ b/app/src/main/java/gui/menu/EndGameView.java @@ -0,0 +1,49 @@ +package gui.menu; + +import game.Player; +import gui.ColorGenerator; +import gui.widget.SudokuRenderer; +import imgui.ImGui; +import imgui.ImVec4; +import sudoku.structure.MultiDoku; + +public class EndGameView extends BaseView { + + private final Player winner; + private float time = 0; + + private static final ImVec4 YELLOW = new ImVec4(1, 1, 0, 1); + + private final SudokuRenderer sudokuRenderer; + + public EndGameView(StateMachine stateMachine, MultiDoku resolved, Player winner) { + super(stateMachine); + this.winner = winner; + this.sudokuRenderer = new SudokuRenderer(resolved); + } + + private ImVec4 getPseudoColor() { + time += ImGui.getIO().getDeltaTime(); + float factor = (float) Math.cos(time); + var color = ColorGenerator.hslToRgb(factor * factor, 0.9f, 0.4f); + return new ImVec4(color.r, color.g, color.b, 1.0f); + } + + private void renderWinText() { + String winText = " a gagné !"; + String text = winner.getPseudo() + winText; + float textWidth = ImGui.calcTextSizeX(text); + ImGui.setCursorPosX(ImGui.getIO().getDisplaySizeX() / 2.0f - textWidth / 2.0f); + ImGui.textColored(getPseudoColor(), winner.getPseudo()); + ImGui.sameLine(); + ImGui.textColored(YELLOW, winText); + } + + @Override + public void render() { + renderWinText(); + this.sudokuRenderer.render(); + renderReturnButton(); + } + +} diff --git a/app/src/main/java/gui/menu/MultiPlayerDokuView.java b/app/src/main/java/gui/menu/MultiPlayerDokuView.java index a072c4d..f9ad32a 100644 --- a/app/src/main/java/gui/menu/MultiPlayerDokuView.java +++ b/app/src/main/java/gui/menu/MultiPlayerDokuView.java @@ -1,6 +1,6 @@ package gui.menu; -import game.Game; +import game.Player; import gui.widget.LeaderboardRenderer; import gui.widget.MultiPlayerCompleteProgress; import gui.widget.SudokuRenderer; @@ -8,7 +8,10 @@ import gui.widget.TimerRenderer; import imgui.ImGui; import network.client.Client; import network.server.Server; +import sudoku.solver.BacktrackingSolver; +import sudoku.solver.Solver; import sudoku.structure.Cell; +import sudoku.structure.MultiDoku; public class MultiPlayerDokuView extends BaseView { @@ -27,10 +30,19 @@ public class MultiPlayerDokuView extends BaseView { this.leaderboardRenderer = new LeaderboardRenderer(client.getGame(), client.getPlayer()); this.sudokuRenderer.onCellChange.connect(this::onCellChange); this.client.onDisconnect.connect(this::onDisconnect); + this.client.onGameEnd.connect(this::onGameEnd); this.timerRenderer = new TimerRenderer(this.client.getGame().getStartTime(), this.client.getGame().getGameDuration()); this.completeProgress = new MultiPlayerCompleteProgress(this.client.getGame()); } + private void onGameEnd(Player winner) { + MultiDoku doku = this.client.getGame().getDoku(); + doku.clearMutableCells(); + Solver solver = new BacktrackingSolver(); + solver.solve(doku); + this.stateMachine.overrideState(new EndGameView(stateMachine, doku, winner)); + } + private void onCellChange(Cell cell) { this.client.sendCellChange(cell); } diff --git a/app/src/main/java/gui/menu/StateMachine.java b/app/src/main/java/gui/menu/StateMachine.java index a175362..bb83800 100644 --- a/app/src/main/java/gui/menu/StateMachine.java +++ b/app/src/main/java/gui/menu/StateMachine.java @@ -27,6 +27,11 @@ public class StateMachine { menus.add(menu); } + public void overrideState(BaseView menu) { + menus.getLast().cleanResources(); + menus.set(menus.size() - 1, menu); + } + public void popState() { menus.getLast().cleanResources(); menus.pop(); diff --git a/app/src/main/java/gui/widget/MultiPlayerCompleteProgress.java b/app/src/main/java/gui/widget/MultiPlayerCompleteProgress.java index d442459..d5bebee 100644 --- a/app/src/main/java/gui/widget/MultiPlayerCompleteProgress.java +++ b/app/src/main/java/gui/widget/MultiPlayerCompleteProgress.java @@ -19,7 +19,7 @@ public class MultiPlayerCompleteProgress { } public void render() { - Player firstPlayer = game.getLeaderboard().get(0); + Player firstPlayer = game.getLeaderboard().getFirst(); ImGui.setCursorPosX(ImGui.getIO().getDisplaySizeX() / 2.0f - progressSize.x / 2.0f); String progressText = firstPlayer.getPseudo() + " - " + (emptyCellCount - firstPlayer.getRemainingCells()) + "/" + emptyCellCount; this.progressBar.render(progressText, progressSize, 1.0f - firstPlayer.getRemainingCells() / (float) emptyCellCount); diff --git a/app/src/main/java/network/client/Client.java b/app/src/main/java/network/client/Client.java index 20bf94b..de7865c 100644 --- a/app/src/main/java/network/client/Client.java +++ b/app/src/main/java/network/client/Client.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.net.UnknownHostException; import java.util.Random; +import common.ConsumerSignal; import common.Signal; import game.Game; import game.Player; @@ -21,6 +22,7 @@ public class Client { public final Signal onDisconnect = new Signal(); public final Signal onClosed = new Signal(); public final Signal onGameStarted = new Signal(); + public final ConsumerSignal onGameEnd = new ConsumerSignal<>(); Player player; diff --git a/app/src/main/java/network/client/ClientConnexion.java b/app/src/main/java/network/client/ClientConnexion.java index 0d56dc1..71945ff 100644 --- a/app/src/main/java/network/client/ClientConnexion.java +++ b/app/src/main/java/network/client/ClientConnexion.java @@ -78,8 +78,9 @@ public class ClientConnexion extends Connexion { @Override public void visitPacket(EndGamePacket packet) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'visitPacket'"); + Player winner = this.client.getGame().getLeaderboard().getFirst(); + this.client.getGame().stopGame(); + this.client.onGameEnd.emit(winner); } @Override diff --git a/app/src/main/java/network/server/Server.java b/app/src/main/java/network/server/Server.java index fcf87be..7c746be 100644 --- a/app/src/main/java/network/server/Server.java +++ b/app/src/main/java/network/server/Server.java @@ -8,7 +8,9 @@ import java.util.List; import game.Game; import game.Player; +import game.Game.GameState; import network.protocol.Packet; +import network.protocol.packets.EndGamePacket; import network.protocol.packets.StartGamePacket; import sudoku.io.SudokuSerializer; import sudoku.structure.MultiDoku; @@ -38,7 +40,16 @@ public class Server { } } - public void update() { + private void checkTimer() { + if (getGame() == null || getGame().getGameState() != GameState.GameGoing) + return; + long now = Instant.now().getEpochSecond(); + long end = getGame().getStartTime().getEpochSecond() + getGame().getGameDuration(); + if (now > end) + stopGame(); + } + + private void checkConnexions() { for (var it = connexions.iterator(); it.hasNext();) { ServerConnexion connexion = it.next(); if (!connexion.update()) { @@ -49,6 +60,11 @@ public class Server { } } + public void update() { + checkTimer(); + checkConnexions(); + } + public void stop() { this.acceptThread.cancel(); this.logicThread.cancel(); @@ -78,4 +94,10 @@ public class Server { broadcastPacket(new StartGamePacket(SudokuSerializer.serializeSudoku(doku).toString(), now, gameDuration)); } + public void stopGame() { + // we don't need to specify the winner since it has to be the first + broadcastPacket(new EndGamePacket()); + getGame().stopGame(); + } + } diff --git a/app/src/main/java/network/server/ServerConnexion.java b/app/src/main/java/network/server/ServerConnexion.java index 5f656dd..68de01b 100644 --- a/app/src/main/java/network/server/ServerConnexion.java +++ b/app/src/main/java/network/server/ServerConnexion.java @@ -153,9 +153,7 @@ public class ServerConnexion extends Connexion { private void checkWin() { if (this.player.getRemainingCells() == 0) { - // we don't need to specify the winner since it has to be the first - this.server.broadcastPacket(new EndGamePacket()); - this.server.getGame().stopGame(); + this.server.stopGame(); } } diff --git a/app/src/main/java/network/server/ServerLogicThread.java b/app/src/main/java/network/server/ServerLogicThread.java index c9b2a4b..d238ca2 100644 --- a/app/src/main/java/network/server/ServerLogicThread.java +++ b/app/src/main/java/network/server/ServerLogicThread.java @@ -19,7 +19,7 @@ public class ServerLogicThread extends Thread { try { Thread.sleep(50); } catch (InterruptedException e) { - // e.printStackTrace(); + e.printStackTrace(); break; } } diff --git a/app/src/main/java/sudoku/structure/MultiDoku.java b/app/src/main/java/sudoku/structure/MultiDoku.java index 569c163..d817eb7 100644 --- a/app/src/main/java/sudoku/structure/MultiDoku.java +++ b/app/src/main/java/sudoku/structure/MultiDoku.java @@ -183,6 +183,15 @@ public class MultiDoku { return emptyCells.get(randomIndex); } + public void clearMutableCells() { + for (Sudoku s : getSubGrids()) { + for (Cell cell : s.getCells()) { + if (cell.isMutable()) + cell.clearCurrentSymbol(); + } + } + } + public MultiDoku clone() { //TODO: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah return SudokuSerializer.deserializeSudoku(SudokuSerializer.serializeSudoku(this));