Prepared grid config logic
This commit is contained in:
@@ -1,5 +1,18 @@
|
||||
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 org.springframework.stereotype.Controller;
|
||||
@@ -8,14 +21,17 @@ import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.servlet.view.RedirectView;
|
||||
|
||||
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
|
||||
public class HttpController {
|
||||
|
||||
GameManager gm;
|
||||
private final GameManager gm;
|
||||
private final Random rand = new Random();
|
||||
|
||||
@GetMapping("/")
|
||||
public String index() {
|
||||
@@ -27,9 +43,22 @@ public class HttpController {
|
||||
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")
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 com.bernard.nodecames.game.GameManager;
|
||||
import com.bernard.nodecames.game.IllegalGameActionException;
|
||||
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 tools.jackson.databind.JsonNode;
|
||||
import tools.jackson.databind.node.JsonNodeFactory;
|
||||
|
||||
@Controller
|
||||
public class WebSocketController {
|
||||
|
||||
private static final JsonNodeFactory jsn = JsonNodeFactory.instance;
|
||||
|
||||
private GameManager gm;
|
||||
private SimpMessagingTemplate template;
|
||||
private JsonDataService json;
|
||||
|
||||
public WebSocketController(GameManager gm, SimpMessagingTemplate template) {
|
||||
public WebSocketController(GameManager gm, SimpMessagingTemplate template, JsonDataService json) {
|
||||
this.gm = gm;
|
||||
this.template = template;
|
||||
this.json = json;
|
||||
gm.setWsc(this);
|
||||
}
|
||||
|
||||
public void onNewEvent(Grid g, GameEvent ge) {
|
||||
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(
|
||||
"/topic/new-phase",
|
||||
gm.phaseData(g.getPhase())
|
||||
json.phaseData(g.getPhase())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -52,7 +51,7 @@ public class WebSocketController {
|
||||
Card c = g.getCards()[cardIndex];
|
||||
this.template.convertAndSend(
|
||||
"/topic/update-card/" + Character.toLowerCase(player),
|
||||
gm.cardData(c, player),
|
||||
json.cardData(c, player),
|
||||
Map.of("cardIndex", cardIndex)
|
||||
);
|
||||
}
|
||||
@@ -61,6 +60,10 @@ public class WebSocketController {
|
||||
publishCardUpdateToPlayer(g, cardIndex, 'B');
|
||||
}
|
||||
|
||||
public void publishError(char issuer, IllegalGameActionException e) {
|
||||
//TODO open error channel
|
||||
}
|
||||
|
||||
@GetMapping("/room/{id}/game/{player}")
|
||||
public Object grid(@PathVariable("id") String gridId, @PathVariable("player") String playerStr) {
|
||||
char player;
|
||||
@@ -73,7 +76,7 @@ public class WebSocketController {
|
||||
|
||||
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")
|
||||
@@ -83,7 +86,12 @@ public class WebSocketController {
|
||||
|
||||
Grid g = gm.findGrid(roomId);
|
||||
|
||||
gm.proposeHint(g, player.charAt(0), hint, wordCount);
|
||||
try {
|
||||
gm.proposeHint(g, player.charAt(0), hint, wordCount);
|
||||
} catch (IllegalGameActionException e) {
|
||||
publishError(player.charAt(0), e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@MessageMapping("point-card")
|
||||
@@ -91,13 +99,23 @@ public class WebSocketController {
|
||||
int cardIndex = content.asObject().get("cardIndex").intValue();
|
||||
|
||||
Grid g = gm.findGrid(roomId);
|
||||
gm.pointCard(g, player.charAt(0), cardIndex);
|
||||
publishCardUpdate(g, cardIndex);
|
||||
try {
|
||||
gm.pointCard(g, player.charAt(0), cardIndex);
|
||||
publishCardUpdate(g, cardIndex);
|
||||
} catch (IllegalGameActionException e) {
|
||||
publishError(player.charAt(0), e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@MessageMapping("end-guessing")
|
||||
public void endGuessing(@Header("room") String roomId, @Header("player") String player) {
|
||||
Grid g = gm.findGrid(roomId);
|
||||
gm.endGuessing(g, player.charAt(0));
|
||||
try {
|
||||
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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
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.frontend.WebSocketController;
|
||||
@@ -24,29 +17,25 @@ import com.bernard.nodecames.model.Card.Color;
|
||||
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.Hint;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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
|
||||
@RequiredArgsConstructor
|
||||
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
|
||||
private static Map<UUID, Grid> games = new HashMap<>();
|
||||
private static Map<UUID, Random> randoms = new HashMap<>();
|
||||
|
||||
@Setter
|
||||
WebSocketController wsc;
|
||||
private WebSocketController wsc = null;
|
||||
|
||||
@Value("classpath:dictionaries/fr.txt")
|
||||
Resource dictFr;
|
||||
private final DictionnariesService dictionnaries;
|
||||
|
||||
private void newGameEvent(Grid g, GameEvent ge) {
|
||||
g.newGameEvent(ge);
|
||||
@@ -58,170 +47,18 @@ public class GameManager {
|
||||
if(!phase.isAnyoneGuessing()) {
|
||||
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) {
|
||||
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
|
||||
*/
|
||||
public boolean allGreenRevealed(Grid g, char player) {
|
||||
private boolean allGreenRevealed(Grid g, char player) {
|
||||
for(Card c : g.getCards()) {
|
||||
if (c.getColor(player) == Color.GREEN && !c.isColorPublic(player) &&
|
||||
!(c.getColor(other(player)) == Color.GREEN && c.isColorPublic(other(player))))
|
||||
@@ -230,6 +67,47 @@ public class GameManager {
|
||||
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 *
|
||||
**********************************/
|
||||
@@ -239,9 +117,9 @@ public class GameManager {
|
||||
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)) {
|
||||
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);
|
||||
@@ -258,7 +136,7 @@ public class GameManager {
|
||||
*/
|
||||
private void endGuessingRound(Grid g, char player) {
|
||||
// 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
|
||||
newGameEvent(g, GameEvent.newSuddenDeath());
|
||||
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)) {
|
||||
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()) {
|
||||
throw new IllegalArgumentException("The given card index is invalid");
|
||||
@@ -289,7 +167,7 @@ public class GameManager {
|
||||
Card c = g.getCards()[cardIndex];
|
||||
|
||||
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);
|
||||
Card.Color guessedColor = c.getColor(other(player));
|
||||
|
||||
@@ -328,9 +206,9 @@ public class GameManager {
|
||||
return guessedColor;
|
||||
}
|
||||
|
||||
public void endGuessing(Grid g, char player) {
|
||||
public void endGuessing(Grid g, char player) throws IllegalGameActionException {
|
||||
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);
|
||||
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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -30,9 +29,7 @@ public class Grid {
|
||||
Phase phase;
|
||||
Optional<Boolean> won;
|
||||
|
||||
public Grid(String name, Card[] cards) {
|
||||
this(name, cards, new ArrayList<>(), null, 0, 0, 0, Phase.bothHinting(), Optional.empty());
|
||||
}
|
||||
GridConfig config;
|
||||
|
||||
public void incrementHintCount() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user