Observer pattern & Archipelago
This commit is contained in:
@@ -1,11 +1,16 @@
|
||||
package com.bernard.nodecames;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -21,6 +26,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.servlet.view.RedirectView;
|
||||
|
||||
import com.bernard.nodecames.frontend.GridConfigEdit;
|
||||
import com.bernard.nodecames.game.ArchipelagoManager;
|
||||
import com.bernard.nodecames.game.GameManager;
|
||||
import com.bernard.nodecames.game.GridManager;
|
||||
import com.bernard.nodecames.game.IllegalGameActionException;
|
||||
@@ -33,6 +39,7 @@ import com.bernard.nodecames.repository.GameRepository;
|
||||
import com.bernard.nodecames.repository.GridRepository;
|
||||
import com.bernard.nodecames.repository.WorldRepository;
|
||||
|
||||
import io.github.archipelagomw.network.ConnectionResult;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@@ -41,6 +48,7 @@ public class HttpController {
|
||||
|
||||
private final GridManager gm;
|
||||
private final GameManager wm;
|
||||
private final ArchipelagoManager am;
|
||||
private final GridRepository gridRepository;
|
||||
private final GameRepository gRepository;
|
||||
private final WorldRepository wRepository;
|
||||
@@ -78,6 +86,7 @@ public class HttpController {
|
||||
model.addAttribute("gridConfig", GridConfigEdit.of(gc));
|
||||
return "grid-config";
|
||||
}
|
||||
|
||||
@PostMapping("/room/{id}/config")
|
||||
public Object setGridConfig(@PathVariable("id") String id,
|
||||
@ModelAttribute("gridConfig") GridConfigEdit gce,
|
||||
@@ -138,6 +147,38 @@ public class HttpController {
|
||||
return "map";
|
||||
}
|
||||
|
||||
@GetMapping("/game/{id}/archipelago")
|
||||
public Object gridConfig(@PathVariable("id") String id, @RequestParam("address") String address,
|
||||
@RequestParam("slot") String slot, Model model) {
|
||||
|
||||
Optional<Game> g = gRepository.findById(UUID.fromString(id));
|
||||
if(!g.isPresent())
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
|
||||
try {
|
||||
CompletableFuture<ConnectionResult> connectionResultFuture = am.openClient(g.get(), address, slot);
|
||||
|
||||
ConnectionResult connectionResult = connectionResultFuture.get(5, TimeUnit.SECONDS);
|
||||
|
||||
if (connectionResult != ConnectionResult.Success) {
|
||||
return new ResponseEntity<>("Could not connect to archipelago server: "+connectionResult.name(), HttpStatus.NOT_FOUND);
|
||||
} else {
|
||||
return new ResponseEntity<>("Connection successful", HttpStatus.OK);
|
||||
}
|
||||
} catch (TimeoutException e) {
|
||||
return new ResponseEntity<>("Timed out connecting to archipelago", HttpStatus.REQUEST_TIMEOUT);
|
||||
} catch (URISyntaxException e) {
|
||||
return new ResponseEntity<>("URI malformed : "+e.getMessage(), HttpStatus.BAD_REQUEST);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
Thread.currentThread().interrupt();
|
||||
return new ResponseEntity<>("Interrupted", HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
} catch (ExecutionException e) {
|
||||
e.printStackTrace();
|
||||
return new ResponseEntity<>("ExecutionException while connecting to Archipelago : "+e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/game/{id}/grid/{world}")
|
||||
public Object gridRedirect(@PathVariable("id") String id, @PathVariable("world") String world, @RequestParam("player") String player) {
|
||||
Optional<Game> g = gRepository.findById(UUID.fromString(id));
|
||||
@@ -148,7 +189,12 @@ public class HttpController {
|
||||
if(!w.isPresent()) {
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
Grid grid = wm.getGrid(g.get(), w.get());
|
||||
Grid grid;
|
||||
try {
|
||||
grid = wm.getGrid(g.get(), w.get());
|
||||
} catch (IllegalGameActionException e) {
|
||||
return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
return new RedirectView("/room/"+grid.getId()+"/grid" + "?player="+player);
|
||||
}
|
||||
|
||||
@@ -162,7 +208,11 @@ public class HttpController {
|
||||
if(!w.isPresent()) {
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
Grid newGrid = wm.resetGrid(g.get(), w.get());
|
||||
return new RedirectView("/room/"+newGrid.getId()+"/grid" + "?player="+player);
|
||||
try {
|
||||
Grid newGrid = wm.resetGrid(g.get(), w.get());
|
||||
return new RedirectView("/room/"+newGrid.getId()+"/grid" + "?player="+player);
|
||||
} catch (IllegalGameActionException e) {
|
||||
return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.bernard.nodecames.frontend;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -17,14 +19,20 @@ import org.springframework.web.bind.annotation.PathVariable;
|
||||
import com.bernard.nodecames.game.GridManager;
|
||||
import com.bernard.nodecames.game.IllegalGameActionException;
|
||||
import com.bernard.nodecames.model.Card;
|
||||
import com.bernard.nodecames.model.Card.Color;
|
||||
import com.bernard.nodecames.model.GameEvent;
|
||||
import com.bernard.nodecames.model.Grid;
|
||||
import com.bernard.nodecames.model.Grid.Phase;
|
||||
import com.bernard.nodecames.model.GridConfig.MutableOption;
|
||||
import com.bernard.nodecames.model.Hint;
|
||||
import com.bernard.nodecames.observer.GridObserver;
|
||||
import com.bernard.nodecames.repository.GridRepository;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import tools.jackson.databind.JsonNode;
|
||||
|
||||
@Controller
|
||||
@AllArgsConstructor
|
||||
public class WebSocketController {
|
||||
|
||||
private final GridManager gm;
|
||||
@@ -32,49 +40,19 @@ public class WebSocketController {
|
||||
private final JsonDataService json;
|
||||
private final GridRepository gRepository;
|
||||
|
||||
public WebSocketController(GridManager gm, SimpMessagingTemplate template, JsonDataService json, GridRepository gRepository) {
|
||||
this.gm = gm;
|
||||
this.template = template;
|
||||
this.json = json;
|
||||
this.gRepository = gRepository;
|
||||
gm.setWsc(this);
|
||||
}
|
||||
|
||||
public void onNewEvent(Grid g, GameEvent ge) {
|
||||
this.template.convertAndSend("/topic/new-event",
|
||||
json.eventData(ge)
|
||||
);
|
||||
}
|
||||
|
||||
public void onNewPhase(Grid g) {
|
||||
this.template.convertAndSend(
|
||||
"/topic/new-phase",
|
||||
json.phaseData(g.getPhase())
|
||||
);
|
||||
}
|
||||
|
||||
public void onConfigChange(Grid g, MutableOption cc, Object data) {
|
||||
this.template.convertAndSend("/topic/config-change",
|
||||
json.gridConfigUpdateData(cc, data)
|
||||
);
|
||||
}
|
||||
|
||||
public void publishCardUpdateToPlayer(Grid g, Card c, char player) {
|
||||
this.template.convertAndSend(
|
||||
"/topic/update-card/" + Character.toLowerCase(player),
|
||||
json.cardData(c, player),
|
||||
Map.of("cardIndex", c.getIndex())
|
||||
);
|
||||
}
|
||||
public void publishCardUpdate(Grid g, Card card) {
|
||||
publishCardUpdateToPlayer(g, card, 'A');
|
||||
publishCardUpdateToPlayer(g, card, 'B');
|
||||
Set<UUID> observed = new HashSet<>();
|
||||
private void registerObserver(Grid g) {
|
||||
if(!observed.contains(g.getId())) {
|
||||
observed.add(g.getId());
|
||||
gm.addObserver(g, new GObserver(g));
|
||||
}
|
||||
}
|
||||
|
||||
public void publishError(char issuer, IllegalGameActionException e) {
|
||||
//TODO open error channel
|
||||
}
|
||||
|
||||
//TODO We should do this with websocket protocol
|
||||
@GetMapping("/room/{id}/game/{player}")
|
||||
public Object grid(@PathVariable("id") String gridId, @PathVariable("player") String playerStr) {
|
||||
char player;
|
||||
@@ -86,14 +64,19 @@ public class WebSocketController {
|
||||
throw new IllegalArgumentException("Unknown player type");
|
||||
|
||||
Optional<Grid> g = gRepository.findById(UUID.fromString(gridId));
|
||||
if(g.isPresent())
|
||||
return new ResponseEntity<>(json.gridData(g.get(), player), HttpStatus.OK);
|
||||
else
|
||||
if(g.isPresent()){
|
||||
Grid grid = g.get();
|
||||
registerObserver(grid);
|
||||
return new ResponseEntity<>(json.gridData(grid, player), HttpStatus.OK);
|
||||
} else
|
||||
return new ResponseEntity<>("Could not find grid with given ID",HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
@MessageMapping("submit-hint")
|
||||
public void submitHint(@Header("room") String roomId, @Header("player") String player, @Payload JsonNode content) {
|
||||
//TODO handle websockets per grid
|
||||
@MessageMapping("/submit-hint")
|
||||
public void submitHint(@Header("room") String roomId,
|
||||
@Header("player") String player,
|
||||
@Payload JsonNode content) {
|
||||
String hint = content.asObject().get("hint").stringValue();
|
||||
int wordCount = content.asObject().get("hintWordCount").intValue();
|
||||
|
||||
@@ -101,7 +84,6 @@ public class WebSocketController {
|
||||
if(!g.isPresent())
|
||||
// 404
|
||||
return;
|
||||
|
||||
try {
|
||||
gm.proposeHint(g.get(), player.charAt(0), hint, wordCount);
|
||||
} catch (IllegalGameActionException e) {
|
||||
@@ -118,10 +100,8 @@ public class WebSocketController {
|
||||
if(!g.isPresent())
|
||||
// 404
|
||||
return;
|
||||
|
||||
try {
|
||||
gm.pointCard(g.get(), player.charAt(0), cardIndex);
|
||||
publishCardUpdate(g.get(), gm.getCard(g.get(), cardIndex));
|
||||
} catch (IllegalGameActionException e) {
|
||||
publishError(player.charAt(0), e);
|
||||
e.printStackTrace();
|
||||
@@ -134,7 +114,6 @@ public class WebSocketController {
|
||||
if(!g.isPresent())
|
||||
// 404
|
||||
return;
|
||||
|
||||
try {
|
||||
gm.endGuessing(g.get(), player.charAt(0));
|
||||
} catch (IllegalGameActionException e) {
|
||||
@@ -156,4 +135,56 @@ public class WebSocketController {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
private class GObserver implements GridObserver {
|
||||
Grid grid;
|
||||
|
||||
public void publishCardUpdateToPlayer(Card c, char player) {
|
||||
template.convertAndSend(
|
||||
"/topic/%s/update-card/%s".formatted(grid.getId(), Character.toLowerCase(player)),
|
||||
json.cardData(c, player),
|
||||
Map.of("cardIndex", c.getIndex())
|
||||
);
|
||||
}
|
||||
public void publishCardUpdate(Card card) {
|
||||
publishCardUpdateToPlayer(card, 'A');
|
||||
publishCardUpdateToPlayer(card, 'B');
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPointCard(char player, Card card, Color color) {
|
||||
publishCardUpdate(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSubmitHint(char player, Hint hint) {}
|
||||
|
||||
@Override
|
||||
public void onGridEnd(boolean won) {}
|
||||
|
||||
@Override
|
||||
public void onGameEvent(GameEvent ge) {
|
||||
template.convertAndSend(
|
||||
"/topic/%s/new-event".formatted(grid.getId()),
|
||||
json.eventData(ge)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewPhase(Phase p) {
|
||||
template.convertAndSend(
|
||||
"/topic/%s/new-phase".formatted(grid.getId()),
|
||||
json.phaseData(p)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigChange(MutableOption cc, Object data) {
|
||||
template.convertAndSend(
|
||||
"/topic/%s/config-change".formatted(grid.getId()),
|
||||
json.gridConfigUpdateData(cc, data)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
package com.bernard.nodecames.game;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.bernard.nodecames.model.Game;
|
||||
import com.bernard.nodecames.model.GridConfig.MutableOption;
|
||||
|
||||
import io.github.archipelagomw.APResult.ResultCode;
|
||||
import io.github.archipelagomw.Client;
|
||||
import io.github.archipelagomw.ClientStatus;
|
||||
import io.github.archipelagomw.events.ArchipelagoEventListener;
|
||||
import io.github.archipelagomw.events.ConnectionResultEvent;
|
||||
import io.github.archipelagomw.events.ReceiveItemEvent;
|
||||
import io.github.archipelagomw.flags.ItemsHandling;
|
||||
import io.github.archipelagomw.network.ConnectionResult;
|
||||
import lombok.Getter;
|
||||
|
||||
public class ArchipelagoClient extends Client {
|
||||
Game game;
|
||||
GameManager gm;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ArchipelagoClient.class);
|
||||
|
||||
public ArchipelagoClient(Game game, GameManager gm, String slot) {
|
||||
super();
|
||||
this.gm = gm;
|
||||
this.game = game;
|
||||
this.setGame("nodecames");
|
||||
this.setName(slot);
|
||||
client.setItemsHandlingFlags(ItemsHandling.SEND_ITEMS | ItemsHandling.SEND_OWN_ITEMS);
|
||||
this.getEventManager().registerListener(this);
|
||||
}
|
||||
|
||||
@ArchipelagoEventListener
|
||||
public void onReceiveItem(ReceiveItemEvent e) throws IllegalGameActionException {
|
||||
log.debug("Received item : {}", e.getItemName());
|
||||
switch(e.getItemName()) {
|
||||
case "progressive_hint_count":
|
||||
gm.unlockMoreHintCount(this.game);
|
||||
break;
|
||||
case "progressive_hint_word_length":
|
||||
gm.unlockMoreHintWordLengthCount(this.game);
|
||||
break;
|
||||
case "progressive_hint_word_count":
|
||||
gm.unlockMoreHintWordCount(this.game);
|
||||
break;
|
||||
case "zero_hint":
|
||||
gm.unlockHintWordCount(this.game, 0);
|
||||
break;
|
||||
case "infinity_hint":
|
||||
gm.unlockHintWordCount(this.game, -1);
|
||||
break;
|
||||
case "one_more_guess":
|
||||
gm.unlockMutable(game, MutableOption.ONE_MORE_GUESS, true);
|
||||
break;
|
||||
case "end_guessing":
|
||||
gm.unlockMutable(game, MutableOption.END_GUESSING, true);
|
||||
break;
|
||||
case "only_one_green_green":
|
||||
gm.unlockMutable(game, MutableOption.ONLY_ONE_GREEN_GREEN, true);
|
||||
break;
|
||||
case "sudden_death":
|
||||
gm.unlockMutable(game, MutableOption.SUDDEN_DEATH, true);
|
||||
break;
|
||||
case "structured_grid":
|
||||
gm.unlockStructuredGrid(game);
|
||||
break;
|
||||
case "hints_in_a_row":
|
||||
gm.unlockMutable(game, MutableOption.HINTS_IN_A_ROW, true);
|
||||
break;
|
||||
case "anyone_starts":
|
||||
gm.unlockAnyoneCanStart(game);
|
||||
break;
|
||||
case "filler":
|
||||
// We do nothing if we receive a filler item
|
||||
break;
|
||||
default:
|
||||
if (e.getItemName().startsWith("unlock_world_")) {
|
||||
int worldIndex = Integer.parseInt(e.getItemName().substring("unlock_world_".length()))-1;
|
||||
String worldName = "%d-%d".formatted((worldIndex/3)+1, (worldIndex%3)+1);
|
||||
gm.unlockWorld(game, worldName);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Received unknown item : "+e.getItemName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
private CompletableFuture<ConnectionResult> connectionResult = new CompletableFuture<>();
|
||||
|
||||
@ArchipelagoEventListener
|
||||
public void onConnectResult(ConnectionResultEvent e) {
|
||||
connectionResult.complete(e.getResult());
|
||||
if(e.getResult() != ConnectionResult.Success) {
|
||||
throw new IllegalStateException("Could not connect to archipelago : %s".formatted(e.getResult().name()));
|
||||
} else {
|
||||
if(this.setGameState(ClientStatus.CLIENT_PLAYING).getCode() != ResultCode.SUCCESS)
|
||||
throw new IllegalStateException("Colud not set state to playing");
|
||||
else {
|
||||
client.sendChat("Hello, here is Nodecames AP client, nice to meet you");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int[] GREEN_COUNT_PER_WORLD = new int[]{6, 9, 9, 12, 16, 16, 20, 25, 25, 30, 36, 36};
|
||||
public static String[] WORLDS = new String[]{"1-1","1-2","1-3","2-1","2-2","2-3","3-1","3-2","3-3","4-1","4-2","4-3"};
|
||||
|
||||
public void checkWordInWorld(String worldName, int wordIndex) {
|
||||
//TODO binarySearch is okay for now, but we'll have to remove this at some point
|
||||
int worldIndex = Arrays.binarySearch(WORLDS, 0, WORLDS.length, worldName);
|
||||
if(wordIndex>GREEN_COUNT_PER_WORLD[wordIndex]-1)return;//We don't have that many checks
|
||||
//TODO this should be stored, not computed, in archipelagoconfig
|
||||
long locationId = IntStream.range(0, worldIndex).map(w -> GREEN_COUNT_PER_WORLD[w]).sum()+1+wordIndex;
|
||||
this.checkLocation(locationId);
|
||||
}
|
||||
public void checkWorldComplete(String worldName) {
|
||||
//TODO binarySearch is okay for now, but we'll have to remove this at some point
|
||||
int worldIndex = Arrays.binarySearch(WORLDS, 0, WORLDS.length, worldName);
|
||||
long locationId = IntStream.range(0, worldIndex+1).map(w -> GREEN_COUNT_PER_WORLD[w]).sum()+1;
|
||||
this.checkLocation(locationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception e) {
|
||||
//TODO make a real handler
|
||||
throw new IllegalStateException("Archipelago error :", e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(String Reason, int attemptingReconnect) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'onClose'");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.bernard.nodecames.game;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.bernard.nodecames.model.Card;
|
||||
import com.bernard.nodecames.model.Card.Color;
|
||||
import com.bernard.nodecames.model.Game;
|
||||
import com.bernard.nodecames.model.GameEvent;
|
||||
import com.bernard.nodecames.model.Grid.Phase;
|
||||
import com.bernard.nodecames.model.GridConfig.MutableOption;
|
||||
import com.bernard.nodecames.model.Hint;
|
||||
import com.bernard.nodecames.model.World;
|
||||
import com.bernard.nodecames.observer.WorldObserver;
|
||||
import com.bernard.nodecames.repository.CardRepository;
|
||||
import com.bernard.nodecames.repository.WorldRepository;
|
||||
|
||||
import io.github.archipelagomw.network.ConnectionResult;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class ArchipelagoManager {
|
||||
|
||||
Map<UUID, ArchipelagoClient> clients = new HashMap<>();
|
||||
|
||||
GameManager gm;
|
||||
CardRepository cRepository;
|
||||
WorldRepository wRepository;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ArchipelagoManager.class);
|
||||
|
||||
public CompletableFuture<ConnectionResult> openClient(Game g, String address, String slot) throws URISyntaxException{
|
||||
ArchipelagoClient client = new ArchipelagoClient(g, gm, slot);
|
||||
|
||||
client.connect(address);
|
||||
clients.put(g.getId(), client);
|
||||
|
||||
Set<World> worlds = wRepository.findByGame(g);
|
||||
log.debug("Registering observer for {} worlds", worlds.size());
|
||||
for(World w : worlds) {
|
||||
gm.addObserver(w, new WObserver(w, client));
|
||||
}
|
||||
return client.getConnectionResult();
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
private class WObserver implements WorldObserver {
|
||||
private World world;
|
||||
ArchipelagoClient client;
|
||||
|
||||
@Override
|
||||
public void onPointCard(char player, Card card, Color color) {
|
||||
// We compute the count of green cards
|
||||
//TODO right now, we only do the strategy of green green cards once
|
||||
//TODO decide wether we or they should have the logic of wether to send the ith check
|
||||
Card[] cards = cRepository.findByGridOrderById_Index(world.getCurrentGrid());
|
||||
int greenCount = (int)Arrays.stream(cards).filter(c ->
|
||||
(c.isColorAPublic() && c.getColorA() == Color.GREEN) ||
|
||||
(c.isColorBPublic() && c.getColorB() == Color.GREEN)
|
||||
).count();
|
||||
log.info("Pointed card, {}th", greenCount);
|
||||
client.checkWordInWorld(world.getName(), greenCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGridEnd(boolean win) {
|
||||
if(win) {
|
||||
client.checkWorldComplete(world.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSubmitHint(char player, Hint hint) {}
|
||||
@Override
|
||||
public void onGameEvent(GameEvent ge) {}
|
||||
@Override
|
||||
public void onNewPhase(Phase p) {}
|
||||
@Override
|
||||
public void onConfigChange(MutableOption cc, Object data) {}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,38 +2,75 @@ package com.bernard.nodecames.game;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.bernard.nodecames.model.Card;
|
||||
import com.bernard.nodecames.model.Card.Color;
|
||||
import com.bernard.nodecames.model.Game;
|
||||
import com.bernard.nodecames.model.GameEvent;
|
||||
import com.bernard.nodecames.model.Grid;
|
||||
import com.bernard.nodecames.model.Grid.Phase;
|
||||
import com.bernard.nodecames.model.GridConfig;
|
||||
import com.bernard.nodecames.model.GridConfig.MutableOption;
|
||||
import com.bernard.nodecames.model.Hint;
|
||||
import com.bernard.nodecames.model.World;
|
||||
import com.bernard.nodecames.model.World.WorldId;
|
||||
import com.bernard.nodecames.observer.GridObserver;
|
||||
import com.bernard.nodecames.observer.WorldObserver;
|
||||
import com.bernard.nodecames.repository.GameRepository;
|
||||
import com.bernard.nodecames.repository.WorldRepository;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class GameManager {
|
||||
|
||||
// Reverse map allowing to pass grid events to the game
|
||||
Map<UUID, World> currentGrids = new HashMap<>();
|
||||
|
||||
private final GridManager gm;
|
||||
private final GameRepository gRepository;
|
||||
private final WorldRepository wRepository;
|
||||
|
||||
public GameManager(GridManager gm, GameRepository gRepository, WorldRepository wRepository) {
|
||||
this.gm = gm;
|
||||
this.gRepository = gRepository;
|
||||
this.wRepository = wRepository;
|
||||
gm.setWm(this);
|
||||
//TODO try putting "World" here instead of WorldId
|
||||
private Map<WorldId, Set<WorldObserver>> observers = new HashMap<>();
|
||||
|
||||
public void addObserver(World w, WorldObserver observer) {
|
||||
if(!observers.containsKey(w.getId()))
|
||||
observers.put(w.getId(), new CopyOnWriteArraySet<>());
|
||||
observers.get(w.getId()).add(observer);
|
||||
if(w.getCurrentGrid() != null)
|
||||
gm.addObserver(w.getCurrentGrid(), observer);
|
||||
}
|
||||
|
||||
private void forEachObserver(World w, Consumer<WorldObserver> action) {
|
||||
observers.getOrDefault(w.getId(), Set.of()).forEach(action);
|
||||
}
|
||||
|
||||
/*
|
||||
* In case of server restart, registers to GridManager observers
|
||||
*/
|
||||
@PostConstruct
|
||||
private void registerRunningObservers() {
|
||||
for(World w : wRepository.findAll()) {
|
||||
if(w.getCurrentGrid() != null && w.getCurrentGrid().getWon() != null) {
|
||||
registerObserver(w, w.getCurrentGrid());
|
||||
}
|
||||
}
|
||||
}
|
||||
private void registerObserver(World w, Grid g) {
|
||||
gm.addObserver(g, new GObserver(g, w));
|
||||
forEachObserver(w, o -> gm.addObserver(g, o));
|
||||
}
|
||||
|
||||
public Game createGame(String name) {
|
||||
@@ -42,10 +79,9 @@ public class GameManager {
|
||||
Game g = new Game(uuid, name);
|
||||
gRepository.save(g);
|
||||
for (Entry<String, GridConfig> wConfig : Game.DEFAULT_WORLDS_CONFIG.entrySet()) {
|
||||
World w = new World(g, wConfig.getKey(), wConfig.getValue(), null, false);
|
||||
World w = new World(g, wConfig.getKey(), wConfig.getValue(), null, false, true);
|
||||
wRepository.save(w);
|
||||
}
|
||||
|
||||
return g;
|
||||
}
|
||||
|
||||
@@ -57,22 +93,26 @@ public class GameManager {
|
||||
return wRepository.findByGame(g);
|
||||
}
|
||||
|
||||
private Grid newGrid(Game g, World w) {
|
||||
private Grid newGrid(Game g, World w) throws IllegalGameActionException {
|
||||
if(w.isLocked())
|
||||
//TODO separate IllegaleGameAction and IllegalGridAction
|
||||
throw new IllegalGameActionException(null, "Cannot create a grid in a locked world");
|
||||
GridConfig gc = w.getInitialConfig();
|
||||
//TODO seed random
|
||||
Grid grid = gm.newGrid(w, gc, new Random());
|
||||
|
||||
w.setCurrentGrid(grid);
|
||||
wRepository.save(w);
|
||||
currentGrids.put(grid.getId(), w);
|
||||
registerObserver(w, grid);
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
public Grid resetGrid(Game g, World world) {
|
||||
public Grid resetGrid(Game g, World world) throws IllegalGameActionException{
|
||||
return newGrid(g, world);
|
||||
}
|
||||
|
||||
public Grid getGrid(Game g, World w) {
|
||||
public Grid getGrid(Game g, World w) throws IllegalGameActionException{
|
||||
if(w.getCurrentGrid() != null) {
|
||||
return w.getCurrentGrid();
|
||||
} else {
|
||||
@@ -80,11 +120,100 @@ public class GameManager {
|
||||
}
|
||||
}
|
||||
|
||||
public void onGameWon(Grid g) {
|
||||
World w = currentGrids.get(g.getId());
|
||||
if(w != null) {
|
||||
w.setWon(true);
|
||||
/**
|
||||
* UPDATE GAME CONFIG
|
||||
*/
|
||||
public void unlockMutable(Game g, MutableOption opt, Object o) throws IllegalGameActionException {
|
||||
Set<World> worlds = wRepository.findByGame(g);
|
||||
for(World w : worlds) {
|
||||
GridConfig initialConfig = w.getInitialConfig();
|
||||
initialConfig.set(opt, o);
|
||||
w.setInitialConfig(initialConfig);
|
||||
wRepository.save(w);
|
||||
Grid grid = w.getCurrentGrid();
|
||||
if(grid != null) {
|
||||
gm.unlock(grid, opt, o);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void unlockStructuredGrid(Game g) {
|
||||
Set<World> worlds = wRepository.findByGame(g);
|
||||
for(World w : worlds) {
|
||||
GridConfig initialConfig = w.getInitialConfig();
|
||||
initialConfig.setStructuredGrid(true);
|
||||
w.setInitialConfig(initialConfig);
|
||||
wRepository.save(w);
|
||||
}
|
||||
}
|
||||
public void unlockAnyoneCanStart(Game g) {
|
||||
Set<World> worlds = wRepository.findByGame(g);
|
||||
for(World w : worlds) {
|
||||
GridConfig initialConfig = w.getInitialConfig();
|
||||
initialConfig.setAnyoneStarts(true);
|
||||
w.setInitialConfig(initialConfig);
|
||||
wRepository.save(w);
|
||||
}
|
||||
}
|
||||
|
||||
public void unlockWorld(Game g, String name) throws IllegalGameActionException {
|
||||
Optional<World> ow = wRepository.findByGameAndId_Name(g, name);
|
||||
if(!ow.isPresent())
|
||||
throw new IllegalGameActionException(null, "Could not find world with name %s".formatted(name));
|
||||
World w = ow.get();
|
||||
w.setLocked(false);
|
||||
wRepository.save(w);
|
||||
}
|
||||
|
||||
public void unlockMoreHintCount(Game game) throws IllegalGameActionException {
|
||||
//TODO Store current hint count elsewhere
|
||||
int currentHintCount = wRepository.findByGame(game).stream().findAny().get().getInitialConfig().getMaxHint();
|
||||
unlockMutable(game, MutableOption.MAX_HINT, currentHintCount+1);
|
||||
}
|
||||
|
||||
public void unlockMoreHintWordLengthCount(Game game) throws IllegalGameActionException {
|
||||
//TODO Store current hint word length elsewhere
|
||||
int currentHintWordLength = wRepository.findByGame(game).stream().findAny().get().getInitialConfig().getHintMaxLength();
|
||||
unlockMutable(game, MutableOption.HINT_MAX_LENGTH, currentHintWordLength+1);
|
||||
}
|
||||
|
||||
public void unlockMoreHintWordCount(Game game) throws IllegalGameActionException {
|
||||
//TODO Store current hint word count elsewhere
|
||||
int currentMaxWordCount = wRepository.findByGame(game).stream().findAny().get().getInitialConfig().getAvailableHintWordCount().stream().mapToInt(i -> i).max().getAsInt();
|
||||
unlockHintWordCount(game, currentMaxWordCount+1);
|
||||
}
|
||||
|
||||
public void unlockHintWordCount(Game game, int i) throws IllegalGameActionException {
|
||||
Set<Integer> currentWordCounts = wRepository.findByGame(game).stream().findAny().get().getInitialConfig().getAvailableHintWordCount();
|
||||
if(currentWordCounts.contains(i))
|
||||
throw new IllegalGameActionException(null, "Word count already unlocked : %d".formatted(i));
|
||||
Set<Integer> newWordCounts = new HashSet<>(currentWordCounts);
|
||||
newWordCounts.add(i);
|
||||
unlockMutable(game, MutableOption.AVAILABLE_HINT_WORD_COUNT, newWordCounts);
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
private class GObserver implements GridObserver {
|
||||
private Grid grid;
|
||||
private World world;
|
||||
|
||||
@Override
|
||||
public void onGridEnd(boolean win) {
|
||||
if(win) {
|
||||
world.setWon(true);
|
||||
wRepository.save(world);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPointCard(char player, Card card, Color color) {}
|
||||
@Override
|
||||
public void onSubmitHint(char player, Hint hint) {}
|
||||
@Override
|
||||
public void onGameEvent(GameEvent ge) {}
|
||||
@Override
|
||||
public void onNewPhase(Phase p) {}
|
||||
@Override
|
||||
public void onConfigChange(MutableOption cc, Object data) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,15 @@ package com.bernard.nodecames.game;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@@ -14,10 +18,9 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.bernard.nodecames.frontend.WebSocketController;
|
||||
import com.bernard.nodecames.model.Card;
|
||||
import com.bernard.nodecames.model.GameEvent;
|
||||
import com.bernard.nodecames.model.Card.Color;
|
||||
import com.bernard.nodecames.model.GameEvent;
|
||||
import com.bernard.nodecames.model.Grid;
|
||||
import com.bernard.nodecames.model.Grid.Phase;
|
||||
import com.bernard.nodecames.model.GridConfig;
|
||||
@@ -25,21 +28,18 @@ import com.bernard.nodecames.model.GridConfig.CardColors;
|
||||
import com.bernard.nodecames.model.GridConfig.MutableOption;
|
||||
import com.bernard.nodecames.model.Hint;
|
||||
import com.bernard.nodecames.model.World;
|
||||
import com.bernard.nodecames.observer.GridObserver;
|
||||
import com.bernard.nodecames.repository.CardRepository;
|
||||
import com.bernard.nodecames.repository.GridRepository;
|
||||
|
||||
import jakarta.annotation.Nullable;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class GridManager {
|
||||
|
||||
@Setter
|
||||
private WebSocketController wsc = null;
|
||||
@Setter
|
||||
private GameManager wm = null;
|
||||
private Map<UUID, Set<GridObserver>> observers = new HashMap<>();
|
||||
|
||||
private final GridRepository gRepository;
|
||||
private final CardRepository cRepository;
|
||||
@@ -48,11 +48,22 @@ public class GridManager {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(GridManager.class);
|
||||
|
||||
public void addObserver(Grid g, GridObserver observer) {
|
||||
if(!observers.containsKey(g.getId()))
|
||||
observers.put(g.getId(), new CopyOnWriteArraySet<>());
|
||||
observers.get(g.getId()).add(observer);
|
||||
}
|
||||
|
||||
private void forEachObserver(Grid g, Consumer<GridObserver> action) {
|
||||
log.debug("The current observers are : {}", observers.get(g.getId()));
|
||||
observers.getOrDefault(g.getId(), Set.of()).forEach(action);
|
||||
}
|
||||
|
||||
private void newGameEvent(Grid g, GameEvent ge) {
|
||||
log.debug("New game event : {}", ge);
|
||||
g.newGameEvent(ge);
|
||||
gRepository.save(g);
|
||||
wsc.onNewEvent(g, ge);
|
||||
forEachObserver(g, o -> o.onGameEvent(ge));
|
||||
}
|
||||
|
||||
private void setPhase(Grid g, Phase phase) {
|
||||
@@ -62,33 +73,40 @@ public class GridManager {
|
||||
g.setCurrentHint(null);
|
||||
}
|
||||
gRepository.save(g);
|
||||
wsc.onNewPhase(g);
|
||||
forEachObserver(g, o -> o.onNewPhase(phase));
|
||||
}
|
||||
private void setWon(Grid g, boolean won) {
|
||||
log.debug("Game won: {}", won);
|
||||
g.setWonOpt(Optional.of(won));
|
||||
gRepository.save(g);
|
||||
forEachObserver(g, o -> o.onGridEnd(won));
|
||||
// Grid will no longer be updated, therefore we stop observing it
|
||||
observers.remove(g.getId());
|
||||
}
|
||||
|
||||
private void setCardPublic(Card c, char player) {
|
||||
private void setCardPointed(Grid g, Card c, char player, Color col) {
|
||||
log.debug("Card set public, index {}", c.getIndex());
|
||||
c.setColorPublic(player, true);
|
||||
cRepository.save(c);
|
||||
forEachObserver(c.getGrid(), o -> o.onPointCard(player, c, col));
|
||||
g.incrementCurrentGuessCount();
|
||||
gRepository.save(g);
|
||||
}
|
||||
|
||||
private void newHint(Grid g, Hint h) {
|
||||
private void newHint(Grid g, char player, Hint h) {
|
||||
log.debug("New hint: {}", h);
|
||||
g.incrementHintCount();
|
||||
g.setCurrentHint(h);
|
||||
g.setCurrentGuessCount(0);
|
||||
gRepository.save(g);
|
||||
forEachObserver(g, o -> o.onSubmitHint(player, h));
|
||||
}
|
||||
|
||||
private void changeConfig(Grid g, MutableOption cc, Object data) {
|
||||
log.debug("Changed config option: {} -> new value: {}", cc.name(), data);
|
||||
g.getConfig().set(cc, data);
|
||||
gRepository.save(g);
|
||||
wsc.onConfigChange(g, cc, data);
|
||||
forEachObserver(g, o -> o.onConfigChange(cc, data));
|
||||
}
|
||||
|
||||
|
||||
@@ -169,12 +187,10 @@ public class GridManager {
|
||||
* GAME ACTIONS *
|
||||
**********************************/
|
||||
private void endGame(Grid g, boolean win) {
|
||||
setWon(g, win);
|
||||
setPhase(g, Phase.GAME_ENDED);
|
||||
newGameEvent(g, GameEvent.newGameEndEvent(win));
|
||||
if(win) {
|
||||
wm.onGameWon(g);
|
||||
}
|
||||
// This must be the last step as it removes observers
|
||||
setWon(g, win);
|
||||
}
|
||||
|
||||
public void proposeHint(Grid g, char player, String hint, int wordCount) throws IllegalGameActionException {
|
||||
@@ -189,7 +205,7 @@ public class GridManager {
|
||||
}
|
||||
|
||||
Hint h = new Hint(hint, wordCount);
|
||||
newHint(g, h);
|
||||
newHint(g, player, h);
|
||||
newGameEvent(g, GameEvent.newHintEvent(h, player));
|
||||
setPhase(g, Phase.guessing(other(player)));
|
||||
}
|
||||
@@ -264,9 +280,9 @@ public class GridManager {
|
||||
|
||||
if(c.isColorPublic(other(player)) || (g.getConfig().isOnlyOneGreenGreen() && c.getColor(player) == Card.Color.GREEN && c.isColorPublic(player)))
|
||||
throw new IllegalGameActionException(g, "Card has already been revealed");
|
||||
setCardPublic(c, other(player));
|
||||
Card.Color guessedColor = c.getColor(other(player));
|
||||
|
||||
setCardPointed(g, c, other(player), guessedColor);
|
||||
|
||||
newGameEvent(g, GameEvent.newGuessEvent(c.getIndex(), player, c.getColor(other(player))));
|
||||
g.incrementCurrentGuessCount();
|
||||
if(guessedColor == Color.BLACK){
|
||||
@@ -369,7 +385,7 @@ public class GridManager {
|
||||
}
|
||||
public void unlockHintCount(Grid g, int newHintCount) throws IllegalGameActionException {
|
||||
if(newHintCount > g.getConfig().getMaxMaxHint()) {
|
||||
throw new IllegalGameActionException(g, "Illegal hint count");
|
||||
throw new IllegalGameActionException(g, "Hint count higher than highest hint count");
|
||||
}
|
||||
if(newHintCount <= g.getConfig().getMaxHint()) {
|
||||
throw new IllegalGameActionException(g, "Cannot lower hint count");
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.bernard.nodecames.model;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.hibernate.annotations.JdbcTypeCode;
|
||||
@@ -10,12 +11,15 @@ import org.hibernate.type.SqlTypes;
|
||||
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Convert;
|
||||
import jakarta.persistence.Embedded;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@@ -21,6 +21,7 @@ import jakarta.persistence.Embeddable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.Value;
|
||||
|
||||
@Getter
|
||||
@@ -50,27 +51,24 @@ public class GridConfig {
|
||||
0,1,2,3,4,5,6,7,8,9,-1
|
||||
);
|
||||
public static final GridConfig BIG_CLASSICAL = new GridConfig(
|
||||
12, true, true, CUSTOM36, true,
|
||||
true, true, 12, BASE_HINTWORDCOUNT, true,
|
||||
12, CUSTOM36, true, true,
|
||||
true, true, true, 12, BASE_HINTWORDCOUNT, true,
|
||||
Integer.MAX_VALUE, true, true,
|
||||
true, false);
|
||||
|
||||
public static final GridConfig CODENAMES_CLASSICAL = new GridConfig(
|
||||
9, true, true, CLASSICAL25, true,
|
||||
9, CLASSICAL25, true, true, true,
|
||||
true, true, 9, BASE_HINTWORDCOUNT, true,
|
||||
Integer.MAX_VALUE, true, true,
|
||||
true, false);
|
||||
|
||||
public static final GridConfig ZERO = new GridConfig(
|
||||
9, false, false, CLASSICAL25, true,
|
||||
true, false, 1, Set.of(1), false,
|
||||
3, false, false,
|
||||
false, false);
|
||||
public static final GridConfig ZERO = zeroWithSize(CLASSICAL25);
|
||||
|
||||
//TODO this 9 should be config
|
||||
public static final GridConfig zeroWithSize(Map<CardColors, Integer> colors) {
|
||||
return new GridConfig(
|
||||
9, false, false, colors, true,
|
||||
true, false, 1, Set.of(1), false,
|
||||
9, colors, true, true,
|
||||
false, false, false, 1, Set.of(1), false,
|
||||
3, false, false,
|
||||
false, false);
|
||||
}
|
||||
@@ -79,8 +77,6 @@ public class GridConfig {
|
||||
// Maximum of maximom number of hints
|
||||
// This is to trigger end of game
|
||||
private int maxMaxHint;
|
||||
private boolean structuredGrid;
|
||||
private boolean anyoneStarts;
|
||||
@Convert(converter = WordDistributionConverter.class)
|
||||
private Map<CardColors, Integer> wordDistribution;
|
||||
private boolean maybeSuddenDeath; //TODO
|
||||
@@ -89,6 +85,11 @@ public class GridConfig {
|
||||
// prevend death and enter WIN_IF_GREEN_GREEN state, else just die
|
||||
private boolean onlyOneGreenGreenPreventDeath;
|
||||
|
||||
// Game mutable options
|
||||
@Setter
|
||||
private boolean structuredGrid;
|
||||
@Setter
|
||||
private boolean anyoneStarts;
|
||||
|
||||
// Mutable options
|
||||
private int maxHint;
|
||||
|
||||
@@ -34,12 +34,15 @@ public class World {
|
||||
|
||||
boolean won;
|
||||
|
||||
public World(Game g, String name, GridConfig initConfig, Grid currentGrid, boolean won) {
|
||||
boolean locked;
|
||||
|
||||
public World(Game g, String name, GridConfig initConfig, Grid currentGrid, boolean won, boolean locked) {
|
||||
this.id = new WorldId(g.getId(), name);
|
||||
this.game = g;
|
||||
this.initialConfig = initConfig;
|
||||
this.currentGrid = currentGrid;
|
||||
this.won = won;
|
||||
this.locked = locked;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.bernard.nodecames.observer;
|
||||
|
||||
import com.bernard.nodecames.model.Card;
|
||||
import com.bernard.nodecames.model.GameEvent;
|
||||
import com.bernard.nodecames.model.Hint;
|
||||
import com.bernard.nodecames.model.Card.Color;
|
||||
import com.bernard.nodecames.model.Grid.Phase;
|
||||
import com.bernard.nodecames.model.GridConfig.MutableOption;
|
||||
|
||||
public interface GridObserver {
|
||||
|
||||
public void onPointCard(char player, Card card, Color color);
|
||||
public void onSubmitHint(char player, Hint hint);
|
||||
|
||||
public void onGridEnd(boolean won);
|
||||
public void onGameEvent(GameEvent ge);
|
||||
public void onNewPhase(Phase p);
|
||||
public void onConfigChange(MutableOption cc, Object data);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.bernard.nodecames.observer;
|
||||
|
||||
public interface WorldObserver extends GridObserver {
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user