package com.bernard.nodecames.model; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.UUID; import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.type.SqlTypes; import jakarta.annotation.Nullable; import jakarta.persistence.AttributeConverter; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Convert; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.Id; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import tools.jackson.databind.JsonNode; import tools.jackson.databind.node.ArrayNode; import tools.jackson.databind.node.JsonNodeFactory; @NoArgsConstructor @AllArgsConstructor @Getter @Setter @Entity public class Grid { @Id UUID id; @ManyToOne(optional = true) World w; //@Type(JsonType.class) @Column(columnDefinition = "json") @Convert(converter = GameEventListAdapter.class) @JdbcTypeCode(SqlTypes.JSON) List gameEvents; @Embedded Hint currentHint = null; int currentGuessCount; int usedHints; int usedWhites; Phase phase; //TODO make this parameter a real Optional with Jdbc understanding that optional means nullable @Nullable Boolean won; @Embedded GridConfig config; public Grid(UUID id, World w, Phase phase, GridConfig config) { this(id, w, List.of(), null, 0, 0, 0, phase, null, config); } public void incrementHintCount() { this.setUsedHints(this.getUsedHints()+1); } public void incrementWhiteCount() { this.setUsedWhites(this.getUsedWhites()+1); } public void incrementCurrentGuessCount() { this.setCurrentGuessCount(this.getCurrentGuessCount()+1); } public void newGameEvent(GameEvent event) { this.gameEvents = new ArrayList<>(gameEvents); this.gameEvents.add(event); } public Optional isWonOpt() { return Optional.ofNullable(this.won); } public void setWonOpt(Optional won) { this.won = won.orElse(null); } @AllArgsConstructor @Getter public enum Phase { HINTING_A('A', ' ', false, false, false), HINTING_B('B', ' ', false, false, false), GUESSING_A(' ', 'A', false, false, false), GUESSING_B(' ', 'B', false, false, false), HINTING_BOTH('_', ' ', false, false, false), SUDDEN_DEATH_BOTH(' ', '_', true, false, false), SUDDEN_DEATH_A(' ', 'A', true, false, false), SUDDEN_DEATH_B(' ', 'B', true, false, false), GAME_ENDED(' ', ' ', false, true, false), // We could go to sudden death OR wait for more hints NEED_MORE_HINTS_A('A', ' ', false, false, false), NEED_MORE_HINTS_B('B', ' ', false, false, false), // We can wait for SUDDEN_DEATH to be unlocked, or wait for more hints NEED_MORE_HINTS_OR_SUDDEN_DEATH_A('A', ' ', false, false, true), NEED_MORE_HINTS_OR_SUDDEN_DEATH_B('B', ' ', false, false, true), // We can only wait for suddenDeath to be unlocked NEED_SUDDEN_DEATH(' ', ' ', true, false, true), // We can either end guessing, or wait for possibility to do one more guess WAITING_FOR_ONE_MORE_A(' ', 'A', false, false, false), WAITING_FOR_ONE_MORE_B(' ', 'B', false, false, false), // We should have lost the game, we win it if we unlock onlyOneGreenGreen WIN_IF_GREEN_GREEN(' ', ' ', false, false, true); // ' ' is none, 'A' is A, 'B' is B, '_' is both private char hinting; private char guessing; private boolean suddenDeath; private boolean gameEnded; private boolean locked; public boolean isHinting(char player) { return (this == HINTING_BOTH) || (this == ((player == 'A')?HINTING_A:HINTING_B)); } public boolean isGuessing(char player) { return this == guessing(player) || this == suddenDeathOnly(player) || this == SUDDEN_DEATH_BOTH; } public boolean isAnyoneGuessing() { return isGuessing('A') || isGuessing('B'); } public static Phase hinting(char player) { return (player == '_')?HINTING_BOTH:((player == 'A')?HINTING_A:HINTING_B); } public static Phase guessing(char player) { return (player == 'A')?GUESSING_A:GUESSING_B; } public static Phase suddenDeathOnly(char player) { return (player == 'A')?SUDDEN_DEATH_A:SUDDEN_DEATH_B; } public static Phase needMoreHints(char player) { return (player == 'A')?NEED_MORE_HINTS_A:NEED_MORE_HINTS_B; } public static Phase needMoreHintsOrSuddenDeath(char player) { return (player == 'A')?NEED_MORE_HINTS_OR_SUDDEN_DEATH_A:NEED_MORE_HINTS_OR_SUDDEN_DEATH_B; } public static Phase waitingForOneMore(char player) { return (player == 'A')?WAITING_FOR_ONE_MORE_A:WAITING_FOR_ONE_MORE_B; } public boolean isWaitingForOneMore() { return this == WAITING_FOR_ONE_MORE_A || this == WAITING_FOR_ONE_MORE_B; } public boolean canPlay() { return !this.isGameEnded() && !this.isLocked(); } public boolean isNeedMoreHints() { return this == NEED_MORE_HINTS_A || this == NEED_MORE_HINTS_B; } public boolean isNeedMoreHintsOrSuddenDeath() { return this == NEED_MORE_HINTS_OR_SUDDEN_DEATH_A || this == NEED_MORE_HINTS_OR_SUDDEN_DEATH_B; } } @AllArgsConstructor public static class GameEventListAdapter implements AttributeConverter, JsonNode> { JsonNodeFactory jsn; @Override public JsonNode convertToDatabaseColumn(List attribute) { ArrayNode out = jsn.arrayNode(attribute.size()); for(GameEvent ge : attribute) { out.add(GameEvent.toJsonNode(ge)); } return out; } @Override public List convertToEntityAttribute(JsonNode dbData) { return dbData.asArray().valueStream().map(GameEvent::fromJsonNode).toList(); } } }