Merge pull request #99 from rubenpirreram/disconnection-during-creatiion-game

Disconnection during creatiion game
This commit is contained in:
rubenpirreram
2026-05-15 16:28:26 +02:00
committed by GitHub
14 changed files with 194 additions and 307 deletions
@@ -86,11 +86,7 @@ public class ClientController {
} }
else else
{ {
try {
client.drawUpperTribeCard(playerUsername, pos); client.drawUpperTribeCard(playerUsername, pos);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
} }
} }
@@ -105,11 +101,7 @@ public class ClientController {
if(!Objects.equals(playerUsername, miniModel.currentState.getCurrentPlayer().getUserName())) if(!Objects.equals(playerUsername, miniModel.currentState.getCurrentPlayer().getUserName()))
view.showError("It's not your turn!"); view.showError("It's not your turn!");
else else
try {
client.drawLowerTribeCard(playerUsername, pos); client.drawLowerTribeCard(playerUsername, pos);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
} }
@@ -123,11 +115,7 @@ public class ClientController {
if(!Objects.equals(playerUsername, miniModel.currentState.getCurrentPlayer().getUserName())) if(!Objects.equals(playerUsername, miniModel.currentState.getCurrentPlayer().getUserName()))
view.showError("It's not your turn!"); view.showError("It's not your turn!");
else { else {
try {
client.drawUpperBuildingCard(playerUsername,pos); client.drawUpperBuildingCard(playerUsername,pos);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
} }
} }
@@ -143,11 +131,7 @@ public class ClientController {
if(!Objects.equals(playerUsername,miniModel.currentState.getCurrentPlayer().getUserName())) if(!Objects.equals(playerUsername,miniModel.currentState.getCurrentPlayer().getUserName()))
view.showError("It's not your turn!"); view.showError("It's not your turn!");
else { else {
try {
client.drawLowerBuildingCard(playerUsername,pos); client.drawLowerBuildingCard(playerUsername,pos);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
} }
} }
@@ -161,11 +145,7 @@ public class ClientController {
if(!Objects.equals(playerUsername,miniModel.currentState.getCurrentPlayer().getUserName())) if(!Objects.equals(playerUsername,miniModel.currentState.getCurrentPlayer().getUserName()))
view.showError("It's not your turn!"); view.showError("It's not your turn!");
else { else {
try {
client.skipTurn(playerUsername); client.skipTurn(playerUsername);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
} }
} }
@@ -182,11 +162,7 @@ public class ClientController {
if(!Objects.equals(playerUsername,miniModel.currentState.getCurrentPlayer().getUserName())) if(!Objects.equals(playerUsername,miniModel.currentState.getCurrentPlayer().getUserName()))
view.showError("It's not your turn!"); view.showError("It's not your turn!");
else { else {
try {
client.pickOptionalTribeCard(playerUsername,pos); client.pickOptionalTribeCard(playerUsername,pos);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
} }
} }
@@ -201,11 +177,7 @@ public class ClientController {
if(!Objects.equals(playerUsername, miniModel.currentState.getCurrentPlayer().getUserName())) if(!Objects.equals(playerUsername, miniModel.currentState.getCurrentPlayer().getUserName()))
view.showError("It's not your turn!"); view.showError("It's not your turn!");
else { else {
try {
client.pickOptionalBuildingCard(playerUsername,pos); client.pickOptionalBuildingCard(playerUsername,pos);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
} }
} }
@@ -219,11 +191,7 @@ public class ClientController {
if(!Objects.equals(playerUsername, miniModel.currentState.getCurrentPlayer().getUserName())) if(!Objects.equals(playerUsername, miniModel.currentState.getCurrentPlayer().getUserName()))
view.showError("It's not your turn!"); view.showError("It's not your turn!");
else { else {
try {
client.noOptionalCard(playerUsername); client.noOptionalCard(playerUsername);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
} }
} }
@@ -237,11 +205,7 @@ public class ClientController {
if(!Objects.equals(playerUsername, miniModel.currentState.getCurrentPlayer().getUserName())) if(!Objects.equals(playerUsername, miniModel.currentState.getCurrentPlayer().getUserName()))
view.showError("It's not your turn!"); view.showError("It's not your turn!");
else { else {
try {
client.slotChoice(playerUsername,pos); client.slotChoice(playerUsername,pos);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
} }
} }
@@ -250,11 +214,7 @@ public class ClientController {
if(!Objects.equals(playerUsername, miniModel.currentState.getCurrentPlayer().getUserName())) if(!Objects.equals(playerUsername, miniModel.currentState.getCurrentPlayer().getUserName()))
view.showError("It's not your turn!"); view.showError("It's not your turn!");
else { else {
try {
client.totemChoice(playerUsername, String.valueOf(miniModel.availableTotems.get(pos))); client.totemChoice(playerUsername, String.valueOf(miniModel.availableTotems.get(pos)));
} catch (RemoteException e) {
throw new RuntimeException(e);
}
} }
} }
@@ -4,6 +4,7 @@ import java.util.Collection;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/** /**
* A {@link LinkedHashMap} with a configurable size limit and an associated action. * A {@link LinkedHashMap} with a configurable size limit and an associated action.
@@ -15,7 +16,7 @@ import java.util.Set;
*/ */
public class LimitedMap<K, V> implements Map<K, V> { public class LimitedMap<K, V> implements Map<K, V> {
private final LinkedHashMap<K, V> map = new LinkedHashMap<>(); private final ConcurrentHashMap<K, V> map = new ConcurrentHashMap<>();
/** /**
* The maximum number of elements allowed in the map before the action is triggered. * The maximum number of elements allowed in the map before the action is triggered.
@@ -96,19 +96,25 @@ public class Game implements Serializable {
return false; return false;
} }
disconnetedPlayers.put(player,true); disconnetedPlayers.put(player,true);
if(currentState.getCurrentPlayer().equals(player)) if (currentState.getGameStage().equals(GameStages.WAITING)) {
if(!currentState.getGameStage().equals(GameStages.TOTEM_CHOICE)) playersList.remove(player);
totemChoiceQueue.remove(player);
return true;
}
if(currentState.getCurrentPlayer().equals(player)) {
if (!currentState.getGameStage().equals(GameStages.TOTEM_CHOICE)) {
nextPlayerSetup(); nextPlayerSetup();
else return true;
{
if(totemChoiceQueue.isEmpty())
{
player.totem=getAvailableTotems().get(new Random().nextInt(getAvailableTotems().size()-1));
} }
else { else {
if (totemChoiceQueue.isEmpty()) {
player.totem = getAvailableTotems().get(new Random().nextInt(getAvailableTotems().size() - 1));
} else {
totemChoiceQueue.add(player); totemChoiceQueue.add(player);
} }
currentState.PlayerUpdate(totemChoiceQueue.poll(), null); currentState.PlayerUpdate(totemChoiceQueue.poll(), null);
return true;
}
} }
return true; return true;
@@ -70,6 +70,11 @@ public abstract class OrderLogicCard implements Serializable {
playerList=new ArrayList<>(); playerList=new ArrayList<>();
} }
if(players.stream().anyMatch(x->x.getUserName().equals(player.getUserName())))
{
return;
}
playerList.removeIf(x->player.getUserName().equals(x.player.getUserName()));
playerList.add(new OrderPlayer(player,false)); playerList.add(new OrderPlayer(player,false));
players.add(player); players.add(player);
} }
@@ -8,26 +8,26 @@ import java.util.Objects;
public interface IClient { public interface IClient {
public boolean connect(String username,int preferredInt); public boolean connect(String username,int preferredInt);
public void drawUpperTribeCard(String playerUsername, int pos) throws RemoteException; public void drawUpperTribeCard(String playerUsername, int pos) ;
public void drawLowerTribeCard(String playerUsername,int pos) throws RemoteException; public void drawLowerTribeCard(String playerUsername,int pos) ;
public void drawUpperBuildingCard(String playerUsername,int pos) throws RemoteException; public void drawUpperBuildingCard(String playerUsername,int pos) ;
public void drawLowerBuildingCard(String playerUsername,int pos) throws RemoteException; public void drawLowerBuildingCard(String playerUsername,int pos) ;
public void skipTurn(String playerUsername) throws RemoteException; public void skipTurn(String playerUsername);
public void pickOptionalTribeCard(String playerUsername,int pos) throws RemoteException; public void pickOptionalTribeCard(String playerUsername,int pos) ;
public void pickOptionalBuildingCard(String playerUsername,int pos) throws RemoteException; public void pickOptionalBuildingCard(String playerUsername,int pos) ;
public void noOptionalCard(String playerUsername) throws RemoteException; public void noOptionalCard(String playerUsername) ;
public void slotChoice(String playerUsername,int pos) throws RemoteException; public void slotChoice(String playerUsername,int pos) ;
public void totemChoice(String playerUsername,String totem) throws RemoteException; public void totemChoice(String playerUsername,String totem) ;
} }
@@ -35,10 +35,13 @@ public class DisconnectedPlayer extends NetworkEvent implements Serializable{
public boolean apply(MiniModel miniModel){ public boolean apply(MiniModel miniModel){
if(isError) if(isError)
return false; return false;
if(miniModel!=null)
{
miniModel.setPlayer(player); miniModel.setPlayer(player);
miniModel.setOrderLogicCard(orderLogicCard); miniModel.setOrderLogicCard(orderLogicCard);
miniModel.setCurrentState(currentState); miniModel.setCurrentState(currentState);
miniModel.setSlotPlayerMap(slotPlayerMap); miniModel.setSlotPlayerMap(slotPlayerMap);
}
return true; return true;
} }
@@ -33,10 +33,12 @@ public class ReconnectPlayer extends NetworkEvent implements Serializable{
public boolean apply(MiniModel miniModel){ public boolean apply(MiniModel miniModel){
if(isError) if(isError)
return false; return false;
if(miniModel!=null) {
miniModel.setPlayer(player); miniModel.setPlayer(player);
miniModel.setOrderLogicCard(orderLogicCard); miniModel.setOrderLogicCard(orderLogicCard);
miniModel.setCurrentState(currentState); miniModel.setCurrentState(currentState);
miniModel.setSlotPlayerMap(slotPlayerMap); miniModel.setSlotPlayerMap(slotPlayerMap);
}
return true; return true;
} }
@@ -134,9 +134,14 @@ public class RMIClient implements IClient {
* @param playerUsername the name of the player performing the action * @param playerUsername the name of the player performing the action
* @param pos the index of the card to draw * @param pos the index of the card to draw
*/ */
public void drawUpperTribeCard(String playerUsername, int pos) throws RemoteException { public void drawUpperTribeCard(String playerUsername, int pos) {
try{
stub.drawUpperTribeCard(playerUsername,pos); stub.drawUpperTribeCard(playerUsername,pos);
} }
catch (RemoteException e){
System.out.println("Error during remote draw upper tribe card");
}
}
/** /**
@@ -145,9 +150,14 @@ public class RMIClient implements IClient {
* @param playerUsername the name of the player performing the action * @param playerUsername the name of the player performing the action
* @param pos the index of the card to draw * @param pos the index of the card to draw
*/ */
public void drawLowerTribeCard(String playerUsername,int pos) throws RemoteException { public void drawLowerTribeCard(String playerUsername,int pos) {
try{
stub.drawLowerTribeCard(playerUsername,pos); stub.drawLowerTribeCard(playerUsername,pos);
} }
catch (RemoteException e){
System.out.println("Error during remote draw lower tribe card");
}
}
/** /**
@@ -156,9 +166,14 @@ public class RMIClient implements IClient {
* @param playerUsername the name of the player performing the action * @param playerUsername the name of the player performing the action
* @param pos the index of the card to draw * @param pos the index of the card to draw
*/ */
public void drawUpperBuildingCard(String playerUsername,int pos) throws RemoteException { public void drawUpperBuildingCard(String playerUsername,int pos) {
try{
stub.drawUpperBuildingCard(playerUsername,pos); stub.drawUpperBuildingCard(playerUsername,pos);
} }
catch (RemoteException e){
System.out.println("Error during remote draw upper building card");
}
}
/** /**
@@ -167,9 +182,14 @@ public class RMIClient implements IClient {
* @param playerUsername the name of the player performing the action * @param playerUsername the name of the player performing the action
* @param pos the index of the card to draw * @param pos the index of the card to draw
*/ */
public void drawLowerBuildingCard(String playerUsername,int pos) throws RemoteException { public void drawLowerBuildingCard(String playerUsername,int pos) {
try{
stub.drawLowerBuildingCard(playerUsername,pos); stub.drawLowerBuildingCard(playerUsername,pos);
} }
catch (RemoteException e){
System.out.println("Error during remote draw lower building card");
}
}
/** /**
@@ -177,9 +197,14 @@ public class RMIClient implements IClient {
* This action is available only when the player cannot draw any tribe card, but still can buy some buildings. * This action is available only when the player cannot draw any tribe card, but still can buy some buildings.
* @param playerUsername the name of the player performing the action * @param playerUsername the name of the player performing the action
*/ */
public void skipTurn(String playerUsername) throws RemoteException { public void skipTurn(String playerUsername) {
try{
stub.skipTurn(playerUsername); stub.skipTurn(playerUsername);
} }
catch (RemoteException e){
System.out.println("Error during remote skip turn");
}
}
@@ -190,9 +215,14 @@ public class RMIClient implements IClient {
* @param playerUsername the name of the player performing the action * @param playerUsername the name of the player performing the action
* @param pos the index of the card to draw * @param pos the index of the card to draw
*/ */
public void pickOptionalTribeCard(String playerUsername,int pos) throws RemoteException { public void pickOptionalTribeCard(String playerUsername,int pos) {
try{
stub.pickOptionalTribeCard(playerUsername,pos); stub.pickOptionalTribeCard(playerUsername,pos);
} }
catch (RemoteException e){
System.out.println("Error during remote pick optional tribe card");
}
}
/** /**
@@ -201,9 +231,14 @@ public class RMIClient implements IClient {
* @param playerUsername the name of the player performing the action * @param playerUsername the name of the player performing the action
* @param pos the index of the card to draw * @param pos the index of the card to draw
*/ */
public void pickOptionalBuildingCard(String playerUsername,int pos) throws RemoteException { public void pickOptionalBuildingCard(String playerUsername,int pos) {
try{
stub.pickOptionalBuildingCard(playerUsername,pos); stub.pickOptionalBuildingCard(playerUsername,pos);
} }
catch (RemoteException e){
System.out.println("Error during remote pick optional building card");
}
}
/** /**
@@ -211,9 +246,14 @@ public class RMIClient implements IClient {
* Available only if the player owns the building 12. * Available only if the player owns the building 12.
* @param playerUsername the name of the player performing the action * @param playerUsername the name of the player performing the action
*/ */
public void noOptionalCard(String playerUsername) throws RemoteException { public void noOptionalCard(String playerUsername) {
try{
stub.noOptionalCard(playerUsername); stub.noOptionalCard(playerUsername);
} }
catch (RemoteException e){
System.out.println("Error during remote no pick optional card");
}
}
/** /**
@@ -221,13 +261,23 @@ public class RMIClient implements IClient {
* @param playerUsername the name of the player performing the action * @param playerUsername the name of the player performing the action
* @param pos the index of the selected slot * @param pos the index of the selected slot
*/ */
public void slotChoice(String playerUsername,int pos) throws RemoteException { public void slotChoice(String playerUsername,int pos) {
try{
stub.slotChoice(playerUsername,pos); stub.slotChoice(playerUsername,pos);
} }
catch (RemoteException e){
System.out.println("Error during remote slot choice");
}
}
//TODO //TODO
public void totemChoice(String playerUsername,String totem) throws RemoteException { public void totemChoice(String playerUsername,String totem) {
try{
stub.totemChoice(playerUsername,totem); stub.totemChoice(playerUsername,totem);
} }
catch (RemoteException e){
System.out.println("Error during remote totem choice");
}
}
} }
@@ -153,12 +153,18 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer {
* Also updates every watchdog with the latest model so disconnect logic * Also updates every watchdog with the latest model so disconnect logic
* knows whose turn it is. * knows whose turn it is.
*/ */
public void notifyAll(NetworkEvent action) throws RemoteException { public void notifyAll(NetworkEvent action){
for (Map.Entry<String, IClientCallback> entry : clients.entrySet()) { for (Map.Entry<String, IClientCallback> entry : clients.entrySet()) {
if (!action.getIsError() || if (!action.getIsError() ||
(action.getIsError() && action.getUsername().equals(entry.getKey()))) { (action.getIsError() && action.getUsername().equals(entry.getKey()))) {
try{
entry.getValue().onAction(action); entry.getValue().onAction(action);
} }
catch (RemoteException e)
{
System.out.println("Remote exception: Exception during action sending attempt " + e.getMessage());
}
}
} }
} }
@@ -167,14 +173,20 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer {
* Mirrors {@code TCPServer.notifyAll(Game)} + the {@code ClientHandler.notifyModel} * Mirrors {@code TCPServer.notifyAll(Game)} + the {@code ClientHandler.notifyModel}
* call that stores the model for disconnect-turn checking. * call that stores the model for disconnect-turn checking.
*/ */
public void notifyAll(MiniModel model) throws RemoteException { public void notifyAll(MiniModel model) {
// Keep every watchdog's game reference up to date // Keep every watchdog's game reference up to date
synchronized (controller){ synchronized (controller){
watchdogs.values().forEach(wd -> wd.setGame(controller.getModel())); watchdogs.values().forEach(wd -> wd.setGame(controller.getModel()));
} }
for (IClientCallback cb : clients.values()) { for (IClientCallback cb : clients.values()) {
try{
cb.onGameInit(model); cb.onGameInit(model);
} }
catch (RemoteException e)
{
System.out.println("Remote exception: Exception during model sending attempt " + e.getMessage());
}
}
} }
@@ -69,12 +69,10 @@ 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, mainPort); 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); NetworkEvent event= new AddPlayer(user, proposedNPlayers);
System.out.println("Sending event: " + event);
socketSend.writeObject(event); socketSend.writeObject(event);
int read= communicationSocket.getInputStream().read(); int read= communicationSocket.getInputStream().read();
if ( read== -1) { if ( read== -1) {
@@ -94,8 +92,6 @@ public class TCPClient implements IClient {
new Thread(this::heartbeatLoop, "heartbeat").start(); new Thread(this::heartbeatLoop, "heartbeat").start();
running = true; running = true;
return true; return true;
} catch (IOException e) { } catch (IOException e) {
@@ -105,7 +101,6 @@ public class TCPClient implements IClient {
} }
private void heartbeatLoop() { private void heartbeatLoop() {
// thread che manda ping ogni 3s
ScheduledExecutorService sender = Executors.newSingleThreadScheduledExecutor(); ScheduledExecutorService sender = Executors.newSingleThreadScheduledExecutor();
sender.scheduleAtFixedRate(() -> { sender.scheduleAtFixedRate(() -> {
try { try {
@@ -117,7 +112,6 @@ public class TCPClient implements IClient {
} }
}, 0, 3, TimeUnit.SECONDS); }, 0, 3, TimeUnit.SECONDS);
// thread corrente: aspetta pong con timeout
try { try {
heartbeatSocket.setSoTimeout(5_000); heartbeatSocket.setSoTimeout(5_000);
while (running) { while (running) {
@@ -142,7 +136,7 @@ public class TCPClient implements IClient {
running = false; running = false;
try { communicationSocket.close(); } catch (IOException ignored) {} try { communicationSocket.close(); } catch (IOException ignored) {}
try { heartbeatSocket.close(); } catch (IOException ignored) {} try { heartbeatSocket.close(); } catch (IOException ignored) {}
controller.view.showError("Connessione al server persa"); controller.view.showError("Connection with server lost");
} }
@@ -162,7 +156,7 @@ public class TCPClient implements IClient {
e.printStackTrace(); e.printStackTrace();
break; break;
} }
if (read instanceof NetworkEvent event) { //TODO: avoid instanceof if (read instanceof NetworkEvent event) {
if (event.getIsError()) { if (event.getIsError()) {
controller.view.showError(event.toString()); controller.view.showError(event.toString());
} else { } else {
@@ -285,7 +279,6 @@ public class TCPClient implements IClient {
private void doEvent(NetworkEvent event) { private void doEvent(NetworkEvent event) {
try { try {
synchronized (socketSend) { synchronized (socketSend) {
System.out.println("Sending event: " + event);
socketSend.writeObject(event); socketSend.writeObject(event);
} }
} catch (IOException e) { } catch (IOException e) {
@@ -97,9 +97,8 @@ public class ServerLauncher {
* All clients (both TCP and RMI) are notified of the event * All clients (both TCP and RMI) are notified of the event
* @return the outcome of applying the event to the controller * @return the outcome of applying the event to the controller
* @throws InterruptedException if an error occurs while accessing the actionQueue * @throws InterruptedException if an error occurs while accessing the actionQueue
* @throws RemoteException if an RMI error occurs
*/ */
public boolean doFirstEvent() throws InterruptedException, RemoteException { public boolean doFirstEvent() throws InterruptedException {
NetworkEvent event = actionQueue.take(); NetworkEvent event = actionQueue.take();
if(gameController.getModel()!=null && !gameController.getModel().getCurrentState().equals(GameStages.ENDED)) if(gameController.getModel()!=null && !gameController.getModel().getCurrentState().equals(GameStages.ENDED))
{ {
@@ -114,19 +113,27 @@ public class ServerLauncher {
disconnectionTimer.cancel(false); disconnectionTimer.cancel(false);
disconnectionTimer = null; disconnectionTimer = null;
} }
int roundPrec=gameController.getModel().getCurrentState().getRound(); int roundPrev=gameController.getModel().getCurrentState().getRound();
synchronized(gameController){ synchronized(gameController){
event.setIsError(!event.apply(gameController)); event.setIsError(!event.apply(gameController));
Game game=gameController.getModel(); Game game=gameController.getModel();
if(!event.getIsError()) if(!event.getIsError())
{ {
if(event.getEventType().equals(EventType.DISCONNECTED_PLAYER)&& game.getCurrentState().getGameStage().equals(GameStages.WAITING))
{
playerList.remove(event.getUsername());
}else {
event.setData(game.getSlotMap(), game.orderLogicCard, game.getCurrentState(), game.getPlayerByUsername(event.getUsername())); event.setData(game.getSlotMap(), game.orderLogicCard, game.getCurrentState(), game.getPlayerByUsername(event.getUsername()));
if(event.getEventType().equals(EventType.TOTEM_CHOICE)) if (event.getEventType().equals(EventType.TOTEM_CHOICE)) {
((TotemChoice) event).setAvailableTotems(gameController.getModel().getAvailableTotems()); ((TotemChoice) event).setAvailableTotems(gameController.getModel().getAvailableTotems());
} else if (event.getEventType().equals(EventType.DISCONNECTED_PLAYER) && game.getCurrentState().equals(GameStages.WAITING)) {
playerList.remove(event.getUsername());
}
}
} }
serverRMI.notifyAll(event); serverRMI.notifyAll(event);
serverTCP.notifyAll(event); serverTCP.notifyAll(event);
if(game.getCurrentState().getRound()!=roundPrec) if(game.getCurrentState().getRound()!=roundPrev)
{ {
ApplyNextRound nextRound=new ApplyNextRound(game.getSlotMap(), game.orderLogicCard, game.getCurrentState(),game.getPlayers()); ApplyNextRound nextRound=new ApplyNextRound(game.getSlotMap(), game.orderLogicCard, game.getCurrentState(),game.getPlayers());
serverRMI.notifyAll(nextRound); serverRMI.notifyAll(nextRound);
@@ -147,15 +154,14 @@ public class ServerLauncher {
else if(!this.gameSave() ){ else if(!this.gameSave() ){
System.out.println("\n!!! Save failed !!!\n"); System.out.println("\n!!! Save failed !!!\n");
} }
} }
if (event.getEventType().equals(EventType.DISCONNECTED_PLAYER) && playerList.values().stream().filter(x -> x).count() == 1) { if (event.getEventType().equals(EventType.DISCONNECTED_PLAYER) && playerList.values().stream().filter(x -> x).count() == 1&& game.getCurrentState().getGameStage() != GameStages.ENDED) {
if (disconnectionTimer != null && !disconnectionTimer.isDone()) { if (disconnectionTimer != null && !disconnectionTimer.isDone()) {
disconnectionTimer.cancel(false); disconnectionTimer.cancel(false);
} }
disconnectionTimer = timerExecutor.schedule(() -> { disconnectionTimer = timerExecutor.schedule(() -> {
System.out.println("Timer scaduto: nessun giocatore riconnesso in 30s."); System.out.println("Timer expired: no player reconnected in 60s.");
}, 30, TimeUnit.SECONDS); }, 1, TimeUnit.MINUTES);
} }
return !event.getIsError(); return !event.getIsError();
} }
@@ -214,7 +220,6 @@ public class ServerLauncher {
} }
playerList.setAction(()->{ playerList.setAction(()->{
new Thread(()->{ new Thread(()->{
try {
System.out.println("\n\nNotifying model"); System.out.println("\n\nNotifying model");
MiniModel miniModel; MiniModel miniModel;
synchronized (gameController) { synchronized (gameController) {
@@ -225,18 +230,14 @@ public class ServerLauncher {
serverTCP.notifyAll(miniModel); serverTCP.notifyAll(miniModel);
view = new TUI(miniModel); view = new TUI(miniModel);
view.fullRender(); view.fullRender();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}).start(); }).start();
}); });
new Thread(()-> { new Thread(()-> {
try { try {
launcher.run(); launcher.run();
} catch (InterruptedException e) { } catch (Exception e)
throw new RuntimeException(e); {
} catch (RemoteException e) { System.out.println("Generic exception occurred"+e.getMessage());
throw new RuntimeException(e);
} }
}).start(); }).start();
@@ -250,11 +251,8 @@ public class ServerLauncher {
* Game creation: TCP/RMI servers send the game model to all players. * Game creation: TCP/RMI servers send the game model to all players.
* Game execution: repeatedly calls doFirstEvent() to process the events in the actionQueue. * Game execution: repeatedly calls doFirstEvent() to process the events in the actionQueue.
* @throws InterruptedException if the TCP server thread is interrupted * @throws InterruptedException if the TCP server thread is interrupted
* @throws RemoteException if an RMI error occurs
*/ */
public void run() throws InterruptedException, RemoteException { public void run() {
// Game execution
while (true) { while (true) {
try{ try{
this.doFirstEvent(); this.doFirstEvent();
@@ -264,9 +262,6 @@ public class ServerLauncher {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
break; break;
} }
catch(RemoteException e){
throw new RuntimeException(e);
}
} }
} }
@@ -29,7 +29,7 @@ public class TotemFXMLController {
ImageView img = new ImageView(new Image(getClass().getResourceAsStream("/GUIImages/Totems/totem_"+ String.valueOf(totem).toLowerCase(Locale.ROOT) +".png"))); ImageView img = new ImageView(new Image(getClass().getResourceAsStream("/GUIImages/Totems/totem_"+ String.valueOf(totem).toLowerCase(Locale.ROOT) +".png")));
img.setFitHeight(240); img.setFitHeight(240);
img.setPreserveRatio(true); img.setPreserveRatio(true);
img.setOnMouseClicked(e -> controller.totemChoice(controller.myUsername)); //img.setOnMouseClicked(e -> controller.totemChoice(controller.myUsername));
mainHBox.getChildren().add(img); mainHBox.getChildren().add(img);
} }
} }
@@ -90,6 +90,8 @@ public class TUI implements IView {
*/ */
@Override @Override
public void render() { public void render() {
if(model != null)
{
if(model.currentState.getGameStage().equals(GameStages.TOTEM_CHOICE)) if(model.currentState.getGameStage().equals(GameStages.TOTEM_CHOICE))
{ {
renderTotems(); renderTotems();
@@ -105,6 +107,7 @@ public class TUI implements IView {
} }
System.out.println("\nYOUR ACTION:"); System.out.println("\nYOUR ACTION:");
} }
}
/** /**
* Renders a full view of the game, combining both player status * Renders a full view of the game, combining both player status
@@ -155,14 +158,13 @@ public class TUI implements IView {
public void renderTotems() { public void renderTotems() {
clearTerminal(); clearTerminal();
var table = new AsciiTable(BorderStyle.ROUNDED, model.availableTotems.size()); var table = new AsciiTable(BorderStyle.ROUNDED, model.availableTotems.size());
List<String> lines = model.availableTotems.stream().map(Totems::toString).toList(); List<String> lines =new ArrayList<>();
int i=0; for(int i=0;i<model.availableTotems.size();i++)
for(String line : lines)
{ {
line = (i++) +". "+ line; lines.add(i+"."+model.availableTotems.get(i));
} }
table.addRow(lines); table.addRow(lines);
System.out.println(model.currentState+"\nTOTEMS AVAILABLE:\n"+table.build()); System.out.println(model.currentState+"\nTOTEMS AVAILABLE:\n"+table.build()+"\nPress 9 and then write the position");
} }
/** /**
@@ -225,7 +227,6 @@ public class TUI implements IView {
table.addRow(List.of("6-PickOptionalBuilding(pos)", "")); table.addRow(List.of("6-PickOptionalBuilding(pos)", ""));
table.addRow(List.of("7-NoOptional", "")); table.addRow(List.of("7-NoOptional", ""));
table.addRow(List.of("8-SkipTurn", "")); table.addRow(List.of("8-SkipTurn", ""));
table.addRow(List.of("9-TotemChoice(pos)", ""));
return table.build(); return table.build();
} }
@@ -1,141 +0,0 @@
package it.polimi.ingsw.gc14.Controller;
import it.polimi.ingsw.gc14.Model.Game;
import it.polimi.ingsw.gc14.Model.Player;
import it.polimi.ingsw.gc14.Network.IClient;
import it.polimi.ingsw.gc14.View.IView;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.rmi.RemoteException;
import static org.junit.jupiter.api.Assertions.*;
class ClientControllerTest {
private FakeView view;
private FakeClient client;
private ClientController controller;
@BeforeEach
void setUp() {
view = new FakeView();
client = new FakeClient();
controller = new ClientController(view);
controller.setClient(client);
}
@Test
void testConstructorInitialization() {
assertSame(view, controller.view);
assertNotNull(controller.localController);
}
@Test
void testSetModelUpdatesLocalControllerAndView() {
Game game = createGame();
controller.setModel(game);
assertSame(game, controller.localController.getModel());
assertSame(game, view.model);
}
@Test
void testOnError() {
controller.onError("Errore di test");
assertEquals("Errore di test", view.lastError);
}
@Test
void testSetClientDelegatesAction() {
FakeClient newClient = new FakeClient();
controller.setClient(newClient);
Game game = createGame();
controller.setModel(game);
String current = game.getCurrentState().getCurrentPlayer().getUserName();
controller.drawUpperTribeCard(current, 0);
assertEquals("drawUpperTribeCard", newClient.lastMethodCalled);
assertEquals(current, newClient.lastUsername);
assertEquals(0, newClient.lastPos);
}
@Test
void testDrawUpperTribeCardDelegatesCorrectly() {
Game game = createGame();
controller.setModel(game);
String current = game.getCurrentState().getCurrentPlayer().getUserName();
controller.drawUpperTribeCard(current, 0);
assertEquals("drawUpperTribeCard", client.lastMethodCalled);
assertEquals(current, client.lastUsername);
assertEquals(0, client.lastPos);
assertNull(view.lastError);
}
@Test
void testDrawUpperTribeCardWrongPlayerShowsError() {
Game game = createGame();
controller.setModel(game);
String current = game.getCurrentState().getCurrentPlayer().getUserName();
String wrong = current.equals("p1") ? "p2" : "p1";
controller.drawUpperTribeCard(wrong, 0);
assertEquals("It's not your turn!", view.lastError);
assertNull(client.lastMethodCalled);
}
@Test
void testSlotChoiceDelegatesCorrectly() {
Game game = createGame();
controller.setModel(game);
String current = game.getCurrentState().getCurrentPlayer().getUserName();
controller.slotChoice(current, 1);
assertEquals("slotChoice", client.lastMethodCalled);
assertEquals(current, client.lastUsername);
assertEquals(1, client.lastPos);
assertNull(view.lastError);
}
private Game createGame() {
Game game = new Game(2);
game.addPlayer(new Player("p1"));
game.addPlayer(new Player("p2"));
return game;
}
private static class FakeView implements IView {
Game model;
String lastError;
@Override public void setModel(Game game) { this.model = game; }
@Override public void render() {}
@Override public void showMessage(String message) {}
@Override public void showError(String message) { this.lastError = message; }
}
private static class FakeClient implements IClient {
String lastMethodCalled;
String lastUsername;
int lastPos;
@Override public boolean connect(String username, int preferredInt) { return true; }
@Override public void drawUpperTribeCard(String u, int pos) throws RemoteException { lastMethodCalled = "drawUpperTribeCard"; lastUsername = u; lastPos = pos; }
@Override public void drawLowerTribeCard(String u, int pos) throws RemoteException { lastMethodCalled = "drawLowerTribeCard"; lastUsername = u; lastPos = pos; }
@Override public void drawUpperBuildingCard(String u, int pos) throws RemoteException { lastMethodCalled = "drawUpperBuildingCard"; lastUsername = u; lastPos = pos; }
@Override public void drawLowerBuildingCard(String u, int pos) throws RemoteException { lastMethodCalled = "drawLowerBuildingCard"; lastUsername = u; lastPos = pos; }
@Override public void skipTurn(String u) throws RemoteException { lastMethodCalled = "skipTurn"; lastUsername = u; }
@Override public void pickOptionalTribeCard(String u, int pos) throws RemoteException { lastMethodCalled = "pickOptionalTribeCard"; lastUsername = u; lastPos = pos; }
@Override public void pickOptionalBuildingCard(String u, int pos) throws RemoteException { lastMethodCalled = "pickOptionalBuildingCard"; lastUsername = u; lastPos = pos; }
@Override public void noOptionalCard(String u) throws RemoteException { lastMethodCalled = "noOptionalCard"; lastUsername = u; }
@Override public void slotChoice(String u, int pos) throws RemoteException { lastMethodCalled = "slotChoice"; lastUsername = u; lastPos = pos; }
}
}