package it.polimi.ingsw.gc14; import it.polimi.ingsw.gc14.Controller.GameController; import it.polimi.ingsw.gc14.Model.Game; import it.polimi.ingsw.gc14.Model.MiniModel; import it.polimi.ingsw.gc14.Model.Player; import it.polimi.ingsw.gc14.Network.CompositeClientBroadcaster; import it.polimi.ingsw.gc14.Network.NetworkEvent; import it.polimi.ingsw.gc14.Network.RMI.Server.RMIServer; import it.polimi.ingsw.gc14.Network.NetworkConfig; import it.polimi.ingsw.gc14.Network.TCP.Server.TCPServer; import java.net.*; import java.rmi.RemoteException; import java.util.*; import java.util.concurrent.*; import java.util.stream.Collectors; /** * Main server entry point. * *

Responsibilities of this class are intentionally limited to wiring: * it creates all components, connects them together, restores a previously * saved game if one exists, and starts the event-processing loop and the * network servers. * *

All game-logic and event-routing decisions are delegated to * {@link GameEventProcessor}; persistence is delegated to {@link SaveManager}. * *

The original {@code ServerLauncher} class is preserved and untouched. * This class is a clean replacement that uses the new component structure. */ public class ServerLauncher { /** Drives the main game-event loop. */ private final GameEventProcessor eventProcessor; /** * Constructs a {@code ServerLauncherTest} and wires all components together. * * @param actionQueue the shared event queue. * @param gameController the server-side game controller. * @param playerList the shared player-status map. * @param broadcaster the broadcaster used to reach all connected clients. * @param saveManager the save manager used to persist game state. */ public ServerLauncher( BlockingQueue actionQueue, GameController gameController, LimitedMap playerList, CompositeClientBroadcaster broadcaster, SaveManager saveManager) { this.eventProcessor = new GameEventProcessor( actionQueue, gameController, playerList, broadcaster, saveManager); } /** * Initializes all server components, wires them together, restores a saved * game if available, and starts the event-processing loop and both network * servers. * * @param args command-line arguments (unused). * @throws RemoteException if the RMI server cannot be created. */ static void main(String[] args) throws RemoteException { LimitedMap playerList = new LimitedMap<>(5, () -> {}); BlockingQueue actionQueue = new LinkedBlockingQueue<>(); GameController gameController = new GameController(); String ip; try { ip = chooseNetworkInterface(new Scanner(System.in)); System.out.println(ip); } catch (Exception e) { throw new RuntimeException(e); } System.setProperty("java.rmi.server.hostname", ip); RMIServer rmiServer = new RMIServer(gameController, NetworkConfig.RMI_PORT, actionQueue, playerList, ip); TCPServer tcpServer = new TCPServer(gameController, NetworkConfig.TCP_PORT, NetworkConfig.HEARTBEAT_PORT, actionQueue, playerList); CompositeClientBroadcaster broadcaster = new CompositeClientBroadcaster(rmiServer, tcpServer); SaveManager saveManager = new SaveManager(ServerLauncher.class); restoreGameIfSaved(gameController, playerList, saveManager); ServerLauncher launcher = new ServerLauncher( actionQueue, gameController, playerList, broadcaster, saveManager); // When all required players have joined, broadcast the initial MiniModel. playerList.setAction(() -> new Thread(() -> { MiniModel miniModel; synchronized (gameController) { Game game = gameController.getModel(); miniModel = new MiniModel( game.getSlotMap(), game.getOrderLogicCard(), game.getCurrentState(), game.getPlayers(), game.getAvailableTotems(), game.getUpperListTribeCards(), game.getLowerListTribeCards(), game.getUpperListBuilding(), game.getLowerListBuilding(), game.getDisconnectedPlayers().entrySet().stream() .filter(Map.Entry::getValue) .map(e -> e.getKey().getUserName()) .collect(Collectors.toCollection(ArrayList::new)) ); } broadcaster.notifyAll(miniModel); }).start()); new Thread(() -> { try { launcher.run(); } catch (Exception e) { System.out.println("Unexpected exception in game loop"); e.printStackTrace(); } }).start(); rmiServer.start(); new Thread(() -> tcpServer.start()).start(); } /** * Runs the event-processing loop until the thread is interrupted. * *

A {@link ConcurrentModificationException} is caught and logged rather */ public void run() { while (true) { try { eventProcessor.doFirstEvent(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } } /** * Attempts to restore a previously saved game. * *

The save is discarded if all but at most one player was offline at the * time of the crash, since there would be nobody to resume the game with. * Otherwise, the model is restored, the player-list limit is set, and each * player who was offline at crash time is pre-populated as offline so the * reconnection flow can handle them correctly. * * @param gameController the controller that will receive the restored model. * @param playerList the player-status map to populate. * @param saveManager the save manager to load from. */ private static void restoreGameIfSaved( GameController gameController, LimitedMap playerList, SaveManager saveManager) { Game game = saveManager.load(); if (game == null) return; long disconnectedCount = game.getDisconnectedPlayers().entrySet().stream() .filter(Map.Entry::getValue).count(); if (disconnectedCount >= game.getNPlayers() - 1) { saveManager.delete(); System.out.println("Save discarded: too many players were offline at crash time."); return; } gameController.setModel(game); playerList.setLimit(game.getNPlayers()); for (Map.Entry entry : game.getDisconnectedPlayers().entrySet()) { if (entry.getValue()) { playerList.put(entry.getKey().getUserName(), false); } } System.out.println("Game restored from save (" + game.getNPlayers() + " players)."); } /** * Lists active non-loopback IPv4 network interfaces and prompts the operator * to choose one. If only one interface is available it is selected automatically. * * @param scanner the scanner used to read the operator's choice. * @return the IPv4 address of the selected interface. * @throws Exception if no valid network interface is available. */ public static String chooseNetworkInterface(Scanner scanner) throws Exception { List ips = new ArrayList<>(); Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); while (interfaces.hasMoreElements()) { NetworkInterface ni = interfaces.nextElement(); if (!ni.isUp() || ni.isLoopback() || ni.isVirtual()) continue; Enumeration addresses = ni.getInetAddresses(); while (addresses.hasMoreElements()) { InetAddress addr = addresses.nextElement(); if (addr instanceof Inet4Address) { System.out.println("[" + ips.size() + "] " + ni.getDisplayName() + " -> " + addr.getHostAddress()); ips.add(addr.getHostAddress()); } } } if (ips.isEmpty()) throw new Exception("No active network interface available"); if (ips.size() == 1) { System.out.println("One interface found, using: " + ips.get(0)); return ips.get(0); } System.out.print("Choose interface: "); int choice = Integer.parseInt(scanner.nextLine().trim()); return ips.get(choice); } }