Remade game logic, added sudden death
This commit is contained in:
@@ -7,7 +7,6 @@ import org.springframework.http.ResponseEntity;
|
||||
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;
|
||||
@@ -15,17 +14,14 @@ import org.springframework.web.bind.annotation.PathVariable;
|
||||
|
||||
import com.bernard.nodecames.game.GameManager;
|
||||
import com.bernard.nodecames.model.Card;
|
||||
import com.bernard.nodecames.model.GameEvent;
|
||||
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;
|
||||
@@ -33,12 +29,25 @@ public class WebSocketController {
|
||||
private GameManager gm;
|
||||
private SimpMessagingTemplate template;
|
||||
|
||||
public void publishNewPhase(Grid g) {
|
||||
this.template.convertAndSend(
|
||||
"/topic/new-phase",
|
||||
g.getPhase().name().toLowerCase()
|
||||
public WebSocketController(GameManager gm, SimpMessagingTemplate template) {
|
||||
this.gm = gm;
|
||||
this.template = template;
|
||||
gm.setWsc(this);
|
||||
}
|
||||
|
||||
public void onNewEvent(Grid g, GameEvent ge) {
|
||||
this.template.convertAndSend("/topic/new-event",
|
||||
gm.eventData(g, ge)
|
||||
);
|
||||
}
|
||||
|
||||
public void onNewPhase(Grid g, Phase p) {
|
||||
this.template.convertAndSend(
|
||||
"/topic/new-phase",
|
||||
gm.phaseData(g.getPhase())
|
||||
);
|
||||
}
|
||||
|
||||
public void publishCardUpdateToPlayer(Grid g, int cardIndex, char player) {
|
||||
Card c = g.getCards()[cardIndex];
|
||||
this.template.convertAndSend(
|
||||
@@ -68,42 +77,27 @@ public class WebSocketController {
|
||||
}
|
||||
|
||||
@MessageMapping("submit-hint")
|
||||
@SendTo("/topic/submit-hint")
|
||||
public JsonNode submitHint(@Header("room") String roomId, @Header("player") String player, @Payload JsonNode content) {
|
||||
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);
|
||||
|
||||
gm.proposeHint(g, player.charAt(0), hint, wordCount);
|
||||
content.asObject().set("player", jsn.stringNode(player));
|
||||
this.publishNewPhase(g);
|
||||
return content;
|
||||
}
|
||||
|
||||
@MessageMapping("point-card")
|
||||
@SendTo("/topic/point-card")
|
||||
public JsonNode pointCard(@Header("room") String roomId, @Header("player") String player, @Payload JsonNode content) {
|
||||
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);
|
||||
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);
|
||||
gm.pointCard(g, player.charAt(0), cardIndex);
|
||||
publishCardUpdate(g, cardIndex);
|
||||
return content;
|
||||
}
|
||||
|
||||
@MessageMapping("end-guessing")
|
||||
@SendTo("/topic/end-guessing")
|
||||
public JsonNode 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);
|
||||
gm.endGuessing(g, player.charAt(0));
|
||||
|
||||
ObjectNode out = jsn.objectNode();
|
||||
out.set("player", jsn.stringNode(player));
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ 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;
|
||||
@@ -17,12 +18,15 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.Resource;
|
||||
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.Grid;
|
||||
import com.bernard.nodecames.model.Grid.Phase;
|
||||
import com.bernard.nodecames.model.Hint;
|
||||
import com.bernard.nodecames.model.Card.Color;
|
||||
|
||||
import lombok.Setter;
|
||||
import tools.jackson.databind.JsonNode;
|
||||
import tools.jackson.databind.node.ArrayNode;
|
||||
import tools.jackson.databind.node.JsonNodeFactory;
|
||||
@@ -33,12 +37,30 @@ 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<>();
|
||||
|
||||
@Setter
|
||||
WebSocketController wsc;
|
||||
|
||||
@Value("classpath:dictionaries/fr.txt")
|
||||
Resource dictFr;
|
||||
|
||||
private void newGameEvent(Grid g, GameEvent ge) {
|
||||
g.newGameEvent(ge);
|
||||
wsc.onNewEvent(g, ge);
|
||||
}
|
||||
|
||||
private void setPhase(Grid g, Phase phase) {
|
||||
g.setPhase(phase);
|
||||
if(!phase.isAnyoneGuessing()) {
|
||||
g.setCurrentHint(null);
|
||||
}
|
||||
wsc.onNewPhase(g, phase);
|
||||
}
|
||||
|
||||
//TODO do Spring boot magic to only generate this once
|
||||
public List<String> allWords() {
|
||||
try (Scanner wordsScanner = new Scanner(dictFr.getInputStream())) {
|
||||
@@ -70,73 +92,10 @@ public class GameManager {
|
||||
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;
|
||||
private char other(char player) {
|
||||
return (player=='A')?'B':'A';
|
||||
}
|
||||
|
||||
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++)
|
||||
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", jsn.stringNode(g.getPhase().name().toLowerCase()));
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
public Grid findGrid(String gridId) {
|
||||
return games.get(UUID.fromString(gridId));
|
||||
}
|
||||
@@ -169,27 +128,159 @@ public class GameManager {
|
||||
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) {
|
||||
for(Card c : g.getCards()) {
|
||||
if (c.getColor(player) == Color.GREEN && !c.isColorPublic(player) &&
|
||||
!(c.getColor(other(player)) == Color.GREEN && c.isColorPublic(other(player))))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**********************************
|
||||
* GAME ACTIONS *
|
||||
**********************************/
|
||||
private void endGame(Grid g, boolean win) {
|
||||
g.setWon(Optional.of(win));
|
||||
setPhase(g, Phase.gameEnded());
|
||||
newGameEvent(g, GameEvent.newGameEndEvent(win));
|
||||
}
|
||||
|
||||
public void proposeHint(Grid g, char player, String hint, int wordCount) {
|
||||
if (!(((player == 'A') && (g.getPhase() == Grid.Phase.HINTING_A)) ||
|
||||
((player == 'B') && (g.getPhase() == Grid.Phase.HINTING_B)) ||
|
||||
g.getPhase() == Grid.Phase.HINTING_ANY)) {
|
||||
if (!g.getPhase().isHinting(player)) {
|
||||
throw new RuntimeException("This action is not allowed now");
|
||||
}
|
||||
|
||||
Hint h = new Hint(hint, wordCount);
|
||||
g.incrementHintCount();
|
||||
g.newGameEvent(GameEvent.newHintEvent(h, player));
|
||||
if (player == 'A')
|
||||
g.setPhase(Grid.Phase.GUESSING_B);
|
||||
else
|
||||
g.setPhase(Grid.Phase.GUESSING_A);
|
||||
g.setCurrentHint(h);
|
||||
g.setCurrentGuessCount(0);
|
||||
newGameEvent(g, GameEvent.newHintEvent(h, player));
|
||||
setPhase(g, Phase.guessing(other(player)));
|
||||
}
|
||||
|
||||
/**
|
||||
Sets the phase of the grid when player ends its guessing round.
|
||||
@param g Must neither be ended neither be in a sudden death
|
||||
*/
|
||||
private void endGuessingRound(Grid g, char player) {
|
||||
// We're done with this round of guesses
|
||||
if(g.getUsedHints() >= MAX_HINT_COUNT) {
|
||||
// We're entering sudden death
|
||||
newGameEvent(g, GameEvent.newSuddenDeath());
|
||||
if(allGreenRevealed(g, player)) {
|
||||
// If all my greens are revealed, i'm the only one to guess
|
||||
setPhase(g, Phase.suddenDeathOnly(player));
|
||||
} else {
|
||||
// Else, we are both guessing
|
||||
setPhase(g, Phase.suddenDeathBoth());
|
||||
}
|
||||
} else {
|
||||
// Next regular hinting round
|
||||
if(allGreenRevealed(g, player)) {
|
||||
// I can't hint anymore
|
||||
setPhase(g, Phase.hinting(other(player)));
|
||||
} else {
|
||||
setPhase(g, Phase.hinting(player));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)))) {
|
||||
if (!g.getPhase().isGuessing(player)) {
|
||||
throw new RuntimeException("This action is not allowed now");
|
||||
}
|
||||
if (cardIndex<0 || cardIndex>g.getCardCount()) {
|
||||
@@ -197,69 +288,52 @@ public class GameManager {
|
||||
}
|
||||
Card c = g.getCards()[cardIndex];
|
||||
|
||||
boolean endedGuessing = false;
|
||||
|
||||
if (player == 'A') {
|
||||
if (c.isRevealedA() || (c.isRevealedB() && c.getColorA() == Card.Color.GREEN)) {
|
||||
throw new RuntimeException("Card has already been revealed");
|
||||
}
|
||||
c.setRevealedA(true);
|
||||
switch(c.getColorB()) {
|
||||
case Card.Color.GREEN:
|
||||
break;
|
||||
case Card.Color.WHITE:
|
||||
g.incrementWhiteCount();
|
||||
endedGuessing = true;
|
||||
break;
|
||||
case Card.Color.BLACK:
|
||||
endedGuessing = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (c.isRevealedB() || (c.isRevealedA() && c.getColorB() == Card.Color.GREEN)) {
|
||||
throw new RuntimeException("Card has already been revealed");
|
||||
}
|
||||
c.setRevealedB(true);
|
||||
switch(c.getColorA()) {
|
||||
case Card.Color.GREEN:
|
||||
break;
|
||||
case Card.Color.WHITE:
|
||||
g.incrementWhiteCount();
|
||||
endedGuessing = true;
|
||||
break;
|
||||
case Card.Color.BLACK:
|
||||
endedGuessing = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
g.newGameEvent(GameEvent.newGuessEvent(c, player));
|
||||
g.incrementCurrentGuessCount();
|
||||
if (!g.getCurrentHint().canStillGuess(g.getCurrentGuessCount()))
|
||||
endedGuessing = true;
|
||||
if(c.isColorPublic(other(player)) || (c.getColor(player) == Card.Color.GREEN && c.isColorPublic(player)))
|
||||
throw new RuntimeException("Card has already been revealed");
|
||||
c.setColorPublic(other(player), true);
|
||||
Card.Color guessedColor = c.getColor(other(player));
|
||||
|
||||
if(endedGuessing) {
|
||||
g.setCurrentHint(null);
|
||||
if (player == 'A')
|
||||
g.setPhase(Grid.Phase.HINTING_A);
|
||||
else
|
||||
g.setPhase(Grid.Phase.HINTING_B);
|
||||
g.newGameEvent(GameEvent.newEndGuessingEvent(false, player));
|
||||
newGameEvent(g, GameEvent.newGuessEvent(c, player));
|
||||
g.incrementCurrentGuessCount();
|
||||
if(guessedColor == Color.BLACK)
|
||||
endGame(g, false);
|
||||
else if (g.getPhase().isSuddenDeath() && guessedColor == Color.WHITE)
|
||||
endGame(g, false);
|
||||
else if (allGreenRevealed(g, other(player)) && guessedColor == Color.GREEN) {
|
||||
// i.e. i made my last guess
|
||||
if (allGreenRevealed(g, player)) {
|
||||
endGame(g, true);
|
||||
} else if (g.getPhase().isSuddenDeath()) {
|
||||
// Sudden death with only the other
|
||||
setPhase(g, Phase.suddenDeathOnly(other(player)));
|
||||
} else {
|
||||
endGuessingRound(g, player);
|
||||
}
|
||||
} else if (!allGreenRevealed(g, other(player)) && guessedColor == Color.GREEN) {
|
||||
// There is still cards i should find
|
||||
if (!g.getPhase().isSuddenDeath() && !g.getCurrentHint().canStillGuess(g.getCurrentGuessCount())) {
|
||||
// We're done with this round of guesses
|
||||
endGuessingRound(g, player);
|
||||
} else {
|
||||
// I continue guessing cards in this round
|
||||
}
|
||||
} else if (guessedColor == Color.WHITE && !g.getPhase().isSuddenDeath()){
|
||||
endGuessingRound(g, player);
|
||||
} else {
|
||||
throw new IllegalStateException("This should logically never happen");
|
||||
}
|
||||
return (player == 'A')?c.getColorB():c.getColorA();
|
||||
if(!g.getPhase().isAnyoneGuessing())
|
||||
// It means this pick ended the guessing turn
|
||||
newGameEvent(g, GameEvent.newEndGuessingEvent(false, player));
|
||||
return guessedColor;
|
||||
}
|
||||
|
||||
|
||||
public void endGuessing(Grid g, char player) {
|
||||
if (!(((player == 'A') && (g.getPhase() == Grid.Phase.GUESSING_A)) ||
|
||||
((player == 'B') && (g.getPhase() == Grid.Phase.GUESSING_B)))) {
|
||||
if (!g.getPhase().isGuessing(player)) {
|
||||
throw new RuntimeException("This action is not allowed now");
|
||||
}
|
||||
|
||||
g.setCurrentHint(null);
|
||||
if (player == 'A')
|
||||
g.setPhase(Grid.Phase.HINTING_B);
|
||||
else
|
||||
g.setPhase(Grid.Phase.HINTING_A);
|
||||
g.newGameEvent(GameEvent.newEndGuessingEvent(true, player));
|
||||
endGuessingRound(g, player);
|
||||
newGameEvent(g, GameEvent.newEndGuessingEvent(true, player));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,14 +13,33 @@ public class Card {
|
||||
|
||||
Color colorA;
|
||||
Color colorB;
|
||||
// revealedA is wether A knows B's color
|
||||
boolean revealedA;
|
||||
boolean revealedB;
|
||||
boolean colorAPublic;
|
||||
boolean colorBPublic;
|
||||
|
||||
public Card(String word, Color colorA, Color colorB) {
|
||||
this(word, colorA, colorB, false, false);
|
||||
}
|
||||
|
||||
public Color getColor(char player) {
|
||||
if(player == 'A') return this.getColorA();
|
||||
else return this.getColorB();
|
||||
}
|
||||
public boolean isColorPublic(char player) {
|
||||
if(player == 'A') return this.isColorAPublic();
|
||||
else return this.isColorBPublic();
|
||||
}
|
||||
public void setColorPublic(char player, boolean colorPublic) {
|
||||
if(player == 'A') this.setColorAPublic(colorPublic);
|
||||
else this.setColorBPublic(colorPublic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s [%s%s(%s%s)]", getWord(),
|
||||
getColorA().name().charAt(0), getColorB().name().charAt(0),
|
||||
isColorAPublic()?'O':'X', isColorBPublic()?'O':'X');
|
||||
}
|
||||
|
||||
public enum Color {
|
||||
GREEN,
|
||||
WHITE,
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.bernard.nodecames.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
|
||||
@@ -28,8 +28,11 @@ public class GameEvent {
|
||||
public static GameEvent newEndGuessingEvent(boolean manual, char issuer) {
|
||||
return new GameEvent(Type.END_GUESSING, manual, issuer);
|
||||
}
|
||||
public static GameEvent newStartGameEvent() {
|
||||
return new GameEvent(Type.START_GAME, null, '_');
|
||||
public static GameEvent newGameEndEvent(boolean won) {
|
||||
return new GameEvent(Type.GAME_END, won, '_');
|
||||
}
|
||||
public static GameEvent newSuddenDeath() {
|
||||
return new GameEvent(Type.SUDDEN_DEATH, Object.class, '_');
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@@ -37,7 +40,8 @@ public class GameEvent {
|
||||
HINT(Hint.class),
|
||||
GUESS(Card.class),
|
||||
END_GUESSING(Boolean.class),
|
||||
START_GAME(Object.class);
|
||||
GAME_END(Boolean.class),
|
||||
SUDDEN_DEATH(Object.class);
|
||||
|
||||
Class<?> dataType;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,10 @@ package com.bernard.nodecames.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
@@ -29,9 +28,10 @@ public class Grid {
|
||||
int usedWhites;
|
||||
|
||||
Phase phase;
|
||||
Optional<Boolean> won;
|
||||
|
||||
public Grid(String name, Card[] cards) {
|
||||
this(name, cards, new ArrayList<>(), null, 0, 0, 0, Phase.HINTING_ANY);
|
||||
this(name, cards, new ArrayList<>(), null, 0, 0, 0, Phase.bothHinting(), Optional.empty());
|
||||
}
|
||||
|
||||
public void incrementHintCount() {
|
||||
@@ -50,12 +50,59 @@ public class Grid {
|
||||
return this.getCards().length;
|
||||
}
|
||||
|
||||
public enum Phase {
|
||||
HINTING_ANY,
|
||||
HINTING_A,
|
||||
HINTING_B,
|
||||
GUESSING_A,
|
||||
GUESSING_B;
|
||||
@EqualsAndHashCode
|
||||
@Getter
|
||||
public static class Phase {
|
||||
private boolean aHinting;
|
||||
private boolean bHinting;
|
||||
private boolean aGuessing;
|
||||
private boolean bGuessing;
|
||||
private boolean suddenDeath;
|
||||
|
||||
boolean gameEnded;
|
||||
|
||||
private Phase(boolean aHinting,
|
||||
boolean bHinting,
|
||||
boolean aGuessing,
|
||||
boolean bGuessing,
|
||||
boolean suddenDeath,
|
||||
boolean gameEnded) {
|
||||
this.aHinting = aHinting;
|
||||
this.bHinting = bHinting;
|
||||
this.aGuessing = aGuessing;
|
||||
this.bGuessing = bGuessing;
|
||||
this.suddenDeath = suddenDeath;
|
||||
this.gameEnded = gameEnded;
|
||||
}
|
||||
|
||||
public boolean isHinting(char player) {
|
||||
return (player == 'A')?this.isAHinting():this.isBHinting();
|
||||
}
|
||||
public boolean isGuessing(char player) {
|
||||
return (player == 'A')?this.isAGuessing():this.isBGuessing();
|
||||
}
|
||||
public boolean isAnyoneGuessing() {
|
||||
return this.isAGuessing() || this.isBGuessing();
|
||||
}
|
||||
|
||||
public static Phase bothHinting() {
|
||||
return new Phase(true, true, false, false, false, false);
|
||||
}
|
||||
public static Phase hinting(char player) {
|
||||
return new Phase((player == 'A'), (player == 'B'), false, false, false, false);
|
||||
}
|
||||
public static Phase guessing(char player) {
|
||||
return new Phase(false, false, (player == 'A'), (player == 'B'), false, false);
|
||||
}
|
||||
public static Phase suddenDeathBoth() {
|
||||
return new Phase(false, false, true, true, true, false);
|
||||
}
|
||||
public static Phase suddenDeathOnly(char player) {
|
||||
return new Phase(false, false, player == 'A', player == 'B', true, false);
|
||||
}
|
||||
public static Phase gameEnded() {
|
||||
return new Phase(false,false,false,false,false,true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,36 +12,34 @@ function getRoomId() {
|
||||
return res[1]
|
||||
}
|
||||
|
||||
/*
|
||||
* Update html to match state
|
||||
*/
|
||||
function updatePhase(phase) {
|
||||
if(phase == "hinting_any" || (phase == "hinting_a" && player == 'A') || (phase == "hinting_b" && player == 'B')) {
|
||||
// Hinting
|
||||
$('.card-button').css('visibility', 'hidden')
|
||||
$('#hint-text').hide()
|
||||
$('#submit-hint').show()
|
||||
$('#other-playing').hide()
|
||||
$('#end-guessing').hide()
|
||||
} else if ((phase == "hinting_b" && player == 'A') || (phase == "hinting_a" && player == 'B')) {
|
||||
// Wait for the other one
|
||||
$('.card-button').css('visibility', 'hidden')
|
||||
$('#hint-text').hide()
|
||||
$('#submit-hint').hide()
|
||||
$('#other-playing').show()
|
||||
$('#end-guessing').hide()
|
||||
} else if ((phase == "guessing_a" && player == 'B') || (phase == "guessing_b" && player == 'A')) {
|
||||
// Wait for the other one
|
||||
$('.card-button').css('visibility', 'hidden')
|
||||
$('#hint-text').show()
|
||||
$('#submit-hint').hide()
|
||||
$('#other-playing').show()
|
||||
$('#end-guessing').hide()
|
||||
} else if ((phase == "guessing_a" && player == 'A') || (phase == "guessing_b" && player == 'B')) {
|
||||
// Guessing
|
||||
p = player.toLowerCase()
|
||||
o = (player=='A')?'b':'a'
|
||||
if(phase[p+'-guessing']) {
|
||||
$('.card-button').css('visibility', 'visible')
|
||||
$('.card-button.untouchable').css('visibility', 'hidden')
|
||||
$('#end-guessing-button').show()
|
||||
} else {
|
||||
$('.card-button').css('visibility', 'hidden')
|
||||
$('#end-guessing-button').hide()
|
||||
}
|
||||
if(phase[p+'-guessing'] || phase[o+'-guessing']) {
|
||||
$('#hint-text').show()
|
||||
} else {
|
||||
$('#hint-text').hide()
|
||||
}
|
||||
if(phase[p+'-hinting']) {
|
||||
$('#submit-hint').show()
|
||||
} else {
|
||||
$('#submit-hint').hide()
|
||||
}
|
||||
if(!phase[p+'-guessing'] & !phase[p+'-hinting']) {
|
||||
$('#other-playing').show()
|
||||
} else {
|
||||
$('#other-playing').hide()
|
||||
$('#end-guessing').show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +47,20 @@ function setHint(hint, wordCount) {
|
||||
$('#hint-text').text(hint+" in "+wordCount)
|
||||
}
|
||||
|
||||
function createCard(i) {
|
||||
html = `
|
||||
<li class="card ${cards[i].color}" id="card-${i}">
|
||||
<span class="card-word">${cards[i].word}</span><br/>
|
||||
<div class="card-bottom-bar">
|
||||
<div id="card-${i}-a-public">A</div>
|
||||
<button class="card-button" id="card-${i}-button">Toucher</button>
|
||||
<div id="card-${i}-b-public">B</div>
|
||||
</div>
|
||||
</li>
|
||||
`
|
||||
$('#cards-list').append($(html))
|
||||
$(`#card-${i}-button`).on('click', pointCard)
|
||||
}
|
||||
function updateCard(i) {
|
||||
card = cards[i]
|
||||
if(card['revealed-a']) {
|
||||
@@ -76,26 +88,102 @@ function updateCard(i) {
|
||||
}
|
||||
}
|
||||
|
||||
function addHintEvent(issuer, hint, hintWordCount) {
|
||||
$("#event-log").append($(`
|
||||
<li>
|
||||
${issuer} proposed "${hint}" in ${hintWordCount} word(s)
|
||||
</li>
|
||||
`))
|
||||
}
|
||||
|
||||
function addPointEvent(issuer, cardIndex, color) {
|
||||
$("#event-log").append($(`
|
||||
<li>
|
||||
${issuer} pointed card ${cards[cardIndex].word} (${color})
|
||||
</li>
|
||||
`))
|
||||
}
|
||||
|
||||
function addEndGuessingEvent(issuer, manual) {
|
||||
if(manual) {
|
||||
$("#event-log").append($(`
|
||||
<li>
|
||||
${issuer} ended guessing
|
||||
</li>
|
||||
`))
|
||||
} else {
|
||||
$("#event-log").append($(`
|
||||
<li>
|
||||
${issuer} can't guess anymore
|
||||
</li>
|
||||
`))
|
||||
}
|
||||
}
|
||||
function addSuddenDeathEvent() {
|
||||
$("#event-log").append($(`
|
||||
<li>
|
||||
Starting sudden death
|
||||
</li>
|
||||
`))
|
||||
}
|
||||
function addEndGameEvent(issuer, won) {
|
||||
if(won) {
|
||||
$("#event-log").append($(`
|
||||
<li>
|
||||
You won the game !
|
||||
</li>
|
||||
`))
|
||||
} else {
|
||||
$("#event-log").append($(`
|
||||
<li>
|
||||
You lost the game !
|
||||
</li>
|
||||
`))
|
||||
}
|
||||
}
|
||||
|
||||
function addEvent(e) {
|
||||
switch(e.type) {
|
||||
case "hint":
|
||||
addHintEvent(e.issuer, e.word, e.wordCount)
|
||||
setHint(e.word, e.wordCount)
|
||||
break
|
||||
case "guess":
|
||||
addPointEvent(e.issuer, e['card-index'], e.color)
|
||||
break
|
||||
case "end_guessing":
|
||||
addEndGuessingEvent(e.issuer, e.manual)
|
||||
break
|
||||
case "game_end":
|
||||
addEndGameEvent(e.issuer, e.win)
|
||||
break
|
||||
case "sudden_death":
|
||||
addSuddenDeathEvent()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Listeners
|
||||
*/
|
||||
|
||||
function initGame(data) {
|
||||
cards = data['cards']
|
||||
$('#cards-list').empty()
|
||||
for(var i = 0; i<cards.length; i++) {
|
||||
html = `
|
||||
<li class="card ${cards[i].color}" id="card-${i}">
|
||||
<span class="card-word">${cards[i].word}</span><br/>
|
||||
<div class="card-bottom-bar">
|
||||
<div id="card-${i}-a-public">A</div>
|
||||
<button class="card-button" id="card-${i}-button">Toucher</button>
|
||||
<div id="card-${i}-b-public">B</div>
|
||||
</div>
|
||||
</li>
|
||||
`
|
||||
$('#cards-list').append($(html))
|
||||
$(`#card-${i}-button`).on('click', pointCard)
|
||||
createCard(i)
|
||||
updateCard(i)
|
||||
}
|
||||
|
||||
updatePhase(data['phase'])
|
||||
events = data.events
|
||||
for(var i = 0; i<events.length; i++) {
|
||||
e = events[i]
|
||||
addEvent(e)
|
||||
}
|
||||
if(data['current-hint']) {
|
||||
setHint(data['current-hint'].word, data['current-hint'].wordCount)
|
||||
}
|
||||
}
|
||||
|
||||
function selectPlayer(e) {
|
||||
@@ -117,10 +205,8 @@ function selectPlayer(e) {
|
||||
|
||||
socket = Stomp.over(new SockJS('/socket'));
|
||||
socket.connect({}, function (frame) {
|
||||
socket.subscribe('/topic/submit-hint', onSubmitHint);
|
||||
socket.subscribe('/topic/point-card', onPointCard);
|
||||
socket.subscribe('/topic/end-guessing', onEndGuessing);
|
||||
socket.subscribe('/topic/new-phase', onNewPhase);
|
||||
socket.subscribe('/topic/new-event', onNewEvent);
|
||||
socket.subscribe('/topic/update-card/' + player.toLowerCase(), onUpdateCard);
|
||||
});
|
||||
}
|
||||
@@ -142,34 +228,13 @@ function pointCard(e) {
|
||||
function endGuessing(e) {
|
||||
socket.send('/app/end-guessing', wsHeaders, "")
|
||||
}
|
||||
|
||||
function onSubmitHint(m) {
|
||||
data = JSON.parse(m.body)
|
||||
setHint(data['hint'], data['hintWordCount'])
|
||||
$("#event-log").append($(`
|
||||
<li>
|
||||
${data.player} proposed "${data['hint']}" in ${data['hintWordCount']} word(s)
|
||||
</li>
|
||||
`))
|
||||
}
|
||||
function onPointCard(m) {
|
||||
data = JSON.parse(m.body)
|
||||
i = data['cardIndex']
|
||||
$("#event-log").append($(`
|
||||
<li>
|
||||
${data.player} pointed card ${cards[i].word} (${data.color})
|
||||
</li>
|
||||
`))
|
||||
}
|
||||
function onEndGuessing(m) {
|
||||
$("#event-log").append($(`
|
||||
<li>
|
||||
Ended guessing
|
||||
</li>
|
||||
`))
|
||||
}
|
||||
function onNewPhase(m) {
|
||||
updatePhase(m.body)
|
||||
phase = JSON.parse(m.body)
|
||||
updatePhase(phase)
|
||||
}
|
||||
function onNewEvent(m) {
|
||||
e = JSON.parse(m.body)
|
||||
addEvent(e)
|
||||
}
|
||||
|
||||
function onUpdateCard(m) {
|
||||
|
||||
Reference in New Issue
Block a user