Files
Progetto-ingegneria-del-sof…/src/main/java/it/polimi/ingsw/gc14/ServerLauncher.java
T
2026-05-15 16:55:23 +02:00

372 lines
16 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.GamePackage.GameStages;
import it.polimi.ingsw.gc14.Model.MiniModel;
import it.polimi.ingsw.gc14.Model.Player;
import it.polimi.ingsw.gc14.Model.Totems;
import it.polimi.ingsw.gc14.Network.EventType;
import it.polimi.ingsw.gc14.Network.NetworkEvent;
import it.polimi.ingsw.gc14.Network.NetworkEvents.ApplyNextRound;
import it.polimi.ingsw.gc14.Network.NetworkEvents.DisconnectedPlayer;
import it.polimi.ingsw.gc14.Network.NetworkEvents.TotemChoice;
import it.polimi.ingsw.gc14.Network.RMI.Server.RMIServer;
import it.polimi.ingsw.gc14.Network.TCP.Server.TCPServer;
import it.polimi.ingsw.gc14.View.TUI.TUI;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.rmi.RemoteException;
import java.util.*;
import java.util.concurrent.*;
import java.net.*;
/**
* Main server launcher that handles both TCP and RMI connections.
* The workflow is divided into two parts: game creation and game execution.
* The process flow for game creation is as follows:
* - The first client (TCP/RMI) requests to join the game by providing a username and the desired number of players
* - The TCP/RMI server checks {@link #playerList} and, if it is empty, sets the number of players according to the first user's request using {@link LimitedMap#setLimit(int)}
* - The TCP/RMI server creates the {@link Game} with the requested number of players and adds the player to {@link #playerList}
* - Other players request to join the game (their requested number of players is ignored)
* - When the number of players in {@link #playerList} reaches the {@link LimitedMap}'s limit, the list calls {@link #run()}
* - All players are notified of the {@link Game}
*
* The process flow for game execution is as follows:
* - The TCP/RMI server receives a {@link NetworkEvent} from a client and adds it to the {@link #actionQueue}
* - The {@link #run()} method repeatedly calls {@link #doFirstEvent()}, which takes the first event in the {@link #actionQueue} and tries to apply it
* - If the event cannot be successfully applied to the model, its {@code isError} flag is set to {@code true}
* - All players are notified of the event
*/
public class ServerLauncher {
/**
* Queue containinetworkTypeng the events to be applied to the game model.
* Thread safe by design.
*/
BlockingQueue<NetworkEvent> actionQueue;
/** Game controller. Used to apply events */
GameController gameController;
/** Server RMI. Handles RMI clients */
RMIServer serverRMI;
/** Server TCP. Handles TCP clients */
TCPServer serverTCP;
/**
* List containing the usernames of joined players.
* {@link LimitedMap}'s limit defines at which size the list calls its action
* Both the limit and the action can be set using {@link LimitedMap#setLimit(int)} and {@link LimitedMap#setAction(Runnable)}
* The limit is set by the first player joining the game. The action consists in calling {@link #run()}
*/
static LimitedMap<String,Boolean> playerList;
static TUI view;
// Campo da aggiungere in ServerLauncher
private final ScheduledExecutorService timerExecutor = Executors.newSingleThreadScheduledExecutor();
private ScheduledFuture<?> disconnectionTimer;
/**
* Class constructor that initializes the attributes.
* @param actionQueue The queue containing the events
* @param gameController The game controller
* @param serverRMI The server RMI
* @param serverTCP The server TCP
*/
public ServerLauncher(BlockingQueue<NetworkEvent> actionQueue, GameController gameController, RMIServer serverRMI, TCPServer serverTCP) {
this.actionQueue = actionQueue;
this.serverRMI = serverRMI;
this.gameController = gameController;
this.gameController.setModel(loadSave());
this.serverTCP = serverTCP;
}
/**
* Takes the first event in the actionQueue and attempts to apply it to the game controller.
* If the event cannot be applied, its isError flag is set to true; otherwise, it is set to false.
* All clients (both TCP and RMI) are notified of the event
* @return the outcome of applying the event to the controller
* @throws InterruptedException if an error occurs while accessing the actionQueue
* @throws RemoteException if an RMI error occurs
*/
public boolean doFirstEvent() throws InterruptedException, RemoteException {
NetworkEvent event = actionQueue.take();
if(gameController.getModel()!=null && !gameController.getModel().getCurrentState().equals(GameStages.ENDED))
{
if(disconnectionTimer!=null && event.getEventType() != EventType.RECONNECT_PLAYER)
{
event.setIsError(true);
serverRMI.notifyAll(event);
serverTCP.notifyAll(event);
return false;
}
if (event.getEventType() == EventType.RECONNECT_PLAYER && disconnectionTimer != null && !disconnectionTimer.isDone()) {
disconnectionTimer.cancel(false);
disconnectionTimer = null;
}
int roundPrec=gameController.getModel().getCurrentState().getRound();
synchronized(gameController){
event.setIsError(!event.apply(gameController));
Game game=gameController.getModel();
if(!event.getIsError())
{
event.setData(game.getSlotMap(),game.orderLogicCard,game.getCurrentState(),game.getPlayerByUsername(event.getUsername()));
if(event.getEventType().equals(EventType.TOTEM_CHOICE))
((TotemChoice)event).setAvailableTotems(gameController.getModel().getAvailableTotems());
}
serverRMI.notifyAll(event);
serverTCP.notifyAll(event);
if(game.getCurrentState().getRound()!=roundPrec)
{
ApplyNextRound nextRound=new ApplyNextRound(game.getSlotMap(), game.orderLogicCard, game.getCurrentState(),game.getPlayers());
serverRMI.notifyAll(nextRound);
serverTCP.notifyAll(nextRound);
}
if(!event.getIsError()){
if(game.getCurrentState().getGameStage() == GameStages.ENDED){
if(!this.deleteSave()){
System.out.println("\n!!! Couldn't delete save !!!\n");
}
for(Map.Entry<String,Boolean> entry:playerList.entrySet()){
if(!entry.getValue())
playerList.remove(entry.getKey());
}
serverRMI.setServerCrashed(false);
serverTCP.setServerCrashed(false);
}
else if(!this.gameSave() ){
System.out.println("\n!!! Save failed !!!\n");
}
}
if (event.getEventType().equals(EventType.DISCONNECTED_PLAYER) && playerList.values().stream().filter(x -> x).count() == 1) {
if (disconnectionTimer != null && !disconnectionTimer.isDone()) {
disconnectionTimer.cancel(false);
}
disconnectionTimer = timerExecutor.schedule(() -> {
System.out.println("Timer scaduto: nessun giocatore riconnesso in 30s.");
}, 30, TimeUnit.SECONDS);
}
return !event.getIsError();
}
}
else{
if(event.getEventType().equals(EventType.DISCONNECTED_PLAYER))
{
playerList.remove(event.getUsername());
if(playerList.isEmpty())
{
gameController.setModel(null);
}
}
return false;
}
}
/**
* The first method executed when the server program is launched.
* It creates all the objects needed: playerList, actionQueue, gameController, serverRMI, serverTCP, launcher.
* Then sets the playerList's action to execute launcher.run() and starts the TCP/RMI servers.
* Note: the model is initialized and set in the controller in TCP/RMI servers when the first user decides the number of players.
*
* @throws InterruptedException if this exception is issued by run method
* @throws RemoteException if this exception is issued by run method
*/
public static void main(String[] args) throws InterruptedException, RemoteException {
playerList = new LimitedMap<String,Boolean>(5, ()->{});
BlockingQueue<NetworkEvent> actionQueue = new LinkedBlockingQueue<>();
GameController gameController = new GameController();
String IP;
boolean serverCrashed;
try {
IP=chooseNetworkInterface(new Scanner(System.in));
} catch (Exception e) {
throw new RuntimeException(e);
}
RMIServer serverRMI = new RMIServer(gameController, 1099, actionQueue, playerList,IP);
TCPServer serverTCP = new TCPServer(gameController, 8080, 8081,actionQueue, playerList);
ServerLauncher launcher = new ServerLauncher(actionQueue, gameController, serverRMI, serverTCP);
if(gameController.getModel() != null){
playerList.setLimit(gameController.getModel().getNPlayers());
for(Map.Entry<Player,Boolean>entry: gameController.getModel().disconnetedPlayers.entrySet())
{
if(entry.getValue())
{
playerList.put(entry.getKey().getUserName(),false);
}
}
serverCrashed = true;
} else {
serverCrashed = false;
}
playerList.setAction(()->{
new Thread(()->{
try {
System.out.println("\n\nNotifying model");
MiniModel miniModel;
synchronized (gameController) {
Game game = gameController.getModel();
miniModel= new MiniModel(game.getBoard(),game.getSlotMap(), game.orderLogicCard,game.getCurrentState(),game.getPlayers(), new ArrayList<>(List.of(Totems.values())));
}
serverRMI.notifyAll(miniModel);
serverTCP.notifyAll(miniModel);
view = new TUI(miniModel);
view.fullRender();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}).start();
});
new Thread(()-> {
try {
launcher.run();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}).start();
serverRMI.start(serverCrashed);
new Thread(()->{serverTCP.start(serverCrashed);}).start();
}
/**
* Creates and executes the game.
* Game creation: TCP/RMI servers send the game model to all players.
* Game execution: repeatedly calls doFirstEvent() to process the events in the actionQueue.
* @throws InterruptedException if the TCP server thread is interrupted
* @throws RemoteException if an RMI error occurs
*/
public void run() throws InterruptedException, RemoteException {
// Game execution
while (true) {
try{
this.doFirstEvent();
if(view!=null)
{
view.fullRender();
}
}
catch(InterruptedException e){
Thread.currentThread().interrupt();
break;
}
catch(RemoteException e){
System.out.println("Exception:"+e.getMessage());
}
}
}
public static String chooseNetworkInterface(Scanner scanner) throws Exception {
List<String> ips = new ArrayList<>();
// Lista tutte le interfacce attive con IP reale
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface ni = interfaces.nextElement();
// Salta loopback, interfacce spente o virtuali
if (!ni.isUp() || ni.isLoopback() || ni.isVirtual()) continue;
Enumeration<InetAddress> addresses = ni.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress addr = addresses.nextElement();
// Solo IPv4
if (addr instanceof Inet4Address) {
System.out.println("[" + ips.size() + "] " + ni.getDisplayName() + " -> " + addr.getHostAddress());
ips.add(addr.getHostAddress());
}
}
}
if (ips.isEmpty()) throw new Exception("Nessuna interfaccia disponibile");
if (ips.size() == 1) {
System.out.println("Una sola interfaccia trovata, uso: " + ips.get(0));
return ips.get(0);
}
System.out.print("Scegli interfaccia: ");
int choice = Integer.parseInt(scanner.nextLine().trim());
return ips.get(choice);
}
private boolean gameSave(){
try{
Path jarPath = Paths.get(getClass().getProtectionDomain().getCodeSource().getLocation().toURI()).getParent();
Path filePath = jarPath.resolve("GameSaves/save.dat");
Files.createDirectories(filePath.getParent());
try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath.toFile()))){
oos.writeObject(this.gameController.getModel());
System.out.println("Game saved to: " + filePath.toAbsolutePath());
return true;
}
catch(IOException e){
e.printStackTrace();
return false;
}
}
catch(IOException e){
System.out.println("Couldn't create directory.");
e.printStackTrace();
return false;
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
private Game loadSave(){
try {
Path jarPath = Paths.get(getClass().getProtectionDomain().getCodeSource().getLocation().toURI()).getParent();
Path filePath = jarPath.resolve("GameSaves/save.dat");
try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath.toFile()))){
return (Game)(ois.readObject());
}
catch(FileNotFoundException e){
return null;
}
catch(IOException e){
e.printStackTrace();
return null;
}
catch(ClassNotFoundException e){
throw new RuntimeException(e);
}
}
catch(URISyntaxException e){
throw new RuntimeException(e);
}
}
private boolean deleteSave(){
try{
Path jarPath = Paths.get(getClass().getProtectionDomain().getCodeSource().getLocation().toURI()).getParent();
Path filePath = jarPath.resolve("GameSaves/save.dat");
Files.delete(filePath);
return true;
}
catch(URISyntaxException e){
return false;
}
catch (IOException e){
return false;
}
}
}