diff --git a/app/src/main/java/common/Signal.java b/app/src/main/java/common/Signal.java new file mode 100644 index 0000000..6e4154d --- /dev/null +++ b/app/src/main/java/common/Signal.java @@ -0,0 +1,31 @@ +package common; + +import java.util.HashSet; +import java.util.Set; + +public class Signal { + + private final Set listeners; + + public Signal() { + this.listeners = new HashSet<>(); + } + + public void connect(Runnable listener) { + this.listeners.add(listener); + } + + public void clear() { + this.listeners.clear(); + } + + public void emit() { + for (Runnable listener : this.listeners) { + listener.run(); + } + } + + // public void disconnect(Runnable listener) { + // this.listeners.remove(listener); + // } +} diff --git a/app/src/main/java/game/Game.java b/app/src/main/java/game/Game.java new file mode 100644 index 0000000..4daf004 --- /dev/null +++ b/app/src/main/java/game/Game.java @@ -0,0 +1,52 @@ +package game; + +import java.util.HashMap; +import java.util.Map; + +import sudoku.structure.MultiDoku; + +public class Game { + + public static enum GameState { + GameNotStarted, GameGoing, GameEnd + } + + private final Map players; + private GameState gameState; + private MultiDoku doku; + + public Game() { + this.players = new HashMap<>(); + this.gameState = GameState.GameNotStarted; + } + + public Player getPlayerById(int id) { + return players.get(id); + } + + public void addPlayer(Player player) { + players.put(player.getId(), player); + } + + public void removePlayer(int id) { + players.remove(id); + } + + public Map getPlayers() { + return players; + } + + public void startGame(MultiDoku doku) { + this.doku = doku; + this.gameState = GameState.GameGoing; + } + + public GameState getGameState() { + return gameState; + } + + public MultiDoku getDoku() { + return doku; + } + +} diff --git a/app/src/main/java/game/Player.java b/app/src/main/java/game/Player.java new file mode 100644 index 0000000..2336152 --- /dev/null +++ b/app/src/main/java/game/Player.java @@ -0,0 +1,25 @@ +package game; + +import java.io.Serializable; + +public class Player implements Serializable { + + static private final long serialVersionUID = 9999; + + private final String pseudo; + private final int id; + + public Player(int id, String pseudo) { + this.pseudo = pseudo; + this.id = id; + } + + public String getPseudo() { + return this.pseudo; + } + + public int getId() { + return this.id; + } + +} diff --git a/app/src/main/java/gui/Main.java b/app/src/main/java/gui/Main.java index f751b4b..50ca9a6 100644 --- a/app/src/main/java/gui/Main.java +++ b/app/src/main/java/gui/Main.java @@ -15,6 +15,11 @@ public class Main extends Application { config.setTitle("Let's play sudoku!"); } + @Override + protected void disposeWindow() { + stateMachine.clear(); + } + @Override protected void initImGui(Configuration config) { super.initImGui(config); diff --git a/app/src/main/java/gui/SudokuRenderer.java b/app/src/main/java/gui/SudokuRenderer.java index a662f34..1d47e6f 100644 --- a/app/src/main/java/gui/SudokuRenderer.java +++ b/app/src/main/java/gui/SudokuRenderer.java @@ -12,36 +12,41 @@ import imgui.flag.ImGuiCol; import imgui.flag.ImGuiStyleVar; import sudoku.structure.Block; import sudoku.structure.Cell; +import sudoku.structure.MultiDoku; import sudoku.structure.Sudoku; public class SudokuRenderer { - private final Sudoku sudoku; + private final MultiDoku doku; + private Sudoku currentSudoku; private int currentIndex = -1; private final Map colorPalette; - public SudokuRenderer(Sudoku sudoku) { - this.sudoku = sudoku; - this.colorPalette = new HashMap<>(); - initColors(); + public SudokuRenderer(MultiDoku doku) { + this.doku = doku; + this.currentSudoku = doku.getSubGrid(0); + this.colorPalette = initColors(); } - private void initColors() { - List colors = ColorGenerator.greatPalette(sudoku.getSize()); + private Map initColors() { + List colors = ColorGenerator.greatPalette(currentSudoku.getSize()); + Map colorPalette = new HashMap<>(); int index = 0; - for (Block block : sudoku.getBlocks()) { + for (Block block : currentSudoku.getBlocks()) { colorPalette.put(block, colors.get(index)); index++; } + return colorPalette; } private void renderPopup() { if (ImGui.beginPopup("editPopup")) { - for (int i = 1; i < sudoku.getSize() + 1; i++) { - if (i % (int) (Math.sqrt(sudoku.getSize())) != 1) + for (int i = 1; i < currentSudoku.getSize() + 1; i++) { + if (i % (int) (Math.sqrt(currentSudoku.getSize())) != 1) ImGui.sameLine(); if (ImGui.button(Integer.toString(i), new ImVec2(50, 50))) { - this.sudoku.setCellSymbol(currentIndex % sudoku.getSize(), currentIndex / sudoku.getSize(), i - 1); + this.currentSudoku.setCellSymbol(currentIndex % currentSudoku.getSize(), + currentIndex / currentSudoku.getSize(), i - 1); ImGui.closeCurrentPopup(); } } @@ -50,30 +55,31 @@ public class SudokuRenderer { } public void render() { - ImGui.begin("Sudoku Window"); - for (int y = 0; y < sudoku.getSize(); y++) { - for (int x = 0; x < sudoku.getSize(); x++) { + ImGui.pushStyleVar(ImGuiStyleVar.FrameBorderSize, 2.0f); + ImGui.pushStyleVar(ImGuiStyleVar.ItemSpacing, new ImVec2(0.0f, 0.0f)); + ImGui.pushStyleColor(ImGuiCol.Border, new ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + for (int y = 0; y < currentSudoku.getSize(); y++) { + for (int x = 0; x < currentSudoku.getSize(); x++) { if (x > 0) ImGui.sameLine(); - int index = y * sudoku.getSize() + x; - Cell cell = sudoku.getCell(x, y); + int index = y * currentSudoku.getSize() + x; + Cell cell = currentSudoku.getCell(x, y); int symbol = cell.getSymbolIndex(); Color blockColor = colorPalette.get(cell.getBlock()); - ImGui.pushStyleVar(ImGuiStyleVar.SelectableTextAlign, new ImVec2(0.5f, 0.5f)); - ImGui.pushStyleColor(ImGuiCol.Header, new ImVec4(blockColor.r, blockColor.g, blockColor.b, 1.0f)); + ImGui.pushStyleColor(ImGuiCol.Button, new ImVec4(blockColor.r, blockColor.g, blockColor.b, 1.0f)); String cellText = ""; if (symbol != -1) cellText += Integer.toString(symbol + 1); - if (ImGui.selectable(cellText + "##" + index, true, 0, new ImVec2(50, 50))) { + if (ImGui.button(cellText + "##" + index, new ImVec2(50, 50))) { ImGui.openPopup("editPopup"); currentIndex = index; } - ImGui.popStyleVar(); ImGui.popStyleColor(); } } + ImGui.popStyleColor(); + ImGui.popStyleVar(2); renderPopup(); - ImGui.end(); } } diff --git a/app/src/main/java/gui/menu/BaseView.java b/app/src/main/java/gui/menu/BaseView.java index 317fdce..7da67ce 100644 --- a/app/src/main/java/gui/menu/BaseView.java +++ b/app/src/main/java/gui/menu/BaseView.java @@ -12,8 +12,16 @@ public abstract class BaseView { public abstract void render(); + public void cleanResources() {} + + public void closeMenu(int count) { + for (int i = 0; i < count; i++) { + this.stateMachine.popState(); + } + } + public void closeMenu() { - this.stateMachine.popState(); + closeMenu(1); } protected void renderReturnButton() { diff --git a/app/src/main/java/gui/menu/ConnexionStatusView.java b/app/src/main/java/gui/menu/ConnexionStatusView.java new file mode 100644 index 0000000..13f527f --- /dev/null +++ b/app/src/main/java/gui/menu/ConnexionStatusView.java @@ -0,0 +1,105 @@ +package gui.menu; + +import java.io.IOException; +import java.net.UnknownHostException; + +import imgui.ImGui; +import network.client.Client; +import network.server.Server; + +public class ConnexionStatusView extends BaseView { + + private Client client; + private Server server; + + private String displayText = "Connecting ..."; + + public ConnexionStatusView(StateMachine stateMachine, String address, short port) + throws UnknownHostException, IOException { + super(stateMachine); + Thread t = new Thread(() -> { + try { + this.client = new Client(address, port); + bindListeners(); + } catch (IOException e) { + e.printStackTrace(); + onDisconnect(); + } + }); + t.start(); + } + + public ConnexionStatusView(StateMachine stateMachine, short port) throws UnknownHostException, IOException { + super(stateMachine); + Thread t = new Thread(() -> { + try { + this.server = new Server(port); + this.client = new Client("localhost", port); + bindListeners(); + } catch (IOException e) { + e.printStackTrace(); + onDisconnect(); + } + }); + t.start(); + } + + private void bindListeners() { + this.client.onConnect.connect(this::onConnect); + this.client.onClosed.connect(this::onLeave); + this.client.onDisconnect.connect(this::onDisconnect); + } + + private ConnexionStatusView(StateMachine stateMachine, Server server, Client client) { + super(stateMachine); + this.client = client; + this.server = server; + } + + public void onConnect() { + this.stateMachine.pushState(new MultiPlayerView(stateMachine, client, server)); + } + + public void onDisconnect() { + if (client != null) { + String reason = client.getDisconnectReason(); + if (reason == null) + displayText = "Le serveur a fermé la connexion !"; + else + displayText = "Vous avez été déconnecté ! Raison : " + client.getDisconnectReason(); + } else { + displayText = "La connexion a échoué !"; + } + + } + + public void onLeave() { + this.client = null; + // on passe le menu de la connexion + this.closeMenu(); + } + + @Override + public void render() { + ImGui.text(displayText); + renderReturnButton(); + } + + @Override + public void closeMenu() { + super.closeMenu(); + cleanResources(); + } + + @Override + public void cleanResources() { + // System.out.println("Bye bye !"); + if (this.server != null) { + this.server.stop(); + } + if (this.client != null) { + this.client.stop(); + } + } + +} diff --git a/app/src/main/java/gui/menu/CreateGameMenu.java b/app/src/main/java/gui/menu/CreateGameMenu.java deleted file mode 100644 index 9b99af7..0000000 --- a/app/src/main/java/gui/menu/CreateGameMenu.java +++ /dev/null @@ -1,17 +0,0 @@ -package gui.menu; - -import imgui.ImGui; - -public class CreateGameMenu extends BaseView { - - public CreateGameMenu(StateMachine stateMachine) { - super(stateMachine); - } - - @Override - public void render() { - ImGui.text("Créer"); - renderReturnButton(); - } - -} diff --git a/app/src/main/java/gui/menu/JoinGameMenu.java b/app/src/main/java/gui/menu/JoinGameMenu.java deleted file mode 100644 index cc64558..0000000 --- a/app/src/main/java/gui/menu/JoinGameMenu.java +++ /dev/null @@ -1,17 +0,0 @@ -package gui.menu; - -import imgui.ImGui; - -public class JoinGameMenu extends BaseView { - - public JoinGameMenu(StateMachine stateMachine) { - super(stateMachine); - } - - @Override - public void render() { - ImGui.text("Rejoindre"); - renderReturnButton(); - } - -} diff --git a/app/src/main/java/gui/menu/MultiMenu.java b/app/src/main/java/gui/menu/MultiMenu.java index 9a23a3f..7b5dc86 100644 --- a/app/src/main/java/gui/menu/MultiMenu.java +++ b/app/src/main/java/gui/menu/MultiMenu.java @@ -1,5 +1,7 @@ package gui.menu; +import java.io.IOException; + import imgui.ImGui; import imgui.ImVec2; import imgui.type.ImInt; @@ -12,6 +14,7 @@ public class MultiMenu extends BaseView { public MultiMenu(StateMachine stateMachine) { super(stateMachine); + address.resize(20); } private void renderCreate() { @@ -19,7 +22,11 @@ public class MultiMenu extends BaseView { ImGui.beginChild("##CreateGame", new ImVec2(displaySize.x / 2.0f, displaySize.y * 8.0f / 9.0f)); ImGui.inputInt("Port", port); if (ImGui.button("Créer")) { - // TODO: create game + try { + this.stateMachine.pushState(new ConnexionStatusView(stateMachine, (short) port.get())); + } catch (IOException e) { + e.printStackTrace(); + } } ImGui.endChild(); } @@ -30,7 +37,12 @@ public class MultiMenu extends BaseView { ImGui.inputText("Adresse", address); ImGui.inputInt("Port", port); if (ImGui.button("Rejoindre")) { - // TODO: join game + try { + this.stateMachine.pushState(new ConnexionStatusView(stateMachine, address.get(), (short) port.get())); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } ImGui.endChild(); } diff --git a/app/src/main/java/gui/menu/MultiPlayerDokuView.java b/app/src/main/java/gui/menu/MultiPlayerDokuView.java new file mode 100644 index 0000000..8974cd0 --- /dev/null +++ b/app/src/main/java/gui/menu/MultiPlayerDokuView.java @@ -0,0 +1,37 @@ +package gui.menu; + +import gui.SudokuRenderer; +import imgui.ImGui; +import network.client.Client; +import network.server.Server; + +public class MultiPlayerDokuView extends BaseView{ + + private final Client client; + private final Server server; + private final SudokuRenderer sudokuRenderer; + + 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.client.onDisconnect.connect(this::onDisconnect); + } + + public void onDisconnect() { + if (server == null) { + closeMenu(); + } + } + + @Override + public void render() { + this.sudokuRenderer.render(); + if (ImGui.button("Quitter")) { + this.client.stop(); + this.closeMenu(3); + } + } + +} diff --git a/app/src/main/java/gui/menu/MultiPlayerView.java b/app/src/main/java/gui/menu/MultiPlayerView.java new file mode 100644 index 0000000..dd42809 --- /dev/null +++ b/app/src/main/java/gui/menu/MultiPlayerView.java @@ -0,0 +1,56 @@ +package gui.menu; + +import game.Player; +import imgui.ImGui; +import network.client.Client; +import network.server.Server; +import sudoku.structure.MultiDoku; +import sudoku.structure.SudokuFactory; + +public class MultiPlayerView extends BaseView { + + private final Client client; + private final Server server; + + public MultiPlayerView(StateMachine stateMachine, Client client, Server server) { + super(stateMachine); + this.client = client; + this.server = server; + this.client.onDisconnect.connect(this::onDisconnect); + this.client.onGameStarted.connect(() -> this.stateMachine.pushState(new MultiPlayerDokuView(stateMachine, client, server))); + } + + @Override + public void closeMenu() { + this.client.forceDisconnect(); + super.closeMenu(); + } + + public void onDisconnect() { + this.stateMachine.popState(); + } + + public void renderGameStatus() { + if (this.server == null) { + ImGui.text("En attente de l'administrateur du serveur ..."); + } else { + if (ImGui.button("Démarrer")) { + // temp + MultiDoku doku = SudokuFactory.createBasicEmptySquareSudoku(5); + this.server.startGame(doku); + } + } + } + + @Override + public void render() { + ImGui.text("Joueurs :"); + { + for (Player player : this.client.getGame().getPlayers().values()) { + ImGui.bulletText(player.getPseudo()); + } + } + renderGameStatus(); + } + +} diff --git a/app/src/main/java/gui/menu/StateMachine.java b/app/src/main/java/gui/menu/StateMachine.java index 85ea897..63000c4 100644 --- a/app/src/main/java/gui/menu/StateMachine.java +++ b/app/src/main/java/gui/menu/StateMachine.java @@ -15,11 +15,19 @@ public class StateMachine { this.menus = new Stack<>(); } + public void clear() { + for (BaseView view : menus) { + view.cleanResources(); + } + menus.clear(); + } + public void pushState(BaseView menu) { menus.add(menu); } public void popState() { + menus.getLast().cleanResources(); menus.pop(); } @@ -35,8 +43,9 @@ public class StateMachine { ImGui.setNextWindowSize(displaySize); ImGui.begin("##Main Window", null, ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoBackground); - menus.getLast().render(); + menus.get(menus.size() - 1).render(); ImGui.end(); + // ImGui.showDemoWindow(); checkEscape(); } diff --git a/app/src/main/java/network/Connexion.java b/app/src/main/java/network/Connexion.java new file mode 100644 index 0000000..1b1e285 --- /dev/null +++ b/app/src/main/java/network/Connexion.java @@ -0,0 +1,40 @@ +package network; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.net.Socket; + +import network.protocol.Packet; +import network.protocol.PacketVisitor; + +public abstract class Connexion implements PacketVisitor { + + final Socket socket; + private final ObjectOutputStream objectOutputStream; + private final ConnexionThread connexionThread; + + public Connexion(Socket socket) throws IOException { + this.socket = socket; + this.objectOutputStream = new ObjectOutputStream(this.socket.getOutputStream()); + this.connexionThread = new ConnexionThread(this); + this.connexionThread.start(); + } + + public boolean isClosed() { + return this.socket.isClosed(); + } + + public synchronized void sendPacket(Packet packet) { + try { + objectOutputStream.writeObject(packet); + objectOutputStream.flush(); + } catch (IOException e) { + System.err.println("Error while sending packet ! " + e.getLocalizedMessage()); + close(); + } + } + + public void close() { + this.connexionThread.cancel(); + } +} diff --git a/app/src/main/java/network/ConnexionThread.java b/app/src/main/java/network/ConnexionThread.java new file mode 100644 index 0000000..6752d92 --- /dev/null +++ b/app/src/main/java/network/ConnexionThread.java @@ -0,0 +1,44 @@ +package network; + +import java.io.IOException; +import java.io.ObjectInputStream; + +import network.protocol.Packet; + +public class ConnexionThread extends Thread { + + private final Connexion connexion; + private final ObjectInputStream objectInputStream; + + public ConnexionThread(Connexion connexion) throws IOException { + this.connexion = connexion; + this.objectInputStream = new ObjectInputStream(this.connexion.socket.getInputStream()); + } + + @Override + public void run() { + while (!interrupted()) { + try { + // System.out.println(objectInputStream.available()); + Object o = objectInputStream.readObject(); + if (o instanceof Packet packet) { + connexion.visitPacket(packet); + } + } catch (ClassNotFoundException | IOException e) { + e.printStackTrace(); + this.connexion.close(); + break; + } + } + } + + public void cancel() { + try { + objectInputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + interrupt(); + } + +} diff --git a/app/src/main/java/network/client/Client.java b/app/src/main/java/network/client/Client.java new file mode 100644 index 0000000..10f5ec8 --- /dev/null +++ b/app/src/main/java/network/client/Client.java @@ -0,0 +1,57 @@ +package network.client; + +import java.io.IOException; +import java.net.UnknownHostException; +import java.util.Random; + +import common.Signal; +import game.Game; +import game.Player; +import network.protocol.packets.LoginPacket; + +public class Client { + private final ClientConnexion clientConnection; + private final Game game; + + public final Signal onConnect = new Signal(); + public final Signal onDisconnect = new Signal(); + public final Signal onClosed = new Signal(); + public final Signal onGameStarted = new Signal(); + + String disconnectReason = null; + + public Client(String address, short port) throws UnknownHostException, IOException { + this.clientConnection = new ClientConnexion(address, port, this); + this.game = new Game(); + // temp + Random r = new Random(); + login("Player" + r.nextInt()); + } + + public void login(String pseudo) { + System.out.println("Logging in with pseudo " + pseudo + " ..."); + this.clientConnection.sendPacket(new LoginPacket(pseudo)); + } + + public void stop() { + this.clientConnection.close(); + } + + public void addPlayer(Player player) { + this.game.addPlayer(player); + } + + public Game getGame() { + return game; + } + + public String getDisconnectReason() { + return disconnectReason; + } + + public void forceDisconnect() { + this.onClosed.emit(); + stop(); + } + +} diff --git a/app/src/main/java/network/client/ClientConnexion.java b/app/src/main/java/network/client/ClientConnexion.java new file mode 100644 index 0000000..6e6fe87 --- /dev/null +++ b/app/src/main/java/network/client/ClientConnexion.java @@ -0,0 +1,76 @@ +package network.client; + +import java.io.IOException; +import java.net.Socket; +import java.net.UnknownHostException; + +import game.Player; +import network.Connexion; +import network.protocol.packets.ConnexionInfoPacket; +import network.protocol.packets.DisconnectPacket; +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 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)); + this.client = client; + } + + @Override + public void close() { + if (!this.isClosed()) { + super.close(); + client.onDisconnect.emit(); + } + } + + @Override + public void visitPacket(ConnexionInfoPacket packet) { + this.player = this.client.getGame().getPlayerById(packet.getConnectionId()); + client.onConnect.emit(); + } + + @Override + public void visitPacket(KeepAlivePacket packet) { + // we just send the packet back to the server + sendPacket(packet); + } + + @Override + public void visitPacket(DisconnectPacket packet) { + this.client.disconnectReason = packet.getReason(); + close(); + } + + @Override + public void visitPacket(LoginPacket packet) { + throw new UnsupportedOperationException("Unimplemented method 'visitPacketLogin'"); + } + + @Override + public void visitPacket(PlayerJoinPacket packet) { + this.client.addPlayer(packet.getPlayer()); + System.out.println("[Client] " + packet.getPlayer().getPseudo() + " joined the game !"); + } + + @Override + public void visitPacket(PlayerLeavePacket packet) { + this.client.getGame().removePlayer(packet.getPlayer()); + } + + @Override + public void visitPacket(StartGamePacket packet) { + this.client.getGame().startGame(SudokuSerializer.deserializeSudoku(packet.getSerializedSudoku())); + this.client.onGameStarted.emit(); + } + +} diff --git a/app/src/main/java/network/protocol/Packet.java b/app/src/main/java/network/protocol/Packet.java new file mode 100644 index 0000000..d7b4c19 --- /dev/null +++ b/app/src/main/java/network/protocol/Packet.java @@ -0,0 +1,11 @@ +package network.protocol; + +import java.io.Serializable; + +public abstract class Packet implements Serializable { + + // public abstract int getId(); + + public abstract void accept(PacketVisitor packetVisitor); + +} diff --git a/app/src/main/java/network/protocol/PacketDispatcher.java b/app/src/main/java/network/protocol/PacketDispatcher.java new file mode 100644 index 0000000..376ab96 --- /dev/null +++ b/app/src/main/java/network/protocol/PacketDispatcher.java @@ -0,0 +1,28 @@ +package network.protocol; + +import java.util.ArrayList; +import java.util.List; + +public class PacketDispatcher { + + private final List handlers; + + public PacketDispatcher() { + this.handlers = new ArrayList<>(); + } + + public void dispatch(Packet packet) { + for (PacketVisitor handler : handlers) { + handler.visitPacket(packet); + } + } + + public void registerHandler(PacketVisitor handler) { + handlers.add(handler); + } + + public void unregisterHandler(PacketVisitor handler) { + handlers.remove(handler); + } + +} diff --git a/app/src/main/java/network/protocol/PacketFactory.java b/app/src/main/java/network/protocol/PacketFactory.java new file mode 100644 index 0000000..14b2347 --- /dev/null +++ b/app/src/main/java/network/protocol/PacketFactory.java @@ -0,0 +1,6 @@ +package network.protocol; + +public class PacketFactory { + + +} diff --git a/app/src/main/java/network/protocol/PacketVisitor.java b/app/src/main/java/network/protocol/PacketVisitor.java new file mode 100644 index 0000000..582a4f3 --- /dev/null +++ b/app/src/main/java/network/protocol/PacketVisitor.java @@ -0,0 +1,25 @@ +package network.protocol; + +import network.protocol.packets.ConnexionInfoPacket; +import network.protocol.packets.DisconnectPacket; +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; + +public interface PacketVisitor { + + default void visitPacket(Packet packet) { + packet.accept(this); + } + + void visitPacket(ConnexionInfoPacket packet); + void visitPacket(DisconnectPacket packet); + void visitPacket(KeepAlivePacket packet); + void visitPacket(LoginPacket packet); + void visitPacket(PlayerJoinPacket packet); + void visitPacket(PlayerLeavePacket packet); + void visitPacket(StartGamePacket packet); + +} diff --git a/app/src/main/java/network/protocol/Packets.java b/app/src/main/java/network/protocol/Packets.java new file mode 100644 index 0000000..9355877 --- /dev/null +++ b/app/src/main/java/network/protocol/Packets.java @@ -0,0 +1,7 @@ +package network.protocol; + +public enum Packets { + + ConnectionInfo, KeepAlive, Disconnect, Login, PlayerJoin, PlayerLeave, StartGame + +} diff --git a/app/src/main/java/network/protocol/packets/ConnexionInfoPacket.java b/app/src/main/java/network/protocol/packets/ConnexionInfoPacket.java new file mode 100644 index 0000000..5be408b --- /dev/null +++ b/app/src/main/java/network/protocol/packets/ConnexionInfoPacket.java @@ -0,0 +1,25 @@ +package network.protocol.packets; + +import network.protocol.Packet; +import network.protocol.PacketVisitor; +import network.protocol.Packets; + +public class ConnexionInfoPacket extends Packet { + + static private final long serialVersionUID = Packets.ConnectionInfo.ordinal(); + + private final int connectionId; + + public ConnexionInfoPacket(int connectionId) { + this.connectionId = connectionId; + } + + public int getConnectionId() { + return connectionId; + } + + @Override + public void accept(PacketVisitor packetVisitor) { + packetVisitor.visitPacket(this); + } +} diff --git a/app/src/main/java/network/protocol/packets/DisconnectPacket.java b/app/src/main/java/network/protocol/packets/DisconnectPacket.java new file mode 100644 index 0000000..bcec9ba --- /dev/null +++ b/app/src/main/java/network/protocol/packets/DisconnectPacket.java @@ -0,0 +1,26 @@ +package network.protocol.packets; + +import network.protocol.Packet; +import network.protocol.PacketVisitor; +import network.protocol.Packets; + +public class DisconnectPacket extends Packet { + + static private final long serialVersionUID = Packets.Disconnect.ordinal(); + + private final String reason; + + public DisconnectPacket(String reason) { + this.reason = reason; + } + + public String getReason() { + return reason; + } + + @Override + public void accept(PacketVisitor packetVisitor) { + packetVisitor.visitPacket(this); + } + +} diff --git a/app/src/main/java/network/protocol/packets/KeepAlivePacket.java b/app/src/main/java/network/protocol/packets/KeepAlivePacket.java new file mode 100644 index 0000000..5804470 --- /dev/null +++ b/app/src/main/java/network/protocol/packets/KeepAlivePacket.java @@ -0,0 +1,26 @@ +package network.protocol.packets; + +import network.protocol.Packet; +import network.protocol.PacketVisitor; +import network.protocol.Packets; + +public class KeepAlivePacket extends Packet { + + static private final long serialVersionUID = Packets.KeepAlive.ordinal(); + + private final long keepAliveId; + + public KeepAlivePacket(long keepAliveId) { + this.keepAliveId = keepAliveId; + } + + public long getKeepAliveId() { + return keepAliveId; + } + + @Override + public void accept(PacketVisitor packetVisitor) { + packetVisitor.visitPacket(this); + } + +} diff --git a/app/src/main/java/network/protocol/packets/LoginPacket.java b/app/src/main/java/network/protocol/packets/LoginPacket.java new file mode 100644 index 0000000..237dd3f --- /dev/null +++ b/app/src/main/java/network/protocol/packets/LoginPacket.java @@ -0,0 +1,26 @@ +package network.protocol.packets; + +import network.protocol.Packet; +import network.protocol.PacketVisitor; +import network.protocol.Packets; + +public class LoginPacket extends Packet { + + static private final long serialVersionUID = Packets.Login.ordinal(); + + private final String pseudo; + + public LoginPacket(String pseudo) { + this.pseudo = pseudo; + } + + public String getPseudo() { + return pseudo; + } + + @Override + public void accept(PacketVisitor packetVisitor) { + packetVisitor.visitPacket(this); + } + +} diff --git a/app/src/main/java/network/protocol/packets/PlayerJoinPacket.java b/app/src/main/java/network/protocol/packets/PlayerJoinPacket.java new file mode 100644 index 0000000..4badf2d --- /dev/null +++ b/app/src/main/java/network/protocol/packets/PlayerJoinPacket.java @@ -0,0 +1,29 @@ +package network.protocol.packets; + +import game.Player; +import network.protocol.Packet; +import network.protocol.PacketVisitor; +import network.protocol.Packets; + +public class PlayerJoinPacket extends Packet{ + + static private final long serialVersionUID = Packets.PlayerJoin.ordinal(); + + private final Player player; + + public PlayerJoinPacket(Player player) { + this.player = player; + } + + public Player getPlayer() { + return player; + } + + @Override + public void accept(PacketVisitor packetVisitor) { + packetVisitor.visitPacket(this); + } + + + +} diff --git a/app/src/main/java/network/protocol/packets/PlayerLeavePacket.java b/app/src/main/java/network/protocol/packets/PlayerLeavePacket.java new file mode 100644 index 0000000..b22b7cc --- /dev/null +++ b/app/src/main/java/network/protocol/packets/PlayerLeavePacket.java @@ -0,0 +1,28 @@ +package network.protocol.packets; + +import network.protocol.Packet; +import network.protocol.PacketVisitor; +import network.protocol.Packets; + +public class PlayerLeavePacket extends Packet{ + + static private final long serialVersionUID = Packets.PlayerLeave.ordinal(); + + private final int playerId; + + public PlayerLeavePacket(int playerId) { + this.playerId = playerId; + } + + public int getPlayer() { + return playerId; + } + + @Override + public void accept(PacketVisitor packetVisitor) { + packetVisitor.visitPacket(this); + } + + + +} diff --git a/app/src/main/java/network/protocol/packets/StartGamePacket.java b/app/src/main/java/network/protocol/packets/StartGamePacket.java new file mode 100644 index 0000000..c8e0ae8 --- /dev/null +++ b/app/src/main/java/network/protocol/packets/StartGamePacket.java @@ -0,0 +1,26 @@ +package network.protocol.packets; + +import network.protocol.Packet; +import network.protocol.PacketVisitor; +import network.protocol.Packets; + +public class StartGamePacket extends Packet { + + static private final long serialVersionUID = Packets.StartGame.ordinal(); + + private final String serializedSudoku; + + public StartGamePacket(String serializedSudoku) { + this.serializedSudoku = serializedSudoku; + } + + public String getSerializedSudoku() { + return serializedSudoku; + } + + @Override + public void accept(PacketVisitor packetVisitor) { + packetVisitor.visitPacket(this); + } + +} diff --git a/app/src/main/java/network/server/KeepAliveHandler.java b/app/src/main/java/network/server/KeepAliveHandler.java new file mode 100644 index 0000000..ae1ba22 --- /dev/null +++ b/app/src/main/java/network/server/KeepAliveHandler.java @@ -0,0 +1,49 @@ +package network.server; + +import java.util.Random; + +import network.protocol.packets.KeepAlivePacket; + +public class KeepAliveHandler { + + private final ServerConnexion serverConnexion; + + private static final int KEEP_ALIVE_COOLDOWN = 5 * 1000; + + private long lastKeepAlive = 0; + private long lastSend = 0; + private volatile boolean keepAliveRecieved = false; + + public KeepAliveHandler(ServerConnexion serverConnexion) { + this.serverConnexion = serverConnexion; + sendKeepAlive(); + } + + public boolean update() { + var currentTime = System.currentTimeMillis(); + if (currentTime - lastSend > KEEP_ALIVE_COOLDOWN) { + if (keepAliveRecieved) { + sendKeepAlive(); + } else { + System.out.println("Zombie"); + serverConnexion.close(); + return false; + } + } + return true; + } + + public void recievedKeepAlive(long keepAliveId) { + if (lastKeepAlive == keepAliveId) + this.keepAliveRecieved = true; + } + + private void sendKeepAlive() { + Random r = new Random(); + lastKeepAlive = r.nextLong(); + lastSend = System.currentTimeMillis(); + keepAliveRecieved = false; + this.serverConnexion.sendPacket(new KeepAlivePacket(lastKeepAlive)); + } + +} diff --git a/app/src/main/java/network/server/Server.java b/app/src/main/java/network/server/Server.java new file mode 100644 index 0000000..c1b35f1 --- /dev/null +++ b/app/src/main/java/network/server/Server.java @@ -0,0 +1,76 @@ +package network.server; + +import java.io.IOException; +import java.net.ServerSocket; +import java.util.ArrayList; +import java.util.List; + +import game.Game; +import game.Player; +import network.protocol.Packet; +import network.protocol.packets.StartGamePacket; +import sudoku.io.SudokuSerializer; +import sudoku.structure.MultiDoku; + +public class Server { + + final ServerSocket serverSocket; + final List connexions; + private final ServerAcceptThread acceptThread; + private final ServerLogicThread logicThread; + private final Game game; + private int nextPlayerId = 0; + + public Server(short port) throws IOException { + this.serverSocket = new ServerSocket(port); + this.connexions = new ArrayList<>(); + this.acceptThread = new ServerAcceptThread(this); + this.acceptThread.start(); + this.logicThread = new ServerLogicThread(this); + this.logicThread.start(); + this.game = new Game(); + } + + public void broadcastPacket(Packet packet) { + for (ServerConnexion connexion : this.connexions) { + connexion.sendPacket(packet); + } + } + + public void update() { + for (var it = connexions.iterator(); it.hasNext();) { + ServerConnexion connexion = it.next(); + if (!connexion.update()) { + connexion.close(); + connexion.nukeConnection(); + it.remove(); + } + } + } + + public void stop() { + this.acceptThread.cancel(); + this.logicThread.cancel(); + for (ServerConnexion connexion : this.connexions) { + connexion.nukeConnection(); + connexion.close(); + } + } + + public Player addPlayer(String pseudo) { + Player p = new Player(nextPlayerId, pseudo); + this.game.addPlayer(p); + nextPlayerId++; + return p; + } + + public Game getGame() { + return game; + } + + public void startGame(MultiDoku doku) { + this.game.startGame(doku); + broadcastPacket(new StartGamePacket(SudokuSerializer.serializeSudoku(doku))); + } + +} diff --git a/app/src/main/java/network/server/ServerAcceptThread.java b/app/src/main/java/network/server/ServerAcceptThread.java new file mode 100644 index 0000000..61800db --- /dev/null +++ b/app/src/main/java/network/server/ServerAcceptThread.java @@ -0,0 +1,36 @@ +package network.server; + +import java.io.IOException; +import java.net.Socket; + +public class ServerAcceptThread extends Thread { + + private final Server server; + + public ServerAcceptThread(Server server) { + this.server = server; + } + + public void cancel() { + try { + this.server.serverSocket.close(); + } catch (IOException e) { + e.printStackTrace(); + } + interrupt(); + } + + @Override + public void run() { + try { + while(!interrupted()) { + Socket newConnection = this.server.serverSocket.accept(); + ServerConnexion serverConnection = new ServerConnexion(newConnection, this.server); + this.server.connexions.add(serverConnection); + } + } catch(IOException e) { + // e.printStackTrace(); + } + } + +} diff --git a/app/src/main/java/network/server/ServerConnexion.java b/app/src/main/java/network/server/ServerConnexion.java new file mode 100644 index 0000000..eaf79c2 --- /dev/null +++ b/app/src/main/java/network/server/ServerConnexion.java @@ -0,0 +1,105 @@ +package network.server; + +import java.io.IOException; +import java.net.Socket; + +import game.Player; +import game.Game.GameState; +import network.Connexion; +import network.protocol.packets.ConnexionInfoPacket; +import network.protocol.packets.DisconnectPacket; +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 sudoku.io.SudokuSerializer; + +public class ServerConnexion extends Connexion { + + private final Server server; + private final KeepAliveHandler keepAliveHandler; + private boolean shouldClose = false; + private Player player = null; + + public ServerConnexion(Socket socket, Server server) throws IOException { + super(socket); + this.server = server; + this.keepAliveHandler = new KeepAliveHandler(this); + } + + public boolean update() { + if (shouldClose | isClosed()) + return false; + return this.keepAliveHandler.update(); + } + + public void nukeConnection() { + if (player != null) { + sendPacket(new DisconnectPacket("Le serveur a été fermé !")); + this.server.broadcastPacket(new PlayerLeavePacket(player.getId())); + this.server.getGame().removePlayer(player.getId()); + } + } + + @Override + public synchronized void close() { + if(shouldClose) + return; + super.close(); + shouldClose = true; + System.out.println("[Server] Closing 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()) + sendPacket(new PlayerJoinPacket(p)); + } + 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()))); + } + } + + @Override + public void visitPacket(KeepAlivePacket packet) { + this.keepAliveHandler.recievedKeepAlive(packet.getKeepAliveId()); + } + + @Override + public void visitPacket(DisconnectPacket packet) { + close(); + } + + @Override + public void visitPacket(LoginPacket packet) { + if (this.player != null) + return; + this.player = this.server.addPlayer(packet.getPseudo()); + finishLogin(); + } + + @Override + public void visitPacket(ConnexionInfoPacket packet) { + throw new UnsupportedOperationException("Unimplemented method 'visitPacketConnexionInfo'"); + } + + @Override + public void visitPacket(PlayerJoinPacket packet) { + throw new UnsupportedOperationException("Unimplemented method 'visitPacketPlayerJoin'"); + } + + @Override + public void visitPacket(PlayerLeavePacket packet) { + throw new UnsupportedOperationException("Unimplemented method 'visitPacketPlayerLeave'"); + } + + @Override + public void visitPacket(StartGamePacket packet) { + throw new UnsupportedOperationException("Unimplemented method 'visitPacketStartGame'"); + } + +} diff --git a/app/src/main/java/network/server/ServerLogicThread.java b/app/src/main/java/network/server/ServerLogicThread.java new file mode 100644 index 0000000..c9b2a4b --- /dev/null +++ b/app/src/main/java/network/server/ServerLogicThread.java @@ -0,0 +1,28 @@ +package network.server; + +public class ServerLogicThread extends Thread { + + private final Server server; + + public ServerLogicThread(Server server) { + this.server = server; + } + + public void cancel() { + interrupt(); + } + + @Override + public void run() { + while (!interrupted()) { + server.update(); + try { + Thread.sleep(50); + } catch (InterruptedException e) { + // e.printStackTrace(); + break; + } + } + } + +}