World config and inventories
This commit is contained in:
@@ -46,6 +46,15 @@
|
|||||||
${pkgs.podman}/bin/podman run -e POSTGRES_USER=nodecames -e POSTGRES_PASSWORD=nodecames-dev -e POSTGRES_DB=nodecames --volume ./.postgres:/var/lib/postgresql -p 127.0.0.1:5432:5432 postgres:18.4 &> .postgres/postgres.log &
|
${pkgs.podman}/bin/podman run -e POSTGRES_USER=nodecames -e POSTGRES_PASSWORD=nodecames-dev -e POSTGRES_DB=nodecames --volume ./.postgres:/var/lib/postgresql -p 127.0.0.1:5432:5432 postgres:18.4 &> .postgres/postgres.log &
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
test-archipelago = pkgs.writeShellApplication {
|
||||||
|
name = "archipelago";
|
||||||
|
text = ''
|
||||||
|
TMP_HOME=$(mktemp -d)
|
||||||
|
HOME=$TMP_HOME ${pkgs.archipelago}/bin/archipelago --update_settings
|
||||||
|
ln -s ${nodecames-apworld} "$TMP_HOME/.local/share/Archipelago/worlds/nodecames.apworld"
|
||||||
|
HOME=$TMP_HOME ${pkgs.archipelago}/bin/archipelago
|
||||||
|
'';
|
||||||
|
};
|
||||||
in {
|
in {
|
||||||
packages.${system} = {
|
packages.${system} = {
|
||||||
default = self.packages.${system}.nodecames;
|
default = self.packages.${system}.nodecames;
|
||||||
@@ -56,6 +65,7 @@
|
|||||||
default = self.apps.${system}.nodecames;
|
default = self.apps.${system}.nodecames;
|
||||||
nodecames = { type = "app"; program = "${nodecames-launcher}/bin/nodecames"; };
|
nodecames = { type = "app"; program = "${nodecames-launcher}/bin/nodecames"; };
|
||||||
postgres-server = { type = "app"; program = "${postgres-server}/bin/postgres-server"; };
|
postgres-server = { type = "app"; program = "${postgres-server}/bin/postgres-server"; };
|
||||||
|
archipelago = { type = "app"; program = "${test-archipelago}/bin/archipelago"; };
|
||||||
};
|
};
|
||||||
|
|
||||||
devShells.${system} = {
|
devShells.${system} = {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import com.bernard.nodecames.game.GameManager;
|
|||||||
import com.bernard.nodecames.game.GridManager;
|
import com.bernard.nodecames.game.GridManager;
|
||||||
import com.bernard.nodecames.game.IllegalGameActionException;
|
import com.bernard.nodecames.game.IllegalGameActionException;
|
||||||
import com.bernard.nodecames.model.Game;
|
import com.bernard.nodecames.model.Game;
|
||||||
|
import com.bernard.nodecames.model.GameConfig;
|
||||||
import com.bernard.nodecames.model.Grid;
|
import com.bernard.nodecames.model.Grid;
|
||||||
import com.bernard.nodecames.model.GridConfig;
|
import com.bernard.nodecames.model.GridConfig;
|
||||||
import com.bernard.nodecames.model.GridConfig.MutableOption;
|
import com.bernard.nodecames.model.GridConfig.MutableOption;
|
||||||
@@ -112,9 +113,9 @@ public class HttpController {
|
|||||||
public RedirectView createRoom(@RequestParam("type") String type) {
|
public RedirectView createRoom(@RequestParam("type") String type) {
|
||||||
GridConfig cfg = switch(type) {
|
GridConfig cfg = switch(type) {
|
||||||
case "zero" -> GridConfig.ZERO;
|
case "zero" -> GridConfig.ZERO;
|
||||||
case "base" -> GridConfig.CODENAMES_CLASSICAL;
|
case "base" -> GridConfig.REGULAR;
|
||||||
case "big" -> GridConfig.BIG_CLASSICAL;
|
case "big" -> GridConfig.BIG;
|
||||||
default -> GridConfig.CODENAMES_CLASSICAL;
|
default -> GridConfig.REGULAR;
|
||||||
};
|
};
|
||||||
Random r = new Random(rand.nextLong());
|
Random r = new Random(rand.nextLong());
|
||||||
Grid g = gm.newGrid(null, cfg, r);
|
Grid g = gm.newGrid(null, cfg, r);
|
||||||
@@ -122,8 +123,13 @@ public class HttpController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/create-game")
|
@GetMapping("/create-game")
|
||||||
public RedirectView createGame(@RequestParam("name") String name) {
|
public RedirectView createGame(@RequestParam("name") String name, @RequestParam("type") String type) {
|
||||||
Game g = wm.createGame(name);
|
GameConfig cfg = switch(type) {
|
||||||
|
case "default" -> GameConfig.DEFAULT_GAME_CONFIG;
|
||||||
|
case "regular" -> GameConfig.REGULAR_GAME_CONFIG;
|
||||||
|
default -> GameConfig.DEFAULT_GAME_CONFIG;
|
||||||
|
};
|
||||||
|
Game g = wm.createGame(name, cfg);
|
||||||
return new RedirectView("/game/"+g.getId().toString()+"/map");
|
return new RedirectView("/game/"+g.getId().toString()+"/map");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,6 +149,7 @@ public class HttpController {
|
|||||||
.toList();
|
.toList();
|
||||||
m.addAttribute("game", g.get());
|
m.addAttribute("game", g.get());
|
||||||
m.addAttribute("worlds", worlds);
|
m.addAttribute("worlds", worlds);
|
||||||
|
m.addAttribute("items", g.get().getInventory().getReceivedItems());
|
||||||
m.addAttribute("player", player);
|
m.addAttribute("player", player);
|
||||||
return "map";
|
return "map";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.bernard.nodecames.frontend;
|
package com.bernard.nodecames.frontend;
|
||||||
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -10,22 +9,23 @@ import com.bernard.nodecames.model.GameEvent;
|
|||||||
import com.bernard.nodecames.model.Grid;
|
import com.bernard.nodecames.model.Grid;
|
||||||
import com.bernard.nodecames.model.Grid.Phase;
|
import com.bernard.nodecames.model.Grid.Phase;
|
||||||
import com.bernard.nodecames.model.GridConfig;
|
import com.bernard.nodecames.model.GridConfig;
|
||||||
import com.bernard.nodecames.model.GridConfig.CardColors;
|
|
||||||
import com.bernard.nodecames.model.GridConfig.MutableOption;
|
import com.bernard.nodecames.model.GridConfig.MutableOption;
|
||||||
|
import com.bernard.nodecames.model.converter.ColorDistributionConverter;
|
||||||
import com.bernard.nodecames.repository.CardRepository;
|
import com.bernard.nodecames.repository.CardRepository;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import tools.jackson.databind.JsonNode;
|
import tools.jackson.databind.JsonNode;
|
||||||
import tools.jackson.databind.node.ArrayNode;
|
import tools.jackson.databind.node.ArrayNode;
|
||||||
import tools.jackson.databind.node.JsonNodeFactory;
|
import tools.jackson.databind.node.JsonNodeFactory;
|
||||||
import tools.jackson.databind.node.ObjectNode;
|
import tools.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@AllArgsConstructor
|
||||||
public class JsonDataService {
|
public class JsonDataService {
|
||||||
|
|
||||||
private final JsonNodeFactory jsn;
|
private final JsonNodeFactory jsn;
|
||||||
private final CardRepository cRepository;
|
private final CardRepository cRepository;
|
||||||
|
private final ColorDistributionConverter wdc;
|
||||||
|
|
||||||
public JsonNode cardData(Card c, char player) {
|
public JsonNode cardData(Card c, char player) {
|
||||||
ObjectNode cardNode = jsn.objectNode();
|
ObjectNode cardNode = jsn.objectNode();
|
||||||
@@ -43,15 +43,7 @@ public class JsonDataService {
|
|||||||
gcNode.set("max-max-hint", jsn.numberNode(gc.getMaxMaxHint()));
|
gcNode.set("max-max-hint", jsn.numberNode(gc.getMaxMaxHint()));
|
||||||
gcNode.set("structured-grid", jsn.booleanNode(gc.isStructuredGrid()));
|
gcNode.set("structured-grid", jsn.booleanNode(gc.isStructuredGrid()));
|
||||||
gcNode.set("anyone-starts", jsn.booleanNode(gc.isAnyoneStarts()));
|
gcNode.set("anyone-starts", jsn.booleanNode(gc.isAnyoneStarts()));
|
||||||
ArrayNode wordDistributionNode = jsn.arrayNode(gc.getWordDistribution().size());
|
gcNode.set("color-distribution", jsn.stringNode(wdc.convertToDatabaseColumn(gc.getColorDistribution())));
|
||||||
for(Entry<CardColors, Integer> e : gc.getWordDistribution().entrySet()) {
|
|
||||||
ObjectNode ccNode = jsn.objectNode();
|
|
||||||
ccNode.set("face-a", jsn.stringNode(e.getKey().getFaceA().name()));
|
|
||||||
ccNode.set("face-b", jsn.stringNode(e.getKey().getFaceB().name()));
|
|
||||||
ccNode.set("count", jsn.numberNode(e.getValue()));
|
|
||||||
wordDistributionNode.add(ccNode);
|
|
||||||
}
|
|
||||||
gcNode.set("word-distribution", wordDistributionNode);
|
|
||||||
gcNode.set("maybe-sudden-death", jsn.booleanNode(gc.isMaybeSuddenDeath()));
|
gcNode.set("maybe-sudden-death", jsn.booleanNode(gc.isMaybeSuddenDeath()));
|
||||||
gcNode.set("maybe-one-more-guess", jsn.booleanNode(gc.isMaybeOneMoreGuess()));
|
gcNode.set("maybe-one-more-guess", jsn.booleanNode(gc.isMaybeOneMoreGuess()));
|
||||||
gcNode.set("only-one-green-green-prevent-death", jsn.booleanNode(gc.isOnlyOneGreenGreenPreventDeath()));
|
gcNode.set("only-one-green-green-prevent-death", jsn.booleanNode(gc.isOnlyOneGreenGreenPreventDeath()));
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.bernard.nodecames.model.Game;
|
import com.bernard.nodecames.model.Game;
|
||||||
import com.bernard.nodecames.model.GridConfig.MutableOption;
|
|
||||||
|
|
||||||
import io.github.archipelagomw.APResult.ResultCode;
|
import io.github.archipelagomw.APResult.ResultCode;
|
||||||
import io.github.archipelagomw.Client;
|
import io.github.archipelagomw.Client;
|
||||||
@@ -37,57 +36,11 @@ public class ArchipelagoClient extends Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ArchipelagoEventListener
|
@ArchipelagoEventListener
|
||||||
public void onReceiveItem(ReceiveItemEvent e) throws IllegalGameActionException {
|
public void onReceiveItem(ReceiveItemEvent e) {
|
||||||
log.debug("Received item : {}", e.getItemName());
|
log.debug("Received item : {}", e.getItemName());
|
||||||
switch(e.getItemName()) {
|
if(e.getIndex() > game.getInventory().getReceivedItems().size())
|
||||||
case "progressive_hint_count":
|
gm.giveItem(game, e.getItemName());
|
||||||
gm.unlockMoreHintCount(this.game);
|
// Else, the item has already been received
|
||||||
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
|
@Getter
|
||||||
|
|||||||
@@ -2,27 +2,28 @@ package com.bernard.nodecames.game;
|
|||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.Map.Entry;
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import com.bernard.nodecames.model.Card;
|
import com.bernard.nodecames.model.Card;
|
||||||
import com.bernard.nodecames.model.Card.Color;
|
import com.bernard.nodecames.model.Card.Color;
|
||||||
import com.bernard.nodecames.model.Game;
|
import com.bernard.nodecames.model.Game;
|
||||||
|
import com.bernard.nodecames.model.GameConfig;
|
||||||
import com.bernard.nodecames.model.GameEvent;
|
import com.bernard.nodecames.model.GameEvent;
|
||||||
import com.bernard.nodecames.model.Grid;
|
import com.bernard.nodecames.model.Grid;
|
||||||
import com.bernard.nodecames.model.Grid.Phase;
|
import com.bernard.nodecames.model.Grid.Phase;
|
||||||
import com.bernard.nodecames.model.GridConfig;
|
import com.bernard.nodecames.model.GridConfig;
|
||||||
import com.bernard.nodecames.model.GridConfig.MutableOption;
|
import com.bernard.nodecames.model.GridConfig.MutableOption;
|
||||||
import com.bernard.nodecames.model.Hint;
|
import com.bernard.nodecames.model.Hint;
|
||||||
|
import com.bernard.nodecames.model.Inventory;
|
||||||
import com.bernard.nodecames.model.World;
|
import com.bernard.nodecames.model.World;
|
||||||
import com.bernard.nodecames.model.World.WorldId;
|
import com.bernard.nodecames.model.World.WorldId;
|
||||||
import com.bernard.nodecames.observer.GridObserver;
|
import com.bernard.nodecames.observer.GridObserver;
|
||||||
@@ -42,6 +43,9 @@ public class GameManager {
|
|||||||
private final GameRepository gRepository;
|
private final GameRepository gRepository;
|
||||||
private final WorldRepository wRepository;
|
private final WorldRepository wRepository;
|
||||||
|
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(GameManager.class);
|
||||||
|
|
||||||
//TODO try putting "World" here instead of WorldId
|
//TODO try putting "World" here instead of WorldId
|
||||||
private Map<WorldId, Set<WorldObserver>> observers = new HashMap<>();
|
private Map<WorldId, Set<WorldObserver>> observers = new HashMap<>();
|
||||||
|
|
||||||
@@ -73,15 +77,9 @@ public class GameManager {
|
|||||||
forEachObserver(w, o -> gm.addObserver(g, o));
|
forEachObserver(w, o -> gm.addObserver(g, o));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Game createGame(String name) {
|
public Game createGame(String name, GameConfig cfg) {
|
||||||
UUID uuid = UUID.randomUUID();
|
Game g = cfg.newGame(gRepository, wRepository, name);
|
||||||
|
|
||||||
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, true);
|
|
||||||
wRepository.save(w);
|
|
||||||
}
|
|
||||||
return g;
|
return g;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +95,7 @@ public class GameManager {
|
|||||||
if(w.isLocked())
|
if(w.isLocked())
|
||||||
//TODO separate IllegaleGameAction and IllegalGridAction
|
//TODO separate IllegaleGameAction and IllegalGridAction
|
||||||
throw new IllegalGameActionException(null, "Cannot create a grid in a locked world");
|
throw new IllegalGameActionException(null, "Cannot create a grid in a locked world");
|
||||||
GridConfig gc = w.getInitialConfig();
|
GridConfig gc = w.makeGridConfig();
|
||||||
//TODO seed random
|
//TODO seed random
|
||||||
Grid grid = gm.newGrid(w, gc, new Random());
|
Grid grid = gm.newGrid(w, gc, new Random());
|
||||||
|
|
||||||
@@ -120,76 +118,23 @@ public class GameManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void giveItem(Game g, String item) {
|
||||||
* UPDATE GAME CONFIG
|
g.getInventory().receiveItem(item);
|
||||||
*/
|
Inventory inv = g.getInventory();
|
||||||
public void unlockMutable(Game g, MutableOption opt, Object o) throws IllegalGameActionException {
|
for(World w : allWorlds(g)) {
|
||||||
Set<World> worlds = wRepository.findByGame(g);
|
if(w.getCurrentGrid() == null) continue;
|
||||||
for(World w : worlds) {
|
|
||||||
GridConfig initialConfig = w.getInitialConfig();
|
|
||||||
initialConfig.set(opt, o);
|
|
||||||
w.setInitialConfig(initialConfig);
|
|
||||||
wRepository.save(w);
|
|
||||||
Grid grid = w.getCurrentGrid();
|
Grid grid = w.getCurrentGrid();
|
||||||
if(grid != null) {
|
Map<MutableOption, Object> updates = w.getConfig().updateConfig(grid.getConfig(), inv);
|
||||||
gm.unlock(grid, opt, o);
|
log.debug("Updating option for world {} : {}", w.getName(), updates);
|
||||||
|
for(Entry<MutableOption, Object> e : updates.entrySet()) {
|
||||||
|
try {
|
||||||
|
gm.unlock(grid, e.getKey(), e.getValue());
|
||||||
|
} catch (IllegalGameActionException ex) {
|
||||||
|
throw new IllegalStateException("World Config sent a set of changes that got us out of game logic", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
gRepository.save(g);
|
||||||
|
|
||||||
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
|
@AllArgsConstructor
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ import org.springframework.stereotype.Service;
|
|||||||
|
|
||||||
import com.bernard.nodecames.model.Card;
|
import com.bernard.nodecames.model.Card;
|
||||||
import com.bernard.nodecames.model.Card.Color;
|
import com.bernard.nodecames.model.Card.Color;
|
||||||
|
import com.bernard.nodecames.model.ColorDistribution.CardColors;
|
||||||
import com.bernard.nodecames.model.GameEvent;
|
import com.bernard.nodecames.model.GameEvent;
|
||||||
import com.bernard.nodecames.model.Grid;
|
import com.bernard.nodecames.model.Grid;
|
||||||
import com.bernard.nodecames.model.Grid.Phase;
|
import com.bernard.nodecames.model.Grid.Phase;
|
||||||
import com.bernard.nodecames.model.GridConfig;
|
import com.bernard.nodecames.model.GridConfig;
|
||||||
import com.bernard.nodecames.model.GridConfig.CardColors;
|
|
||||||
import com.bernard.nodecames.model.GridConfig.MutableOption;
|
import com.bernard.nodecames.model.GridConfig.MutableOption;
|
||||||
import com.bernard.nodecames.model.Hint;
|
import com.bernard.nodecames.model.Hint;
|
||||||
import com.bernard.nodecames.model.World;
|
import com.bernard.nodecames.model.World;
|
||||||
@@ -147,13 +147,13 @@ public class GridManager {
|
|||||||
|
|
||||||
public Grid newGrid(@Nullable World world, GridConfig gc, Random r) {
|
public Grid newGrid(@Nullable World world, GridConfig gc, Random r) {
|
||||||
UUID uuid = UUID.randomUUID();
|
UUID uuid = UUID.randomUUID();
|
||||||
String[] words = dictionnaries.randomWords(gc.getWordCount(), r);
|
String[] words = dictionnaries.randomWords(gc.getColorDistribution().wordCount(), r);
|
||||||
List<CardColors> origDistrib = gc.getWordDistributionAsList();
|
List<CardColors> origDistrib = gc.getColorDistribution().asList();
|
||||||
List<CardColors> distrib;
|
List<CardColors> distrib;
|
||||||
if(!gc.isStructuredGrid()) {
|
if(!gc.isStructuredGrid()) {
|
||||||
List<Color> bColors = origDistrib.stream().map(cc -> cc.getFaceB()).collect(Collectors.toList());
|
List<Color> bColors = origDistrib.stream().map(cc -> cc.getFaceB()).collect(Collectors.toList());
|
||||||
Collections.shuffle(bColors, r);
|
Collections.shuffle(bColors, r);
|
||||||
distrib = IntStream.range(0, gc.getWordCount())
|
distrib = IntStream.range(0, gc.getColorDistribution().wordCount())
|
||||||
.mapToObj(i -> CardColors.of(origDistrib.get(i).getFaceA(), bColors.get(i)))
|
.mapToObj(i -> CardColors.of(origDistrib.get(i).getFaceA(), bColors.get(i)))
|
||||||
.toList();
|
.toList();
|
||||||
} else {
|
} else {
|
||||||
@@ -400,7 +400,10 @@ public class GridManager {
|
|||||||
if (!newAvailableHintWordCounts.containsAll(g.getConfig().getAvailableHintWordCount())
|
if (!newAvailableHintWordCounts.containsAll(g.getConfig().getAvailableHintWordCount())
|
||||||
|| g.getConfig().getAvailableHintWordCount().containsAll(newAvailableHintWordCounts)) {
|
|| g.getConfig().getAvailableHintWordCount().containsAll(newAvailableHintWordCounts)) {
|
||||||
// i.e. If given data is not a strict superset
|
// i.e. If given data is not a strict superset
|
||||||
throw new IllegalGameActionException(g, "Can only add hint word options");
|
throw new IllegalGameActionException(g,
|
||||||
|
"Can only add hint word options (%s) is not a strict superset of (%s)"
|
||||||
|
.formatted(newAvailableHintWordCounts, g.getConfig().getAvailableHintWordCount())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
changeConfig(g, MutableOption.AVAILABLE_HINT_WORD_COUNT, newAvailableHintWordCounts);
|
changeConfig(g, MutableOption.AVAILABLE_HINT_WORD_COUNT, newAvailableHintWordCounts);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
package com.bernard.nodecames.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import com.bernard.nodecames.model.Card.Color;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonValue;
|
||||||
|
|
||||||
|
import lombok.Value;
|
||||||
|
import tools.jackson.core.JacksonException;
|
||||||
|
import tools.jackson.core.JsonParser;
|
||||||
|
import tools.jackson.databind.DeserializationContext;
|
||||||
|
import tools.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
import tools.jackson.databind.deser.std.StdDeserializer;
|
||||||
|
|
||||||
|
@Value(staticConstructor = "of")
|
||||||
|
@JsonDeserialize(using = ColorDistribution.Deserializer.class)
|
||||||
|
public class ColorDistribution {
|
||||||
|
|
||||||
|
public static final ColorDistribution CUSTOM6 =
|
||||||
|
makeDistribution(0, 0, 2, 1, 1, 0);
|
||||||
|
public static final ColorDistribution CUSTOM9 =
|
||||||
|
makeDistribution(0, 1, 1, 2, 2, 1);
|
||||||
|
public static final ColorDistribution CUSTOM12 =
|
||||||
|
makeDistribution(1, 1, 1, 2, 3, 1);
|
||||||
|
public static final ColorDistribution CUSTOM16 =
|
||||||
|
makeDistribution(1, 1, 2, 2, 5, 1);
|
||||||
|
public static final ColorDistribution CUSTOM20 =
|
||||||
|
makeDistribution(1, 1, 4, 2, 5, 1);
|
||||||
|
public static final ColorDistribution CLASSICAL25 =
|
||||||
|
makeDistribution(1, 1, 5, 3, 7, 1);
|
||||||
|
public static final ColorDistribution CUSTOM30 =
|
||||||
|
makeDistribution(2, 2, 5, 3, 7, 1);
|
||||||
|
public static final ColorDistribution CUSTOM36 =
|
||||||
|
makeDistribution(2, 2, 6, 5, 9, 2);
|
||||||
|
|
||||||
|
private final Map<CardColors, Integer> colors;
|
||||||
|
|
||||||
|
public List<CardColors> asList() {
|
||||||
|
List<CardColors> out = new ArrayList<>();
|
||||||
|
for(Entry<CardColors, Integer> e : this.colors.entrySet()) {
|
||||||
|
for(int i=0;i<e.getValue();i++) {
|
||||||
|
out.add(e.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableList(out);
|
||||||
|
}
|
||||||
|
public int anyGreenCount() {
|
||||||
|
return this.colors.get(CardColors.GG) +
|
||||||
|
this.colors.get(CardColors.WG) +
|
||||||
|
this.colors.get(CardColors.GW) +
|
||||||
|
this.colors.get(CardColors.GB) +
|
||||||
|
this.colors.get(CardColors.BG);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int wordCount() {
|
||||||
|
return (int)this.colors.values().stream().collect(Collectors.summarizingInt(i -> i)).getSum();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final ColorDistribution makeDistribution(
|
||||||
|
int wbbwCount, int bggbCount, int wggwCount,
|
||||||
|
int ggCount, int wwCount, int bbCount
|
||||||
|
) {
|
||||||
|
return new ColorDistribution(Map.of(
|
||||||
|
CardColors.BB, bbCount,
|
||||||
|
CardColors.WW, wwCount,
|
||||||
|
CardColors.GG, ggCount,
|
||||||
|
CardColors.WB, wbbwCount,
|
||||||
|
CardColors.BW, wbbwCount,
|
||||||
|
CardColors.GB, bggbCount,
|
||||||
|
CardColors.BG, bggbCount,
|
||||||
|
CardColors.GW, wggwCount,
|
||||||
|
CardColors.WG, wggwCount
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Value(staticConstructor = "of")
|
||||||
|
public static final class CardColors implements Comparable<CardColors> {
|
||||||
|
private Color faceA;
|
||||||
|
private Color faceB;
|
||||||
|
|
||||||
|
public static final CardColors WW = CardColors.of(Color.WHITE, Color.WHITE);
|
||||||
|
public static final CardColors WB = CardColors.of(Color.WHITE, Color.BLACK);
|
||||||
|
public static final CardColors BW = CardColors.of(Color.BLACK, Color.WHITE);
|
||||||
|
public static final CardColors WG = CardColors.of(Color.WHITE, Color.GREEN);
|
||||||
|
public static final CardColors GW = CardColors.of(Color.GREEN, Color.WHITE);
|
||||||
|
public static final CardColors GG = CardColors.of(Color.GREEN, Color.GREEN);
|
||||||
|
public static final CardColors GB = CardColors.of(Color.GREEN, Color.BLACK);
|
||||||
|
public static final CardColors BG = CardColors.of(Color.BLACK, Color.GREEN);
|
||||||
|
public static final CardColors BB = CardColors.of(Color.BLACK, Color.BLACK);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(CardColors o) {
|
||||||
|
int i = this.getFaceA().compareTo(o.getFaceA());
|
||||||
|
return i!=0?i:this.getFaceB().compareTo(o.getFaceB());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* JSON conversion
|
||||||
|
*/
|
||||||
|
|
||||||
|
private static String cardColorsToString(CardColors cc, Integer i) {
|
||||||
|
return "%s%s%d".formatted(cc.getFaceA().getLetter(), cc.getFaceB().getLetter(), i);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CardColors parseCardColors(String s) {
|
||||||
|
if(s.length()<3)
|
||||||
|
throw new IllegalArgumentException("Malformed string");
|
||||||
|
char lA = s.charAt(0);
|
||||||
|
char lB = s.charAt(1);
|
||||||
|
Color cA = Color.findByLetter(lA);
|
||||||
|
Color cB = Color.findByLetter(lB);
|
||||||
|
if (cA == null)
|
||||||
|
throw new IllegalArgumentException("Could not find color corresponding to '"+lA+"' in string '" + s + "'");
|
||||||
|
if (cB == null)
|
||||||
|
throw new IllegalArgumentException("Could not find color corresponding to '"+lB+"'");
|
||||||
|
return CardColors.of(cA, cB);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Integer parseCount(String s) {
|
||||||
|
if(s.length()<3)
|
||||||
|
throw new IllegalArgumentException("Malformed string");
|
||||||
|
String number = s.substring(2);
|
||||||
|
return Integer.parseInt(number, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonValue
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return this.getColors().entrySet().stream()
|
||||||
|
.sorted((e1, e2) -> e1.getKey().compareTo(e2.getKey()))
|
||||||
|
.map(e -> cardColorsToString(e.getKey(), e.getValue()))
|
||||||
|
.collect(Collectors.joining(";"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public static ColorDistribution ofString(String str) {
|
||||||
|
return ColorDistribution.of(Arrays.stream(str.split(";"))
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
ColorDistribution::parseCardColors,
|
||||||
|
ColorDistribution::parseCount
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Deserializer extends StdDeserializer<ColorDistribution> {
|
||||||
|
public Deserializer() {
|
||||||
|
super(ColorDistribution.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ColorDistribution deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
|
||||||
|
String s = p.getString();
|
||||||
|
return ColorDistribution.ofString(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,18 +1,19 @@
|
|||||||
package com.bernard.nodecames.model;
|
package com.bernard.nodecames.model;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.JdbcTypeCode;
|
||||||
|
import org.hibernate.type.SqlTypes;
|
||||||
|
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import jakarta.persistence.GeneratedValue;
|
|
||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.Id;
|
||||||
import lombok.AllArgsConstructor;
|
import jakarta.persistence.OneToMany;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@Entity
|
@Entity
|
||||||
@@ -21,20 +22,22 @@ public class Game {
|
|||||||
@Id
|
@Id
|
||||||
private UUID id;
|
private UUID id;
|
||||||
|
|
||||||
String name;
|
private String name;
|
||||||
|
|
||||||
|
@JdbcTypeCode(SqlTypes.JSON)
|
||||||
|
private GameConfig config;
|
||||||
|
|
||||||
|
@JdbcTypeCode(SqlTypes.JSON)
|
||||||
|
private Inventory inventory = Inventory.empty();
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "game")
|
||||||
|
Set<World> worlds = null;
|
||||||
|
|
||||||
|
public Game(UUID id, String name, GameConfig config) {
|
||||||
|
this();
|
||||||
|
this.setId(id);
|
||||||
|
this.setName(name);
|
||||||
|
this.setConfig(config);
|
||||||
|
}
|
||||||
|
|
||||||
public static final Map<String, GridConfig> DEFAULT_WORLDS_CONFIG = Map.ofEntries(
|
|
||||||
Map.entry("1-1", GridConfig.zeroWithSize(GridConfig.CUSTOM6)),
|
|
||||||
Map.entry("1-2", GridConfig.zeroWithSize(GridConfig.CUSTOM9)),
|
|
||||||
Map.entry("1-3", GridConfig.zeroWithSize(GridConfig.CUSTOM9)),
|
|
||||||
Map.entry("2-1", GridConfig.zeroWithSize(GridConfig.CUSTOM12)),
|
|
||||||
Map.entry("2-2", GridConfig.zeroWithSize(GridConfig.CUSTOM16)),
|
|
||||||
Map.entry("2-3", GridConfig.zeroWithSize(GridConfig.CUSTOM16)),
|
|
||||||
Map.entry("3-1", GridConfig.zeroWithSize(GridConfig.CUSTOM20)),
|
|
||||||
Map.entry("3-2", GridConfig.zeroWithSize(GridConfig.CLASSICAL25)),
|
|
||||||
Map.entry("3-3", GridConfig.zeroWithSize(GridConfig.CLASSICAL25)),
|
|
||||||
Map.entry("4-1", GridConfig.zeroWithSize(GridConfig.CUSTOM30)),
|
|
||||||
Map.entry("4-2", GridConfig.zeroWithSize(GridConfig.CUSTOM36)),
|
|
||||||
Map.entry("4-3", GridConfig.zeroWithSize(GridConfig.CUSTOM36))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.bernard.nodecames.model;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import com.bernard.nodecames.repository.GameRepository;
|
||||||
|
import com.bernard.nodecames.repository.WorldRepository;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Value;
|
||||||
|
|
||||||
|
@Value
|
||||||
|
@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class GameConfig {
|
||||||
|
|
||||||
|
@JsonProperty("worlds")
|
||||||
|
private final Map<String, WorldConfig> worlds;
|
||||||
|
|
||||||
|
//TODO we might have a solution without passing repositories as parameters
|
||||||
|
public Game newGame(GameRepository gRepository, WorldRepository wRepository, String name) {
|
||||||
|
UUID id = UUID.randomUUID();
|
||||||
|
Game g = new Game(id, name, this);
|
||||||
|
gRepository.save(g);
|
||||||
|
for(String wName : worlds.keySet()) {
|
||||||
|
WorldConfig wc = worlds.get(wName);
|
||||||
|
World w = wc.newWorld(g, wName);
|
||||||
|
wRepository.save(w);
|
||||||
|
}
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final GameConfig DEFAULT_GAME_CONFIG = new GameConfig(Map.ofEntries(
|
||||||
|
Map.entry("1-1", WorldConfig.zeroWithSize(ColorDistribution.CUSTOM6)),
|
||||||
|
Map.entry("1-2", WorldConfig.zeroWithSize(ColorDistribution.CUSTOM9)),
|
||||||
|
Map.entry("1-3", WorldConfig.zeroWithSize(ColorDistribution.CUSTOM9)),
|
||||||
|
Map.entry("2-1", WorldConfig.zeroWithSize(ColorDistribution.CUSTOM12)),
|
||||||
|
Map.entry("2-2", WorldConfig.zeroWithSize(ColorDistribution.CUSTOM16)),
|
||||||
|
Map.entry("2-3", WorldConfig.zeroWithSize(ColorDistribution.CUSTOM16)),
|
||||||
|
Map.entry("3-1", WorldConfig.zeroWithSize(ColorDistribution.CUSTOM20)),
|
||||||
|
Map.entry("3-2", WorldConfig.zeroWithSize(ColorDistribution.CLASSICAL25)),
|
||||||
|
Map.entry("3-3", WorldConfig.zeroWithSize(ColorDistribution.CLASSICAL25)),
|
||||||
|
Map.entry("4-1", WorldConfig.zeroWithSize(ColorDistribution.CUSTOM30)),
|
||||||
|
Map.entry("4-2", WorldConfig.zeroWithSize(ColorDistribution.CUSTOM36)),
|
||||||
|
Map.entry("4-3", WorldConfig.zeroWithSize(ColorDistribution.CUSTOM36))
|
||||||
|
));
|
||||||
|
|
||||||
|
public static final GameConfig REGULAR_GAME_CONFIG = new GameConfig(Map.ofEntries(
|
||||||
|
Map.entry("1", WorldConfig.classicalWithSize(ColorDistribution.CUSTOM9, 4)),
|
||||||
|
Map.entry("2", WorldConfig.classicalWithSize(ColorDistribution.CUSTOM16, 7)),
|
||||||
|
Map.entry("3", WorldConfig.classicalWithSize(ColorDistribution.CLASSICAL25, 9)),
|
||||||
|
Map.entry("4", WorldConfig.classicalWithSize(ColorDistribution.CUSTOM36, 15))
|
||||||
|
));
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ package com.bernard.nodecames.model;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.hibernate.annotations.JdbcTypeCode;
|
import org.hibernate.annotations.JdbcTypeCode;
|
||||||
@@ -11,18 +10,17 @@ import org.hibernate.type.SqlTypes;
|
|||||||
|
|
||||||
import jakarta.annotation.Nullable;
|
import jakarta.annotation.Nullable;
|
||||||
import jakarta.persistence.AttributeConverter;
|
import jakarta.persistence.AttributeConverter;
|
||||||
import jakarta.persistence.CascadeType;
|
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.Convert;
|
import jakarta.persistence.Convert;
|
||||||
|
import jakarta.persistence.Converter;
|
||||||
import jakarta.persistence.Embedded;
|
import jakarta.persistence.Embedded;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import jakarta.persistence.FetchType;
|
|
||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.Id;
|
||||||
import jakarta.persistence.ManyToOne;
|
import jakarta.persistence.ManyToOne;
|
||||||
import jakarta.persistence.OneToMany;
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import tools.jackson.databind.JsonNode;
|
import tools.jackson.databind.JsonNode;
|
||||||
import tools.jackson.databind.node.ArrayNode;
|
import tools.jackson.databind.node.ArrayNode;
|
||||||
@@ -41,7 +39,6 @@ public class Grid {
|
|||||||
@ManyToOne(optional = true)
|
@ManyToOne(optional = true)
|
||||||
World w;
|
World w;
|
||||||
|
|
||||||
//@Type(JsonType.class)
|
|
||||||
@Column(columnDefinition = "json")
|
@Column(columnDefinition = "json")
|
||||||
@Convert(converter = GameEventListAdapter.class)
|
@Convert(converter = GameEventListAdapter.class)
|
||||||
@JdbcTypeCode(SqlTypes.JSON)
|
@JdbcTypeCode(SqlTypes.JSON)
|
||||||
@@ -162,10 +159,11 @@ public class Grid {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@AllArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@Converter
|
||||||
public static class GameEventListAdapter implements AttributeConverter<List<GameEvent>, JsonNode> {
|
public static class GameEventListAdapter implements AttributeConverter<List<GameEvent>, JsonNode> {
|
||||||
|
|
||||||
JsonNodeFactory jsn;
|
private final JsonNodeFactory jsn;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JsonNode convertToDatabaseColumn(List<GameEvent> attribute) {
|
public JsonNode convertToDatabaseColumn(List<GameEvent> attribute) {
|
||||||
|
|||||||
@@ -1,28 +1,17 @@
|
|||||||
package com.bernard.nodecames.model;
|
package com.bernard.nodecames.model;
|
||||||
|
|
||||||
import static com.bernard.nodecames.model.GridConfig.CardColors.makeDistribution;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import com.bernard.nodecames.model.Card.Color;
|
import com.bernard.nodecames.model.converter.ColorDistributionConverter;
|
||||||
|
|
||||||
import jakarta.persistence.AttributeConverter;
|
|
||||||
import jakarta.persistence.Convert;
|
import jakarta.persistence.Convert;
|
||||||
import jakarta.persistence.Converter;
|
|
||||||
import jakarta.persistence.Embeddable;
|
import jakarta.persistence.Embeddable;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.Value;
|
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@@ -30,55 +19,19 @@ import lombok.Value;
|
|||||||
@Embeddable
|
@Embeddable
|
||||||
public class GridConfig {
|
public class GridConfig {
|
||||||
|
|
||||||
public static final Map<CardColors, Integer> CUSTOM6 =
|
public static final GridConfig ZERO =
|
||||||
makeDistribution(0, 0, 2, 1, 1, 0);
|
WorldConfig.zeroWithSize(ColorDistribution.CLASSICAL25).makeGridConfig(Inventory.empty());
|
||||||
public static final Map<CardColors, Integer> CUSTOM9 =
|
public static final GridConfig REGULAR =
|
||||||
makeDistribution(0, 1, 1, 2, 2, 1);
|
WorldConfig.classicalWithSize(ColorDistribution.CLASSICAL25, 9).makeGridConfig(Inventory.empty());
|
||||||
public static final Map<CardColors, Integer> CUSTOM12 =
|
public static final GridConfig BIG =
|
||||||
makeDistribution(1, 1, 1, 2, 3, 1);
|
WorldConfig.classicalWithSize(ColorDistribution.CLASSICAL25, 9).makeGridConfig(Inventory.empty());
|
||||||
public static final Map<CardColors, Integer> CUSTOM16 =
|
|
||||||
makeDistribution(1, 1, 2, 2, 5, 1);
|
|
||||||
public static final Map<CardColors, Integer> CUSTOM20 =
|
|
||||||
makeDistribution(1, 1, 4, 2, 5, 1);
|
|
||||||
public static final Map<CardColors, Integer> CLASSICAL25 =
|
|
||||||
makeDistribution(1, 1, 5, 3, 7, 1);
|
|
||||||
public static final Map<CardColors, Integer> CUSTOM30 =
|
|
||||||
makeDistribution(2, 2, 5, 3, 7, 1);
|
|
||||||
public static final Map<CardColors, Integer> CUSTOM36 =
|
|
||||||
makeDistribution(2, 2, 6, 5, 9, 2);
|
|
||||||
|
|
||||||
public static final Set<Integer> BASE_HINTWORDCOUNT = Set.of(
|
|
||||||
0,1,2,3,4,5,6,7,8,9,-1
|
|
||||||
);
|
|
||||||
public static final GridConfig BIG_CLASSICAL = new GridConfig(
|
|
||||||
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, CLASSICAL25, true, true, true,
|
|
||||||
true, true, 9, BASE_HINTWORDCOUNT, true,
|
|
||||||
Integer.MAX_VALUE, true, true,
|
|
||||||
true, 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, colors, true, true,
|
|
||||||
false, false, false, 1, Set.of(1), false,
|
|
||||||
3, false, false,
|
|
||||||
false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Immutable options
|
// Immutable options
|
||||||
// Maximum of maximom number of hints
|
// Maximum of maximom number of hints
|
||||||
// This is to trigger end of game
|
// This is to trigger end of game
|
||||||
private int maxMaxHint;
|
private int maxMaxHint;
|
||||||
@Convert(converter = WordDistributionConverter.class)
|
@Convert(converter = ColorDistributionConverter.class)
|
||||||
private Map<CardColors, Integer> wordDistribution;
|
private ColorDistribution colorDistribution;
|
||||||
private boolean maybeSuddenDeath; //TODO
|
private boolean maybeSuddenDeath; //TODO
|
||||||
private boolean maybeOneMoreGuess; //TODO
|
private boolean maybeOneMoreGuess; //TODO
|
||||||
// If onlyOneGreenGreen is false and having it true would have meant we won,
|
// If onlyOneGreenGreen is false and having it true would have meant we won,
|
||||||
@@ -101,20 +54,6 @@ public class GridConfig {
|
|||||||
private boolean suddenDeath;
|
private boolean suddenDeath;
|
||||||
private boolean hintsInARow;
|
private boolean hintsInARow;
|
||||||
|
|
||||||
public List<CardColors> getWordDistributionAsList() {
|
|
||||||
List<CardColors> out = new ArrayList<>();
|
|
||||||
for(Entry<CardColors, Integer> e : this.getWordDistribution().entrySet()) {
|
|
||||||
for(int i=0;i<e.getValue();i++) {
|
|
||||||
out.add(e.getKey());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Collections.unmodifiableList(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getWordCount() {
|
|
||||||
return (int)this.getWordDistribution().values().stream().collect(Collectors.summarizingInt(i -> i)).getSum();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set(MutableOption opt, Object value) {
|
public void set(MutableOption opt, Object value) {
|
||||||
try{
|
try{
|
||||||
switch(opt) {
|
switch(opt) {
|
||||||
@@ -166,48 +105,9 @@ public class GridConfig {
|
|||||||
return 0 <= i || i == -1;
|
return 0 <= i || i == -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Value(staticConstructor = "of")
|
|
||||||
public static final class CardColors implements Serializable, Comparable<CardColors> {
|
|
||||||
private Color faceA;
|
|
||||||
private Color faceB;
|
|
||||||
|
|
||||||
public static final CardColors WW = CardColors.of(Color.WHITE, Color.WHITE);
|
|
||||||
public static final CardColors WB = CardColors.of(Color.WHITE, Color.BLACK);
|
|
||||||
public static final CardColors BW = CardColors.of(Color.BLACK, Color.WHITE);
|
|
||||||
public static final CardColors WG = CardColors.of(Color.WHITE, Color.GREEN);
|
|
||||||
public static final CardColors GW = CardColors.of(Color.GREEN, Color.WHITE);
|
|
||||||
public static final CardColors GG = CardColors.of(Color.GREEN, Color.GREEN);
|
|
||||||
public static final CardColors GB = CardColors.of(Color.GREEN, Color.BLACK);
|
|
||||||
public static final CardColors BG = CardColors.of(Color.BLACK, Color.GREEN);
|
|
||||||
public static final CardColors BB = CardColors.of(Color.BLACK, Color.BLACK);
|
|
||||||
|
|
||||||
|
|
||||||
public static final Map<CardColors, Integer> makeDistribution(
|
|
||||||
int wbbwCount, int bggbCount, int wggwCount,
|
|
||||||
int ggCount, int wwCount, int bbCount
|
|
||||||
) {
|
|
||||||
return Map.of(
|
|
||||||
BB, bbCount,
|
|
||||||
WW, wwCount,
|
|
||||||
GG, ggCount,
|
|
||||||
WB, wbbwCount,
|
|
||||||
BW, wbbwCount,
|
|
||||||
GB, bggbCount,
|
|
||||||
BG, bggbCount,
|
|
||||||
GW, wggwCount,
|
|
||||||
WG, wggwCount
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(CardColors o) {
|
|
||||||
int i = this.getFaceA().compareTo(o.getFaceA());
|
|
||||||
return i!=0?i:this.getFaceB().compareTo(o.getFaceB());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
|
//TODO do record type
|
||||||
public enum MutableOption {
|
public enum MutableOption {
|
||||||
MAX_HINT(Integer.class),
|
MAX_HINT(Integer.class),
|
||||||
AVAILABLE_HINT_WORD_COUNT(Set.class),
|
AVAILABLE_HINT_WORD_COUNT(Set.class),
|
||||||
@@ -220,52 +120,4 @@ public class GridConfig {
|
|||||||
|
|
||||||
Class<?> dataClass;
|
Class<?> dataClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NoArgsConstructor
|
|
||||||
@Converter
|
|
||||||
public static class WordDistributionConverter implements AttributeConverter<Map<CardColors, Integer>, String> {
|
|
||||||
|
|
||||||
private String cardColorsToString(CardColors cc, Integer i) {
|
|
||||||
return "%s%s%d".formatted(cc.getFaceA().getLetter(), cc.getFaceB().getLetter(), i);
|
|
||||||
}
|
|
||||||
|
|
||||||
private CardColors parseCardColors(String s) {
|
|
||||||
if(s.length()<3)
|
|
||||||
throw new IllegalArgumentException("Malformed string");
|
|
||||||
char lA = s.charAt(0);
|
|
||||||
char lB = s.charAt(1);
|
|
||||||
Color cA = Color.findByLetter(lA);
|
|
||||||
Color cB = Color.findByLetter(lB);
|
|
||||||
if (cA == null)
|
|
||||||
throw new IllegalArgumentException("Could not find color corresponding to '"+lA+"' in string '" + s + "'");
|
|
||||||
if (cB == null)
|
|
||||||
throw new IllegalArgumentException("Could not find color corresponding to '"+lB+"'");
|
|
||||||
return CardColors.of(cA, cB);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Integer parseCount(String s) {
|
|
||||||
if(s.length()<3)
|
|
||||||
throw new IllegalArgumentException("Malformed string");
|
|
||||||
String number = s.substring(2);
|
|
||||||
return Integer.parseInt(number, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String convertToDatabaseColumn(Map<CardColors, Integer> attribute) {
|
|
||||||
return attribute.entrySet().stream()
|
|
||||||
.sorted((e1, e2) -> e1.getKey().compareTo(e2.getKey()))
|
|
||||||
.map(e -> cardColorsToString(e.getKey(), e.getValue()))
|
|
||||||
.collect(Collectors.joining(";"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<CardColors, Integer> convertToEntityAttribute(String dbData) {
|
|
||||||
return Arrays.stream(dbData.split(";"))
|
|
||||||
.collect(Collectors.toMap(
|
|
||||||
this::parseCardColors,
|
|
||||||
this::parseCount
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
package com.bernard.nodecames.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import jakarta.persistence.AttributeConverter;
|
||||||
|
import jakarta.persistence.Converter;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import tools.jackson.databind.JsonNode;
|
||||||
|
import tools.jackson.databind.node.ArrayNode;
|
||||||
|
import tools.jackson.databind.node.JsonNodeFactory;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class Inventory {
|
||||||
|
|
||||||
|
// List of items ids in reception order
|
||||||
|
private List<String> receivedItems = new ArrayList<>();
|
||||||
|
|
||||||
|
private int extraHintCount = 0;
|
||||||
|
|
||||||
|
private boolean extraStructuredGrid = false;
|
||||||
|
private boolean extraAnyoneStarts = false;
|
||||||
|
|
||||||
|
private Set<Integer> extraHintWordCounts = new HashSet<>();
|
||||||
|
private boolean extraOneMoreGuess = false;
|
||||||
|
private int extraHintMaxLength = 0;
|
||||||
|
private boolean extraEndGuessing = false;
|
||||||
|
private boolean extraOnlyOneGreenGreen = false;
|
||||||
|
private boolean extraSuddenDeath = false;
|
||||||
|
private boolean extraHintsInARow = false;
|
||||||
|
|
||||||
|
private Set<String> unlockedWorlds = new HashSet<>();
|
||||||
|
|
||||||
|
public Inventory(List<String> items) {
|
||||||
|
receivedItems = new ArrayList<>(items);
|
||||||
|
for(String item : items)
|
||||||
|
receiveItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Inventory() {}
|
||||||
|
|
||||||
|
public static Inventory empty() {
|
||||||
|
return new Inventory();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String UNLOCK_WORLD_ITEM_PREFIX = "unlock_world_";
|
||||||
|
|
||||||
|
public void receiveItem(String item) {
|
||||||
|
receivedItems.add(item);
|
||||||
|
switch(item) {
|
||||||
|
case "progressive_hint_count":
|
||||||
|
extraHintCount++;
|
||||||
|
break;
|
||||||
|
case "progressive_hint_word_length":
|
||||||
|
extraHintMaxLength++;
|
||||||
|
break;
|
||||||
|
case "progressive_hint_word_count":
|
||||||
|
//TODO get default value from game config
|
||||||
|
int currentMaxHintWordCount = extraHintWordCounts.stream().mapToInt(i -> i).max().orElse(1);
|
||||||
|
extraHintWordCounts.add(currentMaxHintWordCount+1);
|
||||||
|
break;
|
||||||
|
case "zero_hint":
|
||||||
|
extraHintWordCounts.add(0);
|
||||||
|
break;
|
||||||
|
case "infinity_hint":
|
||||||
|
extraHintWordCounts.add(-1);
|
||||||
|
break;
|
||||||
|
case "one_more_guess":
|
||||||
|
extraOneMoreGuess = true;
|
||||||
|
break;
|
||||||
|
case "end_guessing":
|
||||||
|
extraEndGuessing = true;
|
||||||
|
break;
|
||||||
|
case "only_one_green_green":
|
||||||
|
extraOnlyOneGreenGreen = true;
|
||||||
|
break;
|
||||||
|
case "sudden_death":
|
||||||
|
extraSuddenDeath = true;
|
||||||
|
break;
|
||||||
|
case "structured_grid":
|
||||||
|
extraStructuredGrid = true;
|
||||||
|
break;
|
||||||
|
case "hints_in_a_row":
|
||||||
|
extraHintsInARow = true;
|
||||||
|
break;
|
||||||
|
case "anyone_starts":
|
||||||
|
extraAnyoneStarts = true;
|
||||||
|
break;
|
||||||
|
case "filler":
|
||||||
|
// We do nothing if we receive a filler item
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (item.startsWith(UNLOCK_WORLD_ITEM_PREFIX)) {
|
||||||
|
String worldName = item.substring(UNLOCK_WORLD_ITEM_PREFIX.length());
|
||||||
|
this.unlockedWorlds.add(worldName);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Received unknown item : "+item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Converter
|
||||||
|
public static class InventoryConverter implements AttributeConverter<Inventory, JsonNode> {
|
||||||
|
|
||||||
|
private final JsonNodeFactory jsn;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonNode convertToDatabaseColumn(Inventory inv) {
|
||||||
|
ArrayNode out = jsn.arrayNode();
|
||||||
|
for(String s : inv.receivedItems) {
|
||||||
|
out.add(jsn.stringNode(s));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Inventory convertToEntityAttribute(JsonNode json) {
|
||||||
|
ArrayNode arr = json.asArray();
|
||||||
|
List<String> items = new ArrayList<>(arr.size());
|
||||||
|
for(JsonNode itemNode : arr)
|
||||||
|
items.add(itemNode.stringValue());
|
||||||
|
return new Inventory(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,9 +26,6 @@ public class World {
|
|||||||
@ManyToOne
|
@ManyToOne
|
||||||
Game game;
|
Game game;
|
||||||
|
|
||||||
// Config used when resetting this world
|
|
||||||
GridConfig initialConfig;
|
|
||||||
|
|
||||||
@ManyToOne(optional = true)
|
@ManyToOne(optional = true)
|
||||||
Grid currentGrid;
|
Grid currentGrid;
|
||||||
|
|
||||||
@@ -36,10 +33,9 @@ public class World {
|
|||||||
|
|
||||||
boolean locked;
|
boolean locked;
|
||||||
|
|
||||||
public World(Game g, String name, GridConfig initConfig, Grid currentGrid, boolean won, boolean locked) {
|
public World(Game g, String name, Grid currentGrid, boolean won, boolean locked) {
|
||||||
this.id = new WorldId(g.getId(), name);
|
this.id = new WorldId(g.getId(), name);
|
||||||
this.game = g;
|
this.game = g;
|
||||||
this.initialConfig = initConfig;
|
|
||||||
this.currentGrid = currentGrid;
|
this.currentGrid = currentGrid;
|
||||||
this.won = won;
|
this.won = won;
|
||||||
this.locked = locked;
|
this.locked = locked;
|
||||||
@@ -49,6 +45,15 @@ public class World {
|
|||||||
return this.getId().getName();
|
return this.getId().getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public WorldConfig getConfig() {
|
||||||
|
return this.getGame().getConfig().getWorlds().get(this.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public GridConfig makeGridConfig() {
|
||||||
|
WorldConfig wc = getConfig();
|
||||||
|
return wc.makeGridConfig(this.getGame().getInventory());
|
||||||
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
|
|||||||
@@ -0,0 +1,205 @@
|
|||||||
|
package com.bernard.nodecames.model;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
import com.bernard.nodecames.model.GridConfig.MutableOption;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Value;
|
||||||
|
|
||||||
|
@Value
|
||||||
|
@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class WorldConfig {
|
||||||
|
|
||||||
|
@JsonProperty("color_distribution")
|
||||||
|
private ColorDistribution colorDistribution;
|
||||||
|
|
||||||
|
// Base is with no received items
|
||||||
|
// Max is either a cap when items are received
|
||||||
|
|
||||||
|
@JsonProperty("base_hint_count")
|
||||||
|
private int baseHintCount;
|
||||||
|
@JsonProperty("max_hint_count")
|
||||||
|
private int maxHintCount;
|
||||||
|
|
||||||
|
@JsonProperty("base_structured_grid")
|
||||||
|
private boolean baseStructuredGrid;
|
||||||
|
@JsonProperty("max_structured_grid")
|
||||||
|
private boolean maxStructuredGrid;
|
||||||
|
@JsonProperty("base_anyone_starts")
|
||||||
|
private boolean baseAnyoneStarts;
|
||||||
|
@JsonProperty("max_anyone_starts")
|
||||||
|
private boolean maxAnyoneStarts;
|
||||||
|
|
||||||
|
@JsonProperty("base_hint_word_counts")
|
||||||
|
private Set<Integer> baseHintWordCounts;
|
||||||
|
@JsonProperty("max_hint_word_counts")
|
||||||
|
private Set<Integer> maxHintWordCounts;
|
||||||
|
@JsonProperty("base_one_more_guess")
|
||||||
|
private boolean baseOneMoreGuess;
|
||||||
|
@JsonProperty("max_one_more_guess")
|
||||||
|
private boolean maxOneMoreGuess;
|
||||||
|
@JsonProperty("base_hint_max_length")
|
||||||
|
private int baseHintMaxLength;
|
||||||
|
@JsonProperty("max_hint_max_length")
|
||||||
|
private int maxHintMaxLength;
|
||||||
|
@JsonProperty("base_end_guessing")
|
||||||
|
private boolean baseEndGuessing;
|
||||||
|
@JsonProperty("max_end_guessing")
|
||||||
|
private boolean maxEndGuessing;
|
||||||
|
@JsonProperty("base_only_one_green_green")
|
||||||
|
private boolean baseOnlyOneGreenGreen;
|
||||||
|
@JsonProperty("max_only_one_green_green")
|
||||||
|
private boolean maxOnlyOneGreenGreen;
|
||||||
|
@JsonProperty("base_sudden_death")
|
||||||
|
private boolean baseSuddenDeath;
|
||||||
|
@JsonProperty("max_sudden_death")
|
||||||
|
private boolean maxSuddenDeath;
|
||||||
|
@JsonProperty("base_hints_in_a_row")
|
||||||
|
private boolean baseHintsInARow;
|
||||||
|
@JsonProperty("max_hints_in_a_row")
|
||||||
|
private boolean maxHintsInARow;
|
||||||
|
|
||||||
|
@JsonProperty("base_unlocked")
|
||||||
|
private boolean baseUnlocked;
|
||||||
|
|
||||||
|
@JsonProperty("only_one_green_green_prevents_death")
|
||||||
|
private boolean onlyOneGreenGreenPreventsDeath;
|
||||||
|
|
||||||
|
public boolean isStructuredGrid(Inventory i) {
|
||||||
|
return this.baseStructuredGrid || this.maxStructuredGrid && i.isExtraStructuredGrid();
|
||||||
|
}
|
||||||
|
public boolean isAnyoneStarts(Inventory i) {
|
||||||
|
return this.baseAnyoneStarts || this.maxAnyoneStarts && i.isExtraAnyoneStarts();
|
||||||
|
}
|
||||||
|
public boolean isEndGuessing(Inventory i) {
|
||||||
|
return this.baseEndGuessing || this.maxEndGuessing && i.isExtraEndGuessing();
|
||||||
|
}
|
||||||
|
public boolean isOnlyOneGreenGreen(Inventory i) {
|
||||||
|
return this.baseOnlyOneGreenGreen || this.maxOnlyOneGreenGreen && i.isExtraOnlyOneGreenGreen();
|
||||||
|
}
|
||||||
|
public boolean isSuddenDeath(Inventory i) {
|
||||||
|
return this.baseSuddenDeath || this.maxSuddenDeath && i.isExtraSuddenDeath();
|
||||||
|
}
|
||||||
|
public boolean isOneMoreGuess(Inventory i) {
|
||||||
|
return this.baseOneMoreGuess || this.maxOneMoreGuess && i.isExtraOneMoreGuess();
|
||||||
|
}
|
||||||
|
public boolean isHintsInARow(Inventory i) {
|
||||||
|
return this.baseHintsInARow || this.maxHintsInARow && i.isExtraHintsInARow();
|
||||||
|
}
|
||||||
|
public int getHintCount(Inventory i) {
|
||||||
|
return Math.min(this.baseHintCount + i.getExtraHintCount(), this.maxHintCount);
|
||||||
|
}
|
||||||
|
public int getHintMaxLength(Inventory i) {
|
||||||
|
return Math.min(this.baseHintMaxLength + i.getExtraHintMaxLength(), this.maxHintMaxLength);
|
||||||
|
}
|
||||||
|
public Set<Integer> getHintWordCounts(Inventory i) {
|
||||||
|
HashSet<Integer> out = new HashSet<>();
|
||||||
|
out.addAll(baseHintWordCounts);
|
||||||
|
out.addAll(i.getExtraHintWordCounts());
|
||||||
|
out.retainAll(maxHintWordCounts);
|
||||||
|
return Collections.unmodifiableSet(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a GridConfig for the given inventory
|
||||||
|
*/
|
||||||
|
public GridConfig makeGridConfig(Inventory inv) {
|
||||||
|
return new GridConfig(
|
||||||
|
maxHintCount,
|
||||||
|
colorDistribution,
|
||||||
|
maxSuddenDeath,
|
||||||
|
maxOneMoreGuess,
|
||||||
|
onlyOneGreenGreenPreventsDeath,
|
||||||
|
isStructuredGrid(inv),
|
||||||
|
isAnyoneStarts(inv),
|
||||||
|
getHintCount(inv),
|
||||||
|
getHintWordCounts(inv),
|
||||||
|
isOneMoreGuess(inv),
|
||||||
|
getHintMaxLength(inv),
|
||||||
|
isEndGuessing(inv),
|
||||||
|
isOnlyOneGreenGreen(inv),
|
||||||
|
isSuddenDeath(inv),
|
||||||
|
isHintsInARow(inv)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the set of mutable options to be modified according to this new inventory
|
||||||
|
*/
|
||||||
|
public Map<MutableOption, Object> updateConfig(GridConfig b, Inventory i) {
|
||||||
|
Map<MutableOption, Object> out = new EnumMap<>(MutableOption.class);
|
||||||
|
int hintCount = this.getHintCount(i);
|
||||||
|
if(b.getMaxHint() != hintCount)
|
||||||
|
out.put(MutableOption.MAX_HINT, hintCount);
|
||||||
|
Set<Integer> availableHintWordCount = this.getHintWordCounts(i);
|
||||||
|
if(!b.getAvailableHintWordCount().containsAll(availableHintWordCount))
|
||||||
|
out.put(MutableOption.AVAILABLE_HINT_WORD_COUNT, availableHintWordCount);
|
||||||
|
int hintMaxLength = this.getHintMaxLength(i);
|
||||||
|
if(b.getHintMaxLength() != hintMaxLength)
|
||||||
|
out.put(MutableOption.HINT_MAX_LENGTH, hintMaxLength);
|
||||||
|
boolean oneMoreGuess = this.isOneMoreGuess(i);
|
||||||
|
if(b.isOneMoreGuess() != oneMoreGuess)
|
||||||
|
out.put(MutableOption.ONE_MORE_GUESS, oneMoreGuess);
|
||||||
|
boolean endGuessing = this.isEndGuessing(i);
|
||||||
|
if(b.isEndGuessing() != endGuessing)
|
||||||
|
out.put(MutableOption.END_GUESSING, endGuessing);
|
||||||
|
boolean onlyOneGreenGreen = this.isOnlyOneGreenGreen(i);
|
||||||
|
if(b.isOnlyOneGreenGreen() != onlyOneGreenGreen)
|
||||||
|
out.put(MutableOption.ONLY_ONE_GREEN_GREEN, onlyOneGreenGreen);
|
||||||
|
boolean suddenDeath = this.isSuddenDeath(i);
|
||||||
|
if(b.isSuddenDeath() != suddenDeath)
|
||||||
|
out.put(MutableOption.SUDDEN_DEATH, suddenDeath);
|
||||||
|
boolean hintsInARow = this.isHintsInARow(i);
|
||||||
|
if(b.isHintsInARow() != hintsInARow)
|
||||||
|
out.put(MutableOption.HINTS_IN_A_ROW, hintsInARow);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final WorldConfig zeroWithSize(ColorDistribution colors) {
|
||||||
|
return new WorldConfig(
|
||||||
|
colors,
|
||||||
|
1, 15,
|
||||||
|
false, true,
|
||||||
|
false, true,
|
||||||
|
Set.of(1), IntStream.range(-1, colors.anyGreenCount()+1).mapToObj(i->i).collect(Collectors.toSet()),
|
||||||
|
false, true,
|
||||||
|
3, 20,
|
||||||
|
false, true,
|
||||||
|
false, true,
|
||||||
|
false, true,
|
||||||
|
false, true,
|
||||||
|
true, false);
|
||||||
|
}
|
||||||
|
public static final WorldConfig classicalWithSize(ColorDistribution colors, int hintCount) {
|
||||||
|
int cardsToFind = colors.anyGreenCount();
|
||||||
|
Set<Integer> hintWordCounts = IntStream.range(-1, cardsToFind+1).mapToObj(i -> i).collect(Collectors.toSet());
|
||||||
|
return new WorldConfig(
|
||||||
|
colors,
|
||||||
|
hintCount, hintCount,
|
||||||
|
true, true,
|
||||||
|
true, true,
|
||||||
|
hintWordCounts, hintWordCounts,
|
||||||
|
true, true,
|
||||||
|
100, 100,
|
||||||
|
true, true,
|
||||||
|
true, true,
|
||||||
|
true, true,
|
||||||
|
false, false,
|
||||||
|
true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public World newWorld(Game g, String name) {
|
||||||
|
return new World(g, name, null, false, !baseUnlocked);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.bernard.nodecames.model.converter;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.bernard.nodecames.model.ColorDistribution;
|
||||||
|
|
||||||
|
import jakarta.persistence.AttributeConverter;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ColorDistributionConverter implements AttributeConverter<ColorDistribution, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String convertToDatabaseColumn(ColorDistribution attribute) {
|
||||||
|
return attribute.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ColorDistribution convertToEntityAttribute(String dbData) {
|
||||||
|
return ColorDistribution.ofString(dbData);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.bernard.nodecames.model.converter;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.bernard.nodecames.model.GameConfig;
|
||||||
|
import com.bernard.nodecames.model.WorldConfig;
|
||||||
|
|
||||||
|
import jakarta.persistence.AttributeConverter;
|
||||||
|
import jakarta.persistence.Converter;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import tools.jackson.databind.JsonNode;
|
||||||
|
import tools.jackson.databind.node.JsonNodeFactory;
|
||||||
|
import tools.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Converter
|
||||||
|
@Service
|
||||||
|
public class GameConfigConverter implements AttributeConverter<GameConfig, JsonNode> {
|
||||||
|
|
||||||
|
private final JsonNodeFactory jsn;
|
||||||
|
private final WorldConfigConverter wcc;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonNode convertToDatabaseColumn(GameConfig gc) {
|
||||||
|
ObjectNode o = jsn.objectNode();
|
||||||
|
for(Entry<String,WorldConfig> e : gc.getWorlds().entrySet())
|
||||||
|
o.set(e.getKey(), wcc.convertToDatabaseColumn(e.getValue()));
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GameConfig convertToEntityAttribute(JsonNode json) {
|
||||||
|
Map<String, WorldConfig> configs = json.asObject().propertyStream()
|
||||||
|
.collect(Collectors.toMap(Entry::getKey, e -> wcc.convertToEntityAttribute(e.getValue())));
|
||||||
|
return new GameConfig(configs);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.bernard.nodecames.model.converter;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import jakarta.persistence.AttributeConverter;
|
||||||
|
import jakarta.persistence.Converter;
|
||||||
|
import tools.jackson.databind.JsonNode;
|
||||||
|
import tools.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Converter
|
||||||
|
public class JsonNodeConverter implements AttributeConverter<JsonNode, String> {
|
||||||
|
|
||||||
|
ObjectMapper om = new ObjectMapper();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String convertToDatabaseColumn(JsonNode attribute) {
|
||||||
|
return om.writeValueAsString(attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonNode convertToEntityAttribute(String dbData) {
|
||||||
|
return om.readTree(dbData);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package com.bernard.nodecames.model.converter;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.bernard.nodecames.model.WorldConfig;
|
||||||
|
import com.bernard.nodecames.util.Utils;
|
||||||
|
|
||||||
|
import jakarta.persistence.AttributeConverter;
|
||||||
|
import jakarta.persistence.Converter;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import tools.jackson.databind.JsonNode;
|
||||||
|
import tools.jackson.databind.node.JsonNodeFactory;
|
||||||
|
import tools.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Converter
|
||||||
|
@Service
|
||||||
|
public class WorldConfigConverter implements AttributeConverter<WorldConfig, JsonNode> {
|
||||||
|
private final JsonNodeFactory jsn;
|
||||||
|
private final ColorDistributionConverter cdwdc;
|
||||||
|
|
||||||
|
public JsonNode hintWordCountsToJson(Set<Integer> hintWordCounts) {
|
||||||
|
return hintWordCounts.stream().sorted().map(jsn::numberNode).collect(Utils.jsonNodeCollector(jsn));
|
||||||
|
}
|
||||||
|
public Set<Integer> hintWordCountsOfJson(JsonNode json) {
|
||||||
|
return json.asArray().valueStream().map(JsonNode::intValue).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonNode convertToDatabaseColumn(WorldConfig wc) {
|
||||||
|
ObjectNode out = jsn.objectNode();
|
||||||
|
|
||||||
|
out.set("color_distribution", jsn.stringNode(cdwdc.convertToDatabaseColumn(wc.getColorDistribution())));
|
||||||
|
out.set("base_hint_count", jsn.numberNode(wc.getBaseHintCount()));
|
||||||
|
out.set("max_hint_count", jsn.numberNode(wc.getMaxHintCount()));
|
||||||
|
out.set("base_structured_grid", jsn.booleanNode(wc.isBaseStructuredGrid()));
|
||||||
|
out.set("max_structured_grid", jsn.booleanNode(wc.isMaxStructuredGrid()));
|
||||||
|
out.set("base_anyone_starts", jsn.booleanNode(wc.isBaseAnyoneStarts()));
|
||||||
|
out.set("max_anyone_starts", jsn.booleanNode(wc.isMaxAnyoneStarts()));
|
||||||
|
out.set("base_hint_word_counts", hintWordCountsToJson(wc.getBaseHintWordCounts()));
|
||||||
|
out.set("max_hint_word_counts", hintWordCountsToJson(wc.getMaxHintWordCounts()));
|
||||||
|
out.set("base_one_more_guess", jsn.booleanNode(wc.isBaseOneMoreGuess()));
|
||||||
|
out.set("max_one_more_guess", jsn.booleanNode(wc.isMaxOneMoreGuess()));
|
||||||
|
out.set("base_hint_max_length", jsn.numberNode(wc.getBaseHintMaxLength()));
|
||||||
|
out.set("max_hint_max_length", jsn.numberNode(wc.getMaxHintMaxLength()));
|
||||||
|
out.set("base_end_guessing", jsn.booleanNode(wc.isBaseEndGuessing()));
|
||||||
|
out.set("max_end_guessing", jsn.booleanNode(wc.isMaxEndGuessing()));
|
||||||
|
out.set("base_only_one_green_green", jsn.booleanNode(wc.isBaseOnlyOneGreenGreen()));
|
||||||
|
out.set("max_only_one_green_green", jsn.booleanNode(wc.isMaxOnlyOneGreenGreen()));
|
||||||
|
out.set("base_sudden_death", jsn.booleanNode(wc.isBaseSuddenDeath()));
|
||||||
|
out.set("max_sudden_death", jsn.booleanNode(wc.isMaxSuddenDeath()));
|
||||||
|
out.set("base_hints_in_a_row", jsn.booleanNode(wc.isBaseHintsInARow()));
|
||||||
|
out.set("max_hints_in_a_row", jsn.booleanNode(wc.isMaxHintsInARow()));
|
||||||
|
out.set("base_unlocked", jsn.booleanNode(wc.isBaseUnlocked()));
|
||||||
|
out.set("only_one_green_green_prevents_death", jsn.booleanNode(wc.isOnlyOneGreenGreenPreventsDeath()));
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WorldConfig convertToEntityAttribute(JsonNode json) {
|
||||||
|
ObjectNode o = json.asObject();
|
||||||
|
return new WorldConfig(
|
||||||
|
cdwdc.convertToEntityAttribute(o.get("color_distribution").stringValue()),
|
||||||
|
o.get("base_hint_count").intValue(),
|
||||||
|
o.get("max_hint_count").intValue(),
|
||||||
|
o.get("base_structured_grid").booleanValue(),
|
||||||
|
o.get("max_structured_grid").booleanValue(),
|
||||||
|
o.get("base_anyone starts").booleanValue(),
|
||||||
|
o.get("max_anyone_starts").booleanValue(),
|
||||||
|
hintWordCountsOfJson(o.get("base_hint_word_counts")),
|
||||||
|
hintWordCountsOfJson(o.get("max_hint_word_counts")),
|
||||||
|
o.get("base_one_more_guess").booleanValue(),
|
||||||
|
o.get("max_one_more_guess").booleanValue(),
|
||||||
|
o.get("base_hint_max_length").intValue(),
|
||||||
|
o.get("max_hint_max_length").intValue(),
|
||||||
|
o.get("base_end_guessing").booleanValue(),
|
||||||
|
o.get("max_end_guessing").booleanValue(),
|
||||||
|
o.get("base_only_one_green_green").booleanValue(),
|
||||||
|
o.get("max_only_one_green_green").booleanValue(),
|
||||||
|
o.get("base_sudden_death").booleanValue(),
|
||||||
|
o.get("max_sudden_death").booleanValue(),
|
||||||
|
o.get("base_hints_in_a_row").booleanValue(),
|
||||||
|
o.get("max_hints_in_a_row").booleanValue(),
|
||||||
|
o.get("base_unlocked").booleanValue(),
|
||||||
|
o.get("only_one_green_green_prevents_death").booleanValue()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.bernard.nodecames.util;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.BinaryOperator;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Collector;
|
||||||
|
|
||||||
|
import tools.jackson.databind.JsonNode;
|
||||||
|
import tools.jackson.databind.node.ArrayNode;
|
||||||
|
import tools.jackson.databind.node.JsonNodeFactory;
|
||||||
|
|
||||||
|
public class Utils {
|
||||||
|
|
||||||
|
private Utils() {}
|
||||||
|
|
||||||
|
public static final Collector<JsonNode,ArrayNode,ArrayNode> jsonNodeCollector(JsonNodeFactory jsn) {
|
||||||
|
return new Collector<JsonNode,ArrayNode,ArrayNode>() {
|
||||||
|
@Override
|
||||||
|
public Supplier<ArrayNode> supplier() {
|
||||||
|
return jsn::arrayNode;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public BiConsumer<ArrayNode, JsonNode> accumulator() {
|
||||||
|
return ArrayNode::add;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public BinaryOperator<ArrayNode> combiner() {
|
||||||
|
return (a,b) -> {
|
||||||
|
a.addAll(b);
|
||||||
|
return a;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Function<ArrayNode, ArrayNode> finisher() {
|
||||||
|
return Function.identity();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Set<Characteristics> characteristics() {
|
||||||
|
return EnumSet.of(Characteristics.IDENTITY_FINISH);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
alter table if exists game add column config json;
|
||||||
|
alter table if exists game add column inventory json;
|
||||||
|
alter table if exists grid rename column word_distribution to color_distribution;
|
||||||
|
|
||||||
|
alter table if exists world drop column anyone_starts;
|
||||||
|
alter table if exists world drop column available_hint_word_count;
|
||||||
|
alter table if exists world drop column end_guessing;
|
||||||
|
alter table if exists world drop column hint_max_length;
|
||||||
|
alter table if exists world drop column hints_inarow;
|
||||||
|
alter table if exists world drop column max_hint;
|
||||||
|
alter table if exists world drop column max_max_hint;
|
||||||
|
alter table if exists world drop column maybe_one_more_guess;
|
||||||
|
alter table if exists world drop column maybe_sudden_death;
|
||||||
|
alter table if exists world drop column one_more_guess;
|
||||||
|
alter table if exists world drop column only_one_green_green;
|
||||||
|
alter table if exists world drop column only_one_green_green_prevent_death;
|
||||||
|
alter table if exists world drop column structured_grid;
|
||||||
|
alter table if exists world drop column sudden_death;
|
||||||
|
alter table if exists world drop column word_distribution;
|
||||||
@@ -15,3 +15,7 @@ main{
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin-top: 115px;
|
margin-top: 115px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
main section {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
@@ -17,6 +17,10 @@
|
|||||||
<form method="GET" th:action="@{/create-game}">
|
<form method="GET" th:action="@{/create-game}">
|
||||||
<label for="create-game-name">Name : </label>
|
<label for="create-game-name">Name : </label>
|
||||||
<input type="text" id="create-game-name" name="name"/>
|
<input type="text" id="create-game-name" name="name"/>
|
||||||
|
<select name="type">
|
||||||
|
<option value="default">Default</option>
|
||||||
|
<option value="regular">Regular</option>
|
||||||
|
</select>
|
||||||
<input type="submit" value="Create game"/>
|
<input type="submit" value="Create game"/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
@@ -10,15 +10,26 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
<ul>
|
<section>
|
||||||
<li th:each="world : ${worlds}">
|
<h2>Worlds</h2><br/>
|
||||||
<!-- TODO remove this duplication of World span -->
|
<ul>
|
||||||
<span th:if="${world.locked}"> World <span th:text="${world.name}"/> </span>
|
<li th:each="world : ${worlds}">
|
||||||
<a th:if="!${world.locked}" th:href="@{/game/{gid}/grid/{wname}?player={player}(gid=${game.id},wname=${world.name},player=${player})}">World <span th:text="${world.name}"/></a>
|
<!-- TODO remove this duplication of World span -->
|
||||||
<a th:if="!${world.locked}" th:href="@{/game/{gid}/reset/{wname}?player={player}(gid=${game.id},wname=${world.name},player=${player})}">reset</a>
|
<span th:if="${world.locked}"> World <span th:text="${world.name}"/> </span>
|
||||||
<span th:if="${world.won}">Gagné !</span>
|
<a th:if="!${world.locked}" th:href="@{/game/{gid}/grid/{wname}?player={player}(gid=${game.id},wname=${world.name},player=${player})}">World <span th:text="${world.name}"/></a>
|
||||||
</li>
|
<a th:if="!${world.locked}" th:href="@{/game/{gid}/reset/{wname}?player={player}(gid=${game.id},wname=${world.name},player=${player})}">reset</a>
|
||||||
</ul>
|
<span th:if="${world.won}">Gagné !</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h2>Received Items</h2><br/>
|
||||||
|
<ul>
|
||||||
|
<li th:each="item : ${items}">
|
||||||
|
<span th:text="${item}"/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.bernard.nodecames;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import com.bernard.nodecames.model.GameConfig;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import tools.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
class JsonTests {
|
||||||
|
|
||||||
|
ObjectMapper om = new ObjectMapper();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void serializeGameConfig() {
|
||||||
|
GameConfig g = GameConfig.DEFAULT_GAME_CONFIG;
|
||||||
|
|
||||||
|
String json = om.writeValueAsString(g);
|
||||||
|
|
||||||
|
System.out.println("Got json : "+json);
|
||||||
|
|
||||||
|
GameConfig g2 = om.readValue(json, GameConfig.class);
|
||||||
|
|
||||||
|
assertEquals(g, g2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user