+3
@@ -1,6 +1,9 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
<component name="InspectionProjectProfileManager">
|
||||||
<profile version="1.0">
|
<profile version="1.0">
|
||||||
<option name="myName" value="Project Default" />
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,java.io.PrintStream,printf,java.lang.foreign.Arena,ofAuto,java.lang.foreign.Arena,global,java.util.concurrent.Executors,newSingleThreadExecutor" />
|
||||||
|
</inspection_tool>
|
||||||
<inspection_tool class="MissingJavadoc" enabled="true" level="WARNING" enabled_by_default="true" />
|
<inspection_tool class="MissingJavadoc" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
</profile>
|
</profile>
|
||||||
</component>
|
</component>
|
||||||
@@ -42,7 +42,7 @@ public class ClientLauncherGUI extends Application {
|
|||||||
Platform.runLater(() -> loginView.setErrore("Connessione RMI fallita."));
|
Platform.runLater(() -> loginView.setErrore("Connessione RMI fallita."));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
TCPClient client = new TCPClient(controller, ip, 8080);
|
TCPClient client = new TCPClient(controller, ip, 8080,8081);
|
||||||
if (client.connect(nome, numPlayer)) {
|
if (client.connect(nome, numPlayer)) {
|
||||||
controller.setClient(client);
|
controller.setClient(client);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ public class ClientLauncherTUI {
|
|||||||
// TCP
|
// TCP
|
||||||
} else if (networkType == 1) {
|
} else if (networkType == 1) {
|
||||||
// Connect
|
// Connect
|
||||||
TCPClient client = new TCPClient(controller, IP, 8080);
|
TCPClient client = new TCPClient(controller, IP, 8080,8081);
|
||||||
if (client.connect(username, proposedNumPlayers)) {
|
if (client.connect(username, proposedNumPlayers)) {
|
||||||
System.out.println("Succesfully connected to TCP server\n\n");
|
System.out.println("Succesfully connected to TCP server\n\n");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package it.polimi.ingsw.gc14.Controller;
|
package it.polimi.ingsw.gc14.Controller;
|
||||||
|
|
||||||
import it.polimi.ingsw.gc14.Model.Game;
|
import it.polimi.ingsw.gc14.Model.Game;
|
||||||
import it.polimi.ingsw.gc14.Model.GamePackage.GameStages;
|
|
||||||
import it.polimi.ingsw.gc14.Model.Player;
|
import it.polimi.ingsw.gc14.Model.Player;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,7 +29,22 @@ public class GameController {
|
|||||||
*/
|
*/
|
||||||
public GameController() {
|
public GameController() {
|
||||||
}
|
}
|
||||||
|
//TODO
|
||||||
|
public boolean DisconnectedPlayer(String username)
|
||||||
|
{
|
||||||
|
Player player= model.getPlayerByUsername(username);
|
||||||
|
if(player==null)
|
||||||
|
return false;
|
||||||
|
return model.DisconnectedPlayer(player);
|
||||||
|
}
|
||||||
|
//TODO
|
||||||
|
public boolean ReconnectPlayer(String username)
|
||||||
|
{
|
||||||
|
Player player= model.getPlayerByUsername(username);
|
||||||
|
if(player==null)
|
||||||
|
return false;
|
||||||
|
return model.ReconnectPlayer(player);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Returns the game model managed by this controller.
|
* Returns the game model managed by this controller.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
package it.polimi.ingsw.gc14;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An {@link ArrayList} with a configurable size limit and an associated action.
|
|
||||||
* When the number of elements reaches or exceeds the limit, the specified action is automatically triggered.
|
|
||||||
*
|
|
||||||
* @param <T> the type of elements held in this list.
|
|
||||||
*/
|
|
||||||
public class LimitedList<T> extends ArrayList<T> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The maximum number of elements allowed in the list before the action is triggered.
|
|
||||||
*/
|
|
||||||
private int limit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The action to execute when the list size reaches or exceeds the limit.
|
|
||||||
*/
|
|
||||||
private Runnable action;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new {@code LimitedList} with the specified limit and action.
|
|
||||||
*
|
|
||||||
* @param limit the maximum number of elements before the action is triggered.
|
|
||||||
* @param action the action to execute when the limit is reached.
|
|
||||||
*/
|
|
||||||
public LimitedList(int limit, Runnable action) {
|
|
||||||
this.limit = limit;
|
|
||||||
this.action = action;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the specified element to the list.
|
|
||||||
* If the list size reaches or exceeds the limit after the insertion, the configured action is triggered.
|
|
||||||
*
|
|
||||||
* @param element the element to add.
|
|
||||||
* @return {@code true} if the element was successfully added.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean add(T element) {
|
|
||||||
boolean result = super.add(element);
|
|
||||||
if (size() >= limit) {
|
|
||||||
action.run();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a new size limit for this list.
|
|
||||||
*
|
|
||||||
* @param num the new limit.
|
|
||||||
*/
|
|
||||||
public void setLimit(int num) {
|
|
||||||
this.limit = num;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current size limit of this list.
|
|
||||||
*
|
|
||||||
* @return the current limit.
|
|
||||||
*/
|
|
||||||
public int getLimit() {
|
|
||||||
return limit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a new action to execute when the list size reaches or exceeds the limit.
|
|
||||||
*
|
|
||||||
* @param action the new action to set.
|
|
||||||
*/
|
|
||||||
public void setAction(Runnable action) {
|
|
||||||
this.action = action;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package it.polimi.ingsw.gc14;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link LinkedHashMap} with a configurable size limit and an associated action.
|
||||||
|
* When the number of elements reaches or exceeds the limit, the specified action is automatically triggered.
|
||||||
|
* This implementation is thread-safe.
|
||||||
|
*
|
||||||
|
* @param <K> the type of keys maintained by this map.
|
||||||
|
* @param <V> the type of mapped values.
|
||||||
|
*/
|
||||||
|
public class LimitedMap<K, V> implements Map<K, V> {
|
||||||
|
|
||||||
|
private final LinkedHashMap<K, V> map = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of elements allowed in the map before the action is triggered.
|
||||||
|
*/
|
||||||
|
private volatile int limit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The action to execute when the map size reaches or exceeds the limit.
|
||||||
|
*/
|
||||||
|
private volatile Runnable action;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@code LimitedMap} with the specified limit and action.
|
||||||
|
*
|
||||||
|
* @param limit the maximum number of elements before the action is triggered.
|
||||||
|
* @param action the action to execute when the limit is reached.
|
||||||
|
*/
|
||||||
|
public LimitedMap(int limit, Runnable action) {
|
||||||
|
this.limit = limit;
|
||||||
|
this.action = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Associates the specified value with the specified key in this map.
|
||||||
|
* If the map size reaches or exceeds the limit after the insertion, the configured action is triggered.
|
||||||
|
*
|
||||||
|
* @param key the key with which the specified value is to be associated.
|
||||||
|
* @param value the value to be associated with the specified key.
|
||||||
|
* @return the previous value associated with the key, or {@code null} if there was no mapping.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public synchronized V put(K key, V value) {
|
||||||
|
boolean added = true;
|
||||||
|
if(map.size()==limit) {
|
||||||
|
if(!map.containsKey(key))
|
||||||
|
return null;
|
||||||
|
added = false;
|
||||||
|
}
|
||||||
|
V result = map.put(key, value);
|
||||||
|
if (map.size() >= limit && added) {
|
||||||
|
action.run();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized V remove(Object key) { return map.remove(key); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized V get(Object key) { return map.get(key); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized boolean containsKey(Object key) { return map.containsKey(key); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized boolean containsValue(Object value) { return map.containsValue(value); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized int size() { return map.size(); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized boolean isEmpty() { return map.isEmpty(); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void putAll(Map<? extends K, ? extends V> m) { m.forEach(this::put); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void clear() { map.clear(); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized Set<K> keySet() { return map.keySet(); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized Collection<V> values() { return map.values(); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized Set<Entry<K, V>> entrySet() { return map.entrySet(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a new size limit for this map.
|
||||||
|
*
|
||||||
|
* @param num the new limit.
|
||||||
|
*/
|
||||||
|
public void setLimit(int num) { this.limit = num; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current size limit of this map.
|
||||||
|
*
|
||||||
|
* @return the current limit.
|
||||||
|
*/
|
||||||
|
public int getLimit() { return limit; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a new action to execute when the map size reaches or exceeds the limit.
|
||||||
|
*
|
||||||
|
* @param action the new action to set.
|
||||||
|
*/
|
||||||
|
public void setAction(Runnable action) { this.action = action; }
|
||||||
|
}
|
||||||
@@ -41,6 +41,44 @@ public class Game implements Serializable {
|
|||||||
return playersList;
|
return playersList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
public Map<Player,Boolean> disconnetedPlayers = new HashMap<>();
|
||||||
|
//TODO
|
||||||
|
public boolean DisconnectedPlayer(Player player)
|
||||||
|
{
|
||||||
|
if(disconnetedPlayers.containsKey(player) && disconnetedPlayers.get(player))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
disconnetedPlayers.put(player,true);
|
||||||
|
if(currentState.getCurrentPlayer().equals(player))
|
||||||
|
nextPlayerSetup();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
public boolean ReconnectPlayer(Player player)
|
||||||
|
{
|
||||||
|
if(!disconnetedPlayers.containsKey(player))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
disconnetedPlayers.put(player,false);
|
||||||
|
if(currentState.getGameStage().equals(GameStages.SLOT_CHOICE) )
|
||||||
|
{
|
||||||
|
disconnetedPlayers.remove(player);
|
||||||
|
if(!orderLogicCard.players.contains(player))
|
||||||
|
{
|
||||||
|
orderLogicCard.pushNoEffect(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearDisconnected()
|
||||||
|
{
|
||||||
|
disconnetedPlayers.clear();
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Returns the current number of players participating in the game.
|
* Returns the current number of players participating in the game.
|
||||||
* @return the current number of players.
|
* @return the current number of players.
|
||||||
@@ -607,7 +645,10 @@ public class Game implements Serializable {
|
|||||||
|
|
||||||
if (GameStages.SLOT_CHOICE == currentState.getGameStage()) {
|
if (GameStages.SLOT_CHOICE == currentState.getGameStage()) {
|
||||||
Player tempPlayer = orderLogicCard.pull();
|
Player tempPlayer = orderLogicCard.pull();
|
||||||
|
if(disconnetedPlayers.containsKey(tempPlayer) && disconnetedPlayers.get(tempPlayer)) {
|
||||||
|
nextPlayerSetup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (tempPlayer != null) {
|
if (tempPlayer != null) {
|
||||||
currentState.PlayerUpdate(tempPlayer, null);
|
currentState.PlayerUpdate(tempPlayer, null);
|
||||||
return;
|
return;
|
||||||
@@ -652,12 +693,15 @@ public class Game implements Serializable {
|
|||||||
for (Slot s : slotMap.keySet()) {
|
for (Slot s : slotMap.keySet()) {
|
||||||
if (slotMap.get(s) != null) {
|
if (slotMap.get(s) != null) {
|
||||||
currentState.PlayerUpdate(slotMap.get(s), s);
|
currentState.PlayerUpdate(slotMap.get(s), s);
|
||||||
|
if(disconnetedPlayers.containsKey(currentState.getCurrentPlayer())&& disconnetedPlayers.get(currentState.getCurrentPlayer()))
|
||||||
|
{
|
||||||
|
slotMap.put(currentState.getSlot(), null);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
boolean hasDrawableLower = currentState.getNLower() > 0
|
boolean hasDrawableLower = currentState.getNLower() > 0
|
||||||
&& (hasDrawableDown() || !getLowerListBuilding().isEmpty());
|
&& (hasDrawableDown() || !getLowerListBuilding().isEmpty());
|
||||||
boolean hasDrawableUpper = currentState.getNUpper() > 0
|
boolean hasDrawableUpper = currentState.getNUpper() > 0
|
||||||
&& (hasDrawableUp() || !getUpperListBuilding().isEmpty());
|
&& (hasDrawableUp() || !getUpperListBuilding().isEmpty());
|
||||||
|
|
||||||
if (!hasDrawableLower && !hasDrawableUpper) {
|
if (!hasDrawableLower && !hasDrawableUpper) {
|
||||||
// This player also has nothing, skip and continue the loop
|
// This player also has nothing, skip and continue the loop
|
||||||
orderLogicCard.push(currentState.getCurrentPlayer());
|
orderLogicCard.push(currentState.getCurrentPlayer());
|
||||||
@@ -679,6 +723,9 @@ public class Game implements Serializable {
|
|||||||
|
|
||||||
if (optionalPlayer != null) {
|
if (optionalPlayer != null) {
|
||||||
currentState.PlayerUpdate(optionalPlayer, null);
|
currentState.PlayerUpdate(optionalPlayer, null);
|
||||||
|
if(disconnetedPlayers.containsKey(currentState.getCurrentPlayer())&& disconnetedPlayers.get(currentState.getCurrentPlayer())) {
|
||||||
|
nextPlayerSetup();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -718,6 +765,14 @@ public class Game implements Serializable {
|
|||||||
|
|
||||||
if (currentState.getRound() < 10) {
|
if (currentState.getRound() < 10) {
|
||||||
nextRound();
|
nextRound();
|
||||||
|
for(Map.Entry<Player,Boolean> entry: disconnetedPlayers.entrySet())
|
||||||
|
{
|
||||||
|
if(!entry.getValue())
|
||||||
|
{
|
||||||
|
orderLogicCard.pushNoEffect(entry.getKey());
|
||||||
|
disconnetedPlayers.remove(entry.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
currentState.PlayerUpdate(orderLogicCard.pull(), null);
|
currentState.PlayerUpdate(orderLogicCard.pull(), null);
|
||||||
currentState.GameStageUpdate(GameStages.SLOT_CHOICE);
|
currentState.GameStageUpdate(GameStages.SLOT_CHOICE);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -59,6 +59,20 @@ public abstract class OrderLogicCard implements Serializable {
|
|||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Adds the player to the end of the queue, without effects.
|
||||||
|
*
|
||||||
|
* @param player the player to be pushed into the queue.
|
||||||
|
*/
|
||||||
|
public void pushNoEffect(Player player){
|
||||||
|
if(players.size()==0)
|
||||||
|
{
|
||||||
|
playerList=new ArrayList<>();
|
||||||
|
|
||||||
|
}
|
||||||
|
playerList.add(new OrderPlayer(player,false));
|
||||||
|
players.add(player);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes and returns the first player in the queue.
|
* Removes and returns the first player in the queue.
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package it.polimi.ingsw.gc14.Network;
|
||||||
|
//TODO
|
||||||
|
public class ClientPlayer {
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
public boolean connected;
|
||||||
|
|
||||||
|
public ClientPlayer(String username,boolean connected) {
|
||||||
|
this.username = username;
|
||||||
|
this.connected = connected;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,8 @@ public enum EventType {
|
|||||||
DRAW_LOWER_BUILD,
|
DRAW_LOWER_BUILD,
|
||||||
PICK_OPTIONAL_TRIBE,
|
PICK_OPTIONAL_TRIBE,
|
||||||
PICK_OPTIONAL_BUILD,
|
PICK_OPTIONAL_BUILD,
|
||||||
SKIP_UPPER,
|
SKIP_NO_DRAWABLE,
|
||||||
SKIP_LOWER,
|
DISCONNECTED_PLAYER,
|
||||||
|
RECONNECT_PLAYER,
|
||||||
NO_OPTIONAL_CARD
|
NO_OPTIONAL_CARD
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package it.polimi.ingsw.gc14.Network.NetworkEvents;
|
||||||
|
|
||||||
|
import it.polimi.ingsw.gc14.Controller.GameController;
|
||||||
|
import it.polimi.ingsw.gc14.Network.EventType;
|
||||||
|
import it.polimi.ingsw.gc14.Network.NetworkEvent;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NetworkEvent to avoid drawing a card from the lower card list
|
||||||
|
*/
|
||||||
|
public class DisconnectedPlayer extends NetworkEvent implements Serializable{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class constructor.
|
||||||
|
* Initializes all the attributes.
|
||||||
|
* @param username the name of the player requesting the event
|
||||||
|
*/
|
||||||
|
public DisconnectedPlayer(String username){
|
||||||
|
super(username, EventType.DISCONNECTED_PLAYER, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param gameController the Game Controller on which to apply the event
|
||||||
|
* @return true if the player could skipTheTurn, false otherwise
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean apply(GameController gameController){
|
||||||
|
return gameController.DisconnectedPlayer(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package it.polimi.ingsw.gc14.Network.NetworkEvents;
|
||||||
|
|
||||||
|
import it.polimi.ingsw.gc14.Controller.GameController;
|
||||||
|
import it.polimi.ingsw.gc14.Network.EventType;
|
||||||
|
import it.polimi.ingsw.gc14.Network.NetworkEvent;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
public class ReconnectPlayer extends NetworkEvent implements Serializable{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class constructor.
|
||||||
|
* Initializes all the attributes.
|
||||||
|
* @param username the name of the player requesting the event
|
||||||
|
*/
|
||||||
|
public ReconnectPlayer(String username){
|
||||||
|
super(username, EventType.RECONNECT_PLAYER, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param gameController the Game Controller on which to apply the event
|
||||||
|
* @return true if the player could skipTheTurn, false otherwise
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean apply(GameController gameController){
|
||||||
|
return gameController.ReconnectPlayer(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ public class SkipNoDrawable extends NetworkEvent implements Serializable{
|
|||||||
* @param username the name of the player requesting the event
|
* @param username the name of the player requesting the event
|
||||||
*/
|
*/
|
||||||
public SkipNoDrawable(String username){
|
public SkipNoDrawable(String username){
|
||||||
super(username, EventType.SKIP_LOWER, false);
|
super(username, EventType.SKIP_NO_DRAWABLE, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.rmi.RemoteException;
|
|||||||
import java.rmi.registry.LocateRegistry;
|
import java.rmi.registry.LocateRegistry;
|
||||||
import java.rmi.registry.Registry;
|
import java.rmi.registry.Registry;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
import it.polimi.ingsw.gc14.Controller.ClientController;
|
import it.polimi.ingsw.gc14.Controller.ClientController;
|
||||||
import it.polimi.ingsw.gc14.Network.IClient;
|
import it.polimi.ingsw.gc14.Network.IClient;
|
||||||
@@ -18,31 +19,22 @@ import it.polimi.ingsw.gc14.Network.RMI.Server.RMIServer;
|
|||||||
*/
|
*/
|
||||||
public class RMIClient implements IClient {
|
public class RMIClient implements IClient {
|
||||||
|
|
||||||
/** The host address of the RMI server */
|
private static final long PING_INTERVAL_S = 3; // mirrors TCPClient 3 s
|
||||||
|
private static final long PING_TIMEOUT_MS = 5_000; // mirrors SILENCE_THRESHOLD_MS
|
||||||
|
|
||||||
private final String host;
|
private final String host;
|
||||||
|
|
||||||
/** The port of the RMI server */
|
|
||||||
private final int port;
|
private final int port;
|
||||||
|
|
||||||
/** The remote stub used to call methods on the server */
|
|
||||||
private IGameServer stub;
|
private IGameServer stub;
|
||||||
|
private ClientController controller;
|
||||||
/** Client game's controller */
|
|
||||||
ClientController controller;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Local IP address of the RMI client.
|
|
||||||
*/
|
|
||||||
private String myIP;
|
private String myIP;
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
private volatile boolean running = false;
|
||||||
|
|
||||||
|
/** Scheduler that fires ping() every PING_INTERVAL_S seconds. */
|
||||||
|
private ScheduledExecutorService pingSender;
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class constructor.
|
|
||||||
*
|
|
||||||
* @param controller the client controller used to create the callback.
|
|
||||||
* @param host the host address of the RMI server.
|
|
||||||
* @param port the port of the RMI server.
|
|
||||||
* @param myIP the local IP address used by the RMI client.
|
|
||||||
*/
|
|
||||||
public RMIClient(ClientController controller, String host, int port, String myIP) {
|
public RMIClient(ClientController controller, String host, int port, String myIP) {
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
this.host = host;
|
this.host = host;
|
||||||
@@ -52,29 +44,89 @@ public class RMIClient implements IClient {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connects to the RMI server and attempts to join the game.
|
* Connects to the RMI server and starts the heartbeat loop.
|
||||||
* Looks up the RMI registry to retrieve the {@link IGameServer} stub.
|
*
|
||||||
* Then, creates a {@link ClientCallbackImpl} and calls {@link RMIServer#joinGame(String, int, IClientCallback)}.
|
* <p>Mirrors {@code TCPClient.connect()}: after a successful join the
|
||||||
* @param username the player's username
|
* heartbeat channel is opened (here: a scheduler is started instead of
|
||||||
* @param preferredInt the desired number of players
|
* opening a second socket).
|
||||||
* @return true if the player successfully joined the game, false otherwise
|
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public boolean connect(String username, int preferredInt) {
|
public boolean connect(String username, int preferredInt) {
|
||||||
try {
|
try {
|
||||||
System.setProperty("java.rmi.server.hostname", this.myIP);
|
System.setProperty("java.rmi.server.hostname", this.myIP);
|
||||||
Registry registry = LocateRegistry.getRegistry(host, port);
|
Registry registry = LocateRegistry.getRegistry(host, port);
|
||||||
this.stub = (IGameServer) registry.lookup("RMIGameServer");
|
this.stub = (IGameServer) registry.lookup("RMIGameServer");
|
||||||
ClientCallbackImpl callback = new ClientCallbackImpl(controller);
|
this.username = username;
|
||||||
|
|
||||||
return stub.joinGame(username, preferredInt, callback);
|
ClientCallbackImpl callback = new ClientCallbackImpl(controller);
|
||||||
}
|
boolean joined = stub.joinGame(username, preferredInt, callback);
|
||||||
catch (Exception e) {
|
if (!joined) return false;
|
||||||
|
|
||||||
|
running = true;
|
||||||
|
startHeartbeat();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Heartbeat — mirrors TCPClient.heartbeatLoop()
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts sending periodic pings to the server.
|
||||||
|
*
|
||||||
|
* <p>Mirrors the {@code ScheduledExecutorService} in
|
||||||
|
* {@code TCPClient.heartbeatLoop()} that writes {@code PING} every 3 s.
|
||||||
|
* On {@link RemoteException} the server is considered gone and
|
||||||
|
* {@link #disconnect()} is called — mirrors the behaviour on
|
||||||
|
* {@code SocketTimeoutException} / {@code IOException} in the TCP version.
|
||||||
|
*/
|
||||||
|
private void startHeartbeat() {
|
||||||
|
pingSender = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||||
|
Thread t = new Thread(r, "rmi-heartbeat");
|
||||||
|
t.setDaemon(true);
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
|
||||||
|
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
|
pingSender.scheduleAtFixedRate(() -> {
|
||||||
|
Future<?> future = executor.submit(() -> {
|
||||||
|
try {
|
||||||
|
stub.ping(username);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
future.get(PING_TIMEOUT_MS, TimeUnit.MILLISECONDS); // mirrors setSoTimeout(5000)
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
future.cancel(true);
|
||||||
|
System.out.println("RMI ping timeout: " + username);
|
||||||
|
disconnect();
|
||||||
|
} catch (Exception e) {
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
}, 0, PING_INTERVAL_S, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tears down the connection.
|
||||||
|
* Mirrors {@code TCPClient.disconnect()}: stops the heartbeat and notifies
|
||||||
|
* the view.
|
||||||
|
*/
|
||||||
|
private void disconnect() {
|
||||||
|
if (!running) return;
|
||||||
|
running = false;
|
||||||
|
if (pingSender != null) pingSender.shutdownNow();
|
||||||
|
controller.view.showError("Connessione al server persa");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests to draw a tribe card from the upper list.
|
* Requests to draw a tribe card from the upper list.
|
||||||
|
|||||||
@@ -9,47 +9,26 @@ import java.rmi.*;
|
|||||||
*/
|
*/
|
||||||
public interface IGameServer extends Remote {
|
public interface IGameServer extends Remote {
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a player to the game through the remote server.
|
|
||||||
*
|
|
||||||
* @param username the username of the player joining the game.
|
|
||||||
* @param preferredInt the preferred player number or slot selected by the client.
|
|
||||||
* @param callback the client callback used by the server to send updates.
|
|
||||||
* @return {@code true} if the player successfully joins the game;
|
|
||||||
* {@code false} otherwise.
|
|
||||||
* @throws RemoteException if an RMI communication error occurs.
|
|
||||||
*/
|
|
||||||
boolean joinGame(String username, int preferredInt, IClientCallback callback) throws RemoteException;
|
boolean joinGame(String username, int preferredInt, IClientCallback callback) throws RemoteException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a network event to the game server.
|
|
||||||
*
|
|
||||||
* @param event the event to be processed by the server.
|
|
||||||
* @return {@code true} if the event is accepted and processed;
|
|
||||||
* {@code false} otherwise.
|
|
||||||
* @throws RemoteException if an RMI communication error occurs.
|
|
||||||
*/
|
|
||||||
boolean doEvent(NetworkEvent event) throws RemoteException;
|
boolean doEvent(NetworkEvent event) throws RemoteException;
|
||||||
|
|
||||||
void drawUpperTribeCard(String playerUsername, int pos) throws RemoteException;
|
void drawUpperTribeCard(String playerUsername, int pos) throws RemoteException;
|
||||||
|
|
||||||
void drawLowerTribeCard(String playerUsername, int pos) throws RemoteException;
|
void drawLowerTribeCard(String playerUsername, int pos) throws RemoteException;
|
||||||
|
|
||||||
void drawUpperBuildingCard(String playerUsername, int pos) throws RemoteException;
|
void drawUpperBuildingCard(String playerUsername, int pos) throws RemoteException;
|
||||||
|
|
||||||
|
|
||||||
void drawLowerBuildingCard(String playerUsername, int pos) throws RemoteException;
|
void drawLowerBuildingCard(String playerUsername, int pos) throws RemoteException;
|
||||||
|
|
||||||
void skipTurn(String playerUsername) throws RemoteException;
|
void skipTurn(String playerUsername) throws RemoteException;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void pickOptionalTribeCard(String playerUsername, int pos) throws RemoteException;
|
void pickOptionalTribeCard(String playerUsername, int pos) throws RemoteException;
|
||||||
|
|
||||||
void pickOptionalBuildingCard(String playerUsername, int pos) throws RemoteException;
|
void pickOptionalBuildingCard(String playerUsername, int pos) throws RemoteException;
|
||||||
|
|
||||||
|
|
||||||
void noOptionalCard(String playerUsername) throws RemoteException;
|
void noOptionalCard(String playerUsername) throws RemoteException;
|
||||||
|
|
||||||
void slotChoice(String playerUsername, int pos) throws RemoteException;
|
void slotChoice(String playerUsername, int pos) throws RemoteException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Heartbeat: called periodically by the client to signal it is still alive.
|
||||||
|
* Mirrors the PING/PONG mechanism used in the TCP heartbeat channel.
|
||||||
|
*
|
||||||
|
* @param username the username of the client sending the ping.
|
||||||
|
* @throws RemoteException if an RMI communication error occurs.
|
||||||
|
*/
|
||||||
|
void ping(String username) throws RemoteException;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package it.polimi.ingsw.gc14.Network.RMI.Server;
|
||||||
|
|
||||||
|
import it.polimi.ingsw.gc14.LimitedMap;
|
||||||
|
import it.polimi.ingsw.gc14.Model.Game;
|
||||||
|
import it.polimi.ingsw.gc14.Network.NetworkEvent;
|
||||||
|
import it.polimi.ingsw.gc14.Network.NetworkEvents.DisconnectedPlayer;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server-side heartbeat watchdog for a single RMI client.
|
||||||
|
*
|
||||||
|
* <p>Mirrors {@code HeartbeatHandler} used in the TCP stack, but adapted for RMI:
|
||||||
|
* instead of reading raw bytes from a dedicated socket, it relies on {@link #receivePing()}
|
||||||
|
* being called by {@link RMIServer#ping(String)} every time the client sends a ping.
|
||||||
|
*
|
||||||
|
* <p>If no ping is received within {@value SILENCE_THRESHOLD_MS} ms the player is
|
||||||
|
* considered disconnected and {@link #disconnect()} is invoked, which:
|
||||||
|
* <ul>
|
||||||
|
* <li>stops the watchdog;</li>
|
||||||
|
* <li>marks the player as offline in {@code playerList};</li>
|
||||||
|
* <li>removes the callback from {@code clients};</li>
|
||||||
|
* <li>optionally pushes a {@link DisconnectedPlayer} event if it was that
|
||||||
|
* player's turn.</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public class RMIHeartbeat {
|
||||||
|
|
||||||
|
private static final long SILENCE_THRESHOLD_MS = 5_000;
|
||||||
|
|
||||||
|
private String username = "";
|
||||||
|
private final LimitedMap<String, Boolean> playerList;
|
||||||
|
private final Map<String, ?> clients; // ConcurrentHashMap<String, IClientCallback>
|
||||||
|
private final BlockingQueue<NetworkEvent> actionQueue;
|
||||||
|
|
||||||
|
/** Last time a ping was received from this client. */
|
||||||
|
private volatile long lastPingTime = System.currentTimeMillis();
|
||||||
|
private volatile boolean running = true;
|
||||||
|
|
||||||
|
/** Reference to the current game model — needed to check whose turn it is. */
|
||||||
|
private volatile Game game;
|
||||||
|
|
||||||
|
private final ScheduledExecutorService watchdog =
|
||||||
|
Executors.newSingleThreadScheduledExecutor(r -> {
|
||||||
|
Thread t = new Thread(r, "rmi-watchdog-" + username);
|
||||||
|
t.setDaemon(true);
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
|
||||||
|
public RMIHeartbeat(
|
||||||
|
String username,
|
||||||
|
LimitedMap<String, Boolean> playerList,
|
||||||
|
Map<String, ?> clients,
|
||||||
|
BlockingQueue<NetworkEvent> actionQueue) {
|
||||||
|
|
||||||
|
this.username = username;
|
||||||
|
this.playerList = playerList;
|
||||||
|
this.clients = clients;
|
||||||
|
this.actionQueue = actionQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called by {@link RMIServer} whenever it starts tracking this player. */
|
||||||
|
public void start() {
|
||||||
|
watchdog.scheduleAtFixedRate(() -> {
|
||||||
|
if (System.currentTimeMillis() - lastPingTime > SILENCE_THRESHOLD_MS) {
|
||||||
|
System.out.println("RMI heartbeat timeout: " + username);
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
}, 1, 1, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by {@link RMIServer#ping(String)} each time the client pings.
|
||||||
|
* Resets the silence timer — mirrors writing {@code lastReceivedTime} in
|
||||||
|
* {@code HeartbeatHandler}.
|
||||||
|
*/
|
||||||
|
public void receivePing() {
|
||||||
|
lastPingTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the server to keep the watchdog up-to-date with the current game
|
||||||
|
* model (needed to check whose turn it is on disconnect).
|
||||||
|
*/
|
||||||
|
public void setGame(Game game) {
|
||||||
|
this.game = game;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private void disconnect() {
|
||||||
|
if (!running) return;
|
||||||
|
running = false;
|
||||||
|
watchdog.shutdownNow();
|
||||||
|
|
||||||
|
// Mark player as offline
|
||||||
|
playerList.put(username, false);
|
||||||
|
|
||||||
|
// Remove RMI callback so notifyAll skips this client
|
||||||
|
clients.remove(username);
|
||||||
|
actionQueue.add(new DisconnectedPlayer(username));
|
||||||
|
System.out.println("RMI disconnected: " + username);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package it.polimi.ingsw.gc14.Network.RMI.Server;
|
package it.polimi.ingsw.gc14.Network.RMI.Server;
|
||||||
|
|
||||||
import it.polimi.ingsw.gc14.Controller.GameController;
|
import it.polimi.ingsw.gc14.Controller.GameController;
|
||||||
import it.polimi.ingsw.gc14.LimitedList;
|
import it.polimi.ingsw.gc14.LimitedMap;
|
||||||
import it.polimi.ingsw.gc14.Model.Game;
|
import it.polimi.ingsw.gc14.Model.Game;
|
||||||
import it.polimi.ingsw.gc14.Network.NetworkEvent;
|
import it.polimi.ingsw.gc14.Network.NetworkEvent;
|
||||||
import it.polimi.ingsw.gc14.Network.NetworkEvents.*;
|
import it.polimi.ingsw.gc14.Network.NetworkEvents.*;
|
||||||
@@ -24,42 +24,33 @@ import java.rmi.*;
|
|||||||
*/
|
*/
|
||||||
public class RMIServer extends UnicastRemoteObject implements IGameServer {
|
public class RMIServer extends UnicastRemoteObject implements IGameServer {
|
||||||
private String host;
|
private String host;
|
||||||
/** Server game's controller */
|
|
||||||
private GameController controller;
|
private GameController controller;
|
||||||
|
|
||||||
/** Server game's model */
|
|
||||||
private Game model;
|
private Game model;
|
||||||
|
|
||||||
/** RMI registry */
|
|
||||||
private Registry registry;
|
private Registry registry;
|
||||||
|
|
||||||
/** RMI port */
|
|
||||||
private int nPort;
|
private int nPort;
|
||||||
|
|
||||||
/** Map containing the associations between a player's username and its callback */
|
/** username → callback */
|
||||||
private final Map<String, IClientCallback> clients = new ConcurrentHashMap<>();
|
private final Map<String, IClientCallback> clients = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
/** Queue containing the events to be applied to the game model */
|
/**
|
||||||
|
* username → watchdog.
|
||||||
|
* One watchdog per connected player, mirrors {@code pendingHeartbeat} / per-socket
|
||||||
|
* HeartbeatHandler in the TCP stack.
|
||||||
|
*/
|
||||||
|
private final Map<String, RMIHeartbeat> watchdogs = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
BlockingQueue<NetworkEvent> actionQueue;
|
BlockingQueue<NetworkEvent> actionQueue;
|
||||||
|
private LimitedMap<String, Boolean> playerList;
|
||||||
|
|
||||||
/**
|
private boolean serverCrashed;
|
||||||
* List containing the usernames of joined players.
|
public void setServerCrashed(boolean serverCrashed) {
|
||||||
* {@link LimitedList}'s limit defines at which size the list calls its action. The limit can be set using {@link LimitedList#setLimit(int)}.
|
this.serverCrashed = serverCrashed;
|
||||||
*/
|
}
|
||||||
private LimitedList<String> playerList;
|
|
||||||
|
|
||||||
|
public RMIServer(GameController controller, int nPort,
|
||||||
/**
|
BlockingQueue<NetworkEvent> actionQueue,
|
||||||
* Class constructor that initializes the attributes.
|
LimitedMap<String, Boolean> playerList,
|
||||||
*
|
String host) throws RemoteException {
|
||||||
* @param controller the game controller.
|
|
||||||
* @param nPort the RMI port.
|
|
||||||
* @param actionQueue the action queue.
|
|
||||||
* @param playerList the players' usernames list.
|
|
||||||
* @param host the host address of the RMI server.
|
|
||||||
* @throws RemoteException if an RMI error occurs.
|
|
||||||
*/
|
|
||||||
public RMIServer(GameController controller, int nPort, BlockingQueue<NetworkEvent> actionQueue,LimitedList<String> playerList,String host) throws RemoteException {
|
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
this.nPort = nPort;
|
this.nPort = nPort;
|
||||||
this.actionQueue = actionQueue;
|
this.actionQueue = actionQueue;
|
||||||
@@ -67,21 +58,45 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer {
|
|||||||
this.host = host;
|
this.host = host;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Join
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows a player to join the game.
|
* {@inheritDoc}
|
||||||
* If the desired number of player is invalid, the request is rejected.
|
*
|
||||||
* If this is the first player, a new game model is created and passed to the controller. Additionally, the playerList's limit is set.
|
* <p>After a successful join a {@link RMIHeartbeat} is created and
|
||||||
* Then, if the controller successfully adds the player, the username is added to {@link #playerList} and {@link #clients}.
|
* started for the new player — mirrors creating a {@code HeartbeatHandler} in
|
||||||
* @param username The player's name
|
* {@code TCPServer.acceptHeartbeat()}.
|
||||||
* @param preferredInt The desired number of players
|
|
||||||
* @param callback The client's callback interface
|
|
||||||
* @return true if the player successfully joined the game, false otherwise
|
|
||||||
*/
|
*/
|
||||||
public boolean joinGame(String username, int preferredInt, IClientCallback callback) {
|
@Override
|
||||||
if (preferredInt<2 || preferredInt>5) {
|
public boolean joinGame(String username, int preferredInt, IClientCallback callback)
|
||||||
return false;
|
throws RemoteException {
|
||||||
}
|
if (preferredInt < 2 || preferredInt > 5) return false;
|
||||||
|
|
||||||
synchronized (controller) {
|
synchronized (controller) {
|
||||||
|
if(serverCrashed)
|
||||||
|
{
|
||||||
|
if(controller.getModel().getPlayers().stream().anyMatch(p -> p.getUserName().equals(username))&& !playerList.containsKey(username)) {
|
||||||
|
clients.put(username, callback);
|
||||||
|
playerList.put(username, true);
|
||||||
|
startWatchdog(username);
|
||||||
|
System.out.println("(After crash)Reconnected player: " + username);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (playerList.containsKey(username) && !playerList.get(username)) {
|
||||||
|
playerList.put(username, true);
|
||||||
|
clients.put(username, callback);
|
||||||
|
System.out.println("Reconnected player: " + username);
|
||||||
|
startWatchdog(username);
|
||||||
|
callback.onGameInit(controller.getModel());
|
||||||
|
System.out.println("Model sent: " + username);
|
||||||
|
actionQueue.add(new ReconnectPlayer(username));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
if (playerList.isEmpty()) {
|
if (playerList.isEmpty()) {
|
||||||
model = new Game(preferredInt);
|
model = new Game(preferredInt);
|
||||||
controller.setModel(model);
|
controller.setModel(model);
|
||||||
@@ -89,15 +104,77 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer {
|
|||||||
}
|
}
|
||||||
if (controller.addPlayer(username)) {
|
if (controller.addPlayer(username)) {
|
||||||
clients.put(username, callback);
|
clients.put(username, callback);
|
||||||
playerList.add(username);
|
playerList.put(username, true);
|
||||||
|
startWatchdog(username);
|
||||||
System.out.println("Accepted player: " + username);
|
System.out.println("Accepted player: " + username);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// Reconnection: player was offline
|
||||||
|
if (playerList.containsKey(username) && !playerList.get(username)) {
|
||||||
|
playerList.put(username, true);
|
||||||
|
clients.put(username, callback);
|
||||||
|
System.out.println("Reconnected player: " + username);
|
||||||
|
startWatchdog(username);
|
||||||
|
callback.onGameInit(controller.getModel());
|
||||||
|
System.out.println("Model sent: " + username);
|
||||||
|
actionQueue.add(new ReconnectPlayer(username));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Heartbeat — called by RMIClient every ~3 s
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives a heartbeat ping from the client.
|
||||||
|
* Mirrors the server reading {@code PING} and replying {@code PONG} in
|
||||||
|
* {@code HeartbeatHandler.run()}.
|
||||||
|
*
|
||||||
|
* @param username the username of the pinging client.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void ping(String username) throws RemoteException {
|
||||||
|
RMIHeartbeat wd = watchdogs.get(username);
|
||||||
|
if (wd != null) wd.receivePing();
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Game model propagation — keep watchdogs in sync
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies all clients of a new event.
|
||||||
|
* Also updates every watchdog with the latest model so disconnect logic
|
||||||
|
* knows whose turn it is.
|
||||||
|
*/
|
||||||
|
public void notifyAll(NetworkEvent action) throws RemoteException {
|
||||||
|
for (Map.Entry<String, IClientCallback> entry : clients.entrySet()) {
|
||||||
|
if (!action.getIsError() ||
|
||||||
|
(action.getIsError() && action.getUsername().equals(entry.getKey()))) {
|
||||||
|
entry.getValue().onAction(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies all clients of a new game model and keeps watchdogs up-to-date.
|
||||||
|
* Mirrors {@code TCPServer.notifyAll(Game)} + the {@code ClientHandler.notifyModel}
|
||||||
|
* call that stores the model for disconnect-turn checking.
|
||||||
|
*/
|
||||||
|
public void notifyAll(Game model) throws RemoteException {
|
||||||
|
this.model = model;
|
||||||
|
// Keep every watchdog's game reference up to date
|
||||||
|
watchdogs.values().forEach(wd -> wd.setGame(model));
|
||||||
|
for (IClientCallback cb : clients.values()) {
|
||||||
|
cb.onGameInit(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Push an action in actionQueue.
|
* Push an action in actionQueue.
|
||||||
@@ -205,35 +282,6 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// RMI's internal methods
|
|
||||||
/**
|
|
||||||
* Sends an action to the RMI clients.
|
|
||||||
*
|
|
||||||
* <p>If the action is an error, it is sent only to the client associated with
|
|
||||||
* the action username. Otherwise, it is sent to all connected RMI clients.
|
|
||||||
*
|
|
||||||
* @param action the network action to send.
|
|
||||||
* @throws RemoteException if an RMI communication error occurs.
|
|
||||||
*/
|
|
||||||
public void notifyAll(NetworkEvent action) throws RemoteException {
|
|
||||||
for (Map.Entry<String,IClientCallback> entry : clients.entrySet()) {
|
|
||||||
if(!action.getIsError() ||(action.getIsError()&& action.getUsername().equals(entry.getKey())))
|
|
||||||
entry.getValue().onAction(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a game model to all RMI clients.
|
|
||||||
*
|
|
||||||
* @param model the game model to send to all connected RMI clients.
|
|
||||||
* @throws RemoteException if an RMI communication error occurs.
|
|
||||||
*/
|
|
||||||
public void notifyAll(Game model) throws RemoteException {
|
|
||||||
for (IClientCallback cb : clients.values()) {
|
|
||||||
cb.onGameInit(model);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -242,29 +290,25 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer {
|
|||||||
* Starts the RMI server.
|
* Starts the RMI server.
|
||||||
* @return true if the server starts successfully, false otherwise
|
* @return true if the server starts successfully, false otherwise
|
||||||
*/
|
*/
|
||||||
public boolean start() {
|
public boolean start(boolean serverCrashed) {
|
||||||
|
this.serverCrashed = serverCrashed;
|
||||||
try {
|
try {
|
||||||
System.setProperty("java.rmi.server.hostname", host); // o il tuo IP/hostname
|
System.setProperty("java.rmi.server.hostname", host);
|
||||||
registry = LocateRegistry.createRegistry(nPort);
|
registry = LocateRegistry.createRegistry(nPort);
|
||||||
registry.rebind("RMIGameServer", this);
|
registry.rebind("RMIGameServer", this);
|
||||||
System.out.println("RMI Server started on port: " + nPort);
|
System.out.println("RMI Server started on port: " + nPort);
|
||||||
return true;
|
return true;
|
||||||
}
|
} catch (Exception e) {
|
||||||
catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops the RMI server.
|
|
||||||
* @return true if the server stops successfully, false otherwise
|
|
||||||
*/
|
|
||||||
public boolean stop() {
|
public boolean stop() {
|
||||||
try {
|
try {
|
||||||
registry.unbind("RMIGameServer");
|
registry.unbind("RMIGameServer");
|
||||||
UnicastRemoteObject.unexportObject(this, true);
|
UnicastRemoteObject.unexportObject(this, true);
|
||||||
|
watchdogs.values().forEach(wd -> { /* watchdogs shut themselves down */ });
|
||||||
System.out.println("RMI Server fermato");
|
System.out.println("RMI Server fermato");
|
||||||
return true;
|
return true;
|
||||||
} catch (RemoteException | NotBoundException e) {
|
} catch (RemoteException | NotBoundException e) {
|
||||||
@@ -274,4 +318,19 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and starts a {@link RMIHeartbeat} for {@code username}.
|
||||||
|
* Also seeds the watchdog with the current model if one already exists
|
||||||
|
* (reconnection case).
|
||||||
|
*/
|
||||||
|
private void startWatchdog(String username) {
|
||||||
|
RMIHeartbeat wd = new RMIHeartbeat(
|
||||||
|
username, playerList, clients, actionQueue);
|
||||||
|
if (model != null) wd.setGame(model);
|
||||||
|
watchdogs.put(username, wd);
|
||||||
|
wd.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,16 @@ import it.polimi.ingsw.gc14.Network.NetworkEvents.*;
|
|||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Client TCP. Sends and receives messages with the TCP server.
|
* Client TCP. Sends and receives messages with the TCP server.
|
||||||
*/
|
*/
|
||||||
public class TCPClient implements IClient {
|
public class TCPClient implements IClient {
|
||||||
|
private static final int PING = 1;
|
||||||
|
private static final int PONG = 2;
|
||||||
|
|
||||||
/** Socket TCP */
|
/** Socket TCP */
|
||||||
Socket communicationSocket;
|
Socket communicationSocket;
|
||||||
@@ -29,8 +34,14 @@ public class TCPClient implements IClient {
|
|||||||
/** IP address of the server to connect to */
|
/** IP address of the server to connect to */
|
||||||
String hostname;
|
String hostname;
|
||||||
|
|
||||||
|
private boolean running;
|
||||||
/** TCP port */
|
/** TCP port */
|
||||||
int port;
|
int mainPort;
|
||||||
|
|
||||||
|
int heartbeatPort;
|
||||||
|
private Socket heartbeatSocket;
|
||||||
|
private OutputStream heartbeatOut;
|
||||||
|
private InputStream heartbeatIn;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,10 +50,11 @@ public class TCPClient implements IClient {
|
|||||||
* @param hostname The IP address of the server
|
* @param hostname The IP address of the server
|
||||||
* @param port The TCP port of the server
|
* @param port The TCP port of the server
|
||||||
*/
|
*/
|
||||||
public TCPClient(ClientController controller, String hostname, int port) {
|
public TCPClient(ClientController controller, String hostname, int mainPort,int heartbeatPort ) {
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
this.hostname = hostname;
|
this.hostname = hostname;
|
||||||
this.port = port;
|
this.mainPort = mainPort;
|
||||||
|
this.heartbeatPort = heartbeatPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -56,27 +68,82 @@ public class TCPClient implements IClient {
|
|||||||
*/
|
*/
|
||||||
public boolean connect(String user, int proposedNPlayers) {
|
public boolean connect(String user, int proposedNPlayers) {
|
||||||
try {
|
try {
|
||||||
|
// Socket principale
|
||||||
communicationSocket = new Socket(hostname, port);
|
communicationSocket = new Socket(hostname, mainPort);
|
||||||
socketSend = new ObjectOutputStream(communicationSocket.getOutputStream());
|
socketSend = new ObjectOutputStream(communicationSocket.getOutputStream());
|
||||||
socketReceive = new ObjectInputStream(communicationSocket.getInputStream());
|
socketReceive = new ObjectInputStream(communicationSocket.getInputStream());
|
||||||
|
NetworkEvent event= new AddPlayer(user, proposedNPlayers);
|
||||||
|
System.out.println("Sending event: " + event);
|
||||||
doEvent(new AddPlayer(user, proposedNPlayers));
|
socketSend.writeObject(event);
|
||||||
if (communicationSocket.getInputStream().read() == -1) {
|
int read= communicationSocket.getInputStream().read();
|
||||||
|
if ( read== -1) {
|
||||||
System.out.println("Could not connect to server");
|
System.out.println("Could not connect to server");
|
||||||
return false;
|
return false;
|
||||||
} else {
|
|
||||||
Thread listener = new Thread(() -> receiveMessage());
|
|
||||||
listener.start();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
new Thread(this::receiveMessage, "tcp-reader").start();
|
||||||
|
|
||||||
|
// Socket heartbeat
|
||||||
|
this.heartbeatSocket = new Socket(hostname, heartbeatPort);
|
||||||
|
this.heartbeatOut =heartbeatSocket.getOutputStream() ;
|
||||||
|
this.heartbeatIn = heartbeatSocket.getInputStream();
|
||||||
|
|
||||||
|
// manda subito username per associare i due socket lato server
|
||||||
|
new ObjectOutputStream(heartbeatSocket.getOutputStream()).writeObject(user);
|
||||||
|
heartbeatOut.flush();
|
||||||
|
new Thread(this::heartbeatLoop, "heartbeat").start();
|
||||||
|
|
||||||
|
running = true;
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void heartbeatLoop() {
|
||||||
|
// thread che manda ping ogni 3s
|
||||||
|
ScheduledExecutorService sender = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
sender.scheduleAtFixedRate(() -> {
|
||||||
|
try {
|
||||||
|
heartbeatOut.write(PING);
|
||||||
|
heartbeatOut.flush();
|
||||||
|
} catch (IOException e) {
|
||||||
|
sender.shutdownNow();
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
}, 0, 3, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
// thread corrente: aspetta pong con timeout
|
||||||
|
try {
|
||||||
|
heartbeatSocket.setSoTimeout(5_000);
|
||||||
|
while (running) {
|
||||||
|
int b = heartbeatIn.read();
|
||||||
|
if (b == -1 || b != PONG) {
|
||||||
|
disconnect();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// pong ricevuto → server vivo
|
||||||
|
}
|
||||||
|
} catch (SocketTimeoutException e) {
|
||||||
|
System.out.println("Server heartbeat timeout");
|
||||||
|
disconnect();
|
||||||
|
} catch (IOException e) {
|
||||||
|
disconnect();
|
||||||
|
} finally {
|
||||||
|
sender.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void disconnect() {
|
||||||
|
if (!running) return;
|
||||||
|
running = false;
|
||||||
|
try { communicationSocket.close(); } catch (IOException ignored) {}
|
||||||
|
try { heartbeatSocket.close(); } catch (IOException ignored) {}
|
||||||
|
controller.view.showError("Connessione al server persa");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listens continuously for incoming objects from the server.
|
* Listens continuously for incoming objects from the server.
|
||||||
@@ -94,7 +161,6 @@ public class TCPClient implements IClient {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (read instanceof NetworkEvent event) { //TODO: avoid instanceof
|
if (read instanceof NetworkEvent event) { //TODO: avoid instanceof
|
||||||
if (event.getIsError()) {
|
if (event.getIsError()) {
|
||||||
controller.view.showError(event.toString());
|
controller.view.showError(event.toString());
|
||||||
@@ -102,6 +168,7 @@ public class TCPClient implements IClient {
|
|||||||
event.apply(controller.localController);
|
event.apply(controller.localController);
|
||||||
controller.view.render();
|
controller.view.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (read instanceof Game model) {
|
} else if (read instanceof Game model) {
|
||||||
controller.setModel(model);
|
controller.setModel(model);
|
||||||
controller.view.render();
|
controller.view.render();
|
||||||
@@ -216,7 +283,10 @@ public class TCPClient implements IClient {
|
|||||||
*/
|
*/
|
||||||
private void doEvent(NetworkEvent event) {
|
private void doEvent(NetworkEvent event) {
|
||||||
try {
|
try {
|
||||||
|
synchronized (socketSend) {
|
||||||
|
System.out.println("Sending event: " + event);
|
||||||
socketSend.writeObject(event);
|
socketSend.writeObject(event);
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package it.polimi.ingsw.gc14.Network.TCP.Server;
|
package it.polimi.ingsw.gc14.Network.TCP.Server;
|
||||||
|
|
||||||
|
import it.polimi.ingsw.gc14.LimitedMap;
|
||||||
import it.polimi.ingsw.gc14.Model.Game;
|
import it.polimi.ingsw.gc14.Model.Game;
|
||||||
import it.polimi.ingsw.gc14.Network.NetworkEvent;
|
import it.polimi.ingsw.gc14.Network.NetworkEvent;
|
||||||
|
import it.polimi.ingsw.gc14.Network.NetworkEvents.DisconnectedPlayer;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
@@ -20,6 +22,9 @@ public class ClientHandler implements Runnable {
|
|||||||
*/
|
*/
|
||||||
private final String username;
|
private final String username;
|
||||||
|
|
||||||
|
private boolean running ;
|
||||||
|
|
||||||
|
private Game game;
|
||||||
/**
|
/**
|
||||||
* Returns the username associated with this client.
|
* Returns the username associated with this client.
|
||||||
*
|
*
|
||||||
@@ -43,6 +48,9 @@ public class ClientHandler implements Runnable {
|
|||||||
*/
|
*/
|
||||||
List<ClientHandler> clientHandlers;
|
List<ClientHandler> clientHandlers;
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
LimitedMap<String,Boolean> limitedMap;
|
||||||
|
|
||||||
/** Queue containing the events to be applied to the game model */
|
/** Queue containing the events to be applied to the game model */
|
||||||
BlockingQueue<NetworkEvent> actionQueue;
|
BlockingQueue<NetworkEvent> actionQueue;
|
||||||
|
|
||||||
@@ -57,13 +65,14 @@ public class ClientHandler implements Runnable {
|
|||||||
* @param clientHandlers the shared list of all active client handlers.
|
* @param clientHandlers the shared list of all active client handlers.
|
||||||
* @param actionQueue the queue containing incoming events.
|
* @param actionQueue the queue containing incoming events.
|
||||||
*/
|
*/
|
||||||
public ClientHandler(String username, Socket clientSocket, ObjectOutputStream out, ObjectInputStream in, List<ClientHandler> clientHandlers, BlockingQueue<NetworkEvent> actionQueue) {
|
public ClientHandler(String username, Socket clientSocket, ObjectOutputStream out, ObjectInputStream in, List<ClientHandler> clientHandlers, LimitedMap<String,Boolean> playersMap, BlockingQueue<NetworkEvent> actionQueue) {
|
||||||
this.username=username;
|
this.username=username;
|
||||||
this.clientSocket = clientSocket;
|
this.clientSocket = clientSocket;
|
||||||
this.in = in;
|
this.in = in;
|
||||||
this.out = out;
|
this.out = out;
|
||||||
this.clientHandlers = clientHandlers;
|
this.clientHandlers = clientHandlers;
|
||||||
this.actionQueue = actionQueue;
|
this.actionQueue = actionQueue;
|
||||||
|
this.limitedMap = playersMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -74,13 +83,15 @@ public class ClientHandler implements Runnable {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
|
running = true;
|
||||||
while (true) {
|
while (running) {
|
||||||
|
synchronized (out){
|
||||||
NetworkEvent event = (NetworkEvent) in.readObject();
|
NetworkEvent event = (NetworkEvent) in.readObject();
|
||||||
if (!actionQueue.add(event)) {
|
if (!actionQueue.add(event)) {
|
||||||
System.out.println("Error inserting action into queue");
|
System.out.println("Error inserting action into queue");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
clientHandlers.remove(this);
|
clientHandlers.remove(this);
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -90,6 +101,7 @@ public class ClientHandler implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a {@link NetworkEvent} to the client.
|
* Sends a {@link NetworkEvent} to the client.
|
||||||
* @param event The network event to send to the client.
|
* @param event The network event to send to the client.
|
||||||
@@ -109,10 +121,20 @@ public class ClientHandler implements Runnable {
|
|||||||
* @param game The current state of the game to send to the client.
|
* @param game The current state of the game to send to the client.
|
||||||
*/
|
*/
|
||||||
public synchronized void notifyModel(Game game) {
|
public synchronized void notifyModel(Game game) {
|
||||||
|
this.game = game;
|
||||||
try {
|
try {
|
||||||
out.writeObject(game);
|
out.writeObject(game);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void disconnect() {
|
||||||
|
running = false;
|
||||||
|
clientHandlers.remove(this);
|
||||||
|
limitedMap.put(username, false);
|
||||||
|
actionQueue.add(new DisconnectedPlayer(username));
|
||||||
|
System.out.println("Disconnected player: " + username);
|
||||||
|
try { clientSocket.close(); } catch (IOException ignored) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package it.polimi.ingsw.gc14.Network.TCP.Server;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.SynchronousQueue;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class HeartbeatHandler implements Runnable {
|
||||||
|
|
||||||
|
private static final long SILENCE_THRESHOLD_MS = 5_000;
|
||||||
|
private static final long KEEPALIVE_INTERVAL_MS = 3_000;
|
||||||
|
|
||||||
|
private final String username;
|
||||||
|
private final Socket socket;
|
||||||
|
private final InputStream in;
|
||||||
|
private final OutputStream out;
|
||||||
|
|
||||||
|
private static final int PING = 1;
|
||||||
|
private static final int PONG = 2;
|
||||||
|
|
||||||
|
// riferimento al ClientHandler principale per disconnetterlo insieme
|
||||||
|
private final ClientHandler mainHandler;
|
||||||
|
|
||||||
|
private volatile long lastReceivedTime = System.currentTimeMillis();
|
||||||
|
private volatile boolean running = true;
|
||||||
|
|
||||||
|
private final ScheduledExecutorService watchdog =
|
||||||
|
Executors.newSingleThreadScheduledExecutor();
|
||||||
|
|
||||||
|
public HeartbeatHandler(String username, Socket socket, ClientHandler mainHandler)
|
||||||
|
throws IOException {
|
||||||
|
this.username = username;
|
||||||
|
this.socket = socket;
|
||||||
|
this.mainHandler = mainHandler;
|
||||||
|
this.in = socket.getInputStream();
|
||||||
|
this.out = socket.getOutputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
startWatchdog();
|
||||||
|
try {
|
||||||
|
while (running) {
|
||||||
|
int b = in.read(); // blocca finché non arriva un byte
|
||||||
|
if (b == -1) { disconnect(); break; } // stream chiusa
|
||||||
|
if (b == PING) {
|
||||||
|
lastReceivedTime = System.currentTimeMillis();
|
||||||
|
out.write(PONG);
|
||||||
|
out.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startWatchdog() {
|
||||||
|
watchdog.scheduleAtFixedRate(() -> {
|
||||||
|
if (System.currentTimeMillis() - lastReceivedTime > SILENCE_THRESHOLD_MS) {
|
||||||
|
System.out.println("Heartbeat timeout: " + username);
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
}, 1, 1, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void disconnect() {
|
||||||
|
running = false;
|
||||||
|
watchdog.shutdownNow();
|
||||||
|
mainHandler.disconnect(); // disconnette anche il socket principale
|
||||||
|
System.out.println("Disconnected: " + username);
|
||||||
|
try { socket.close(); } catch (IOException ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,161 +1,238 @@
|
|||||||
package it.polimi.ingsw.gc14.Network.TCP.Server;
|
package it.polimi.ingsw.gc14.Network.TCP.Server;
|
||||||
|
|
||||||
import it.polimi.ingsw.gc14.Controller.GameController;
|
import it.polimi.ingsw.gc14.Controller.GameController;
|
||||||
import it.polimi.ingsw.gc14.LimitedList;
|
import it.polimi.ingsw.gc14.LimitedMap;
|
||||||
import it.polimi.ingsw.gc14.Model.Game;
|
import it.polimi.ingsw.gc14.Model.Game;
|
||||||
|
import it.polimi.ingsw.gc14.Model.Player;
|
||||||
|
import it.polimi.ingsw.gc14.Network.ClientPlayer;
|
||||||
import it.polimi.ingsw.gc14.Network.EventType;
|
import it.polimi.ingsw.gc14.Network.EventType;
|
||||||
import it.polimi.ingsw.gc14.Network.NetworkEvent;
|
import it.polimi.ingsw.gc14.Network.NetworkEvent;
|
||||||
import it.polimi.ingsw.gc14.Network.NetworkEvents.AddPlayer;
|
import it.polimi.ingsw.gc14.Network.NetworkEvents.AddPlayer;
|
||||||
|
import it.polimi.ingsw.gc14.Network.NetworkEvents.ReconnectPlayer;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Server TCP. Accepts connections and manages all client handlers.
|
* Server TCP. Accepts connections and manages all client handlers.
|
||||||
*/
|
*/
|
||||||
public class TCPServer {
|
public class TCPServer {
|
||||||
|
|
||||||
/** TCP port */
|
|
||||||
int port;
|
int port;
|
||||||
|
int heartbeatPort; // ← nuova porta
|
||||||
|
|
||||||
/** Number of currently connected clients */
|
int connectedPlayers;
|
||||||
int ConnectedPlayers;
|
|
||||||
|
|
||||||
/** Socket TCP */
|
|
||||||
ServerSocket socketTCP;
|
ServerSocket socketTCP;
|
||||||
|
ServerSocket heartbeatSocketTCP; // ← nuovo ServerSocket
|
||||||
|
|
||||||
/** Server game's controller */
|
final GameController controller;
|
||||||
GameController controller;
|
|
||||||
|
|
||||||
/** Queue containing the events to be applied to the game model */
|
|
||||||
BlockingQueue<NetworkEvent> actionQueue;
|
BlockingQueue<NetworkEvent> actionQueue;
|
||||||
|
LimitedMap<String, Boolean> playerList;
|
||||||
|
List<ClientHandler> clientHandlers;
|
||||||
|
|
||||||
/**
|
boolean serverCrashed;
|
||||||
* List containing the usernames of joined players.
|
public void setServerCrashed(boolean serverCrashed) {
|
||||||
* {@link LimitedList}'s limit defines at which size the list calls its action. The limit can be set using {@link LimitedList#setLimit(int)}.
|
this.serverCrashed = serverCrashed;
|
||||||
*/
|
}
|
||||||
private LimitedList<String> playerList;
|
|
||||||
|
|
||||||
/** List containing all client's handlers */
|
// Mappa temporanea: username → ClientHandler
|
||||||
private List<ClientHandler> clientHandlers;
|
// Serve per associare il socket heartbeat al giusto ClientHandler
|
||||||
|
private final Map<String, ClientHandler> pendingHeartbeat = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public TCPServer(GameController controller, int port, int heartbeatPort,
|
||||||
/**
|
BlockingQueue<NetworkEvent> actionQueue, LimitedMap<String, Boolean> playerList) {
|
||||||
* Class constructor that initializes the attributes.
|
|
||||||
* @param controller The game controller
|
|
||||||
* @param port The TCP port
|
|
||||||
* @param actionQueue The action queue
|
|
||||||
* @param playerList The player's usernames list
|
|
||||||
*/
|
|
||||||
public TCPServer(GameController controller, int port, BlockingQueue<NetworkEvent> actionQueue, LimitedList<String> playerList){
|
|
||||||
this.port = port;
|
this.port = port;
|
||||||
this.ConnectedPlayers = 0;
|
this.heartbeatPort = heartbeatPort;
|
||||||
this.socketTCP = null;
|
this.connectedPlayers = 0;
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
this.actionQueue = actionQueue;
|
this.actionQueue = actionQueue;
|
||||||
this.playerList = playerList;
|
this.playerList = playerList;
|
||||||
this.clientHandlers = new ArrayList<>();
|
this.clientHandlers = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void start(boolean serverCrashed) {
|
||||||
/**
|
this.serverCrashed = serverCrashed;
|
||||||
* Starts the TCP server.
|
|
||||||
* If the first event is not AddPlayer, the request is rejected.
|
|
||||||
* If the desired number of player is invalid, the request is rejected.
|
|
||||||
* If this is the first player to connect, a new game model is created and passed to the controller. Additionally, the playerList's limit is set.
|
|
||||||
* If the controller successfully adds the player, the username is added to {@link #playerList} and the handler is added to {@link #clientHandlers}.
|
|
||||||
* If any error occurs, the server sends -1 back to the client. Otherwise, it sends 1.
|
|
||||||
*/
|
|
||||||
public void start(){
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
socketTCP = new ServerSocket(port);
|
socketTCP = new ServerSocket(port);
|
||||||
}
|
heartbeatSocketTCP = new ServerSocket(heartbeatPort);
|
||||||
catch (IOException e){
|
} catch (IOException e) {
|
||||||
System.out.println("Could not start the server TCP on port: " + port);
|
System.out.println("Could not start TCP server");
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
System.out.println("Server TCP started on port: " + port);
|
|
||||||
Socket clientSocket;
|
|
||||||
|
|
||||||
|
System.out.println("TCP server started on port: " + port);
|
||||||
|
System.out.println("Heartbeat server started on port: " + heartbeatPort);
|
||||||
|
|
||||||
|
// Thread separato per accettare le connessioni heartbeat
|
||||||
|
new Thread(this::acceptHeartbeat, "heartbeat-acceptor").start();
|
||||||
|
|
||||||
|
// Loop principale — invariato nella logica, cambia solo la creazione del ClientHandler
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
clientSocket = socketTCP.accept();
|
Socket clientSocket = socketTCP.accept();
|
||||||
ObjectOutputStream clientSend = new ObjectOutputStream(clientSocket.getOutputStream());
|
ObjectOutputStream clientSend = new ObjectOutputStream(clientSocket.getOutputStream());
|
||||||
ObjectInputStream clientReceive = new ObjectInputStream(clientSocket.getInputStream());
|
ObjectInputStream clientReceive = new ObjectInputStream(clientSocket.getInputStream());
|
||||||
NetworkEvent event = (NetworkEvent) clientReceive.readObject();
|
NetworkEvent event = (NetworkEvent) clientReceive.readObject();
|
||||||
|
|
||||||
if (!(event.getEventType() == EventType.ADD_PLAYER)) {
|
if (!(event.getEventType() == EventType.ADD_PLAYER)) {
|
||||||
clientSocket.getOutputStream().write((int)(-1));
|
clientSocket.getOutputStream().write(-1);
|
||||||
clientSocket.close();
|
clientSocket.close();
|
||||||
System.out.println("Invalid parameters. Connection terminated.\n");
|
System.out.println("Invalid parameters. Connection terminated.");
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
else{
|
|
||||||
AddPlayer eventAddPlayer = (AddPlayer) event;
|
AddPlayer eventAddPlayer = (AddPlayer) event;
|
||||||
|
|
||||||
if (eventAddPlayer.getProposedNPlayer() < 2 || eventAddPlayer.getProposedNPlayer() > 5) {
|
if (eventAddPlayer.getProposedNPlayer() < 2 || eventAddPlayer.getProposedNPlayer() > 5) {
|
||||||
clientSocket.getOutputStream().write((int) (-1));
|
clientSocket.getOutputStream().write(-1);
|
||||||
clientSocket.close();
|
clientSocket.close();
|
||||||
System.out.println("Invalid parameters. Connection terminated.\n");
|
System.out.println("Invalid parameters. Connection terminated.");
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (controller) {
|
synchronized (controller) {
|
||||||
|
|
||||||
|
String username = eventAddPlayer.getUsername();
|
||||||
|
if(serverCrashed){
|
||||||
|
if(controller.getModel().getPlayers().stream().anyMatch(p -> p.getUserName().equals(username))&& !playerList.containsKey(username)){
|
||||||
|
playerList.put(username, true);
|
||||||
|
System.out.println("(After crash)Reconnected player: " + username);
|
||||||
|
ClientHandler handler = new ClientHandler(
|
||||||
|
username, clientSocket, clientSend, clientReceive,
|
||||||
|
clientHandlers,playerList, actionQueue
|
||||||
|
);
|
||||||
|
clientSocket.getOutputStream().write(1);
|
||||||
|
pendingHeartbeat.put(username, handler);
|
||||||
|
Thread thread = new Thread(handler);
|
||||||
|
thread.start();
|
||||||
|
clientHandlers.add(handler);
|
||||||
|
connectedPlayers++;
|
||||||
|
}
|
||||||
|
else if(playerList.containsKey(username) && !playerList.get(username)){
|
||||||
|
// riconnessione
|
||||||
|
playerList.put(username, true);
|
||||||
|
System.out.println("Reconnected player: " + username);
|
||||||
|
|
||||||
|
ClientHandler handler = new ClientHandler(
|
||||||
|
username, clientSocket, clientSend, clientReceive,
|
||||||
|
clientHandlers, playerList, actionQueue
|
||||||
|
);
|
||||||
|
clientSocket.getOutputStream().write(1);
|
||||||
|
pendingHeartbeat.put(username, handler);
|
||||||
|
handler.notifyModel(controller.getModel());
|
||||||
|
Thread thread = new Thread(handler);
|
||||||
|
thread.start();
|
||||||
|
clientHandlers.add(handler);
|
||||||
|
connectedPlayers++;
|
||||||
|
actionQueue.add(new ReconnectPlayer(username));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
clientSocket.getOutputStream().write(-1);
|
||||||
|
clientSocket.close();
|
||||||
|
System.out.println("Player could not be added. Connection terminated.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
if (playerList.isEmpty()) {
|
if (playerList.isEmpty()) {
|
||||||
Game model = new Game(eventAddPlayer.getProposedNPlayer());
|
Game model = new Game(eventAddPlayer.getProposedNPlayer());
|
||||||
controller.setModel(model);
|
controller.setModel(model);
|
||||||
playerList.setLimit(eventAddPlayer.getProposedNPlayer());
|
playerList.setLimit(eventAddPlayer.getProposedNPlayer());
|
||||||
}
|
}
|
||||||
if (controller.addPlayer(eventAddPlayer.getUsername())) {
|
if (controller.addPlayer(username)) {
|
||||||
playerList.add(eventAddPlayer.getUsername());
|
// nuovo giocatore
|
||||||
clientSocket.getOutputStream().write((int) (1));
|
playerList.put(username, true);
|
||||||
|
System.out.println("Accepted player: " + username);
|
||||||
|
ClientHandler handler = new ClientHandler(
|
||||||
|
username, clientSocket, clientSend, clientReceive,
|
||||||
|
clientHandlers,playerList, actionQueue
|
||||||
|
);
|
||||||
|
clientSocket.getOutputStream().write(1);
|
||||||
|
pendingHeartbeat.put(username, handler);
|
||||||
|
Thread thread = new Thread(handler);
|
||||||
|
thread.start();
|
||||||
|
clientHandlers.add(handler);
|
||||||
|
connectedPlayers++;
|
||||||
|
// metti in attesa del socket heartbeat
|
||||||
|
}
|
||||||
|
else if(playerList.containsKey(username) && !playerList.get(username)){
|
||||||
|
// riconnessione
|
||||||
|
playerList.put(username, true);
|
||||||
|
System.out.println("Reconnected player: " + username);
|
||||||
|
|
||||||
System.out.println("Accepted player: " + eventAddPlayer.getUsername());
|
ClientHandler handler = new ClientHandler(
|
||||||
ClientHandler clientHandler = new ClientHandler(eventAddPlayer.getUsername(),clientSocket, clientSend, clientReceive, clientHandlers, actionQueue);
|
username, clientSocket, clientSend, clientReceive,
|
||||||
clientHandlers.add(clientHandler);
|
clientHandlers, playerList, actionQueue
|
||||||
ConnectedPlayers++;
|
);
|
||||||
|
clientSocket.getOutputStream().write(1);
|
||||||
Thread t = new Thread(clientHandler);
|
pendingHeartbeat.put(username, handler);
|
||||||
t.start();
|
handler.notifyModel(controller.getModel());
|
||||||
} else {
|
Thread thread = new Thread(handler);
|
||||||
clientSocket.getOutputStream().write((int) (-1));
|
thread.start();
|
||||||
|
clientHandlers.add(handler);
|
||||||
|
connectedPlayers++;
|
||||||
|
actionQueue.add(new ReconnectPlayer(username));
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
clientSocket.getOutputStream().write(-1);
|
||||||
clientSocket.close();
|
clientSocket.close();
|
||||||
System.out.println("Player could not be added. Connection terminated.\n");
|
System.out.println("Player could not be added. Connection terminated.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
catch(IOException e){
|
catch(IOException | ClassNotFoundException e){
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
catch(ClassNotFoundException e){
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends an action to all TCP clients.
|
* Accetta connessioni sul socket heartbeat e le associa al ClientHandler giusto.
|
||||||
*
|
* Il client manda subito il proprio username per identificarsi.
|
||||||
* @param event the network event to send to all connected TCP clients.
|
|
||||||
*/
|
*/
|
||||||
|
private void acceptHeartbeat() {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
Socket hbSocket = heartbeatSocketTCP.accept();
|
||||||
|
ObjectInputStream hbIn = new ObjectInputStream(hbSocket.getInputStream());
|
||||||
|
|
||||||
|
// il client manda subito il suo username
|
||||||
|
String username = (String) hbIn.readObject();
|
||||||
|
|
||||||
|
ClientHandler handler = pendingHeartbeat.remove(username);
|
||||||
|
if (handler != null) {
|
||||||
|
HeartbeatHandler hb = new HeartbeatHandler(username, hbSocket, handler);
|
||||||
|
new Thread(hb, "heartbeat-" + username).start();
|
||||||
|
System.out.println("Heartbeat connected for: " + username);
|
||||||
|
} else {
|
||||||
|
System.out.println("No pending handler for: " + username + ", closing heartbeat.");
|
||||||
|
hbSocket.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException | ClassNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void notifyAll(NetworkEvent event) {
|
public void notifyAll(NetworkEvent event) {
|
||||||
clientHandlers.forEach((x) -> {
|
clientHandlers.forEach(h -> {
|
||||||
if(!event.getIsError()||(event.getIsError()&& event.getUsername().equals(x.getUsername())))
|
if (!event.getIsError() || event.getUsername().equals(h.getUsername()))
|
||||||
x.notifyEvent(event);
|
h.notifyEvent(event);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a game model to all TCP clients.
|
|
||||||
*
|
|
||||||
* @param model the game model to send to all connected TCP clients.
|
|
||||||
*/
|
|
||||||
public void notifyAll(Game model) {
|
public void notifyAll(Game model) {
|
||||||
clientHandlers.forEach((x) -> x.notifyModel(model));
|
clientHandlers.forEach(h -> h.notifyModel(model));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,16 +3,22 @@ package it.polimi.ingsw.gc14;
|
|||||||
|
|
||||||
import it.polimi.ingsw.gc14.Controller.GameController;
|
import it.polimi.ingsw.gc14.Controller.GameController;
|
||||||
import it.polimi.ingsw.gc14.Model.Game;
|
import it.polimi.ingsw.gc14.Model.Game;
|
||||||
|
import it.polimi.ingsw.gc14.Model.GamePackage.GameStages;
|
||||||
|
import it.polimi.ingsw.gc14.Model.Player;
|
||||||
|
import it.polimi.ingsw.gc14.Network.EventType;
|
||||||
import it.polimi.ingsw.gc14.Network.NetworkEvent;
|
import it.polimi.ingsw.gc14.Network.NetworkEvent;
|
||||||
import it.polimi.ingsw.gc14.Network.NetworkEvents.AddPlayer;
|
import it.polimi.ingsw.gc14.Network.NetworkEvents.DisconnectedPlayer;
|
||||||
import it.polimi.ingsw.gc14.Network.RMI.Server.RMIServer;
|
import it.polimi.ingsw.gc14.Network.RMI.Server.RMIServer;
|
||||||
import it.polimi.ingsw.gc14.Network.TCP.Server.TCPServer;
|
import it.polimi.ingsw.gc14.Network.TCP.Server.TCPServer;
|
||||||
import it.polimi.ingsw.gc14.View.TUI.TUI;
|
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.rmi.RemoteException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
|
|
||||||
|
|
||||||
@@ -21,10 +27,10 @@ import java.net.*;
|
|||||||
* The workflow is divided into two parts: game creation and game execution.
|
* The workflow is divided into two parts: game creation and game execution.
|
||||||
* The process flow for game creation is as follows:
|
* 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 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 LimitedList#setLimit(int)}
|
* - 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}
|
* - 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)
|
* - 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 LimitedList}'s limit, the list calls {@link #run()}
|
* - 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}
|
* - All players are notified of the {@link Game}
|
||||||
*
|
*
|
||||||
* The process flow for game execution is as follows:
|
* The process flow for game execution is as follows:
|
||||||
@@ -52,15 +58,19 @@ public class ServerLauncher {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* List containing the usernames of joined players.
|
* List containing the usernames of joined players.
|
||||||
* {@link LimitedList}'s limit defines at which size the list calls its action
|
* {@link LimitedMap}'s limit defines at which size the list calls its action
|
||||||
* Both the limit and the action can be set using {@link LimitedList#setLimit(int)} and {@link LimitedList#setAction(Runnable)}
|
* 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()}
|
* The limit is set by the first player joining the game. The action consists in calling {@link #run()}
|
||||||
*/
|
*/
|
||||||
static LimitedList<String> playerList;
|
static LimitedMap<String,Boolean> playerList;
|
||||||
|
|
||||||
TUI view;
|
static TUI view;
|
||||||
|
|
||||||
|
|
||||||
|
// Campo da aggiungere in ServerLauncher
|
||||||
|
private final ScheduledExecutorService timerExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
private ScheduledFuture<?> disconnectionTimer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class constructor that initializes the attributes.
|
* Class constructor that initializes the attributes.
|
||||||
* @param actionQueue The queue containing the events
|
* @param actionQueue The queue containing the events
|
||||||
@@ -72,6 +82,7 @@ public class ServerLauncher {
|
|||||||
this.actionQueue = actionQueue;
|
this.actionQueue = actionQueue;
|
||||||
this.serverRMI = serverRMI;
|
this.serverRMI = serverRMI;
|
||||||
this.gameController = gameController;
|
this.gameController = gameController;
|
||||||
|
this.gameController.setModel(loadSave());
|
||||||
this.serverTCP = serverTCP;
|
this.serverTCP = serverTCP;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,13 +97,58 @@ public class ServerLauncher {
|
|||||||
*/
|
*/
|
||||||
public boolean doFirstEvent() throws InterruptedException, RemoteException {
|
public boolean doFirstEvent() throws InterruptedException, RemoteException {
|
||||||
NetworkEvent event = actionQueue.take();
|
NetworkEvent event = actionQueue.take();
|
||||||
event.setIsError(!event.apply(gameController));
|
if(gameController.getModel()!=null && !gameController.getModel().getCurrentState().equals(GameStages.ENDED))
|
||||||
|
{
|
||||||
|
if(disconnectionTimer!=null && event.getEventType() != EventType.RECONNECT_PLAYER)
|
||||||
|
{
|
||||||
|
event.setIsError(true);
|
||||||
serverRMI.notifyAll(event);
|
serverRMI.notifyAll(event);
|
||||||
serverTCP.notifyAll(event);
|
serverTCP.notifyAll(event);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (event.getEventType() == EventType.RECONNECT_PLAYER && disconnectionTimer != null && !disconnectionTimer.isDone()) {
|
||||||
|
disconnectionTimer.cancel(false);
|
||||||
|
disconnectionTimer = null;
|
||||||
|
}
|
||||||
|
synchronized(gameController){
|
||||||
|
event.setIsError(!event.apply(gameController));
|
||||||
|
serverRMI.notifyAll(event);
|
||||||
|
serverTCP.notifyAll(event);
|
||||||
|
if(!event.getIsError()){
|
||||||
|
if(this.gameController.getModel().getCurrentState().getGameStage() == GameStages.ENDED){
|
||||||
|
this.deleteSave();
|
||||||
|
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();
|
return !event.getIsError();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
if(event.getEventType().equals(EventType.DISCONNECTED_PLAYER))
|
||||||
|
{
|
||||||
|
playerList.remove(event.getUsername());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,10 +161,11 @@ public class ServerLauncher {
|
|||||||
* @throws RemoteException 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 {
|
public static void main(String[] args) throws InterruptedException, RemoteException {
|
||||||
playerList = new LimitedList<>(5, ()->{});
|
playerList = new LimitedMap<String,Boolean>(5, ()->{});
|
||||||
BlockingQueue<NetworkEvent> actionQueue = new LinkedBlockingQueue<>();
|
BlockingQueue<NetworkEvent> actionQueue = new LinkedBlockingQueue<>();
|
||||||
GameController gameController = new GameController();
|
GameController gameController = new GameController();
|
||||||
String IP;
|
String IP;
|
||||||
|
boolean serverCrashed;
|
||||||
try {
|
try {
|
||||||
IP=chooseNetworkInterface(new Scanner(System.in));
|
IP=chooseNetworkInterface(new Scanner(System.in));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -116,10 +173,34 @@ public class ServerLauncher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
RMIServer serverRMI = new RMIServer(gameController, 1099, actionQueue, playerList,IP);
|
RMIServer serverRMI = new RMIServer(gameController, 1099, actionQueue, playerList,IP);
|
||||||
TCPServer serverTCP = new TCPServer(gameController, 8080, actionQueue, playerList);
|
TCPServer serverTCP = new TCPServer(gameController, 8080, 8081,actionQueue, playerList);
|
||||||
ServerLauncher launcher = new ServerLauncher(actionQueue, gameController, serverRMI, serverTCP);
|
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(()->{
|
playerList.setAction(()->{
|
||||||
|
new Thread(()->{
|
||||||
|
try {
|
||||||
|
System.out.println("\n\nNotifying model");
|
||||||
|
serverRMI.notifyAll(gameController.getModel());
|
||||||
|
serverTCP.notifyAll(gameController.getModel());
|
||||||
|
view = new TUI(gameController.getModel());
|
||||||
|
view.fullRender();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
});
|
||||||
new Thread(()-> {
|
new Thread(()-> {
|
||||||
try {
|
try {
|
||||||
launcher.run();
|
launcher.run();
|
||||||
@@ -129,10 +210,9 @@ public class ServerLauncher {
|
|||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
});
|
|
||||||
|
|
||||||
serverRMI.start();
|
serverRMI.start(serverCrashed);
|
||||||
new Thread(()->{serverTCP.start();}).start();
|
new Thread(()->{serverTCP.start(serverCrashed);}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -144,26 +224,23 @@ public class ServerLauncher {
|
|||||||
* @throws RemoteException if an RMI error occurs
|
* @throws RemoteException if an RMI error occurs
|
||||||
*/
|
*/
|
||||||
public void run() throws InterruptedException, RemoteException {
|
public void run() throws InterruptedException, RemoteException {
|
||||||
// Game creation
|
|
||||||
System.out.println("\n\nNotifying model");
|
|
||||||
serverRMI.notifyAll(gameController.getModel());
|
|
||||||
serverTCP.notifyAll(gameController.getModel());
|
|
||||||
this.view = new TUI(gameController.getModel());
|
|
||||||
this.view.fullRender();
|
|
||||||
|
|
||||||
// Game execution
|
// Game execution
|
||||||
while (true) {
|
while (true) {
|
||||||
try{
|
try{
|
||||||
this.doFirstEvent();
|
this.doFirstEvent();
|
||||||
this.view.fullRender();
|
this.view.fullRender();
|
||||||
} catch (InterruptedException e) {
|
}
|
||||||
|
catch(InterruptedException e){
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
break;
|
break;
|
||||||
} catch (RemoteException e) {
|
}
|
||||||
|
catch(RemoteException e){
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String chooseNetworkInterface(Scanner scanner) throws Exception {
|
public static String chooseNetworkInterface(Scanner scanner) throws Exception {
|
||||||
List<String> ips = new ArrayList<>();
|
List<String> ips = new ArrayList<>();
|
||||||
|
|
||||||
@@ -196,4 +273,67 @@ public class ServerLauncher {
|
|||||||
int choice = Integer.parseInt(scanner.nextLine().trim());
|
int choice = Integer.parseInt(scanner.nextLine().trim());
|
||||||
return ips.get(choice);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user