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 &
|
||||
'';
|
||||
|
||||
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 {
|
||||
packages.${system} = {
|
||||
default = self.packages.${system}.nodecames;
|
||||
@@ -56,6 +65,7 @@
|
||||
default = self.apps.${system}.nodecames;
|
||||
nodecames = { type = "app"; program = "${nodecames-launcher}/bin/nodecames"; };
|
||||
postgres-server = { type = "app"; program = "${postgres-server}/bin/postgres-server"; };
|
||||
archipelago = { type = "app"; program = "${test-archipelago}/bin/archipelago"; };
|
||||
};
|
||||
|
||||
devShells.${system} = {
|
||||
|
||||
@@ -31,6 +31,7 @@ import com.bernard.nodecames.game.GameManager;
|
||||
import com.bernard.nodecames.game.GridManager;
|
||||
import com.bernard.nodecames.game.IllegalGameActionException;
|
||||
import com.bernard.nodecames.model.Game;
|
||||
import com.bernard.nodecames.model.GameConfig;
|
||||
import com.bernard.nodecames.model.Grid;
|
||||
import com.bernard.nodecames.model.GridConfig;
|
||||
import com.bernard.nodecames.model.GridConfig.MutableOption;
|
||||
@@ -112,9 +113,9 @@ public class HttpController {
|
||||
public RedirectView createRoom(@RequestParam("type") String type) {
|
||||
GridConfig cfg = switch(type) {
|
||||
case "zero" -> GridConfig.ZERO;
|
||||
case "base" -> GridConfig.CODENAMES_CLASSICAL;
|
||||
case "big" -> GridConfig.BIG_CLASSICAL;
|
||||
default -> GridConfig.CODENAMES_CLASSICAL;
|
||||
case "base" -> GridConfig.REGULAR;
|
||||
case "big" -> GridConfig.BIG;
|
||||
default -> GridConfig.REGULAR;
|
||||
};
|
||||
Random r = new Random(rand.nextLong());
|
||||
Grid g = gm.newGrid(null, cfg, r);
|
||||
@@ -122,8 +123,13 @@ public class HttpController {
|
||||
}
|
||||
|
||||
@GetMapping("/create-game")
|
||||
public RedirectView createGame(@RequestParam("name") String name) {
|
||||
Game g = wm.createGame(name);
|
||||
public RedirectView createGame(@RequestParam("name") String name, @RequestParam("type") String type) {
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -143,6 +149,7 @@ public class HttpController {
|
||||
.toList();
|
||||
m.addAttribute("game", g.get());
|
||||
m.addAttribute("worlds", worlds);
|
||||
m.addAttribute("items", g.get().getInventory().getReceivedItems());
|
||||
m.addAttribute("player", player);
|
||||
return "map";
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.bernard.nodecames.frontend;
|
||||
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
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.Phase;
|
||||
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.converter.ColorDistributionConverter;
|
||||
import com.bernard.nodecames.repository.CardRepository;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
import tools.jackson.databind.JsonNode;
|
||||
import tools.jackson.databind.node.ArrayNode;
|
||||
import tools.jackson.databind.node.JsonNodeFactory;
|
||||
import tools.jackson.databind.node.ObjectNode;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class JsonDataService {
|
||||
|
||||
private final JsonNodeFactory jsn;
|
||||
private final CardRepository cRepository;
|
||||
private final ColorDistributionConverter wdc;
|
||||
|
||||
public JsonNode cardData(Card c, char player) {
|
||||
ObjectNode cardNode = jsn.objectNode();
|
||||
@@ -43,15 +43,7 @@ public class JsonDataService {
|
||||
gcNode.set("max-max-hint", jsn.numberNode(gc.getMaxMaxHint()));
|
||||
gcNode.set("structured-grid", jsn.booleanNode(gc.isStructuredGrid()));
|
||||
gcNode.set("anyone-starts", jsn.booleanNode(gc.isAnyoneStarts()));
|
||||
ArrayNode wordDistributionNode = jsn.arrayNode(gc.getWordDistribution().size());
|
||||
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("color-distribution", jsn.stringNode(wdc.convertToDatabaseColumn(gc.getColorDistribution())));
|
||||
gcNode.set("maybe-sudden-death", jsn.booleanNode(gc.isMaybeSuddenDeath()));
|
||||
gcNode.set("maybe-one-more-guess", jsn.booleanNode(gc.isMaybeOneMoreGuess()));
|
||||
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 com.bernard.nodecames.model.Game;
|
||||
import com.bernard.nodecames.model.GridConfig.MutableOption;
|
||||
|
||||
import io.github.archipelagomw.APResult.ResultCode;
|
||||
import io.github.archipelagomw.Client;
|
||||
@@ -37,57 +36,11 @@ public class ArchipelagoClient extends Client {
|
||||
}
|
||||
|
||||
@ArchipelagoEventListener
|
||||
public void onReceiveItem(ReceiveItemEvent e) throws IllegalGameActionException {
|
||||
public void onReceiveItem(ReceiveItemEvent e) {
|
||||
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());
|
||||
}
|
||||
}
|
||||
if(e.getIndex() > game.getInventory().getReceivedItems().size())
|
||||
gm.giveItem(game, e.getItemName());
|
||||
// Else, the item has already been received
|
||||
}
|
||||
|
||||
@Getter
|
||||
|
||||
@@ -2,27 +2,28 @@ 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.Map.Entry;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
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.GameConfig;
|
||||
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.Inventory;
|
||||
import com.bernard.nodecames.model.World;
|
||||
import com.bernard.nodecames.model.World.WorldId;
|
||||
import com.bernard.nodecames.observer.GridObserver;
|
||||
@@ -42,6 +43,9 @@ public class GameManager {
|
||||
private final GameRepository gRepository;
|
||||
private final WorldRepository wRepository;
|
||||
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(GameManager.class);
|
||||
|
||||
//TODO try putting "World" here instead of WorldId
|
||||
private Map<WorldId, Set<WorldObserver>> observers = new HashMap<>();
|
||||
|
||||
@@ -73,15 +77,9 @@ public class GameManager {
|
||||
forEachObserver(w, o -> gm.addObserver(g, o));
|
||||
}
|
||||
|
||||
public Game createGame(String name) {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
public Game createGame(String name, GameConfig cfg) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -97,7 +95,7 @@ public class GameManager {
|
||||
if(w.isLocked())
|
||||
//TODO separate IllegaleGameAction and IllegalGridAction
|
||||
throw new IllegalGameActionException(null, "Cannot create a grid in a locked world");
|
||||
GridConfig gc = w.getInitialConfig();
|
||||
GridConfig gc = w.makeGridConfig();
|
||||
//TODO seed random
|
||||
Grid grid = gm.newGrid(w, gc, new Random());
|
||||
|
||||
@@ -120,76 +118,23 @@ public class GameManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
public void giveItem(Game g, String item) {
|
||||
g.getInventory().receiveItem(item);
|
||||
Inventory inv = g.getInventory();
|
||||
for(World w : allWorlds(g)) {
|
||||
if(w.getCurrentGrid() == null) continue;
|
||||
Grid grid = w.getCurrentGrid();
|
||||
if(grid != null) {
|
||||
gm.unlock(grid, opt, o);
|
||||
Map<MutableOption, Object> updates = w.getConfig().updateConfig(grid.getConfig(), inv);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
gRepository.save(g);
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
|
||||
@@ -20,11 +20,11 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
import com.bernard.nodecames.model.Card;
|
||||
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.Grid;
|
||||
import com.bernard.nodecames.model.Grid.Phase;
|
||||
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.Hint;
|
||||
import com.bernard.nodecames.model.World;
|
||||
@@ -147,13 +147,13 @@ public class GridManager {
|
||||
|
||||
public Grid newGrid(@Nullable World world, GridConfig gc, Random r) {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
String[] words = dictionnaries.randomWords(gc.getWordCount(), r);
|
||||
List<CardColors> origDistrib = gc.getWordDistributionAsList();
|
||||
String[] words = dictionnaries.randomWords(gc.getColorDistribution().wordCount(), r);
|
||||
List<CardColors> origDistrib = gc.getColorDistribution().asList();
|
||||
List<CardColors> distrib;
|
||||
if(!gc.isStructuredGrid()) {
|
||||
List<Color> bColors = origDistrib.stream().map(cc -> cc.getFaceB()).collect(Collectors.toList());
|
||||
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)))
|
||||
.toList();
|
||||
} else {
|
||||
@@ -400,7 +400,10 @@ public class GridManager {
|
||||
if (!newAvailableHintWordCounts.containsAll(g.getConfig().getAvailableHintWordCount())
|
||||
|| g.getConfig().getAvailableHintWordCount().containsAll(newAvailableHintWordCounts)) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.hibernate.annotations.JdbcTypeCode;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import lombok.AllArgsConstructor;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@@ -21,20 +22,22 @@ public class Game {
|
||||
@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.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.hibernate.annotations.JdbcTypeCode;
|
||||
@@ -11,18 +10,17 @@ 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.Converter;
|
||||
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;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import tools.jackson.databind.JsonNode;
|
||||
import tools.jackson.databind.node.ArrayNode;
|
||||
@@ -41,7 +39,6 @@ public class Grid {
|
||||
@ManyToOne(optional = true)
|
||||
World w;
|
||||
|
||||
//@Type(JsonType.class)
|
||||
@Column(columnDefinition = "json")
|
||||
@Convert(converter = GameEventListAdapter.class)
|
||||
@JdbcTypeCode(SqlTypes.JSON)
|
||||
@@ -162,10 +159,11 @@ public class Grid {
|
||||
}
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@RequiredArgsConstructor
|
||||
@Converter
|
||||
public static class GameEventListAdapter implements AttributeConverter<List<GameEvent>, JsonNode> {
|
||||
|
||||
JsonNodeFactory jsn;
|
||||
private final JsonNodeFactory jsn;
|
||||
|
||||
@Override
|
||||
public JsonNode convertToDatabaseColumn(List<GameEvent> attribute) {
|
||||
|
||||
@@ -1,28 +1,17 @@
|
||||
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.Entry;
|
||||
import java.util.Set;
|
||||
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.Converter;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.Value;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@@ -30,55 +19,19 @@ import lombok.Value;
|
||||
@Embeddable
|
||||
public class GridConfig {
|
||||
|
||||
public static final Map<CardColors, Integer> CUSTOM6 =
|
||||
makeDistribution(0, 0, 2, 1, 1, 0);
|
||||
public static final Map<CardColors, Integer> CUSTOM9 =
|
||||
makeDistribution(0, 1, 1, 2, 2, 1);
|
||||
public static final Map<CardColors, Integer> CUSTOM12 =
|
||||
makeDistribution(1, 1, 1, 2, 3, 1);
|
||||
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);
|
||||
}
|
||||
public static final GridConfig ZERO =
|
||||
WorldConfig.zeroWithSize(ColorDistribution.CLASSICAL25).makeGridConfig(Inventory.empty());
|
||||
public static final GridConfig REGULAR =
|
||||
WorldConfig.classicalWithSize(ColorDistribution.CLASSICAL25, 9).makeGridConfig(Inventory.empty());
|
||||
public static final GridConfig BIG =
|
||||
WorldConfig.classicalWithSize(ColorDistribution.CLASSICAL25, 9).makeGridConfig(Inventory.empty());
|
||||
|
||||
// Immutable options
|
||||
// Maximum of maximom number of hints
|
||||
// This is to trigger end of game
|
||||
private int maxMaxHint;
|
||||
@Convert(converter = WordDistributionConverter.class)
|
||||
private Map<CardColors, Integer> wordDistribution;
|
||||
@Convert(converter = ColorDistributionConverter.class)
|
||||
private ColorDistribution colorDistribution;
|
||||
private boolean maybeSuddenDeath; //TODO
|
||||
private boolean maybeOneMoreGuess; //TODO
|
||||
// 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 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) {
|
||||
try{
|
||||
switch(opt) {
|
||||
@@ -166,48 +105,9 @@ public class GridConfig {
|
||||
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
|
||||
@Getter
|
||||
//TODO do record type
|
||||
public enum MutableOption {
|
||||
MAX_HINT(Integer.class),
|
||||
AVAILABLE_HINT_WORD_COUNT(Set.class),
|
||||
@@ -220,52 +120,4 @@ public class GridConfig {
|
||||
|
||||
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
|
||||
Game game;
|
||||
|
||||
// Config used when resetting this world
|
||||
GridConfig initialConfig;
|
||||
|
||||
@ManyToOne(optional = true)
|
||||
Grid currentGrid;
|
||||
|
||||
@@ -36,10 +33,9 @@ public class World {
|
||||
|
||||
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.game = g;
|
||||
this.initialConfig = initConfig;
|
||||
this.currentGrid = currentGrid;
|
||||
this.won = won;
|
||||
this.locked = locked;
|
||||
@@ -49,6 +45,15 @@ public class World {
|
||||
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
|
||||
@NoArgsConstructor
|
||||
@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;
|
||||
margin-top: 115px;
|
||||
}
|
||||
|
||||
main section {
|
||||
padding: 1em;
|
||||
}
|
||||
@@ -17,6 +17,10 @@
|
||||
<form method="GET" th:action="@{/create-game}">
|
||||
<label for="create-game-name">Name : </label>
|
||||
<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"/>
|
||||
</form>
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<section>
|
||||
<h2>Worlds</h2><br/>
|
||||
<ul>
|
||||
<li th:each="world : ${worlds}">
|
||||
<!-- TODO remove this duplication of World span -->
|
||||
@@ -19,6 +21,15 @@
|
||||
<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>
|
||||
</body>
|
||||
</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