Added a config page

This commit is contained in:
Mysaa Java
2026-09-11 03:04:02 +02:00
parent 920e378dd5
commit 498503ae59
6 changed files with 220 additions and 21 deletions
@@ -1,15 +1,25 @@
package com.bernard.nodecames; package com.bernard.nodecames;
import java.util.Map.Entry;
import java.util.Random; import java.util.Random;
import java.util.UUID; import java.util.UUID;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.view.RedirectView; import org.springframework.web.servlet.view.RedirectView;
import com.bernard.nodecames.frontend.GridConfigEdit;
import com.bernard.nodecames.game.GameManager; import com.bernard.nodecames.game.GameManager;
import com.bernard.nodecames.game.IllegalGameActionException;
import com.bernard.nodecames.model.Grid;
import com.bernard.nodecames.model.GridConfig; import com.bernard.nodecames.model.GridConfig;
import com.bernard.nodecames.model.GridConfig.MutableOption;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -30,6 +40,30 @@ public class HttpController {
return "grid"; return "grid";
} }
@GetMapping("/room/{id}/config")
public String gridConfig(@PathVariable("id") String id, Model model) {
GridConfig gc = gm.findGrid(id).getConfig();
model.addAttribute("gridConfig", GridConfigEdit.of(gc));
return "grid-config";
}
@PostMapping("/room/{id}/config")
public String setGridConfig(@PathVariable("id") String id,
@ModelAttribute("gridConfig") GridConfigEdit gce,
BindingResult br,
Model model
) {
Grid g = gm.findGrid(id);
for(Entry<MutableOption, Object> e : gce.getModifications(g.getConfig()).entrySet()) {
try {
gm.unlock(g, e.getKey(), e.getValue());
} catch (IllegalGameActionException ige) {
br.addError(new ObjectError(e.getKey().name(), ige.getMessage()));
}
}
model.addAttribute("contactForm", gce);
return "grid-config";
}
@GetMapping("/create-room") @GetMapping("/create-room")
@@ -0,0 +1,80 @@
package com.bernard.nodecames.frontend;
import java.util.EnumMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.bernard.nodecames.model.GridConfig;
import com.bernard.nodecames.model.GridConfig.MutableOption;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class GridConfigEdit {
private int maxHint;
private String availableHintWordCountStr; // Comma separated integers
private boolean oneMoreGuess;
private int hintMaxLength;
private boolean endGuessing;
private boolean onlyOneGreenGreen;
private boolean suddenDeath;
private boolean hintsInARow;
public static GridConfigEdit of(GridConfig gc) {
return new GridConfigEdit(
gc.getMaxHint(),
gc.getAvailableHintWordCount()
.stream()
.sorted()
.map(Object::toString)
.collect(Collectors.joining(",")),
gc.isOneMoreGuess(),
gc.getHintMaxLength(),
gc.isEndGuessing(),
gc.isOnlyOneGreenGreen(),
gc.isSuddenDeath(),
gc.isHintsInARow()
);
}
public Set<Integer> getAvailableHintWordCount() {
return Stream.of(this.getAvailableHintWordCountStr().split(","))
.map(Integer::parseInt)
.collect(Collectors.toSet());
}
public Map<MutableOption, Object> getMutableOptions() {
return Map.of(
MutableOption.MAX_HINT, this.getMaxHint(),
MutableOption.AVAILABLE_HINT_WORD_COUNT, this.getAvailableHintWordCount(),
MutableOption.ONE_MORE_GUESS, this.isOneMoreGuess(),
MutableOption.HINT_MAX_LENGTH, this.getHintMaxLength(),
MutableOption.END_GUESSING, this.isEndGuessing(),
MutableOption.ONLY_ONE_GREEN_GREEN, this.isOnlyOneGreenGreen(),
MutableOption.SUDDEN_DEATH, this.isSuddenDeath(),
MutableOption.HINTS_IN_A_ROW, this.isHintsInARow()
);
}
/**
* Returns the list of modified in this edit compared to the GridConfig
*/
public Map<MutableOption, Object> getModifications(GridConfig orig) {
Map<MutableOption, Object> out = new EnumMap<>(MutableOption.class);
Map<MutableOption, Object> selfOpts = this.getMutableOptions();
for(Entry<MutableOption, Object> e : orig.getMutableOptions().entrySet()) {
Object newValue = selfOpts.get(e.getKey());
if(!newValue.equals(e.getValue())) {
out.put(e.getKey(), newValue);
}
}
return out;
}
}
@@ -305,10 +305,43 @@ public class GameManager {
/** /**
* UPDATE GAME CONFIG * UPDATE GAME CONFIG
*/ */
public void unlock(Grid g, MutableOption opt, Object o) throws IllegalGameActionException {
switch(opt) {
case MutableOption.MAX_HINT:
unlockHintCount(g, (Integer)o);
break;
case MutableOption.AVAILABLE_HINT_WORD_COUNT:
unlockNewWordCounts(g, (Set<Integer>)o);
break;
case MutableOption.ONE_MORE_GUESS:
unlockOneMoreGuess(g);
break;
case MutableOption.HINT_MAX_LENGTH:
unlockHintMaxLength(g, (Integer)o);
break;
case MutableOption.END_GUESSING:
unlockEndGuessing(g);
break;
case MutableOption.ONLY_ONE_GREEN_GREEN:
unlockOnlyOneGreenGreen(g);
break;
case MutableOption.SUDDEN_DEATH:
unlockSuddenDeath(g);
break;
case MutableOption.HINTS_IN_A_ROW:
unlockHintsInARow(g);
break;
default:
throw new IllegalStateException("Unknown MutableOption to edit");
}
}
public void unlockHintCount(Grid g, int newHintCount) throws IllegalGameActionException { public void unlockHintCount(Grid g, int newHintCount) throws IllegalGameActionException {
if(newHintCount > g.getConfig().getMaxMaxHint() || newHintCount <= g.getConfig().getMaxHint()) { if(newHintCount > g.getConfig().getMaxMaxHint()) {
throw new IllegalGameActionException(g, "Illegal hint count"); throw new IllegalGameActionException(g, "Illegal hint count");
} }
if(newHintCount <= g.getConfig().getMaxHint()) {
throw new IllegalGameActionException(g, "Cannot lower hint count");
}
changeConfig(g, MutableOption.MAX_HINT, newHintCount); changeConfig(g, MutableOption.MAX_HINT, newHintCount);
if(g.getPhase().isNeedMoreHints() || g.getPhase().isNeedMoreHintsOrSuddenDeath()) { if(g.getPhase().isNeedMoreHints() || g.getPhase().isNeedMoreHintsOrSuddenDeath()) {
setPhase(g, Phase.hinting(g.getPhase().getHinting())); setPhase(g, Phase.hinting(g.getPhase().getHinting()));
@@ -326,7 +359,7 @@ public class GameManager {
public void unlockOneMoreGuess(Grid g) throws IllegalGameActionException { public void unlockOneMoreGuess(Grid g) throws IllegalGameActionException {
if(g.getConfig().isOneMoreGuess()) if(g.getConfig().isOneMoreGuess())
throw new IllegalGameActionException(g, "Cannot set value, already set"); throw new IllegalGameActionException(g, "Cannot unlock oneMoreGuess as it is already unlocked");
changeConfig(g, MutableOption.ONE_MORE_GUESS, true); changeConfig(g, MutableOption.ONE_MORE_GUESS, true);
if(g.getPhase().isWaitingForOneMore()) { if(g.getPhase().isWaitingForOneMore()) {
setPhase(g, Phase.guessing(g.getPhase().getGuessing())); setPhase(g, Phase.guessing(g.getPhase().getGuessing()));
@@ -342,13 +375,13 @@ public class GameManager {
public void unlockEndGuessing(Grid g) throws IllegalGameActionException { public void unlockEndGuessing(Grid g) throws IllegalGameActionException {
if(g.getConfig().isEndGuessing()) if(g.getConfig().isEndGuessing())
throw new IllegalGameActionException(g, "Cannot set value, already set"); throw new IllegalGameActionException(g, "Cannot unlock endGuessing as it is already unlocked");
changeConfig(g, MutableOption.END_GUESSING, true); changeConfig(g, MutableOption.END_GUESSING, true);
} }
public void unlockOnlyOneGreenGreen(Grid g) throws IllegalGameActionException { public void unlockOnlyOneGreenGreen(Grid g) throws IllegalGameActionException {
if(g.getConfig().isOnlyOneGreenGreen()) if(g.getConfig().isOnlyOneGreenGreen())
throw new IllegalGameActionException(g, "Cannot set value, already set"); throw new IllegalGameActionException(g, "Cannot unlock onlyOneGreenGreen as it is already unlocked");
changeConfig(g, MutableOption.ONLY_ONE_GREEN_GREEN, true); changeConfig(g, MutableOption.ONLY_ONE_GREEN_GREEN, true);
if(g.getPhase() == Phase.WIN_IF_GREEN_GREEN) { if(g.getPhase() == Phase.WIN_IF_GREEN_GREEN) {
endGame(g, true); endGame(g, true);
@@ -357,7 +390,7 @@ public class GameManager {
public void unlockSuddenDeath(Grid g) throws IllegalGameActionException { public void unlockSuddenDeath(Grid g) throws IllegalGameActionException {
if(g.getConfig().isSuddenDeath()) if(g.getConfig().isSuddenDeath())
throw new IllegalGameActionException(g, "Cannot set value, already set"); throw new IllegalGameActionException(g, "Cannot unlock suddenDeath as it is already unlocked");
changeConfig(g, MutableOption.SUDDEN_DEATH,true); changeConfig(g, MutableOption.SUDDEN_DEATH,true);
if (g.getPhase().isNeedMoreHintsOrSuddenDeath()) { if (g.getPhase().isNeedMoreHintsOrSuddenDeath()) {
setPhase(g, Phase.needMoreHints(g.getPhase().getHinting())); setPhase(g, Phase.needMoreHints(g.getPhase().getHinting()));
@@ -369,7 +402,7 @@ public class GameManager {
public void unlockHintsInARow(Grid g) throws IllegalGameActionException { public void unlockHintsInARow(Grid g) throws IllegalGameActionException {
if(g.getConfig().isHintsInARow()) if(g.getConfig().isHintsInARow())
throw new IllegalGameActionException(g, "Cannot set value, already set"); throw new IllegalGameActionException(g, "Cannot unlock hintsInARow as it is already unlocked");
changeConfig(g, MutableOption.HINTS_IN_A_ROW,true); changeConfig(g, MutableOption.HINTS_IN_A_ROW,true);
if ((g.getPhase() == Phase.HINTING_A || g.getPhase() == Phase.HINTING_B) if ((g.getPhase() == Phase.HINTING_A || g.getPhase() == Phase.HINTING_B)
&& !allGreenRevealed(g, 'A', g.getConfig().isOnlyOneGreenGreen()) && !allGreenRevealed(g, 'A', g.getConfig().isOnlyOneGreenGreen())
@@ -1,17 +1,5 @@
package com.bernard.nodecames.model; package com.bernard.nodecames.model;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import com.bernard.nodecames.model.Card.Color;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.Value;
import lombok.With;
import static com.bernard.nodecames.model.GridConfig.CardColors.BB; 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.BG;
import static com.bernard.nodecames.model.GridConfig.CardColors.BW; import static com.bernard.nodecames.model.GridConfig.CardColors.BW;
@@ -22,6 +10,17 @@ 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.WG;
import static com.bernard.nodecames.model.GridConfig.CardColors.WW; import static com.bernard.nodecames.model.GridConfig.CardColors.WW;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.bernard.nodecames.model.Card.Color;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Value;
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public class GridConfig { public class GridConfig {
@@ -107,6 +106,23 @@ public class GridConfig {
} }
} }
public Map<MutableOption, Object> getMutableOptions() {
return Map.of(
MutableOption.MAX_HINT, this.getMaxHint(),
MutableOption.AVAILABLE_HINT_WORD_COUNT, this.getAvailableHintWordCount(),
MutableOption.ONE_MORE_GUESS, this.isOneMoreGuess(),
MutableOption.HINT_MAX_LENGTH, this.getHintMaxLength(),
MutableOption.END_GUESSING, this.isEndGuessing(),
MutableOption.ONLY_ONE_GREEN_GREEN, this.isOnlyOneGreenGreen(),
MutableOption.SUDDEN_DEATH, this.isSuddenDeath(),
MutableOption.HINTS_IN_A_ROW, this.isHintsInARow()
);
}
public static final boolean isGenericallyValidHintWordCount(int i) {
return 0 <= i || i == -1;
}
@Value(staticConstructor = "of") @Value(staticConstructor = "of")
public static final class CardColors { public static final class CardColors {
Color faceA; Color faceA;
+1 -3
View File
@@ -300,7 +300,7 @@ function onUpdateCard(m) {
function onConfigChange(m) { function onConfigChange(m) {
data = JSON.parse(m.body) data = JSON.parse(m.body)
for(const [key, value] of data.entries()) { for(const [key, value] of Object.entries(data)) {
console.log("New config option : ", key, "=", value) console.log("New config option : ", key, "=", value)
gc[key] = value gc[key] = value
switch(key) { switch(key) {
@@ -320,8 +320,6 @@ function onConfigChange(m) {
break; break;
} }
} }
cards[i] = data
updateCard(i)
} }
function initialize() { function initialize() {
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="fr" dir="ltr">
<head>
<div th:replace="~{html-head}"/>
<link rel="stylesheet" th:href="@{/css/grid.css}"/>
<script th:src="@{/js/jquery-4.0.0.min.js}" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.6.1/sockjs.min.js" integrity="sha512-1QvjE7BtotQjkq8PxLeF6P46gEpBRXuskzIVgjFpekzFVF4yjRgrQvTG1MTOJ3yQgvTteKAcO7DSZI92+u/yZw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js" integrity="sha512-iKDtgDyTHjAitUDdLljGhenhPwrbBfqTKWO1mkhSFH3A7blITC9MhYon6SjnMhp4o0rADGw9yAC6EW4t5a4K3g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script th:src="@{/js/grid.js}" type="text/javascript"></script>
</head>
<body>
<main>
<form method="POST"
action="" th:object="${gridConfig}">
<ul th:if="${#fields.hasErrors('global')}">
<li th:each="error : ${#fields.errors('global')}" th:text="${error}">error</li>
</ul>
<label th:field="maxHint" for="field-max-hint">Maximum number of hints</label>
<input th:field="*{maxHint}" id="field-max-hint"/> <br/>
<label th:field="availableHintWordCountStr" for="field-available-hint-word-count">Available hint's word counts<label>
<input th:field="*{availableHintWordCountStr}" id="field-available-hint-word-count"/> <br/>
<label th:field="oneMoreGuess" for="field-one-more-guess">Can do one more guess than hint word count</label>
<input type="checkbox" th:field="*{oneMoreGuess}" id="field-one-more-guess"/> <br/>
<label th:field="hintMaxLength" for="field-hint-max-length">Maximum length of hint words</label>
<input th:field="*{hintMaxLength}" id="field-hint-max-length"/> <br/>
<label th:field="endGuessing" for="field-end-guessing">Can press end guessing before the number of words</label>
<input type="checkbox" th:field="*{endGuessing}" id="field-end-guessing"/> <br/>
<label th:field="onlyOneGreenGreen" for="field-only-one-green-green">Green-Green cards only have to be pressed by one side</label>
<input type="checkbox" th:field="*{onlyOneGreenGreen}" id="field-only-one-green-green"/> <br/>
<label th:field="suddenDeath" for="field-sudden-death">Before losing beccause no more hints, goes to sudden death</label>
<input type="checkbox" th:field="*{suddenDeath}" id="field-sudden-death"/> <br/>
<label th:field="hintsInARow" for="field-hints-in-a-row">Someone can propose two hints in a row</label>
<input type="checkbox" th:field="*{hintsInARow}" id="field-hints-in-a-row"/> <br/>
<input type="submit" value="Change config" />
</form>
</main>
</body>
</html>