220 lines
8.7 KiB
Java
220 lines
8.7 KiB
Java
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.
|
|
*
|
|
* <p>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.
|
|
*
|
|
* <p>All game-logic and event-routing decisions are delegated to
|
|
* {@link GameEventProcessor}; persistence is delegated to {@link SaveManager}.
|
|
*
|
|
* <p>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<NetworkEvent> actionQueue,
|
|
GameController gameController,
|
|
LimitedMap<String, Boolean> 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<String, Boolean> playerList = new LimitedMap<>(5, () -> {});
|
|
BlockingQueue<NetworkEvent> 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.
|
|
*
|
|
* <p>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.
|
|
*
|
|
* <p>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<String, Boolean> 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<Player, Boolean> 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<String> ips = new ArrayList<>();
|
|
|
|
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
|
|
while (interfaces.hasMoreElements()) {
|
|
NetworkInterface ni = interfaces.nextElement();
|
|
if (!ni.isUp() || ni.isLoopback() || ni.isVirtual()) continue;
|
|
|
|
Enumeration<InetAddress> 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);
|
|
}
|
|
}
|