Fix: full FA refactor

This commit is contained in:
2026-06-14 13:49:54 +02:00
parent 3888529b96
commit 6c070e1ce5
5 changed files with 37 additions and 25 deletions
@@ -210,7 +210,7 @@ public class GameController {
* Ends the game due to forfeit: all remaining players are absent, * Ends the game due to forfeit: all remaining players are absent,
* so the game is terminated and final scores are computed. * so the game is terminated and final scores are computed.
*/ */
public synchronized void endGameForFeit() { public synchronized void endGameForfeit() {
model.endGameForFeit(); model.endGameForfeit();
} }
} }
@@ -29,7 +29,7 @@ import java.util.stream.Collectors;
* </ul> * </ul>
* *
* <p>This class is not thread-safe by itself: it relies on the caller * <p>This class is not thread-safe by itself: it relies on the caller
* (the game loop in {@code ServerLauncherTest}) to drive it from a single * (the game loop in {@code ServerLauncher}) to drive it from a single
* thread via {@link #doFirstEvent()}. * thread via {@link #doFirstEvent()}.
*/ */
public class GameEventProcessor { public class GameEventProcessor {
@@ -40,9 +40,13 @@ public class GameEventProcessor {
private final CompositeClientBroadcaster broadcaster; private final CompositeClientBroadcaster broadcaster;
private final SaveManager saveManager; private final SaveManager saveManager;
/** Single-thread executor used exclusively for the forfeit timer. */ /** Single-thread executor used exclusively for the forfeit timer. Daemon so it does not block JVM shutdown. */
private final ScheduledExecutorService timerExecutor = private final ScheduledExecutorService timerExecutor =
Executors.newSingleThreadScheduledExecutor(); Executors.newSingleThreadScheduledExecutor(r -> {
Thread t = new Thread(r, "forfeit-timer");
t.setDaemon(true);
return t;
});
/** /**
* Handle to the running forfeit timer, or {@code null} when no timer is active. * Handle to the running forfeit timer, or {@code null} when no timer is active.
@@ -267,7 +271,7 @@ public class GameEventProcessor {
} }
disconnectionTimer = timerExecutor.schedule( disconnectionTimer = timerExecutor.schedule(
() -> endGameForFeit(game), () -> endGameForfeit(game),
1, TimeUnit.MINUTES 1, TimeUnit.MINUTES
); );
} }
@@ -278,9 +282,9 @@ public class GameEventProcessor {
* *
* @param game the game model captured when the timer was scheduled. * @param game the game model captured when the timer was scheduled.
*/ */
private void endGameForFeit(Game game) { private void endGameForfeit(Game game) {
synchronized (gameController) { synchronized (gameController) {
gameController.endGameForFeit(); gameController.endGameForfeit();
EndedGame forfeitEnd = new EndedGame( EndedGame forfeitEnd = new EndedGame(
game.getSlotMap(), game.getOrderLogicCard(), game.getSlotMap(), game.getOrderLogicCard(),
game.getCurrentState(), game.getPlayerStanding() game.getCurrentState(), game.getPlayerStanding()
@@ -363,7 +367,7 @@ public class GameEventProcessor {
List<String> toRemove = playerList.entrySet().stream() List<String> toRemove = playerList.entrySet().stream()
.filter(e -> !e.getValue()) .filter(e -> !e.getValue())
.map(Map.Entry::getKey) .map(Map.Entry::getKey)
.collect(Collectors.toList()); .toList();
toRemove.forEach(playerList::remove); toRemove.forEach(playerList::remove);
} }
} }
@@ -1,15 +1,14 @@
package it.polimi.ingsw.gc14; package it.polimi.ingsw.gc14;
import java.util.Collection; import java.util.Collection;
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; import java.util.concurrent.ConcurrentHashMap;
/** /**
* A {@link LinkedHashMap} with a configurable size limit and an associated action. * A {@link ConcurrentHashMap}-backed map 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. * When the number of elements reaches or exceeds the limit, the specified action is automatically triggered.
* This implementation is thread-safe. * This implementation is thread-safe for {@code put}; callers that need compound operations must synchronize externally.
* *
* @param <K> the type of keys maintained by this map. * @param <K> the type of keys maintained by this map.
* @param <V> the type of mapped values. * @param <V> the type of mapped values.
@@ -63,7 +62,7 @@ public class LimitedMap<K, V> implements Map<K, V> {
} }
@Override @Override
public V remove(Object key) { return map.remove(key); } public synchronized V remove(Object key) { return map.remove(key); }
@Override @Override
public V get(Object key) { return map.get(key); } public V get(Object key) { return map.get(key); }
@@ -112,7 +111,10 @@ public class LimitedMap<K, V> implements Map<K, V> {
/** /**
* Sets a new action to execute when the map size reaches or exceeds the limit. * Sets a new action to execute when the map size reaches or exceeds the limit.
* *
* @param action the new action to set. * @param action the new action to set; must not be {@code null}.
*/ */
public void setAction(Runnable action) { this.action = action; } public void setAction(Runnable action) {
if (action == null) throw new IllegalArgumentException("action must not be null");
this.action = action;
}
} }
@@ -1006,7 +1006,7 @@ public class Game implements Serializable {
* in the first position of the final ranking. The remaining players are * in the first position of the final ranking. The remaining players are
* ordered by prestige value and, in case of a tie, by food value. * ordered by prestige value and, in case of a tie, by food value.
*/ */
public synchronized void endGameForFeit() { public synchronized void endGameForfeit() {
Player winner = playersList.stream() Player winner = playersList.stream()
.filter(x -> !disconnectedPlayers.containsKey(x) || !disconnectedPlayers.get(x)) .filter(x -> !disconnectedPlayers.containsKey(x) || !disconnectedPlayers.get(x))
.findFirst().orElse(null); .findFirst().orElse(null);
@@ -2,7 +2,14 @@ package it.polimi.ingsw.gc14;
import it.polimi.ingsw.gc14.Model.Game; import it.polimi.ingsw.gc14.Model.Game;
import java.io.*; import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@@ -55,8 +62,8 @@ public class SaveManager {
public boolean save(Game game) { public boolean save(Game game) {
try { try {
Files.createDirectories(filePath.getParent()); Files.createDirectories(filePath.getParent());
try (ObjectOutputStream oos = try (ObjectOutputStream oos = new ObjectOutputStream(
new ObjectOutputStream(new FileOutputStream(filePath.toFile()))) { new BufferedOutputStream(new FileOutputStream(filePath.toFile())))) {
oos.writeObject(game); oos.writeObject(game);
System.out.println("Game saved to: " + filePath.toAbsolutePath()); System.out.println("Game saved to: " + filePath.toAbsolutePath());
return true; return true;
@@ -76,8 +83,8 @@ public class SaveManager {
* the classpath (indicates a deployment mismatch). * the classpath (indicates a deployment mismatch).
*/ */
public Game load() { public Game load() {
try (ObjectInputStream ois = try (ObjectInputStream ois = new ObjectInputStream(
new ObjectInputStream(new FileInputStream(filePath.toFile()))) { new BufferedInputStream(new FileInputStream(filePath.toFile())))) {
return (Game) ois.readObject(); return (Game) ois.readObject();
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
return null; return null;
@@ -97,8 +104,7 @@ public class SaveManager {
*/ */
public boolean delete() { public boolean delete() {
try { try {
Files.delete(filePath); return Files.deleteIfExists(filePath);
return true;
} catch (IOException e) { } catch (IOException e) {
return false; return false;
} }