diff --git a/src/main/java/com/bernard/nodecames/HttpController.java b/src/main/java/com/bernard/nodecames/HttpController.java index b593e9c..52b4f38 100644 --- a/src/main/java/com/bernard/nodecames/HttpController.java +++ b/src/main/java/com/bernard/nodecames/HttpController.java @@ -3,9 +3,12 @@ package com.bernard.nodecames; import java.util.Comparator; import java.util.List; import java.util.Map.Entry; +import java.util.Optional; import java.util.Random; import java.util.UUID; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; @@ -18,14 +21,17 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.view.RedirectView; import com.bernard.nodecames.frontend.GridConfigEdit; +import com.bernard.nodecames.game.GameManager; import com.bernard.nodecames.game.GridManager; import com.bernard.nodecames.game.IllegalGameActionException; -import com.bernard.nodecames.game.GameManager; import com.bernard.nodecames.model.Game; import com.bernard.nodecames.model.Grid; import com.bernard.nodecames.model.GridConfig; import com.bernard.nodecames.model.GridConfig.MutableOption; import com.bernard.nodecames.model.World; +import com.bernard.nodecames.repository.GameRepository; +import com.bernard.nodecames.repository.GridRepository; +import com.bernard.nodecames.repository.WorldRepository; import lombok.RequiredArgsConstructor; @@ -35,6 +41,9 @@ public class HttpController { private final GridManager gm; private final GameManager wm; + private final GridRepository gridRepository; + private final GameRepository gRepository; + private final WorldRepository wRepository; private final Random rand = new Random(); @GetMapping("/") @@ -45,33 +54,43 @@ public class HttpController { } @GetMapping("/room/{id}/grid") - public String grid(@PathVariable("id") String id, @RequestParam(value = "player", required = false) String player, Model m) { + public Object grid(@PathVariable("id") String id, @RequestParam(value = "player", required = false) String player, Model m) { if(player == null) { m.addAttribute("redirect", "/room/"+id+"/grid"); return "player-select"; } - Grid grid = gm.findGrid(id); + Optional grid = gridRepository.findById(UUID.fromString(id)); + if(!grid.isPresent()) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } m.addAttribute("player", player); - m.addAttribute("grid", grid); + m.addAttribute("grid", grid.get()); return "grid"; } @GetMapping("/room/{id}/config") - public String gridConfig(@PathVariable("id") String id, Model model) { - GridConfig gc = gm.findGrid(id).getConfig(); + public Object gridConfig(@PathVariable("id") String id, Model model) { + Optional grid = gridRepository.findById(UUID.fromString(id)); + if(!grid.isPresent()) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + GridConfig gc = grid.get().getConfig(); model.addAttribute("gridConfig", GridConfigEdit.of(gc)); return "grid-config"; } @PostMapping("/room/{id}/config") - public String setGridConfig(@PathVariable("id") String id, + public Object setGridConfig(@PathVariable("id") String id, @ModelAttribute("gridConfig") GridConfigEdit gce, BindingResult br, Model model ) { - Grid g = gm.findGrid(id); - for(Entry e : gce.getModifications(g.getConfig()).entrySet()) { + Optional grid = gridRepository.findById(UUID.fromString(id)); + if(!grid.isPresent()) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + for(Entry e : gce.getModifications(grid.get().getConfig()).entrySet()) { try { - gm.unlock(g, e.getKey(), e.getValue()); + gm.unlock(grid.get(), e.getKey(), e.getValue()); } catch (IllegalGameActionException ige) { br.addError(new ObjectError(e.getKey().name(), ige.getMessage())); } @@ -80,34 +99,34 @@ public class HttpController { return "grid-config"; } - - @GetMapping("/create-room") public RedirectView createRoom() { Random r = new Random(rand.nextLong()); - Grid g = gm.newGrid(GridConfig.ZERO, r); + Grid g = gm.newGrid(null, GridConfig.ZERO, r); return new RedirectView("/room/"+g.getId()+"/grid"); } + @GetMapping("/create-game") public RedirectView createGame(@RequestParam("name") String name) { - UUID uuid = wm.createGame(name); - return new RedirectView("/game/"+uuid.toString()+"/map"); + Game g = wm.createGame(name); + return new RedirectView("/game/"+g.getId().toString()+"/map"); } @GetMapping("/game/{id}/map") - public String getMap(@PathVariable("id") String id, @RequestParam(value = "player", required = false) String player, Model m) { + public Object getMap(@PathVariable("id") String id, @RequestParam(value = "player", required = false) String player, Model m) { if(player == null) { m.addAttribute("redirect", "/game/"+id+"/map"); return "player-select"; } - Game g = wm.getGame(id); - List worlds = g.getWorlds() - .entrySet() + Optional g = gRepository.findById(UUID.fromString(id)); + if(!g.isPresent()) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + List worlds = wm.allWorlds(g.get()) .stream() - .map(Entry::getValue) .sorted(Comparator.comparing(World::getName)) .toList(); - m.addAttribute("game", g); + m.addAttribute("game", g.get()); m.addAttribute("worlds", worlds); m.addAttribute("player", player); return "map"; @@ -115,15 +134,29 @@ public class HttpController { @GetMapping("/game/{id}/grid/{world}") public Object gridRedirect(@PathVariable("id") String id, @PathVariable("world") String world, @RequestParam("player") String player) { - Game g = wm.getGame(id); - Grid grid = wm.getGrid(g, world); + Optional g = gRepository.findById(UUID.fromString(id)); + if(!g.isPresent()) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + Optional w = wRepository.findByGameAndId_Name(g.get(), world); + if(!w.isPresent()) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + Grid grid = wm.getGrid(g.get(), w.get()); return new RedirectView("/room/"+grid.getId()+"/grid" + "?player="+player); } @GetMapping("/game/{id}/reset/{world}") public Object resetWorld(@PathVariable("id") String id, @PathVariable("world") String world, @RequestParam("player") String player) { - Game g = wm.getGame(id); - Grid newGrid = wm.resetGrid(g, world); + Optional g = gRepository.findById(UUID.fromString(id)); + if(!g.isPresent()) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + Optional w = wRepository.findByGameAndId_Name(g.get(), world); + if(!w.isPresent()) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + Grid newGrid = wm.resetGrid(g.get(), w.get()); return new RedirectView("/room/"+newGrid.getId()+"/grid" + "?player="+player); } } diff --git a/src/main/java/com/bernard/nodecames/NodecamesApplication.java b/src/main/java/com/bernard/nodecames/NodecamesApplication.java index 32a769a..860eb39 100644 --- a/src/main/java/com/bernard/nodecames/NodecamesApplication.java +++ b/src/main/java/com/bernard/nodecames/NodecamesApplication.java @@ -2,6 +2,9 @@ package com.bernard.nodecames; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +import tools.jackson.databind.node.JsonNodeFactory; @SpringBootApplication public class NodecamesApplication { @@ -10,4 +13,9 @@ public class NodecamesApplication { SpringApplication.run(NodecamesApplication.class, args); } + @Bean + public JsonNodeFactory theJsonNodeFactory() { + return JsonNodeFactory.instance; + } + } diff --git a/src/main/java/com/bernard/nodecames/frontend/JsonDataService.java b/src/main/java/com/bernard/nodecames/frontend/JsonDataService.java index f7b2a3c..d2916de 100644 --- a/src/main/java/com/bernard/nodecames/frontend/JsonDataService.java +++ b/src/main/java/com/bernard/nodecames/frontend/JsonDataService.java @@ -1,28 +1,31 @@ package com.bernard.nodecames.frontend; +import java.util.Map.Entry; import java.util.Set; -import java.util.stream.IntStream; import org.springframework.stereotype.Service; import com.bernard.nodecames.model.Card; 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.Grid.Phase; -import com.bernard.nodecames.model.Hint; +import com.bernard.nodecames.repository.CardRepository; +import lombok.RequiredArgsConstructor; 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 public class JsonDataService { - private static final JsonNodeFactory jsn = JsonNodeFactory.instance; + private final JsonNodeFactory jsn; + private final CardRepository cRepository; public JsonNode cardData(Card c, char player) { ObjectNode cardNode = jsn.objectNode(); @@ -41,10 +44,11 @@ public class JsonDataService { gcNode.set("structured-grid", jsn.booleanNode(gc.isStructuredGrid())); gcNode.set("anyone-starts", jsn.booleanNode(gc.isAnyoneStarts())); ArrayNode wordDistributionNode = jsn.arrayNode(gc.getWordDistribution().size()); - for(CardColors c : gc.getWordDistribution()) { + for(Entry e : gc.getWordDistribution().entrySet()) { ObjectNode ccNode = jsn.objectNode(); - ccNode.set("face-a", jsn.stringNode(c.getFaceA().name())); - ccNode.set("face-b", jsn.stringNode(c.getFaceB().name())); + 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); @@ -102,35 +106,8 @@ public class JsonDataService { return config; } - public JsonNode eventData(Grid g, GameEvent ge) { - ObjectNode geNode = jsn.objectNode(); - geNode.set("type", jsn.stringNode(ge.getType().name().toLowerCase())); - geNode.set("issuer", jsn.stringNode(Character.toString(ge.getIssuer()))); - switch (ge.getType()) { - case GameEvent.Type.HINT: - Hint hint = (Hint)ge.getData(); - geNode.set("word", jsn.stringNode(hint.getWord())); - geNode.set("wordCount", jsn.numberNode(hint.getWordCount())); - break; - case GameEvent.Type.GUESS: - Card c = (Card)ge.getData(); - Integer pos = IntStream.range(0, g.getCardCount()) - .filter(i -> c == g.getCards()[i]) - .findFirst() - .getAsInt(); - geNode.set("card-index", jsn.numberNode(pos)); - geNode.set("color", jsn.stringNode(((ge.getIssuer()=='A')?c.getColorB():c.getColorA()).name().toLowerCase())); - break; - case GameEvent.Type.END_GUESSING: - geNode.set("manual", jsn.booleanNode((Boolean)ge.getData())); - break; - case GameEvent.Type.GAME_END: - geNode.set("win", jsn.booleanNode((Boolean)ge.getData())); - break; - case GameEvent.Type.SUDDEN_DEATH: - break; - } - return geNode; + public JsonNode eventData(GameEvent ge) { + return GameEvent.toJsonNode(ge); } public JsonNode phaseData(Phase p) { @@ -148,18 +125,19 @@ public class JsonDataService { public JsonNode gridData(Grid g, char player) { - ArrayNode cardsNode = jsn.arrayNode(g.getCardCount()); - for (int i = 0; i < g.getCardCount(); i++) - cardsNode.add(cardData(g.getCards()[i], player)); + Card[] cards = cRepository.findByGridOrderById_Index(g); + ArrayNode cardsNode = jsn.arrayNode(cards.length); + for (int i = 0; i < cards.length; i++) + cardsNode.add(cardData(cards[i], player)); ArrayNode eventsNode = jsn.arrayNode(g.getGameEvents().size()); for(GameEvent ge : g.getGameEvents()) - eventsNode.add(eventData(g, ge)); + eventsNode.add(eventData(ge)); ObjectNode currentHintNode = jsn.objectNode(); if(g.getCurrentHint() != null) { currentHintNode.set("word", jsn.stringNode(g.getCurrentHint().getWord())); - currentHintNode.set("wordCount", jsn.numberNode(g.getCurrentHint().getWordCount())); + currentHintNode.set("word-count", jsn.numberNode(g.getCurrentHint().getWordCount())); } ObjectNode out = jsn.objectNode(); diff --git a/src/main/java/com/bernard/nodecames/frontend/WebSocketController.java b/src/main/java/com/bernard/nodecames/frontend/WebSocketController.java index 3f15fa0..6190572 100644 --- a/src/main/java/com/bernard/nodecames/frontend/WebSocketController.java +++ b/src/main/java/com/bernard/nodecames/frontend/WebSocketController.java @@ -1,6 +1,8 @@ package com.bernard.nodecames.frontend; import java.util.Map; +import java.util.Optional; +import java.util.UUID; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -18,26 +20,29 @@ import com.bernard.nodecames.model.Card; import com.bernard.nodecames.model.GameEvent; import com.bernard.nodecames.model.Grid; import com.bernard.nodecames.model.GridConfig.MutableOption; +import com.bernard.nodecames.repository.GridRepository; import tools.jackson.databind.JsonNode; @Controller public class WebSocketController { - private GridManager gm; - private SimpMessagingTemplate template; - private JsonDataService json; + private final GridManager gm; + private final SimpMessagingTemplate template; + private final JsonDataService json; + private final GridRepository gRepository; - public WebSocketController(GridManager gm, SimpMessagingTemplate template, JsonDataService json) { + public WebSocketController(GridManager gm, SimpMessagingTemplate template, JsonDataService json, GridRepository gRepository) { this.gm = gm; this.template = template; this.json = json; + this.gRepository = gRepository; gm.setWsc(this); } public void onNewEvent(Grid g, GameEvent ge) { this.template.convertAndSend("/topic/new-event", - json.eventData(g, ge) + json.eventData(ge) ); } @@ -54,17 +59,16 @@ public class WebSocketController { ); } - public void publishCardUpdateToPlayer(Grid g, int cardIndex, char player) { - Card c = g.getCards()[cardIndex]; + public void publishCardUpdateToPlayer(Grid g, Card c, char player) { this.template.convertAndSend( "/topic/update-card/" + Character.toLowerCase(player), json.cardData(c, player), - Map.of("cardIndex", cardIndex) + Map.of("cardIndex", c.getIndex()) ); } - public void publishCardUpdate(Grid g, int cardIndex) { - publishCardUpdateToPlayer(g, cardIndex, 'A'); - publishCardUpdateToPlayer(g, cardIndex, 'B'); + public void publishCardUpdate(Grid g, Card card) { + publishCardUpdateToPlayer(g, card, 'A'); + publishCardUpdateToPlayer(g, card, 'B'); } public void publishError(char issuer, IllegalGameActionException e) { @@ -81,20 +85,25 @@ public class WebSocketController { else throw new IllegalArgumentException("Unknown player type"); - Grid g = gm.findGrid(gridId); - - return new ResponseEntity<>(json.gridData(g, player), HttpStatus.OK); + Optional g = gRepository.findById(UUID.fromString(gridId)); + if(g.isPresent()) + return new ResponseEntity<>(json.gridData(g.get(), player), HttpStatus.OK); + else + return new ResponseEntity<>("Could not find grid with given ID",HttpStatus.NOT_FOUND); } @MessageMapping("submit-hint") public void submitHint(@Header("room") String roomId, @Header("player") String player, @Payload JsonNode content) { String hint = content.asObject().get("hint").stringValue(); int wordCount = content.asObject().get("hintWordCount").intValue(); - - Grid g = gm.findGrid(roomId); + + Optional g = gRepository.findById(UUID.fromString(roomId)); + if(!g.isPresent()) + // 404 + return; try { - gm.proposeHint(g, player.charAt(0), hint, wordCount); + gm.proposeHint(g.get(), player.charAt(0), hint, wordCount); } catch (IllegalGameActionException e) { publishError(player.charAt(0), e); e.printStackTrace(); @@ -104,11 +113,15 @@ public class WebSocketController { @MessageMapping("point-card") public void pointCard(@Header("room") String roomId, @Header("player") String player, @Payload JsonNode content) { int cardIndex = content.asObject().get("cardIndex").intValue(); - - Grid g = gm.findGrid(roomId); + + Optional g = gRepository.findById(UUID.fromString(roomId)); + if(!g.isPresent()) + // 404 + return; + try { - gm.pointCard(g, player.charAt(0), cardIndex); - publishCardUpdate(g, cardIndex); + gm.pointCard(g.get(), player.charAt(0), cardIndex); + publishCardUpdate(g.get(), gm.getCard(g.get(), cardIndex)); } catch (IllegalGameActionException e) { publishError(player.charAt(0), e); e.printStackTrace(); @@ -117,9 +130,13 @@ public class WebSocketController { @MessageMapping("end-guessing") public void endGuessing(@Header("room") String roomId, @Header("player") String player) { - Grid g = gm.findGrid(roomId); + Optional g = gRepository.findById(UUID.fromString(roomId)); + if(!g.isPresent()) + // 404 + return; + try { - gm.endGuessing(g, player.charAt(0)); + gm.endGuessing(g.get(), player.charAt(0)); } catch (IllegalGameActionException e) { publishError(player.charAt(0), e); e.printStackTrace(); @@ -128,9 +145,12 @@ public class WebSocketController { @MessageMapping("enter-sudden-death") public void enterSuddenDeath(@Header("room") String roomId, @Header("player") String player) { - Grid g = gm.findGrid(roomId); + Optional g = gRepository.findById(UUID.fromString(roomId)); + if(!g.isPresent()) + // 404 + return; try { - gm.enterSuddenDeath(g); + gm.enterSuddenDeath(g.get()); } catch (IllegalGameActionException e) { publishError(player.charAt(0), e); e.printStackTrace(); diff --git a/src/main/java/com/bernard/nodecames/game/GameManager.java b/src/main/java/com/bernard/nodecames/game/GameManager.java index aa3c8ad..8b1ed33 100644 --- a/src/main/java/com/bernard/nodecames/game/GameManager.java +++ b/src/main/java/com/bernard/nodecames/game/GameManager.java @@ -3,9 +3,10 @@ package com.bernard.nodecames.game; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Map.Entry; import java.util.Random; +import java.util.Set; import java.util.UUID; -import java.util.stream.Collectors; import org.springframework.stereotype.Service; @@ -13,80 +14,77 @@ import com.bernard.nodecames.model.Game; import com.bernard.nodecames.model.Grid; import com.bernard.nodecames.model.GridConfig; import com.bernard.nodecames.model.World; +import com.bernard.nodecames.repository.GameRepository; +import com.bernard.nodecames.repository.WorldRepository; + +import jakarta.validation.constraints.NotNull; @Service public class GameManager { - - Map games = new HashMap<>(); - Map randoms = new HashMap<>(); // Reverse map allowing to pass grid events to the game Map currentGrids = new HashMap<>(); private final GridManager gm; + private final GameRepository gRepository; + private final WorldRepository wRepository; - public GameManager(GridManager gm) { + public GameManager(GridManager gm, GameRepository gRepository, WorldRepository wRepository) { this.gm = gm; + this.gRepository = gRepository; + this.wRepository = wRepository; gm.setWm(this); } - public UUID createGame(String name) { + public Game createGame(String name) { UUID uuid = UUID.randomUUID(); - randoms.put(uuid, new Random()); - Game g = new Game(uuid, name, null); - Map worlds = Game.DEFAULT_WORLDS_CONFIG - .entrySet() - .stream() - .collect(Collectors.toMap( - Map.Entry::getKey, - e -> new World(g, e.getKey(), e.getValue(), null, false) - )); - g.setWorlds(worlds); + Game g = new Game(uuid, name); + gRepository.save(g); + for (Entry wConfig : Game.DEFAULT_WORLDS_CONFIG.entrySet()) { + World w = new World(g, wConfig.getKey(), wConfig.getValue(), null, false); + wRepository.save(w); + } - - games.put(uuid, g); - - return uuid; + return g; } public Collection allGames() { - return games.values(); + return gRepository.findAll(); + } + + public Set allWorlds(@NotNull Game g) { + return wRepository.findByGame(g); } - private Grid newGrid(Game g, String world) { - - World w = g.getWorld(world); + private Grid newGrid(Game g, World w) { GridConfig gc = w.getInitialConfig(); - Grid grid = gm.newGrid(gc, new Random(randoms.get(g.getId()).nextLong())); + Grid grid = gm.newGrid(w, gc, new Random()); w.setCurrentGrid(grid); + wRepository.save(w); currentGrids.put(grid.getId(), w); return grid; } - public Grid resetGrid(Game g, String world) { + public Grid resetGrid(Game g, World world) { return newGrid(g, world); } - public Grid getGrid(Game g, String world) { - World w = g.getWorld(world); + public Grid getGrid(Game g, World w) { if(w.getCurrentGrid() != null) { return w.getCurrentGrid(); } else { - return newGrid(g, world); + return newGrid(g, w); } } - public Game getGame(String id) { - return games.get(UUID.fromString(id)); - } - public void onGameWon(Grid g) { World w = currentGrids.get(g.getId()); if(w != null) { w.setWon(true); + wRepository.save(w); } } } diff --git a/src/main/java/com/bernard/nodecames/game/GridManager.java b/src/main/java/com/bernard/nodecames/game/GridManager.java index 7e2d04d..5e9048a 100644 --- a/src/main/java/com/bernard/nodecames/game/GridManager.java +++ b/src/main/java/com/bernard/nodecames/game/GridManager.java @@ -2,9 +2,7 @@ package com.bernard.nodecames.game; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Random; import java.util.Set; @@ -18,15 +16,19 @@ import org.springframework.stereotype.Service; import com.bernard.nodecames.frontend.WebSocketController; import com.bernard.nodecames.model.Card; -import com.bernard.nodecames.model.Card.Color; import com.bernard.nodecames.model.GameEvent; +import com.bernard.nodecames.model.Card.Color; 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; +import com.bernard.nodecames.repository.CardRepository; +import com.bernard.nodecames.repository.GridRepository; +import jakarta.annotation.Nullable; import lombok.RequiredArgsConstructor; import lombok.Setter; @@ -34,40 +36,67 @@ import lombok.Setter; @RequiredArgsConstructor public class GridManager { - //TODO Remove static, make something more Spring Boot-y - private static Map games = new HashMap<>(); - private static Map randoms = new HashMap<>(); - @Setter private WebSocketController wsc = null; @Setter private GameManager wm = null; + private final GridRepository gRepository; + private final CardRepository cRepository; + private final DictionnariesService dictionnaries; private static final Logger log = LoggerFactory.getLogger(GridManager.class); private void newGameEvent(Grid g, GameEvent ge) { - g.newGameEvent(ge); log.debug("New game event : {}", ge); + g.newGameEvent(ge); + gRepository.save(g); wsc.onNewEvent(g, ge); } private void setPhase(Grid g, Phase phase) { - g.setPhase(phase); log.debug("New phase : {}", phase.name()); + g.setPhase(phase); if(!phase.isAnyoneGuessing()) { g.setCurrentHint(null); } + gRepository.save(g); wsc.onNewPhase(g); } + private void setWon(Grid g, boolean won) { + log.debug("Game won: {}", won); + g.setWonOpt(Optional.of(won)); + gRepository.save(g); + } + + private void newHint(Grid g, Hint h) { + log.debug("New hint: {}", h); + g.incrementHintCount(); + g.setCurrentHint(h); + g.setCurrentGuessCount(0); + gRepository.save(g); + } private void changeConfig(Grid g, MutableOption cc, Object data) { - g.getConfig().set(cc, data); log.debug("Changed config option: {} -> new value: {}", cc.name(), data); + g.getConfig().set(cc, data); + gRepository.save(g); wsc.onConfigChange(g, cc, data); } + + + + public int getCardCount(Grid g) { + return cRepository.countByGrid(g); + } + public Card[] getCards(Grid g) { + return cRepository.findByGridOrderById_Index(g); + } + public Card getCard(Grid g, int index) { + return cRepository.findByGridAndId_Index(g, index); + } private char other(char player) { return (player=='A')?'B':'A'; } @@ -77,7 +106,7 @@ public class GridManager { if onlyoneGreenGreen is false, checks only the side of this player */ private boolean allGreenRevealed(Grid g, char player, boolean onlyOneGreenGreen) { - for(Card c : g.getCards()) { + for(Card c : getCards(g)) { if ((c.getColor(player) == Color.GREEN && !c.isColorPublic(player)) && (!onlyOneGreenGreen || (c.getColor(player) == Color.GREEN && !c.isColorPublic(player)))) return false; @@ -92,31 +121,23 @@ public class GridManager { return !g.getConfig().isOnlyOneGreenGreen() && allGreenRevealed(g, 'A', true) && allGreenRevealed(g, 'B', true); } - public Grid findGrid(String gridId) { - return games.get(UUID.fromString(gridId)); - } - - public Grid newGrid(GridConfig gc, Random r) { + public Grid newGrid(@Nullable World world, GridConfig gc, Random r) { UUID uuid = UUID.randomUUID(); - randoms.put(uuid, r); String[] words = dictionnaries.randomWords(gc.getWordCount(), r); - List cards = new ArrayList<>(gc.getWordCount()); - List origDistrib = gc.getWordDistribution(); + List origDistrib = gc.getWordDistributionAsList(); List distrib; if(!gc.isStructuredGrid()) { List bColors = origDistrib.stream().map(cc -> cc.getFaceB()).collect(Collectors.toList()); Collections.shuffle(bColors, r); distrib = IntStream.range(0, gc.getWordCount()) - .mapToObj(i -> CardColors.of(origDistrib.get(i).getFaceA(), bColors.get(i))) - .toList(); + .mapToObj(i -> CardColors.of(origDistrib.get(i).getFaceA(), bColors.get(i))) + .toList(); } else { distrib = origDistrib; } - for (int j = 0; j < distrib.size(); j++) { - cards.add(new Card(words[j], distrib.get(j).getFaceA(), distrib.get(j).getFaceB())); - } - Collections.shuffle(cards, r); - + distrib = new ArrayList<>(distrib); + Collections.shuffle(distrib, r); + Phase startPhase; if(gc.isAnyoneStarts()) { startPhase = Phase.HINTING_BOTH; @@ -126,19 +147,14 @@ public class GridManager { else startPhase = Phase.hinting('B'); } + + Grid g = new Grid(uuid, world, startPhase, gc); + gRepository.save(g); - Grid g = new Grid(uuid, - (Card[])cards.toArray(new Card[cards.size()]), - new ArrayList<>(), - null, - 0, - 0, - 0, - startPhase, - Optional.empty(), - gc); - - games.put(uuid, g); + for (int j = 0; j < distrib.size(); j++) { + Card card = new Card(g, j, words[j], distrib.get(j).getFaceA(), distrib.get(j).getFaceB()); + cRepository.save(card); + } return g; } @@ -147,7 +163,7 @@ public class GridManager { * GAME ACTIONS * **********************************/ private void endGame(Grid g, boolean win) { - g.setWon(Optional.of(win)); + setWon(g, win); setPhase(g, Phase.GAME_ENDED); newGameEvent(g, GameEvent.newGameEndEvent(win)); if(win) { @@ -167,9 +183,7 @@ public class GridManager { } Hint h = new Hint(hint, wordCount); - g.incrementHintCount(); - g.setCurrentHint(h); - g.setCurrentGuessCount(0); + newHint(g, h); newGameEvent(g, GameEvent.newHintEvent(h, player)); setPhase(g, Phase.guessing(other(player))); } @@ -237,17 +251,17 @@ public class GridManager { if (!g.getPhase().canPlay() || !g.getPhase().isGuessing(player)) { throw new IllegalGameActionException(g, "This action is not allowed now"); } - if (cardIndex<0 || cardIndex>g.getCardCount()) { + if (cardIndex<0 || cardIndex>getCardCount(g)) { throw new IllegalArgumentException("The given card index is invalid"); } - Card c = g.getCards()[cardIndex]; + Card c = getCard(g, cardIndex); if(c.isColorPublic(other(player)) || (g.getConfig().isOnlyOneGreenGreen() && c.getColor(player) == Card.Color.GREEN && c.isColorPublic(player))) throw new IllegalGameActionException(g, "Card has already been revealed"); - c.setColorPublic(other(player), true); + c.setColorPublic(other(player), true);//TODO XXX BDD Card.Color guessedColor = c.getColor(other(player)); - newGameEvent(g, GameEvent.newGuessEvent(c, player)); + newGameEvent(g, GameEvent.newGuessEvent(c.getIndex(), player, c.getColor(other(player)))); g.incrementCurrentGuessCount(); if(guessedColor == Color.BLACK){ if(g.getConfig().isOnlyOneGreenGreenPreventDeath() && onlyOneGreenGreenWouldWin(g)) { @@ -323,7 +337,7 @@ public class GridManager { unlockHintCount(g, (Integer)o); break; case MutableOption.AVAILABLE_HINT_WORD_COUNT: - unlockNewWordCounts(g, (Set)o); + unlockNewWordCounts(g, ((Set)o).stream().map(i -> (Integer)i).collect(Collectors.toSet())); break; case MutableOption.ONE_MORE_GUESS: unlockOneMoreGuess(g); diff --git a/src/main/java/com/bernard/nodecames/model/Card.java b/src/main/java/com/bernard/nodecames/model/Card.java index 2c8036c..74fca5c 100644 --- a/src/main/java/com/bernard/nodecames/model/Card.java +++ b/src/main/java/com/bernard/nodecames/model/Card.java @@ -1,14 +1,31 @@ package com.bernard.nodecames.model; +import java.util.UUID; + +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MapsId; import lombok.AllArgsConstructor; +import lombok.Data; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; @AllArgsConstructor +@NoArgsConstructor @Getter @Setter +@Entity public class Card { + @EmbeddedId + CardId id; + + @MapsId("gridId") + @ManyToOne + Grid grid; + String word; Color colorA; @@ -16,8 +33,12 @@ public class Card { boolean colorAPublic; boolean colorBPublic; - public Card(String word, Color colorA, Color colorB) { - this(word, colorA, colorB, false, false); + public Card(Grid g, int index, String word, Color colorA, Color colorB) { + this(new CardId(g.getId(), index), g, word, colorA, colorB, false, false); + } + + public int getIndex() { + return this.getId().getIndex(); } public Color getColor(char player) { @@ -40,9 +61,29 @@ public class Card { isColorAPublic()?'O':'X', isColorBPublic()?'O':'X'); } + @AllArgsConstructor + @Getter public enum Color { - GREEN, - WHITE, - BLACK; + GREEN('G'), + WHITE('W'), + BLACK('B'); + + private char letter; + + public static final Color findByLetter(char letter) { + for(Color c : Color.values()) { + if(c.letter == letter) + return c; + } + return null; + } + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class CardId { + UUID gridId; + int index; } } diff --git a/src/main/java/com/bernard/nodecames/model/Game.java b/src/main/java/com/bernard/nodecames/model/Game.java index 0cf3aa1..25b33c9 100644 --- a/src/main/java/com/bernard/nodecames/model/Game.java +++ b/src/main/java/com/bernard/nodecames/model/Game.java @@ -3,6 +3,7 @@ package com.bernard.nodecames.model; import java.util.Map; import java.util.UUID; +import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import lombok.AllArgsConstructor; @@ -14,16 +15,14 @@ import lombok.Setter; @AllArgsConstructor @Getter @Setter +@Entity public class Game { @Id - @GeneratedValue private UUID id; String name; - Map worlds; - public static final Map DEFAULT_WORLDS_CONFIG = Map.ofEntries( Map.entry("1-1", GridConfig.zeroWithSize(GridConfig.CUSTOM6)), Map.entry("1-2", GridConfig.zeroWithSize(GridConfig.CUSTOM9)), @@ -38,8 +37,4 @@ public class Game { Map.entry("4-2", GridConfig.zeroWithSize(GridConfig.CUSTOM36)), Map.entry("4-3", GridConfig.zeroWithSize(GridConfig.CUSTOM36)) ); - - public World getWorld(String world) { - return this.getWorlds().get(world); - } } diff --git a/src/main/java/com/bernard/nodecames/model/GameEvent.java b/src/main/java/com/bernard/nodecames/model/GameEvent.java index 1c2b1fa..c07c295 100644 --- a/src/main/java/com/bernard/nodecames/model/GameEvent.java +++ b/src/main/java/com/bernard/nodecames/model/GameEvent.java @@ -1,49 +1,92 @@ package com.bernard.nodecames.model; -import lombok.AllArgsConstructor; -import lombok.Getter; +import com.bernard.nodecames.model.Card.Color; -@Getter -public class GameEvent { +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.JsonNodeFactory; +import tools.jackson.databind.node.ObjectNode; - Type type; - - Object data; - - // 'A' for player A, 'B' for player B, '_' for none - char issuer; - - private GameEvent(Type type, Object data, char issuer){ - this.data = data; - this.issuer = issuer; - this.type = type; - } +public sealed interface GameEvent { public static GameEvent newHintEvent(Hint h, char issuer) { - return new GameEvent(Type.HINT, h, issuer); + return new HintEvent(issuer, h); } - public static GameEvent newGuessEvent(Card c, char issuer) { - return new GameEvent(Type.GUESS, c, issuer); + public static GameEvent newGuessEvent(int index, char issuer, Color color) { + return new GuessEvent(issuer, index, color); } public static GameEvent newEndGuessingEvent(boolean manual, char issuer) { - return new GameEvent(Type.END_GUESSING, manual, issuer); + return new EndGuessingEvent(issuer, manual); } public static GameEvent newGameEndEvent(boolean won) { - return new GameEvent(Type.GAME_END, won, '_'); + return new GameEndEvent(won); } public static GameEvent newSuddenDeath() { - return new GameEvent(Type.SUDDEN_DEATH, Object.class, '_'); + return new EnterSuddenDeathEvent(); } - @AllArgsConstructor - public enum Type { - HINT(Hint.class), - GUESS(Card.class), - END_GUESSING(Boolean.class), - GAME_END(Boolean.class), - SUDDEN_DEATH(Object.class); - - Class dataType; + public static JsonNode toJsonNode(GameEvent ge) { + JsonNodeFactory jsn = JsonNodeFactory.instance; + ObjectNode o = jsn.objectNode(); + o.set("type", jsn.stringNode(ge.getClass().getSimpleName())); + switch (ge) { + case HintEvent(char issuer, Hint hint): + o.set("issuer", jsn.stringNode(Character.toString(issuer))); + o.set("word", jsn.stringNode(hint.getWord())); + o.set("word-count", jsn.numberNode(hint.getWordCount())); + break; + case GuessEvent(char issuer, int index, Color color): + o.set("issuer", jsn.stringNode(Character.toString(issuer))); + o.set("card-index", jsn.numberNode(index)); + o.set("color", jsn.stringNode(color.name().toLowerCase())); + break; + case EndGuessingEvent(char issuer, boolean manual): + o.set("issuer", jsn.stringNode(Character.toString(issuer))); + o.set("manual", jsn.booleanNode(manual)); + break; + case GameEndEvent(boolean win): + o.set("win", jsn.booleanNode(win)); + break; + case EnterSuddenDeathEvent(): + break; + } + return o; } - + public static GameEvent fromJsonNode(JsonNode o) { + String type = o.get("type").stringValue(); + return switch(type) { + case "HintEvent" -> + new HintEvent( + o.get("issuer").asString().charAt(0), + new Hint( + o.get("word").stringValue(), + o.get("word-count").intValue() + ) + ); + case "GuessEvent" -> + new GuessEvent( + o.get("issuer").asString().charAt(0), + o.get("card-index").intValue(), + Color.valueOf(o.get("color").stringValue().toUpperCase()) + ); + case "EndGuessingEvent" -> + new EndGuessingEvent( + o.get("issuer").asString().charAt(0), + o.get("manual").booleanValue() + ); + case "GameEndEvent" -> + new GameEndEvent( + o.get("win").booleanValue() + ); + case "EnterSuddenDeathEvent" -> + new EnterSuddenDeathEvent(); + default -> throw new IllegalArgumentException("Unknown event type : " + type); + }; + } + } + +record HintEvent(char issuer, Hint hint) implements GameEvent {} +record GuessEvent(char issuer, int index, Color color) implements GameEvent {} +record EndGuessingEvent(char issuer, boolean manual) implements GameEvent {} +record GameEndEvent(boolean win) implements GameEvent {} +record EnterSuddenDeathEvent() implements GameEvent {} \ No newline at end of file diff --git a/src/main/java/com/bernard/nodecames/model/Grid.java b/src/main/java/com/bernard/nodecames/model/Grid.java index 5117ff1..17de405 100644 --- a/src/main/java/com/bernard/nodecames/model/Grid.java +++ b/src/main/java/com/bernard/nodecames/model/Grid.java @@ -1,27 +1,49 @@ package com.bernard.nodecames.model; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +import jakarta.annotation.Nullable; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.ArrayNode; +import tools.jackson.databind.node.JsonNodeFactory; @NoArgsConstructor @AllArgsConstructor @Getter @Setter +@Entity public class Grid { @Id UUID id; - Card[] cards; + @ManyToOne(optional = true) + World w; + //@Type(JsonType.class) + @Column(columnDefinition = "json") + @Convert(converter = GameEventListAdapter.class) + @JdbcTypeCode(SqlTypes.JSON) List gameEvents; + + @Embedded Hint currentHint = null; int currentGuessCount; @@ -29,10 +51,18 @@ public class Grid { int usedWhites; Phase phase; - Optional won; + //TODO make this parameter a real Optional with Jdbc understanding that optional means nullable + @Nullable + Boolean won; + + @Embedded GridConfig config; + public Grid(UUID id, World w, Phase phase, GridConfig config) { + this(id, w, List.of(), null, 0, 0, 0, phase, null, config); + } + public void incrementHintCount() { this.setUsedHints(this.getUsedHints()+1); } @@ -43,10 +73,14 @@ public class Grid { this.setCurrentGuessCount(this.getCurrentGuessCount()+1); } public void newGameEvent(GameEvent event) { + this.gameEvents = new ArrayList<>(gameEvents); this.gameEvents.add(event); } - public int getCardCount() { - return this.getCards().length; + public Optional isWonOpt() { + return Optional.ofNullable(this.won); + } + public void setWonOpt(Optional won) { + this.won = won.orElse(null); } @AllArgsConstructor @@ -124,4 +158,24 @@ public class Grid { } } + @AllArgsConstructor + public static class GameEventListAdapter implements AttributeConverter, JsonNode> { + + JsonNodeFactory jsn; + + @Override + public JsonNode convertToDatabaseColumn(List attribute) { + ArrayNode out = jsn.arrayNode(attribute.size()); + for(GameEvent ge : attribute) { + out.add(GameEvent.toJsonNode(ge)); + } + return out; + } + + @Override + public List convertToEntityAttribute(JsonNode dbData) { + return dbData.asArray().valueStream().map(GameEvent::fromJsonNode).toList(); + } + + } } diff --git a/src/main/java/com/bernard/nodecames/model/GridConfig.java b/src/main/java/com/bernard/nodecames/model/GridConfig.java index b566032..62588f0 100644 --- a/src/main/java/com/bernard/nodecames/model/GridConfig.java +++ b/src/main/java/com/bernard/nodecames/model/GridConfig.java @@ -2,38 +2,48 @@ 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 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.Value; @Getter @AllArgsConstructor +@NoArgsConstructor +@Embeddable public class GridConfig { - public static final List CUSTOM6 = + public static final Map CUSTOM6 = makeDistribution(0, 0, 2, 1, 1, 0); - public static final List CUSTOM9 = + public static final Map CUSTOM9 = makeDistribution(0, 1, 1, 2, 2, 1); - public static final List CUSTOM12 = + public static final Map CUSTOM12 = makeDistribution(1, 1, 1, 2, 3, 1); - public static final List CUSTOM16 = + public static final Map CUSTOM16 = makeDistribution(1, 1, 2, 2, 5, 1); - public static final List CUSTOM20 = + public static final Map CUSTOM20 = makeDistribution(1, 1, 4, 2, 5, 1); - public static final List CLASSICAL25 = + public static final Map CLASSICAL25 = makeDistribution(1, 1, 5, 3, 7, 1); - public static final List CUSTOM30 = + public static final Map CUSTOM30 = makeDistribution(2, 2, 5, 3, 7, 1); - public static final List CUSTOM36 = + public static final Map CUSTOM36 = makeDistribution(2, 2, 6, 5, 9, 2); public static final Set BASE_HINTWORDCOUNT = Set.of( @@ -51,7 +61,7 @@ public class GridConfig { 3, false, false, false, false); - public static final GridConfig zeroWithSize(List colors) { + public static final GridConfig zeroWithSize(Map colors) { return new GridConfig( 9, false, false, colors, true, true, false, 1, Set.of(1), false, @@ -65,7 +75,8 @@ public class GridConfig { private int maxMaxHint; private boolean structuredGrid; private boolean anyoneStarts; - private List wordDistribution; + @Convert(converter = WordDistributionConverter.class) + private Map wordDistribution; private boolean maybeSuddenDeath; //TODO private boolean maybeOneMoreGuess; //TODO // If onlyOneGreenGreen is false and having it true would have meant we won, @@ -83,8 +94,18 @@ public class GridConfig { private boolean suddenDeath; private boolean hintsInARow; + public List getWordDistributionAsList() { + List out = new ArrayList<>(); + for(Entry e : this.getWordDistribution().entrySet()) { + for(int i=0;i i)).getSum(); } public void set(MutableOption opt, Object value) { @@ -139,9 +160,9 @@ public class GridConfig { } @Value(staticConstructor = "of") - public static final class CardColors { - Color faceA; - Color faceB; + public static final class CardColors implements Serializable, Comparable { + 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); @@ -154,19 +175,27 @@ public class GridConfig { public static final CardColors BB = CardColors.of(Color.BLACK, Color.BLACK); - public static final List makeDistribution( + public static final Map makeDistribution( int wbbwCount, int bggbCount, int wggwCount, int ggCount, int wwCount, int bbCount ) { - int total = (wbbwCount + bggbCount + wggwCount)*2 + ggCount + wwCount + bbCount; - List out = new ArrayList<>(total); - for(int i=0;i dataClass; } + + @NoArgsConstructor + @Converter + public static class WordDistributionConverter implements AttributeConverter, 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 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 convertToEntityAttribute(String dbData) { + return Arrays.stream(dbData.split(";")) + .collect(Collectors.toMap( + this::parseCardColors, + this::parseCount + )); + } + + } } diff --git a/src/main/java/com/bernard/nodecames/model/Hint.java b/src/main/java/com/bernard/nodecames/model/Hint.java index 24f9dec..c0008b8 100644 --- a/src/main/java/com/bernard/nodecames/model/Hint.java +++ b/src/main/java/com/bernard/nodecames/model/Hint.java @@ -1,10 +1,16 @@ package com.bernard.nodecames.model; +import jakarta.persistence.Embeddable; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; @AllArgsConstructor +@NoArgsConstructor @Getter +@Setter +@Embeddable public class Hint { String word; diff --git a/src/main/java/com/bernard/nodecames/model/World.java b/src/main/java/com/bernard/nodecames/model/World.java index 1efb7ce..0b4dea1 100644 --- a/src/main/java/com/bernard/nodecames/model/World.java +++ b/src/main/java/com/bernard/nodecames/model/World.java @@ -1,27 +1,58 @@ package com.bernard.nodecames.model; -import jakarta.annotation.Nullable; +import java.util.UUID; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; import jakarta.persistence.Entity; -import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MapsId; import lombok.AllArgsConstructor; +import lombok.Data; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; -@AllArgsConstructor @Getter @Setter +@Entity +@NoArgsConstructor public class World { + + @EmbeddedId + WorldId id; + @MapsId("gameId") + @ManyToOne Game game; - String name; - // Config used when resetting this world GridConfig initialConfig; - @Nullable + @ManyToOne(optional = true) Grid currentGrid; boolean won; + public World(Game g, String name, GridConfig initConfig, Grid currentGrid, boolean won) { + this.id = new WorldId(g.getId(), name); + this.game = g; + this.initialConfig = initConfig; + this.currentGrid = currentGrid; + this.won = won; + } + + public String getName() { + return this.getId().getName(); + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @Embeddable + public static class WorldId { + UUID gameId; + String name; + } + } diff --git a/src/main/java/com/bernard/nodecames/repository/CardRepository.java b/src/main/java/com/bernard/nodecames/repository/CardRepository.java new file mode 100644 index 0000000..1264873 --- /dev/null +++ b/src/main/java/com/bernard/nodecames/repository/CardRepository.java @@ -0,0 +1,13 @@ +package com.bernard.nodecames.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.bernard.nodecames.model.Card; +import com.bernard.nodecames.model.Grid; +import com.bernard.nodecames.model.Card.CardId; + +public interface CardRepository extends JpaRepository { + public Card[] findByGridOrderById_Index(Grid g); + public Card findByGridAndId_Index(Grid g, int index); + public int countByGrid(Grid g); +} diff --git a/src/main/java/com/bernard/nodecames/repository/GameRepository.java b/src/main/java/com/bernard/nodecames/repository/GameRepository.java new file mode 100644 index 0000000..5c7d01b --- /dev/null +++ b/src/main/java/com/bernard/nodecames/repository/GameRepository.java @@ -0,0 +1,10 @@ +package com.bernard.nodecames.repository; + +import java.util.UUID; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.bernard.nodecames.model.Game; + +public interface GameRepository extends JpaRepository { +} diff --git a/src/main/java/com/bernard/nodecames/repository/GridRepository.java b/src/main/java/com/bernard/nodecames/repository/GridRepository.java new file mode 100644 index 0000000..2a82dbc --- /dev/null +++ b/src/main/java/com/bernard/nodecames/repository/GridRepository.java @@ -0,0 +1,10 @@ +package com.bernard.nodecames.repository; + +import java.util.UUID; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.bernard.nodecames.model.Grid; + +public interface GridRepository extends JpaRepository { +} diff --git a/src/main/java/com/bernard/nodecames/repository/WorldRepository.java b/src/main/java/com/bernard/nodecames/repository/WorldRepository.java new file mode 100644 index 0000000..0605cc2 --- /dev/null +++ b/src/main/java/com/bernard/nodecames/repository/WorldRepository.java @@ -0,0 +1,15 @@ +package com.bernard.nodecames.repository; + +import java.util.Optional; +import java.util.Set; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.bernard.nodecames.model.Game; +import com.bernard.nodecames.model.World; +import com.bernard.nodecames.model.World.WorldId; + +public interface WorldRepository extends JpaRepository{ + public Optional findByGameAndId_Name(Game g, String name); + public Set findByGame(Game g); +} diff --git a/src/main/resources/db/migration/V1_0_0__initial.sql b/src/main/resources/db/migration/V1_0_0__initial.sql new file mode 100644 index 0000000..e1981ed --- /dev/null +++ b/src/main/resources/db/migration/V1_0_0__initial.sql @@ -0,0 +1,8 @@ +create table card (index integer not null, colora smallint check ((colora between 0 and 2)), colorapublic boolean not null, colorb smallint check ((colorb between 0 and 2)), colorbpublic boolean not null, word varchar(255), grid_id uuid not null, primary key (grid_id, index)); +create table game (id uuid not null, name varchar(255), primary key (id)); +create table grid (id uuid not null, anyone_starts boolean, available_hint_word_count integer array, end_guessing boolean, hint_max_length integer, hints_inarow boolean, max_hint integer, max_max_hint integer, maybe_one_more_guess boolean, maybe_sudden_death boolean, one_more_guess boolean, only_one_green_green boolean, only_one_green_green_prevent_death boolean, structured_grid boolean, sudden_death boolean, word_distribution varchar(255), current_guess_count integer not null, word varchar(255), word_count integer, game_events json, phase smallint check ((phase between 0 and 16)), used_hints integer not null, used_whites integer not null, won boolean, w_game_id uuid, w_name varchar(255), primary key (id)); +create table world (name varchar(255) not null, anyone_starts boolean, available_hint_word_count integer array, end_guessing boolean, hint_max_length integer, hints_inarow boolean, max_hint integer, max_max_hint integer, maybe_one_more_guess boolean, maybe_sudden_death boolean, one_more_guess boolean, only_one_green_green boolean, only_one_green_green_prevent_death boolean, structured_grid boolean, sudden_death boolean, word_distribution varchar(255), won boolean not null, game_id uuid not null, current_grid_id uuid, primary key (game_id, name)); +alter table if exists card add constraint FK5x40wglgnltvcrme1tqa8ilsx foreign key (grid_id) references grid; +alter table if exists grid add constraint FK583ks7m95vxertpybyo2s5h6n foreign key (w_game_id, w_name) references world; +alter table if exists world add constraint FKo96aqpsvpf22bjeqk2w6l8jvx foreign key (current_grid_id) references grid; +alter table if exists world add constraint FKe9tajxpu9c9llbtdaldt6t18s foreign key (game_id) references game; diff --git a/src/main/resources/static/js/grid.js b/src/main/resources/static/js/grid.js index 4bb02f3..60cec55 100644 --- a/src/main/resources/static/js/grid.js +++ b/src/main/resources/static/js/grid.js @@ -22,12 +22,12 @@ function updatePhase() { $('#hint-counter-den').text(gc["max-hint"]) if(currentHint) { - $('#hint-text').text(currentHint['word']+" in "+currentHint['wordCount']) + $('#hint-text').text(currentHint['word']+" in "+currentHint['word-count']) $('#guess-counter-num').text(currentGuessCount) if(gc['one-more-guess']) { - $('#guess-counter-den').text(currentHint['wordCount'] + "(+1)") + $('#guess-counter-den').text(currentHint['word-count'] + "(+1)") } else { - $('#guess-counter-den').text(currentHint['wordCount']) + $('#guess-counter-den').text(currentHint['word-count']) } } if(registeredAvailableHintWordCountInvalid) { @@ -56,7 +56,7 @@ function updatePhase() { } if((phase['name'] == 'waiting_for_one_more_'+p) || (phase[p+'-guessing'] && - (gc['end-guessing'] || (currentHint && currentGuessCount >= currentHint['wordCount'])))) { + (gc['end-guessing'] || (currentHint && currentGuessCount >= currentHint['word-count'])))) { $('#end-guessing-button').show() } else { $('#end-guessing-button').hide() @@ -179,28 +179,28 @@ function addEndGameEvent(issuer, won) { function addEvent(e) { switch(e.type) { - case "hint": - addHintEvent(e.issuer, e.word, e.wordCount) - currentHint = {word: e.word, wordCount: e.wordCount} + case "HintEvent": + addHintEvent(e.issuer, e.word, e['word-count']) + currentHint = {word: e.word, 'word-count': e['word-count']} currentHintCount += 1 currentGuessCount = 0 updatePhase() break - case "guess": + case "GuessEvent": addPointEvent(e.issuer, e['card-index'], e.color) currentGuessCount += 1 break - case "end_guessing": + case "EndGuessingEvent": addEndGuessingEvent(e.issuer, e.manual) currentHint = null currentGuessCount = 0 break - case "game_end": + case "GameEndEvent": addEndGameEvent(e.issuer, e.win) currentHint = null currentGuessCount = 0 break - case "sudden_death": + case "EnterSuddenDeathEvent": addSuddenDeathEvent() currentHint = null currentGuessCount = 0 diff --git a/src/test/java/com/bernard/nodecames/NodeCamesApplicationTests.java b/src/test/java/com/bernard/nodecames/NodeCamesApplicationTests.java index 1ed67ab..746f2e3 100644 --- a/src/test/java/com/bernard/nodecames/NodeCamesApplicationTests.java +++ b/src/test/java/com/bernard/nodecames/NodeCamesApplicationTests.java @@ -2,7 +2,10 @@ package com.bernard.nodecames; import org.springframework.boot.test.context.SpringBootTest; +import lombok.RequiredArgsConstructor; + @SpringBootTest +@RequiredArgsConstructor class NodeCamesApplicationTests { }