Prepared grid config logic

This commit is contained in:
Mysaa Java
2026-09-04 22:42:04 +02:00
parent cfccdcea5e
commit 3b68f3f42f
8 changed files with 355 additions and 202 deletions
@@ -1,5 +1,18 @@
package com.bernard.nodecames; package com.bernard.nodecames;
import static com.bernard.nodecames.model.GridConfig.CardColors.BB;
import static com.bernard.nodecames.model.GridConfig.CardColors.BG;
import static com.bernard.nodecames.model.GridConfig.CardColors.BW;
import static com.bernard.nodecames.model.GridConfig.CardColors.GB;
import static com.bernard.nodecames.model.GridConfig.CardColors.GG;
import static com.bernard.nodecames.model.GridConfig.CardColors.GW;
import static com.bernard.nodecames.model.GridConfig.CardColors.WB;
import static com.bernard.nodecames.model.GridConfig.CardColors.WG;
import static com.bernard.nodecames.model.GridConfig.CardColors.WW;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
@@ -8,14 +21,17 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.servlet.view.RedirectView; import org.springframework.web.servlet.view.RedirectView;
import com.bernard.nodecames.game.GameManager; import com.bernard.nodecames.game.GameManager;
import com.bernard.nodecames.model.GridConfig;
import com.bernard.nodecames.model.GridConfig.CardColors;
import lombok.AllArgsConstructor; import lombok.RequiredArgsConstructor;
@AllArgsConstructor @RequiredArgsConstructor
@Controller @Controller
public class HttpController { public class HttpController {
GameManager gm; private final GameManager gm;
private final Random rand = new Random();
@GetMapping("/") @GetMapping("/")
public String index() { public String index() {
@@ -27,9 +43,22 @@ public class HttpController {
return "grid"; return "grid";
} }
public static final List<CardColors> CLASSICAL25 = List.of(
BB,BW,WB,BG,GB,GG,GG,GG,WG,WG,WG,WG,WG,GW,GW,GW,GW,GW,WW,WW,WW,WW,WW,WW,WW
);
public static final Set<Integer> BASE_HINTWORDCOUNT = Set.of(
0,1,2,3,4,5,6,7,8,9,-1
);
public static final GridConfig CODENAMES_CLASSICAL = new GridConfig(
9, true, true, CLASSICAL25,
9, BASE_HINTWORDCOUNT, true,
Integer.MAX_VALUE, true, false,
true, false);
@GetMapping("/create-room") @GetMapping("/create-room")
public RedirectView createRoom() { public RedirectView createRoom() {
UUID uuid = gm.newGrid(); Random r = new Random(rand.nextLong());
UUID uuid = gm.newGrid(CODENAMES_CLASSICAL, r);
return new RedirectView("/room/"+uuid.toString()+"/grid"); return new RedirectView("/room/"+uuid.toString()+"/grid");
} }
} }
@@ -0,0 +1,104 @@
package com.bernard.nodecames.frontend;
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.Hint;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.node.ArrayNode;
import tools.jackson.databind.node.JsonNodeFactory;
import tools.jackson.databind.node.ObjectNode;
@Service
public class JsonDataService {
private static final JsonNodeFactory jsn = JsonNodeFactory.instance;
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.isColorBPublic()) || (player=='B' && c.isColorAPublic()))
cardNode.set("otherColor", jsn.stringNode((player=='A'?c.getColorB():c.getColorA()).name().toLowerCase()));
cardNode.set("revealed-a", jsn.booleanNode(c.isColorBPublic()));
cardNode.set("revealed-b", jsn.booleanNode(c.isColorAPublic()));
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));
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 phaseData(Phase p) {
ObjectNode out = jsn.objectNode();
out.set("a-guessing", jsn.booleanNode(p.isAGuessing()));
out.set("b-guessing", jsn.booleanNode(p.isBGuessing()));
out.set("a-hinting", jsn.booleanNode(p.isAHinting()));
out.set("b-hinting", jsn.booleanNode(p.isBHinting()));
out.set("sudden-death", jsn.booleanNode(p.isSuddenDeath()));
out.set("game-ended", jsn.booleanNode(p.isGameEnded()));
return out;
}
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));
ArrayNode eventsNode = jsn.arrayNode(g.getGameEvents().size());
for(GameEvent ge : g.getGameEvents())
eventsNode.add(eventData(g, 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()));
}
ObjectNode out = jsn.objectNode();
out.set("cards", cardsNode);
out.set("phase", phaseData(g.getPhase()));
out.set("events", eventsNode);
if(g.getCurrentHint() != null)
out.set("current-hint", currentHintNode);
out.set("current-guess-count", jsn.numberNode(g.getCurrentGuessCount()));
out.set("used-hints", jsn.numberNode(g.getUsedHints()));
out.set("used-whites", jsn.numberNode(g.getUsedWhites()));
return out;
}
}
@@ -13,38 +13,37 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import com.bernard.nodecames.game.GameManager; import com.bernard.nodecames.game.GameManager;
import com.bernard.nodecames.game.IllegalGameActionException;
import com.bernard.nodecames.model.Card; import com.bernard.nodecames.model.Card;
import com.bernard.nodecames.model.GameEvent; import com.bernard.nodecames.model.GameEvent;
import com.bernard.nodecames.model.Grid; import com.bernard.nodecames.model.Grid;
import com.bernard.nodecames.model.Grid.Phase;
import tools.jackson.databind.JsonNode; import tools.jackson.databind.JsonNode;
import tools.jackson.databind.node.JsonNodeFactory;
@Controller @Controller
public class WebSocketController { public class WebSocketController {
private static final JsonNodeFactory jsn = JsonNodeFactory.instance;
private GameManager gm; private GameManager gm;
private SimpMessagingTemplate template; private SimpMessagingTemplate template;
private JsonDataService json;
public WebSocketController(GameManager gm, SimpMessagingTemplate template) { public WebSocketController(GameManager gm, SimpMessagingTemplate template, JsonDataService json) {
this.gm = gm; this.gm = gm;
this.template = template; this.template = template;
this.json = json;
gm.setWsc(this); gm.setWsc(this);
} }
public void onNewEvent(Grid g, GameEvent ge) { public void onNewEvent(Grid g, GameEvent ge) {
this.template.convertAndSend("/topic/new-event", this.template.convertAndSend("/topic/new-event",
gm.eventData(g, ge) json.eventData(g, ge)
); );
} }
public void onNewPhase(Grid g, Phase p) { public void onNewPhase(Grid g) {
this.template.convertAndSend( this.template.convertAndSend(
"/topic/new-phase", "/topic/new-phase",
gm.phaseData(g.getPhase()) json.phaseData(g.getPhase())
); );
} }
@@ -52,7 +51,7 @@ public class WebSocketController {
Card c = g.getCards()[cardIndex]; Card c = g.getCards()[cardIndex];
this.template.convertAndSend( this.template.convertAndSend(
"/topic/update-card/" + Character.toLowerCase(player), "/topic/update-card/" + Character.toLowerCase(player),
gm.cardData(c, player), json.cardData(c, player),
Map.of("cardIndex", cardIndex) Map.of("cardIndex", cardIndex)
); );
} }
@@ -61,6 +60,10 @@ public class WebSocketController {
publishCardUpdateToPlayer(g, cardIndex, 'B'); publishCardUpdateToPlayer(g, cardIndex, 'B');
} }
public void publishError(char issuer, IllegalGameActionException e) {
//TODO open error channel
}
@GetMapping("/room/{id}/game/{player}") @GetMapping("/room/{id}/game/{player}")
public Object grid(@PathVariable("id") String gridId, @PathVariable("player") String playerStr) { public Object grid(@PathVariable("id") String gridId, @PathVariable("player") String playerStr) {
char player; char player;
@@ -73,7 +76,7 @@ public class WebSocketController {
Grid g = gm.findGrid(gridId); Grid g = gm.findGrid(gridId);
return new ResponseEntity<>(gm.gridData(g, player), HttpStatus.OK); return new ResponseEntity<>(json.gridData(g, player), HttpStatus.OK);
} }
@MessageMapping("submit-hint") @MessageMapping("submit-hint")
@@ -83,7 +86,12 @@ public class WebSocketController {
Grid g = gm.findGrid(roomId); Grid g = gm.findGrid(roomId);
try {
gm.proposeHint(g, player.charAt(0), hint, wordCount); gm.proposeHint(g, player.charAt(0), hint, wordCount);
} catch (IllegalGameActionException e) {
publishError(player.charAt(0), e);
e.printStackTrace();
}
} }
@MessageMapping("point-card") @MessageMapping("point-card")
@@ -91,13 +99,23 @@ public class WebSocketController {
int cardIndex = content.asObject().get("cardIndex").intValue(); int cardIndex = content.asObject().get("cardIndex").intValue();
Grid g = gm.findGrid(roomId); Grid g = gm.findGrid(roomId);
try {
gm.pointCard(g, player.charAt(0), cardIndex); gm.pointCard(g, player.charAt(0), cardIndex);
publishCardUpdate(g, cardIndex); publishCardUpdate(g, cardIndex);
} catch (IllegalGameActionException e) {
publishError(player.charAt(0), e);
e.printStackTrace();
}
} }
@MessageMapping("end-guessing") @MessageMapping("end-guessing")
public void endGuessing(@Header("room") String roomId, @Header("player") String player) { public void endGuessing(@Header("room") String roomId, @Header("player") String player) {
Grid g = gm.findGrid(roomId); Grid g = gm.findGrid(roomId);
try {
gm.endGuessing(g, player.charAt(0)); gm.endGuessing(g, player.charAt(0));
} catch (IllegalGameActionException e) {
publishError(player.charAt(0), e);
e.printStackTrace();
}
} }
} }
@@ -0,0 +1,57 @@
package com.bernard.nodecames.game;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Scanner;
import java.util.Set;
import javax.management.RuntimeErrorException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import lombok.AllArgsConstructor;
import lombok.Setter;
@Service
public class DictionnariesService {
@Value("classpath:dictionaries/fr.txt")
@Setter
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 RuntimeException(e);
}
}
public String[] randomWords(int count, Random r) {
List<String> allWords = allWords();
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;
}
}
@@ -1,21 +1,14 @@
package com.bernard.nodecames.game; package com.bernard.nodecames.game;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Random; import java.util.Random;
import java.util.Scanner;
import java.util.Set;
import java.util.UUID; 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 org.springframework.stereotype.Service;
import com.bernard.nodecames.frontend.WebSocketController; import com.bernard.nodecames.frontend.WebSocketController;
@@ -24,29 +17,25 @@ import com.bernard.nodecames.model.Card.Color;
import com.bernard.nodecames.model.GameEvent; import com.bernard.nodecames.model.GameEvent;
import com.bernard.nodecames.model.Grid; import com.bernard.nodecames.model.Grid;
import com.bernard.nodecames.model.Grid.Phase; import com.bernard.nodecames.model.Grid.Phase;
import com.bernard.nodecames.model.GridConfig;
import com.bernard.nodecames.model.GridConfig.CardColors;
import com.bernard.nodecames.model.Hint; import com.bernard.nodecames.model.Hint;
import lombok.RequiredArgsConstructor;
import lombok.Setter; import lombok.Setter;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.node.ArrayNode;
import tools.jackson.databind.node.JsonNodeFactory;
import tools.jackson.databind.node.ObjectNode;
@Service @Service
@RequiredArgsConstructor
public class GameManager { public class GameManager {
private static final JsonNodeFactory jsn = JsonNodeFactory.instance;
public static final int MAX_HINT_COUNT = 9;
//TODO Remove static, make something more Spring Boot-y //TODO Remove static, make something more Spring Boot-y
private static Map<UUID, Grid> games = new HashMap<>(); private static Map<UUID, Grid> games = new HashMap<>();
private static Map<UUID, Random> randoms = new HashMap<>();
@Setter @Setter
WebSocketController wsc; private WebSocketController wsc = null;
@Value("classpath:dictionaries/fr.txt") private final DictionnariesService dictionnaries;
Resource dictFr;
private void newGameEvent(Grid g, GameEvent ge) { private void newGameEvent(Grid g, GameEvent ge) {
g.newGameEvent(ge); g.newGameEvent(ge);
@@ -58,170 +47,18 @@ public class GameManager {
if(!phase.isAnyoneGuessing()) { if(!phase.isAnyoneGuessing()) {
g.setCurrentHint(null); g.setCurrentHint(null);
} }
wsc.onNewPhase(g, phase); wsc.onNewPhase(g);
} }
//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;
}
private char other(char player) { private char other(char player) {
return (player=='A')?'B':'A'; return (player=='A')?'B':'A';
} }
public Grid findGrid(String gridId) {
return games.get(UUID.fromString(gridId));
}
public UUID newGrid() {
UUID uuid = UUID.randomUUID();
String[] words = randomWords(25);
List<Card> cards = new ArrayList<>(25);
int i = 0;
cards.add(new Card(words[i++], Color.BLACK, Color.BLACK));
for (;i < 4; i++)
cards.add(new Card(words[i], Color.GREEN, Color.GREEN));
for (;i < 9; i++)
cards.add(new Card(words[i], Color.WHITE, Color.GREEN));
for (;i < 14; i++)
cards.add(new Card(words[i], Color.GREEN, Color.WHITE));
cards.add(new Card(words[i++], Color.GREEN, Color.BLACK));
cards.add(new Card(words[i++], Color.BLACK, Color.GREEN));
cards.add(new Card(words[i++], Color.WHITE, Color.BLACK));
cards.add(new Card(words[i++], Color.BLACK, Color.WHITE));
for (;i < 25; i++)
cards.add(new Card(words[i], Color.WHITE, Color.WHITE));
Collections.shuffle(cards);
Grid g = new Grid(uuid.toString(), (Card[])cards.toArray(new Card[cards.size()]));
games.put(uuid, g);
return uuid;
}
/**********************************
* JSON ACCESSORS *
**********************************/
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.isColorBPublic()) || (player=='B' && c.isColorAPublic()))
cardNode.set("otherColor", jsn.stringNode((player=='A'?c.getColorB():c.getColorA()).name().toLowerCase()));
cardNode.set("revealed-a", jsn.booleanNode(c.isColorBPublic()));
cardNode.set("revealed-b", jsn.booleanNode(c.isColorAPublic()));
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));
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 phaseData(Phase p) {
ObjectNode out = jsn.objectNode();
out.set("a-guessing", jsn.booleanNode(p.isAGuessing()));
out.set("b-guessing", jsn.booleanNode(p.isBGuessing()));
out.set("a-hinting", jsn.booleanNode(p.isAHinting()));
out.set("b-hinting", jsn.booleanNode(p.isBHinting()));
out.set("sudden-death", jsn.booleanNode(p.isSuddenDeath()));
out.set("game-ended", jsn.booleanNode(p.isGameEnded()));
return out;
}
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));
ArrayNode eventsNode = jsn.arrayNode(g.getGameEvents().size());
for(GameEvent ge : g.getGameEvents())
eventsNode.add(eventData(g, 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()));
}
ObjectNode out = jsn.objectNode();
out.set("cards", cardsNode);
out.set("phase", phaseData(g.getPhase()));
out.set("events", eventsNode);
if(g.getCurrentHint() != null)
out.set("current-hint", currentHintNode);
out.set("current-guess-count", jsn.numberNode(g.getCurrentGuessCount()));
out.set("used-hints", jsn.numberNode(g.getUsedHints()));
out.set("used-whites", jsn.numberNode(g.getUsedWhites()));
return out;
}
/*################################*
* GAME LOGIC *
*################################*/
/** /**
@returns true iff all green cards of this player have been revealed either side @returns true iff all green cards of this player have been revealed either side
*/ */
public boolean allGreenRevealed(Grid g, char player) { private boolean allGreenRevealed(Grid g, char player) {
for(Card c : g.getCards()) { for(Card c : g.getCards()) {
if (c.getColor(player) == Color.GREEN && !c.isColorPublic(player) && if (c.getColor(player) == Color.GREEN && !c.isColorPublic(player) &&
!(c.getColor(other(player)) == Color.GREEN && c.isColorPublic(other(player)))) !(c.getColor(other(player)) == Color.GREEN && c.isColorPublic(other(player))))
@@ -230,6 +67,47 @@ public class GameManager {
return true; return true;
} }
public Grid findGrid(String gridId) {
return games.get(UUID.fromString(gridId));
}
public UUID newGrid(GridConfig gc, Random r) {
UUID uuid = UUID.randomUUID();
randoms.put(uuid, r);
String[] words = dictionnaries.randomWords(gc.getWordCount(), r);
List<Card> cards = new ArrayList<>(gc.getWordCount());
List<CardColors> distrib = gc.getWordDistribution();
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);
Phase startPhase;
if(gc.isAnyoneStarts()) {
startPhase = Phase.bothHinting();
} else {
if(r.nextBoolean())
startPhase = Phase.hinting('A');
else
startPhase = Phase.hinting('B');
}
Grid g = new Grid(uuid.toString(),
(Card[])cards.toArray(new Card[cards.size()]),
new ArrayList<>(),
null,
0,
0,
0,
startPhase,
Optional.empty(),
gc);
games.put(uuid, g);
return uuid;
}
/********************************** /**********************************
* GAME ACTIONS * * GAME ACTIONS *
**********************************/ **********************************/
@@ -239,9 +117,9 @@ public class GameManager {
newGameEvent(g, GameEvent.newGameEndEvent(win)); newGameEvent(g, GameEvent.newGameEndEvent(win));
} }
public void proposeHint(Grid g, char player, String hint, int wordCount) { public void proposeHint(Grid g, char player, String hint, int wordCount) throws IllegalGameActionException {
if (!g.getPhase().isHinting(player)) { if (!g.getPhase().isHinting(player)) {
throw new RuntimeException("This action is not allowed now"); throw new IllegalGameActionException(g, "This action is not allowed now");
} }
Hint h = new Hint(hint, wordCount); Hint h = new Hint(hint, wordCount);
@@ -258,7 +136,7 @@ public class GameManager {
*/ */
private void endGuessingRound(Grid g, char player) { private void endGuessingRound(Grid g, char player) {
// We're done with this round of guesses // We're done with this round of guesses
if(g.getUsedHints() >= MAX_HINT_COUNT) { if(g.getUsedHints() >= g.getConfig().getMaxHint()) {
// We're entering sudden death // We're entering sudden death
newGameEvent(g, GameEvent.newSuddenDeath()); newGameEvent(g, GameEvent.newSuddenDeath());
if(allGreenRevealed(g, player)) { if(allGreenRevealed(g, player)) {
@@ -279,9 +157,9 @@ public class GameManager {
} }
} }
public Card.Color pointCard(Grid g, char player, int cardIndex) { public Card.Color pointCard(Grid g, char player, int cardIndex) throws IllegalGameActionException {
if (!g.getPhase().isGuessing(player)) { if (!g.getPhase().isGuessing(player)) {
throw new RuntimeException("This action is not allowed now"); throw new IllegalGameActionException(g, "This action is not allowed now");
} }
if (cardIndex<0 || cardIndex>g.getCardCount()) { if (cardIndex<0 || cardIndex>g.getCardCount()) {
throw new IllegalArgumentException("The given card index is invalid"); throw new IllegalArgumentException("The given card index is invalid");
@@ -289,7 +167,7 @@ public class GameManager {
Card c = g.getCards()[cardIndex]; Card c = g.getCards()[cardIndex];
if(c.isColorPublic(other(player)) || (c.getColor(player) == Card.Color.GREEN && c.isColorPublic(player))) if(c.isColorPublic(other(player)) || (c.getColor(player) == Card.Color.GREEN && c.isColorPublic(player)))
throw new RuntimeException("Card has already been revealed"); throw new IllegalGameActionException(g, "Card has already been revealed");
c.setColorPublic(other(player), true); c.setColorPublic(other(player), true);
Card.Color guessedColor = c.getColor(other(player)); Card.Color guessedColor = c.getColor(other(player));
@@ -328,9 +206,9 @@ public class GameManager {
return guessedColor; return guessedColor;
} }
public void endGuessing(Grid g, char player) { public void endGuessing(Grid g, char player) throws IllegalGameActionException {
if (!g.getPhase().isGuessing(player)) { if (!g.getPhase().isGuessing(player)) {
throw new RuntimeException("This action is not allowed now"); throw new IllegalGameActionException(g, "This action is not allowed now");
} }
endGuessingRound(g, player); endGuessingRound(g, player);
newGameEvent(g, GameEvent.newEndGuessingEvent(true, player)); newGameEvent(g, GameEvent.newEndGuessingEvent(true, player));
@@ -0,0 +1,16 @@
package com.bernard.nodecames.game;
import com.bernard.nodecames.model.Grid;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public class IllegalGameActionException extends Exception {
private final Grid g;
private final String message;
}
@@ -1,6 +1,5 @@
package com.bernard.nodecames.model; package com.bernard.nodecames.model;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@@ -30,9 +29,7 @@ public class Grid {
Phase phase; Phase phase;
Optional<Boolean> won; Optional<Boolean> won;
public Grid(String name, Card[] cards) { GridConfig config;
this(name, cards, new ArrayList<>(), null, 0, 0, 0, Phase.bothHinting(), Optional.empty());
}
public void incrementHintCount() { public void incrementHintCount() {
this.setUsedHints(this.getUsedHints()+1); this.setUsedHints(this.getUsedHints()+1);
@@ -0,0 +1,54 @@
package com.bernard.nodecames.model;
import java.util.List;
import java.util.Set;
import com.bernard.nodecames.model.Card.Color;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Value;
@Getter
@AllArgsConstructor
public class GridConfig {
// Immutable options
// Maximum of maximom number of hints
// This is to trigger end of game
private int maxMaxHint;//TODO
private boolean structuredGrid;//TODO
private boolean anyoneStarts;
private List<CardColors> wordDistribution;
// Mutable options
private int maxHint;//TODO
private Set<Integer> availableHintWordCount;//TODO
private boolean oneMoreGuess;//TODO
private int hintMaxLength;//TODO
private boolean endGuessing;//TODO
private boolean onlyOneGreenGreen;//TODO
private boolean suddenDeath;//TODO
private boolean hintsInARow;//TODO
public int getWordCount() {
return wordDistribution.size();
}
@Value(staticConstructor = "of")
public static final class CardColors {
Color faceA;
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);
}
}