Added card styles

This commit is contained in:
Mysaa Java
2026-08-25 05:22:18 +02:00
parent 639a5565ad
commit 142d08cc28
7 changed files with 1006 additions and 72 deletions
@@ -9,8 +9,13 @@ import org.springframework.web.servlet.view.RedirectView;
import com.bernard.nodecames.game.GameManager;
import lombok.AllArgsConstructor;
@AllArgsConstructor
@Controller
public class HttpController {
GameManager gm;
@GetMapping("/")
public String index() {
@@ -24,7 +29,7 @@ public class HttpController {
@GetMapping("/create-room")
public RedirectView createRoom() {
UUID uuid = GameManager.newGrid();
UUID uuid = gm.newGrid();
return new RedirectView("/room/"+uuid.toString()+"/grid");
}
}
@@ -1,30 +1,56 @@
package com.bernard.nodecames.frontend;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import com.bernard.nodecames.game.GameManager;
import com.bernard.nodecames.model.Card;
import com.bernard.nodecames.model.Grid;
import com.bernard.nodecames.model.Card.Color;
import com.bernard.nodecames.model.Grid.Phase;
import lombok.AllArgsConstructor;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.node.JsonNodeFactory;
import tools.jackson.databind.node.ObjectNode;
@Controller
@AllArgsConstructor
public class WebSocketController {
private static final JsonNodeFactory jsn = JsonNodeFactory.instance;
@Autowired
GameManager gm;
private GameManager gm;
private SimpMessagingTemplate template;
public void publishNewPhase(Grid g) {
this.template.convertAndSend(
"/topic/new-phase",
g.getPhase().name().toLowerCase()
);
}
public void publishCardUpdateToPlayer(Grid g, int cardIndex, char player) {
Card c = g.getCards()[cardIndex];
this.template.convertAndSend(
"/topic/update-card/" + Character.toLowerCase(player),
gm.cardData(c, player),
Map.of("cardIndex", cardIndex)
);
}
public void publishCardUpdate(Grid g, int cardIndex) {
publishCardUpdateToPlayer(g, cardIndex, 'A');
publishCardUpdateToPlayer(g, cardIndex, 'B');
}
@GetMapping("/room/{id}/game/{player}")
public Object grid(@PathVariable("id") String gridId, @PathVariable("player") String playerStr) {
@@ -36,9 +62,9 @@ public class WebSocketController {
else
throw new IllegalArgumentException("Unknown player type");
Grid g = GameManager.findGrid(gridId);
Grid g = gm.findGrid(gridId);
return new ResponseEntity<>(GameManager.gridData(g, player), HttpStatus.OK);
return new ResponseEntity<>(gm.gridData(g, player), HttpStatus.OK);
}
@MessageMapping("submit-hint")
@@ -47,10 +73,11 @@ public class WebSocketController {
String hint = content.asObject().get("hint").stringValue();
int wordCount = content.asObject().get("hintWordCount").intValue();
Grid g = GameManager.findGrid(roomId);
Grid g = gm.findGrid(roomId);
gm.proposeHint(g, player.charAt(0), hint, wordCount);
content.asObject().set("player", jsn.stringNode(player));
this.publishNewPhase(g);
return content;
}
@@ -59,17 +86,24 @@ public class WebSocketController {
public JsonNode pointCard(@Header("room") String roomId, @Header("player") String player, @Payload JsonNode content) {
int cardIndex = content.asObject().get("cardIndex").intValue();
Grid g = GameManager.findGrid(roomId);
gm.pointCard(g, player.charAt(0), cardIndex);
Grid g = gm.findGrid(roomId);
Color c = gm.pointCard(g, player.charAt(0), cardIndex);
content.asObject().set("color", jsn.stringNode(c.name().toLowerCase()));
content.asObject().set("player", jsn.stringNode(player));
if(g.getPhase() != Phase.GUESSING_A && g.getPhase() != Phase.GUESSING_B)
publishNewPhase(g);
publishCardUpdate(g, cardIndex);
return content;
}
@MessageMapping("end-guessing")
@SendTo("/topic/end-guessing")
public String endGuessing(@Header("room") String roomId, @Header("player") String player) {
Grid g = GameManager.findGrid(roomId);
public JsonNode endGuessing(@Header("room") String roomId, @Header("player") String player) {
Grid g = gm.findGrid(roomId);
gm.endGuessing(g, player.charAt(0));
return "";
ObjectNode out = jsn.objectNode();
out.set("player", jsn.stringNode(player));
return out;
}
}
@@ -1,14 +1,20 @@
package com.bernard.nodecames.game;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Scanner;
import java.util.Set;
import java.util.UUID;
import java.util.stream.IntStream;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import com.bernard.nodecames.model.Card;
@@ -27,48 +33,90 @@ public class GameManager {
private static final JsonNodeFactory jsn = JsonNodeFactory.instance;
//TODO Remove static, make something more Spring Boot-y
private static Map<UUID, Grid> games = new HashMap<>();
public static JsonNode gridData(Grid g, char player) {
@Value("classpath:dictionaries/fr.txt")
Resource dictFr;
//TODO do Spring boot magic to only generate this once
public List<String> allWords() {
try (Scanner wordsScanner = new Scanner(dictFr.getInputStream())) {
List<String> out = new ArrayList<>();
while (wordsScanner.hasNextLine())
out.add(wordsScanner.nextLine());
return Collections.unmodifiableList(out);
} catch (IOException e) {
throw new IllegalStateException("Could not read internal dictionnary", e);
}
}
public String[] randomWords(int count) {
List<String> allWords = allWords();
//TODO seed the games
Random r = new Random();
if(count > allWords.size())
throw new IllegalArgumentException("Not enough words in the dictionary");
String[] out = new String[count];
Set<Integer> already = new HashSet<>();
int k;
for(int i = 0; i<count; i++) {
do {
k = r.nextInt(allWords.size());
} while (already.contains(k));
out[i] = allWords.get(k);
already.add(k);
}
return out;
}
public JsonNode cardData(Card c, char player) {
ObjectNode cardNode = jsn.objectNode();
cardNode.set("word", jsn.stringNode(c.getWord()));
cardNode.set("color", jsn.stringNode((player=='A'?c.getColorA():c.getColorB()).name().toLowerCase()));
if((player=='A' && c.isRevealedA()) || (player=='B' && c.isRevealedB()))
cardNode.set("otherColor", jsn.stringNode((player=='A'?c.getColorB():c.getColorA()).name().toLowerCase()));
cardNode.set("revealed-a", jsn.booleanNode(c.isRevealedA()));
cardNode.set("revealed-b", jsn.booleanNode(c.isRevealedB()));
return cardNode;
}
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));
break;
case GameEvent.Type.END_GUESSING:
geNode.set("manual", jsn.booleanNode((Boolean)ge.getData()));
break;
case GameEvent.Type.START_GAME:
break;
}
return geNode;
}
public JsonNode gridData(Grid g, char player) {
ArrayNode cardsNode = jsn.arrayNode(g.getCardCount());
for (int i = 0; i < g.getCardCount(); i++) {
ObjectNode cardNode = jsn.objectNode();
Card c = g.getCards()[i];
cardNode.set("word", jsn.stringNode(c.getWord()));
cardNode.set("color", jsn.stringNode((player=='A'?c.getColorA():c.getColorB()).name().toLowerCase()));
if((player=='A' && c.isRevealedA()) || (player=='B' && c.isRevealedB()))
cardNode.set("otherColor", jsn.stringNode((player=='A'?c.getColorB():c.getColorA()).name().toLowerCase()));
cardsNode.add(cardNode);
}
for (int i = 0; i < g.getCardCount(); i++)
cardsNode.add(cardData(g.getCards()[i], player));
ArrayNode eventsNode = jsn.arrayNode(g.getGameEvents().size());
for(GameEvent ge : g.getGameEvents()) {
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));
break;
case GameEvent.Type.END_GUESSING:
geNode.set("manual", jsn.booleanNode((Boolean)ge.getData()));
break;
case GameEvent.Type.START_GAME:
break;
}
eventsNode.add(geNode);
}
for(GameEvent ge : g.getGameEvents())
eventsNode.add(eventData(g, ge));
ObjectNode currentHintNode = jsn.objectNode();
if(g.getCurrentHint() != null) {
@@ -89,15 +137,13 @@ public class GameManager {
return out;
}
public static Grid findGrid(String gridId) {
public Grid findGrid(String gridId) {
return games.get(UUID.fromString(gridId));
}
public static UUID newGrid() {
public UUID newGrid() {
UUID uuid = UUID.randomUUID();
String[] words = new String[25];
for (int i = 0; i < words.length; i++)
words[i] = "MotNumero"+Integer.toString(i);
String[] words = randomWords(25);
List<Card> cards = new ArrayList<>(25);
int i = 0;
cards.add(new Card(words[i++], Color.BLACK, Color.BLACK));
@@ -141,7 +187,7 @@ public class GameManager {
g.setCurrentGuessCount(0);
}
public void pointCard(Grid g, char player, int cardIndex) {
public Card.Color pointCard(Grid g, char player, int cardIndex) {
if (!(((player == 'A') && (g.getPhase() == Grid.Phase.GUESSING_A)) ||
((player == 'B') && (g.getPhase() == Grid.Phase.GUESSING_B)))) {
throw new RuntimeException("This action is not allowed now");
@@ -199,7 +245,7 @@ public class GameManager {
g.setPhase(Grid.Phase.HINTING_B);
g.newGameEvent(GameEvent.newEndGuessingEvent(false, player));
}
return (player == 'A')?c.getColorB():c.getColorA();
}
public void endGuessing(Grid g, char player) {