Observer pattern & Archipelago
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
|
||||||
|
## License
|
||||||
|
Dictionaries taken from [this project](https://github.com/LandazuriPaul/Codenames)
|
||||||
@@ -31,6 +31,7 @@ dependencies {
|
|||||||
implementation 'org.flywaydb:flyway-core:_'
|
implementation 'org.flywaydb:flyway-core:_'
|
||||||
implementation 'org.flywaydb:flyway-database-postgresql:_'
|
implementation 'org.flywaydb:flyway-database-postgresql:_'
|
||||||
implementation 'jakarta.validation:jakarta.validation-api:_'
|
implementation 'jakarta.validation:jakarta.validation-api:_'
|
||||||
|
implementation 'io.github.archipelagomw:Java-Client:_'
|
||||||
|
|
||||||
//TODO use webjars
|
//TODO use webjars
|
||||||
runtimeOnly 'org.webjars:jquery:_'
|
runtimeOnly 'org.webjars:jquery:_'
|
||||||
|
|||||||
@@ -39,6 +39,8 @@
|
|||||||
echo "Launching NodeCames"
|
echo "Launching NodeCames"
|
||||||
${jdk}/bin/java -jar ${nodecames}/nodecames-${version}.jar --spring.datasource.url=$NODECAMES_DATABASE --spring.datasource.password=$NODECAMES_PASSWORD "$@"
|
${jdk}/bin/java -jar ${nodecames}/nodecames-${version}.jar --spring.datasource.url=$NODECAMES_DATABASE --spring.datasource.password=$NODECAMES_PASSWORD "$@"
|
||||||
'';
|
'';
|
||||||
|
postgres = pkgs.postgresql_18;
|
||||||
|
# TODO make his more nix
|
||||||
postgres-server = pkgs.writeShellScriptBin "postgres-server" ''
|
postgres-server = pkgs.writeShellScriptBin "postgres-server" ''
|
||||||
mkdir -p .postgres
|
mkdir -p .postgres
|
||||||
${pkgs.podman}/bin/podman run -e POSTGRES_USER=nodecames -e POSTGRES_PASSWORD=nodecames-dev -e POSTGRES_DB=nodecames --volume ./.postgres:/var/lib/postgresql -p 127.0.0.1:5432:5432 postgres:18.4 &> .postgres/postgres.log &
|
${pkgs.podman}/bin/podman run -e POSTGRES_USER=nodecames -e POSTGRES_PASSWORD=nodecames-dev -e POSTGRES_DB=nodecames --volume ./.postgres:/var/lib/postgresql -p 127.0.0.1:5432:5432 postgres:18.4 &> .postgres/postgres.log &
|
||||||
@@ -61,6 +63,7 @@
|
|||||||
packages = [
|
packages = [
|
||||||
jdk
|
jdk
|
||||||
gradle
|
gradle
|
||||||
|
postgres
|
||||||
(pkgs.vscode-with-extensions.override {
|
(pkgs.vscode-with-extensions.override {
|
||||||
vscode = pkgs.vscodium;
|
vscode = pkgs.vscodium;
|
||||||
vscodeExtensions = with pkgs.vscode-extensions; [
|
vscodeExtensions = with pkgs.vscode-extensions; [
|
||||||
|
|||||||
@@ -103,17 +103,30 @@
|
|||||||
<sha256 value="19889dbdf1b254b2601a5ee645b8147a974644882297684c798afe5d63d78dfe" origin="Generated by Gradle"/>
|
<sha256 value="19889dbdf1b254b2601a5ee645b8147a974644882297684c798afe5d63d78dfe" origin="Generated by Gradle"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</component>
|
||||||
<component group="com.google.errorprone" name="error_prone_annotations" version="2.38.0">
|
<component group="com.google.code.gson" name="gson" version="2.13.2">
|
||||||
<artifact name="error_prone_annotations-2.38.0.jar">
|
<artifact name="gson-2.13.2.jar">
|
||||||
<sha256 value="6661d5335090a5fc61dd869d2095bc6c1e2156e3aa47a6e4ababdf64c99a7889" origin="Generated by Gradle"/>
|
<sha256 value="dd0ce1b55a3ed2080cb70f9c655850cda86c206862310009dcb5e5c95265a5e0" origin="Generated by Gradle"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
<artifact name="error_prone_annotations-2.38.0.pom">
|
<artifact name="gson-2.13.2.pom">
|
||||||
<sha256 value="3007bef8aff3ae8ea12d81c3feacb4f15979b2cf5c3e38fc9189a98dea141167" origin="Generated by Gradle"/>
|
<sha256 value="3aa06aa7c0f9af092961a42d09578e4324be146348a0ee6ed47857f7c2677b76" origin="Generated by Gradle"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</component>
|
||||||
<component group="com.google.errorprone" name="error_prone_parent" version="2.38.0">
|
<component group="com.google.code.gson" name="gson-parent" version="2.13.2">
|
||||||
<artifact name="error_prone_parent-2.38.0.pom">
|
<artifact name="gson-parent-2.13.2.pom">
|
||||||
<sha256 value="e62458a6a3e63081bc7c57b3c0fac9f04f76ce32f606532db69fe2b3d47b934c" origin="Generated by Gradle"/>
|
<sha256 value="83ab528a9d50fd76aeb8ad6f727b3ee9cb766586255774ed16ca8c4c76d9dacd" origin="Generated by Gradle"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="com.google.errorprone" name="error_prone_annotations" version="2.41.0">
|
||||||
|
<artifact name="error_prone_annotations-2.41.0.jar">
|
||||||
|
<sha256 value="a56e782b5b50811ac204073a355a21d915a2107fce13ec711331ad036f660fcc" origin="Generated by Gradle"/>
|
||||||
|
</artifact>
|
||||||
|
<artifact name="error_prone_annotations-2.41.0.pom">
|
||||||
|
<sha256 value="a151df1e2e0b48618d8b06a180748a29b3abb39b1b2396f6a1c879a727488c6e" origin="Generated by Gradle"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
|
<component group="com.google.errorprone" name="error_prone_parent" version="2.41.0">
|
||||||
|
<artifact name="error_prone_parent-2.41.0.pom">
|
||||||
|
<sha256 value="c538388d760a5c1c98dcf06f6ed3cfe5f11a651827db5cbd2ed8288c795cad42" origin="Generated by Gradle"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</component>
|
||||||
<component group="com.google.protobuf" name="protobuf-bom" version="4.35.1">
|
<component group="com.google.protobuf" name="protobuf-bom" version="4.35.1">
|
||||||
@@ -280,6 +293,17 @@
|
|||||||
<sha256 value="7c490839bb11df9ae7c37f9c31994a4b423bd826245d34abe6c864f5e61d46e9" origin="Generated by Gradle"/>
|
<sha256 value="7c490839bb11df9ae7c37f9c31994a4b423bd826245d34abe6c864f5e61d46e9" origin="Generated by Gradle"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</component>
|
||||||
|
<component group="io.github.archipelagomw" name="Java-Client" version="0.2.1">
|
||||||
|
<artifact name="Java-Client-0.2.1.jar">
|
||||||
|
<sha256 value="9f3ff0a1a3cd99cbc9090e819c00d3890b775e17cbac47201677972953337a33" origin="Generated by Gradle"/>
|
||||||
|
</artifact>
|
||||||
|
<artifact name="Java-Client-0.2.1.module">
|
||||||
|
<sha256 value="c4b35dff4ed88b82e4a9133efdfbc6efb6a23bca3f9124e1b606a37b10240c4f" origin="Generated by Gradle"/>
|
||||||
|
</artifact>
|
||||||
|
<artifact name="Java-Client-0.2.1.pom">
|
||||||
|
<sha256 value="812e1984b148cb856afb290029c224de7dfb99a11a28dd9b6a402d2ed761dd06" origin="Generated by Gradle"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
<component group="io.grpc" name="grpc-bom" version="1.83.1">
|
<component group="io.grpc" name="grpc-bom" version="1.83.1">
|
||||||
<artifact name="grpc-bom-1.83.1.pom">
|
<artifact name="grpc-bom-1.83.1.pom">
|
||||||
<sha256 value="58cc1c0c0e6dc3aa28a6b8f6c8cc1cbdbdc47843cfba282d07f694c2b1af4f8b" origin="Generated by Gradle"/>
|
<sha256 value="58cc1c0c0e6dc3aa28a6b8f6c8cc1cbdbdc47843cfba282d07f694c2b1af4f8b" origin="Generated by Gradle"/>
|
||||||
@@ -1044,6 +1068,14 @@
|
|||||||
<sha256 value="aeabde04eca66e1ac30f9df3cc36c9b07a25a37c52043b33418e274ce5a23616" origin="Generated by Gradle"/>
|
<sha256 value="aeabde04eca66e1ac30f9df3cc36c9b07a25a37c52043b33418e274ce5a23616" origin="Generated by Gradle"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</component>
|
||||||
|
<component group="org.java-websocket" name="Java-WebSocket" version="1.6.0">
|
||||||
|
<artifact name="Java-WebSocket-1.6.0.jar">
|
||||||
|
<sha256 value="eae29213e4f16515639c28957200f011b3967fffcada1962cf0255d24919c22f" origin="Generated by Gradle"/>
|
||||||
|
</artifact>
|
||||||
|
<artifact name="Java-WebSocket-1.6.0.pom">
|
||||||
|
<sha256 value="a8e84d553c34d793e2ea7e4a54b073f44ddb0bb442f4dd6fad0cecd755d1ceac" origin="Generated by Gradle"/>
|
||||||
|
</artifact>
|
||||||
|
</component>
|
||||||
<component group="org.jboss" name="jboss-parent" version="52">
|
<component group="org.jboss" name="jboss-parent" version="52">
|
||||||
<artifact name="jboss-parent-52.pom">
|
<artifact name="jboss-parent-52.pom">
|
||||||
<sha256 value="0615b429e3a21a0d6bd925d09ede2937892034272b4cd04bb9ab617d57e72b42" origin="Generated by Gradle"/>
|
<sha256 value="0615b429e3a21a0d6bd925d09ede2937892034272b4cd04bb9ab617d57e72b42" origin="Generated by Gradle"/>
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ ITEM_NAME_TO_ID = {
|
|||||||
"structured_grid" : 9,
|
"structured_grid" : 9,
|
||||||
"hints_in_a_row" : 10,
|
"hints_in_a_row" : 10,
|
||||||
"anyone_starts" : 11,
|
"anyone_starts" : 11,
|
||||||
"filler": 12
|
"progressive_hint_word_count": 12,
|
||||||
|
"progressive_hint_word_length": 13,
|
||||||
|
"filler": 14
|
||||||
}
|
}
|
||||||
for w in range(1,12+1):
|
for w in range(1,12+1):
|
||||||
ITEM_NAME_TO_ID[f"unlock_world_{w}"] = 100+w
|
ITEM_NAME_TO_ID[f"unlock_world_{w}"] = 100+w
|
||||||
@@ -35,14 +37,21 @@ def get_random_filler_item_name(world: NodecamesWorld) -> str:
|
|||||||
def create_item_with_correct_classification(
|
def create_item_with_correct_classification(
|
||||||
world: NodecamesWorld, name: str
|
world: NodecamesWorld, name: str
|
||||||
) -> NodecamesItem:
|
) -> NodecamesItem:
|
||||||
return NodecamesItem(name, ItemClassification.progression, ITEM_NAME_TO_ID[name], world.player)
|
classification = ItemClassification.progression
|
||||||
|
if name == "filler":
|
||||||
|
classification = ItemClassification.filler
|
||||||
|
return NodecamesItem(name, classification, ITEM_NAME_TO_ID[name], world.player)
|
||||||
|
|
||||||
|
|
||||||
def create_all_items(world: NodecamesWorld) -> None:
|
def create_all_items(world: NodecamesWorld) -> None:
|
||||||
itempool: list[Item] = []
|
itempool: list[Item] = []
|
||||||
|
|
||||||
hintItemCount = world.options.max_hint_count - world.options.max_hint_length
|
hintItemCount = world.options.max_hint_count - world.options.start_hint_count
|
||||||
itempool += [world.create_item("progressive_hint_count") for _ in range(hintItemCount)]
|
itempool += [world.create_item("progressive_hint_count") for _ in range(hintItemCount)]
|
||||||
|
hintWordLengthItemCount = world.options.max_hint_length - world.options.start_hint_length
|
||||||
|
itempool += [world.create_item("progressive_hint_word_length") for _ in range(hintWordLengthItemCount)]
|
||||||
|
hintWordCountItemCount = world.options.max_hint_word_count - world.options.start_hint_word_count
|
||||||
|
itempool += [world.create_item("progressive_hint_word_count") for _ in range(hintWordCountItemCount)]
|
||||||
itempool += [
|
itempool += [
|
||||||
world.create_item("zero_hint"),
|
world.create_item("zero_hint"),
|
||||||
world.create_item("infinity_hint"),
|
world.create_item("infinity_hint"),
|
||||||
|
|||||||
@@ -58,6 +58,29 @@ class MaxHintLength(Range):
|
|||||||
range_end = 30
|
range_end = 30
|
||||||
default = 10
|
default = 10
|
||||||
|
|
||||||
|
class StartHintWordCount(Range):
|
||||||
|
"""
|
||||||
|
Hint Word count available at the beginning of the game
|
||||||
|
"""
|
||||||
|
|
||||||
|
display_name = "Start Hint Word Count"
|
||||||
|
|
||||||
|
range_start = 1
|
||||||
|
range_end = 16
|
||||||
|
default = 1
|
||||||
|
|
||||||
|
|
||||||
|
class MaxHintWordCount(Range):
|
||||||
|
"""
|
||||||
|
Maximal hint word count we can get.
|
||||||
|
"""
|
||||||
|
|
||||||
|
display_name = "Max Hint Word Count"
|
||||||
|
|
||||||
|
range_start = 1
|
||||||
|
range_end = 16
|
||||||
|
default = 16
|
||||||
|
|
||||||
|
|
||||||
class OnOffDefault(Choice):
|
class OnOffDefault(Choice):
|
||||||
option_Off = 0
|
option_Off = 0
|
||||||
@@ -171,6 +194,8 @@ class NodecamesOptions(PerGameCommonOptions):
|
|||||||
max_hint_count: MaxHintCount
|
max_hint_count: MaxHintCount
|
||||||
start_hint_length: StartHintLength
|
start_hint_length: StartHintLength
|
||||||
max_hint_length: MaxHintLength
|
max_hint_length: MaxHintLength
|
||||||
|
start_hint_word_count: StartHintWordCount
|
||||||
|
max_hint_word_count: MaxHintWordCount
|
||||||
zero_hint: ZeroHint
|
zero_hint: ZeroHint
|
||||||
infinity_hint: InfinityHint
|
infinity_hint: InfinityHint
|
||||||
one_more_guess: OneMoreGuess
|
one_more_guess: OneMoreGuess
|
||||||
@@ -218,6 +243,8 @@ option_presets = {
|
|||||||
"max_hint_count": 10,
|
"max_hint_count": 10,
|
||||||
"start_hint_length": 3,
|
"start_hint_length": 3,
|
||||||
"max_hint_length": 12,
|
"max_hint_length": 12,
|
||||||
|
"start_hint_word_count": 1,
|
||||||
|
"max_hint_word_count": 16,
|
||||||
"zero_hint": 1,
|
"zero_hint": 1,
|
||||||
"infinity_hint": 1,
|
"infinity_hint": 1,
|
||||||
"one_more_guess": 1,
|
"one_more_guess": 1,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class NodecamesWorld(World):
|
|||||||
Nodecames is a word game, inspired by Code Names by Vlaada Chvátil
|
Nodecames is a word game, inspired by Code Names by Vlaada Chvátil
|
||||||
"""
|
"""
|
||||||
|
|
||||||
game = "Nodecames"
|
game = "nodecames"
|
||||||
|
|
||||||
options_dataclass = nodecames_options.NodecamesOptions
|
options_dataclass = nodecames_options.NodecamesOptions
|
||||||
options: nodecames_options.NodecamesOptions
|
options: nodecames_options.NodecamesOptions
|
||||||
@@ -40,6 +40,10 @@ class NodecamesWorld(World):
|
|||||||
def get_filler_item_name(self) -> str:
|
def get_filler_item_name(self) -> str:
|
||||||
return items.get_random_filler_item_name(self)
|
return items.get_random_filler_item_name(self)
|
||||||
|
|
||||||
|
def generate_output(self, outdir : str) -> None:
|
||||||
|
# Generates the output file
|
||||||
|
pass
|
||||||
|
|
||||||
# Data sent to the client
|
# Data sent to the client
|
||||||
def fill_slot_data(self) -> Mapping[str, Any]:
|
def fill_slot_data(self) -> Mapping[str, Any]:
|
||||||
return self.options.as_dict(
|
return self.options.as_dict(
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
package com.bernard.nodecames;
|
package com.bernard.nodecames;
|
||||||
|
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -21,6 +26,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||||||
import org.springframework.web.servlet.view.RedirectView;
|
import org.springframework.web.servlet.view.RedirectView;
|
||||||
|
|
||||||
import com.bernard.nodecames.frontend.GridConfigEdit;
|
import com.bernard.nodecames.frontend.GridConfigEdit;
|
||||||
|
import com.bernard.nodecames.game.ArchipelagoManager;
|
||||||
import com.bernard.nodecames.game.GameManager;
|
import com.bernard.nodecames.game.GameManager;
|
||||||
import com.bernard.nodecames.game.GridManager;
|
import com.bernard.nodecames.game.GridManager;
|
||||||
import com.bernard.nodecames.game.IllegalGameActionException;
|
import com.bernard.nodecames.game.IllegalGameActionException;
|
||||||
@@ -33,6 +39,7 @@ import com.bernard.nodecames.repository.GameRepository;
|
|||||||
import com.bernard.nodecames.repository.GridRepository;
|
import com.bernard.nodecames.repository.GridRepository;
|
||||||
import com.bernard.nodecames.repository.WorldRepository;
|
import com.bernard.nodecames.repository.WorldRepository;
|
||||||
|
|
||||||
|
import io.github.archipelagomw.network.ConnectionResult;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -41,6 +48,7 @@ public class HttpController {
|
|||||||
|
|
||||||
private final GridManager gm;
|
private final GridManager gm;
|
||||||
private final GameManager wm;
|
private final GameManager wm;
|
||||||
|
private final ArchipelagoManager am;
|
||||||
private final GridRepository gridRepository;
|
private final GridRepository gridRepository;
|
||||||
private final GameRepository gRepository;
|
private final GameRepository gRepository;
|
||||||
private final WorldRepository wRepository;
|
private final WorldRepository wRepository;
|
||||||
@@ -78,6 +86,7 @@ public class HttpController {
|
|||||||
model.addAttribute("gridConfig", GridConfigEdit.of(gc));
|
model.addAttribute("gridConfig", GridConfigEdit.of(gc));
|
||||||
return "grid-config";
|
return "grid-config";
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/room/{id}/config")
|
@PostMapping("/room/{id}/config")
|
||||||
public Object setGridConfig(@PathVariable("id") String id,
|
public Object setGridConfig(@PathVariable("id") String id,
|
||||||
@ModelAttribute("gridConfig") GridConfigEdit gce,
|
@ModelAttribute("gridConfig") GridConfigEdit gce,
|
||||||
@@ -138,6 +147,38 @@ public class HttpController {
|
|||||||
return "map";
|
return "map";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/game/{id}/archipelago")
|
||||||
|
public Object gridConfig(@PathVariable("id") String id, @RequestParam("address") String address,
|
||||||
|
@RequestParam("slot") String slot, Model model) {
|
||||||
|
|
||||||
|
Optional<Game> g = gRepository.findById(UUID.fromString(id));
|
||||||
|
if(!g.isPresent())
|
||||||
|
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||||
|
|
||||||
|
try {
|
||||||
|
CompletableFuture<ConnectionResult> connectionResultFuture = am.openClient(g.get(), address, slot);
|
||||||
|
|
||||||
|
ConnectionResult connectionResult = connectionResultFuture.get(5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
if (connectionResult != ConnectionResult.Success) {
|
||||||
|
return new ResponseEntity<>("Could not connect to archipelago server: "+connectionResult.name(), HttpStatus.NOT_FOUND);
|
||||||
|
} else {
|
||||||
|
return new ResponseEntity<>("Connection successful", HttpStatus.OK);
|
||||||
|
}
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
return new ResponseEntity<>("Timed out connecting to archipelago", HttpStatus.REQUEST_TIMEOUT);
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
return new ResponseEntity<>("URI malformed : "+e.getMessage(), HttpStatus.BAD_REQUEST);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return new ResponseEntity<>("Interrupted", HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return new ResponseEntity<>("ExecutionException while connecting to Archipelago : "+e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/game/{id}/grid/{world}")
|
@GetMapping("/game/{id}/grid/{world}")
|
||||||
public Object gridRedirect(@PathVariable("id") String id, @PathVariable("world") String world, @RequestParam("player") String player) {
|
public Object gridRedirect(@PathVariable("id") String id, @PathVariable("world") String world, @RequestParam("player") String player) {
|
||||||
Optional<Game> g = gRepository.findById(UUID.fromString(id));
|
Optional<Game> g = gRepository.findById(UUID.fromString(id));
|
||||||
@@ -148,7 +189,12 @@ public class HttpController {
|
|||||||
if(!w.isPresent()) {
|
if(!w.isPresent()) {
|
||||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||||
}
|
}
|
||||||
Grid grid = wm.getGrid(g.get(), w.get());
|
Grid grid;
|
||||||
|
try {
|
||||||
|
grid = wm.getGrid(g.get(), w.get());
|
||||||
|
} catch (IllegalGameActionException e) {
|
||||||
|
return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
return new RedirectView("/room/"+grid.getId()+"/grid" + "?player="+player);
|
return new RedirectView("/room/"+grid.getId()+"/grid" + "?player="+player);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +208,11 @@ public class HttpController {
|
|||||||
if(!w.isPresent()) {
|
if(!w.isPresent()) {
|
||||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||||
}
|
}
|
||||||
Grid newGrid = wm.resetGrid(g.get(), w.get());
|
try {
|
||||||
return new RedirectView("/room/"+newGrid.getId()+"/grid" + "?player="+player);
|
Grid newGrid = wm.resetGrid(g.get(), w.get());
|
||||||
|
return new RedirectView("/room/"+newGrid.getId()+"/grid" + "?player="+player);
|
||||||
|
} catch (IllegalGameActionException e) {
|
||||||
|
return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.bernard.nodecames.frontend;
|
package com.bernard.nodecames.frontend;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
@@ -17,14 +19,20 @@ import org.springframework.web.bind.annotation.PathVariable;
|
|||||||
import com.bernard.nodecames.game.GridManager;
|
import com.bernard.nodecames.game.GridManager;
|
||||||
import com.bernard.nodecames.game.IllegalGameActionException;
|
import com.bernard.nodecames.game.IllegalGameActionException;
|
||||||
import com.bernard.nodecames.model.Card;
|
import com.bernard.nodecames.model.Card;
|
||||||
|
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.GridConfig.MutableOption;
|
import com.bernard.nodecames.model.GridConfig.MutableOption;
|
||||||
|
import com.bernard.nodecames.model.Hint;
|
||||||
|
import com.bernard.nodecames.observer.GridObserver;
|
||||||
import com.bernard.nodecames.repository.GridRepository;
|
import com.bernard.nodecames.repository.GridRepository;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
import tools.jackson.databind.JsonNode;
|
import tools.jackson.databind.JsonNode;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
|
@AllArgsConstructor
|
||||||
public class WebSocketController {
|
public class WebSocketController {
|
||||||
|
|
||||||
private final GridManager gm;
|
private final GridManager gm;
|
||||||
@@ -32,49 +40,19 @@ public class WebSocketController {
|
|||||||
private final JsonDataService json;
|
private final JsonDataService json;
|
||||||
private final GridRepository gRepository;
|
private final GridRepository gRepository;
|
||||||
|
|
||||||
public WebSocketController(GridManager gm, SimpMessagingTemplate template, JsonDataService json, GridRepository gRepository) {
|
Set<UUID> observed = new HashSet<>();
|
||||||
this.gm = gm;
|
private void registerObserver(Grid g) {
|
||||||
this.template = template;
|
if(!observed.contains(g.getId())) {
|
||||||
this.json = json;
|
observed.add(g.getId());
|
||||||
this.gRepository = gRepository;
|
gm.addObserver(g, new GObserver(g));
|
||||||
gm.setWsc(this);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void onNewEvent(Grid g, GameEvent ge) {
|
|
||||||
this.template.convertAndSend("/topic/new-event",
|
|
||||||
json.eventData(ge)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onNewPhase(Grid g) {
|
|
||||||
this.template.convertAndSend(
|
|
||||||
"/topic/new-phase",
|
|
||||||
json.phaseData(g.getPhase())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onConfigChange(Grid g, MutableOption cc, Object data) {
|
|
||||||
this.template.convertAndSend("/topic/config-change",
|
|
||||||
json.gridConfigUpdateData(cc, data)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void publishCardUpdateToPlayer(Grid g, Card c, char player) {
|
|
||||||
this.template.convertAndSend(
|
|
||||||
"/topic/update-card/" + Character.toLowerCase(player),
|
|
||||||
json.cardData(c, player),
|
|
||||||
Map.of("cardIndex", c.getIndex())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
public void publishCardUpdate(Grid g, Card card) {
|
|
||||||
publishCardUpdateToPlayer(g, card, 'A');
|
|
||||||
publishCardUpdateToPlayer(g, card, 'B');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void publishError(char issuer, IllegalGameActionException e) {
|
public void publishError(char issuer, IllegalGameActionException e) {
|
||||||
//TODO open error channel
|
//TODO open error channel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO We should do this with websocket protocol
|
||||||
@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;
|
||||||
@@ -86,14 +64,19 @@ public class WebSocketController {
|
|||||||
throw new IllegalArgumentException("Unknown player type");
|
throw new IllegalArgumentException("Unknown player type");
|
||||||
|
|
||||||
Optional<Grid> g = gRepository.findById(UUID.fromString(gridId));
|
Optional<Grid> g = gRepository.findById(UUID.fromString(gridId));
|
||||||
if(g.isPresent())
|
if(g.isPresent()){
|
||||||
return new ResponseEntity<>(json.gridData(g.get(), player), HttpStatus.OK);
|
Grid grid = g.get();
|
||||||
else
|
registerObserver(grid);
|
||||||
|
return new ResponseEntity<>(json.gridData(grid, player), HttpStatus.OK);
|
||||||
|
} else
|
||||||
return new ResponseEntity<>("Could not find grid with given ID",HttpStatus.NOT_FOUND);
|
return new ResponseEntity<>("Could not find grid with given ID",HttpStatus.NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
@MessageMapping("submit-hint")
|
//TODO handle websockets per grid
|
||||||
public void submitHint(@Header("room") String roomId, @Header("player") String player, @Payload JsonNode content) {
|
@MessageMapping("/submit-hint")
|
||||||
|
public void submitHint(@Header("room") String roomId,
|
||||||
|
@Header("player") String player,
|
||||||
|
@Payload JsonNode content) {
|
||||||
String hint = content.asObject().get("hint").stringValue();
|
String hint = content.asObject().get("hint").stringValue();
|
||||||
int wordCount = content.asObject().get("hintWordCount").intValue();
|
int wordCount = content.asObject().get("hintWordCount").intValue();
|
||||||
|
|
||||||
@@ -101,7 +84,6 @@ public class WebSocketController {
|
|||||||
if(!g.isPresent())
|
if(!g.isPresent())
|
||||||
// 404
|
// 404
|
||||||
return;
|
return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
gm.proposeHint(g.get(), player.charAt(0), hint, wordCount);
|
gm.proposeHint(g.get(), player.charAt(0), hint, wordCount);
|
||||||
} catch (IllegalGameActionException e) {
|
} catch (IllegalGameActionException e) {
|
||||||
@@ -118,10 +100,8 @@ public class WebSocketController {
|
|||||||
if(!g.isPresent())
|
if(!g.isPresent())
|
||||||
// 404
|
// 404
|
||||||
return;
|
return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
gm.pointCard(g.get(), player.charAt(0), cardIndex);
|
gm.pointCard(g.get(), player.charAt(0), cardIndex);
|
||||||
publishCardUpdate(g.get(), gm.getCard(g.get(), cardIndex));
|
|
||||||
} catch (IllegalGameActionException e) {
|
} catch (IllegalGameActionException e) {
|
||||||
publishError(player.charAt(0), e);
|
publishError(player.charAt(0), e);
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -134,7 +114,6 @@ public class WebSocketController {
|
|||||||
if(!g.isPresent())
|
if(!g.isPresent())
|
||||||
// 404
|
// 404
|
||||||
return;
|
return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
gm.endGuessing(g.get(), player.charAt(0));
|
gm.endGuessing(g.get(), player.charAt(0));
|
||||||
} catch (IllegalGameActionException e) {
|
} catch (IllegalGameActionException e) {
|
||||||
@@ -156,4 +135,56 @@ public class WebSocketController {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
private class GObserver implements GridObserver {
|
||||||
|
Grid grid;
|
||||||
|
|
||||||
|
public void publishCardUpdateToPlayer(Card c, char player) {
|
||||||
|
template.convertAndSend(
|
||||||
|
"/topic/%s/update-card/%s".formatted(grid.getId(), Character.toLowerCase(player)),
|
||||||
|
json.cardData(c, player),
|
||||||
|
Map.of("cardIndex", c.getIndex())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
public void publishCardUpdate(Card card) {
|
||||||
|
publishCardUpdateToPlayer(card, 'A');
|
||||||
|
publishCardUpdateToPlayer(card, 'B');
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPointCard(char player, Card card, Color color) {
|
||||||
|
publishCardUpdate(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSubmitHint(char player, Hint hint) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onGridEnd(boolean won) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onGameEvent(GameEvent ge) {
|
||||||
|
template.convertAndSend(
|
||||||
|
"/topic/%s/new-event".formatted(grid.getId()),
|
||||||
|
json.eventData(ge)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNewPhase(Phase p) {
|
||||||
|
template.convertAndSend(
|
||||||
|
"/topic/%s/new-phase".formatted(grid.getId()),
|
||||||
|
json.phaseData(p)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigChange(MutableOption cc, Object data) {
|
||||||
|
template.convertAndSend(
|
||||||
|
"/topic/%s/config-change".formatted(grid.getId()),
|
||||||
|
json.gridConfigUpdateData(cc, data)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
package com.bernard.nodecames.game;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.bernard.nodecames.model.Game;
|
||||||
|
import com.bernard.nodecames.model.GridConfig.MutableOption;
|
||||||
|
|
||||||
|
import io.github.archipelagomw.APResult.ResultCode;
|
||||||
|
import io.github.archipelagomw.Client;
|
||||||
|
import io.github.archipelagomw.ClientStatus;
|
||||||
|
import io.github.archipelagomw.events.ArchipelagoEventListener;
|
||||||
|
import io.github.archipelagomw.events.ConnectionResultEvent;
|
||||||
|
import io.github.archipelagomw.events.ReceiveItemEvent;
|
||||||
|
import io.github.archipelagomw.flags.ItemsHandling;
|
||||||
|
import io.github.archipelagomw.network.ConnectionResult;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
public class ArchipelagoClient extends Client {
|
||||||
|
Game game;
|
||||||
|
GameManager gm;
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ArchipelagoClient.class);
|
||||||
|
|
||||||
|
public ArchipelagoClient(Game game, GameManager gm, String slot) {
|
||||||
|
super();
|
||||||
|
this.gm = gm;
|
||||||
|
this.game = game;
|
||||||
|
this.setGame("nodecames");
|
||||||
|
this.setName(slot);
|
||||||
|
client.setItemsHandlingFlags(ItemsHandling.SEND_ITEMS | ItemsHandling.SEND_OWN_ITEMS);
|
||||||
|
this.getEventManager().registerListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ArchipelagoEventListener
|
||||||
|
public void onReceiveItem(ReceiveItemEvent e) throws IllegalGameActionException {
|
||||||
|
log.debug("Received item : {}", e.getItemName());
|
||||||
|
switch(e.getItemName()) {
|
||||||
|
case "progressive_hint_count":
|
||||||
|
gm.unlockMoreHintCount(this.game);
|
||||||
|
break;
|
||||||
|
case "progressive_hint_word_length":
|
||||||
|
gm.unlockMoreHintWordLengthCount(this.game);
|
||||||
|
break;
|
||||||
|
case "progressive_hint_word_count":
|
||||||
|
gm.unlockMoreHintWordCount(this.game);
|
||||||
|
break;
|
||||||
|
case "zero_hint":
|
||||||
|
gm.unlockHintWordCount(this.game, 0);
|
||||||
|
break;
|
||||||
|
case "infinity_hint":
|
||||||
|
gm.unlockHintWordCount(this.game, -1);
|
||||||
|
break;
|
||||||
|
case "one_more_guess":
|
||||||
|
gm.unlockMutable(game, MutableOption.ONE_MORE_GUESS, true);
|
||||||
|
break;
|
||||||
|
case "end_guessing":
|
||||||
|
gm.unlockMutable(game, MutableOption.END_GUESSING, true);
|
||||||
|
break;
|
||||||
|
case "only_one_green_green":
|
||||||
|
gm.unlockMutable(game, MutableOption.ONLY_ONE_GREEN_GREEN, true);
|
||||||
|
break;
|
||||||
|
case "sudden_death":
|
||||||
|
gm.unlockMutable(game, MutableOption.SUDDEN_DEATH, true);
|
||||||
|
break;
|
||||||
|
case "structured_grid":
|
||||||
|
gm.unlockStructuredGrid(game);
|
||||||
|
break;
|
||||||
|
case "hints_in_a_row":
|
||||||
|
gm.unlockMutable(game, MutableOption.HINTS_IN_A_ROW, true);
|
||||||
|
break;
|
||||||
|
case "anyone_starts":
|
||||||
|
gm.unlockAnyoneCanStart(game);
|
||||||
|
break;
|
||||||
|
case "filler":
|
||||||
|
// We do nothing if we receive a filler item
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (e.getItemName().startsWith("unlock_world_")) {
|
||||||
|
int worldIndex = Integer.parseInt(e.getItemName().substring("unlock_world_".length()))-1;
|
||||||
|
String worldName = "%d-%d".formatted((worldIndex/3)+1, (worldIndex%3)+1);
|
||||||
|
gm.unlockWorld(game, worldName);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Received unknown item : "+e.getItemName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private CompletableFuture<ConnectionResult> connectionResult = new CompletableFuture<>();
|
||||||
|
|
||||||
|
@ArchipelagoEventListener
|
||||||
|
public void onConnectResult(ConnectionResultEvent e) {
|
||||||
|
connectionResult.complete(e.getResult());
|
||||||
|
if(e.getResult() != ConnectionResult.Success) {
|
||||||
|
throw new IllegalStateException("Could not connect to archipelago : %s".formatted(e.getResult().name()));
|
||||||
|
} else {
|
||||||
|
if(this.setGameState(ClientStatus.CLIENT_PLAYING).getCode() != ResultCode.SUCCESS)
|
||||||
|
throw new IllegalStateException("Colud not set state to playing");
|
||||||
|
else {
|
||||||
|
client.sendChat("Hello, here is Nodecames AP client, nice to meet you");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int[] GREEN_COUNT_PER_WORLD = new int[]{6, 9, 9, 12, 16, 16, 20, 25, 25, 30, 36, 36};
|
||||||
|
public static String[] WORLDS = new String[]{"1-1","1-2","1-3","2-1","2-2","2-3","3-1","3-2","3-3","4-1","4-2","4-3"};
|
||||||
|
|
||||||
|
public void checkWordInWorld(String worldName, int wordIndex) {
|
||||||
|
//TODO binarySearch is okay for now, but we'll have to remove this at some point
|
||||||
|
int worldIndex = Arrays.binarySearch(WORLDS, 0, WORLDS.length, worldName);
|
||||||
|
if(wordIndex>GREEN_COUNT_PER_WORLD[wordIndex]-1)return;//We don't have that many checks
|
||||||
|
//TODO this should be stored, not computed, in archipelagoconfig
|
||||||
|
long locationId = IntStream.range(0, worldIndex).map(w -> GREEN_COUNT_PER_WORLD[w]).sum()+1+wordIndex;
|
||||||
|
this.checkLocation(locationId);
|
||||||
|
}
|
||||||
|
public void checkWorldComplete(String worldName) {
|
||||||
|
//TODO binarySearch is okay for now, but we'll have to remove this at some point
|
||||||
|
int worldIndex = Arrays.binarySearch(WORLDS, 0, WORLDS.length, worldName);
|
||||||
|
long locationId = IntStream.range(0, worldIndex+1).map(w -> GREEN_COUNT_PER_WORLD[w]).sum()+1;
|
||||||
|
this.checkLocation(locationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Exception e) {
|
||||||
|
//TODO make a real handler
|
||||||
|
throw new IllegalStateException("Archipelago error :", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClose(String Reason, int attemptingReconnect) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'onClose'");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package com.bernard.nodecames.game;
|
||||||
|
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.bernard.nodecames.model.Card;
|
||||||
|
import com.bernard.nodecames.model.Card.Color;
|
||||||
|
import com.bernard.nodecames.model.Game;
|
||||||
|
import com.bernard.nodecames.model.GameEvent;
|
||||||
|
import com.bernard.nodecames.model.Grid.Phase;
|
||||||
|
import com.bernard.nodecames.model.GridConfig.MutableOption;
|
||||||
|
import com.bernard.nodecames.model.Hint;
|
||||||
|
import com.bernard.nodecames.model.World;
|
||||||
|
import com.bernard.nodecames.observer.WorldObserver;
|
||||||
|
import com.bernard.nodecames.repository.CardRepository;
|
||||||
|
import com.bernard.nodecames.repository.WorldRepository;
|
||||||
|
|
||||||
|
import io.github.archipelagomw.network.ConnectionResult;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ArchipelagoManager {
|
||||||
|
|
||||||
|
Map<UUID, ArchipelagoClient> clients = new HashMap<>();
|
||||||
|
|
||||||
|
GameManager gm;
|
||||||
|
CardRepository cRepository;
|
||||||
|
WorldRepository wRepository;
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ArchipelagoManager.class);
|
||||||
|
|
||||||
|
public CompletableFuture<ConnectionResult> openClient(Game g, String address, String slot) throws URISyntaxException{
|
||||||
|
ArchipelagoClient client = new ArchipelagoClient(g, gm, slot);
|
||||||
|
|
||||||
|
client.connect(address);
|
||||||
|
clients.put(g.getId(), client);
|
||||||
|
|
||||||
|
Set<World> worlds = wRepository.findByGame(g);
|
||||||
|
log.debug("Registering observer for {} worlds", worlds.size());
|
||||||
|
for(World w : worlds) {
|
||||||
|
gm.addObserver(w, new WObserver(w, client));
|
||||||
|
}
|
||||||
|
return client.getConnectionResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
private class WObserver implements WorldObserver {
|
||||||
|
private World world;
|
||||||
|
ArchipelagoClient client;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPointCard(char player, Card card, Color color) {
|
||||||
|
// We compute the count of green cards
|
||||||
|
//TODO right now, we only do the strategy of green green cards once
|
||||||
|
//TODO decide wether we or they should have the logic of wether to send the ith check
|
||||||
|
Card[] cards = cRepository.findByGridOrderById_Index(world.getCurrentGrid());
|
||||||
|
int greenCount = (int)Arrays.stream(cards).filter(c ->
|
||||||
|
(c.isColorAPublic() && c.getColorA() == Color.GREEN) ||
|
||||||
|
(c.isColorBPublic() && c.getColorB() == Color.GREEN)
|
||||||
|
).count();
|
||||||
|
log.info("Pointed card, {}th", greenCount);
|
||||||
|
client.checkWordInWorld(world.getName(), greenCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onGridEnd(boolean win) {
|
||||||
|
if(win) {
|
||||||
|
client.checkWorldComplete(world.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSubmitHint(char player, Hint hint) {}
|
||||||
|
@Override
|
||||||
|
public void onGameEvent(GameEvent ge) {}
|
||||||
|
@Override
|
||||||
|
public void onNewPhase(Phase p) {}
|
||||||
|
@Override
|
||||||
|
public void onConfigChange(MutableOption cc, Object data) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,38 +2,75 @@ package com.bernard.nodecames.game;
|
|||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.bernard.nodecames.model.Card;
|
||||||
|
import com.bernard.nodecames.model.Card.Color;
|
||||||
import com.bernard.nodecames.model.Game;
|
import com.bernard.nodecames.model.Game;
|
||||||
|
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.GridConfig;
|
import com.bernard.nodecames.model.GridConfig;
|
||||||
|
import com.bernard.nodecames.model.GridConfig.MutableOption;
|
||||||
|
import com.bernard.nodecames.model.Hint;
|
||||||
import com.bernard.nodecames.model.World;
|
import com.bernard.nodecames.model.World;
|
||||||
|
import com.bernard.nodecames.model.World.WorldId;
|
||||||
|
import com.bernard.nodecames.observer.GridObserver;
|
||||||
|
import com.bernard.nodecames.observer.WorldObserver;
|
||||||
import com.bernard.nodecames.repository.GameRepository;
|
import com.bernard.nodecames.repository.GameRepository;
|
||||||
import com.bernard.nodecames.repository.WorldRepository;
|
import com.bernard.nodecames.repository.WorldRepository;
|
||||||
|
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@AllArgsConstructor
|
||||||
public class GameManager {
|
public class GameManager {
|
||||||
|
|
||||||
// Reverse map allowing to pass grid events to the game
|
|
||||||
Map<UUID, World> currentGrids = new HashMap<>();
|
|
||||||
|
|
||||||
private final GridManager gm;
|
private final GridManager gm;
|
||||||
private final GameRepository gRepository;
|
private final GameRepository gRepository;
|
||||||
private final WorldRepository wRepository;
|
private final WorldRepository wRepository;
|
||||||
|
|
||||||
public GameManager(GridManager gm, GameRepository gRepository, WorldRepository wRepository) {
|
//TODO try putting "World" here instead of WorldId
|
||||||
this.gm = gm;
|
private Map<WorldId, Set<WorldObserver>> observers = new HashMap<>();
|
||||||
this.gRepository = gRepository;
|
|
||||||
this.wRepository = wRepository;
|
public void addObserver(World w, WorldObserver observer) {
|
||||||
gm.setWm(this);
|
if(!observers.containsKey(w.getId()))
|
||||||
|
observers.put(w.getId(), new CopyOnWriteArraySet<>());
|
||||||
|
observers.get(w.getId()).add(observer);
|
||||||
|
if(w.getCurrentGrid() != null)
|
||||||
|
gm.addObserver(w.getCurrentGrid(), observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void forEachObserver(World w, Consumer<WorldObserver> action) {
|
||||||
|
observers.getOrDefault(w.getId(), Set.of()).forEach(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In case of server restart, registers to GridManager observers
|
||||||
|
*/
|
||||||
|
@PostConstruct
|
||||||
|
private void registerRunningObservers() {
|
||||||
|
for(World w : wRepository.findAll()) {
|
||||||
|
if(w.getCurrentGrid() != null && w.getCurrentGrid().getWon() != null) {
|
||||||
|
registerObserver(w, w.getCurrentGrid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void registerObserver(World w, Grid g) {
|
||||||
|
gm.addObserver(g, new GObserver(g, w));
|
||||||
|
forEachObserver(w, o -> gm.addObserver(g, o));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Game createGame(String name) {
|
public Game createGame(String name) {
|
||||||
@@ -42,10 +79,9 @@ public class GameManager {
|
|||||||
Game g = new Game(uuid, name);
|
Game g = new Game(uuid, name);
|
||||||
gRepository.save(g);
|
gRepository.save(g);
|
||||||
for (Entry<String, GridConfig> wConfig : Game.DEFAULT_WORLDS_CONFIG.entrySet()) {
|
for (Entry<String, GridConfig> wConfig : Game.DEFAULT_WORLDS_CONFIG.entrySet()) {
|
||||||
World w = new World(g, wConfig.getKey(), wConfig.getValue(), null, false);
|
World w = new World(g, wConfig.getKey(), wConfig.getValue(), null, false, true);
|
||||||
wRepository.save(w);
|
wRepository.save(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
return g;
|
return g;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,22 +93,26 @@ public class GameManager {
|
|||||||
return wRepository.findByGame(g);
|
return wRepository.findByGame(g);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Grid newGrid(Game g, World w) {
|
private Grid newGrid(Game g, World w) throws IllegalGameActionException {
|
||||||
|
if(w.isLocked())
|
||||||
|
//TODO separate IllegaleGameAction and IllegalGridAction
|
||||||
|
throw new IllegalGameActionException(null, "Cannot create a grid in a locked world");
|
||||||
GridConfig gc = w.getInitialConfig();
|
GridConfig gc = w.getInitialConfig();
|
||||||
|
//TODO seed random
|
||||||
Grid grid = gm.newGrid(w, gc, new Random());
|
Grid grid = gm.newGrid(w, gc, new Random());
|
||||||
|
|
||||||
w.setCurrentGrid(grid);
|
w.setCurrentGrid(grid);
|
||||||
wRepository.save(w);
|
wRepository.save(w);
|
||||||
currentGrids.put(grid.getId(), w);
|
registerObserver(w, grid);
|
||||||
|
|
||||||
return grid;
|
return grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Grid resetGrid(Game g, World world) {
|
public Grid resetGrid(Game g, World world) throws IllegalGameActionException{
|
||||||
return newGrid(g, world);
|
return newGrid(g, world);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Grid getGrid(Game g, World w) {
|
public Grid getGrid(Game g, World w) throws IllegalGameActionException{
|
||||||
if(w.getCurrentGrid() != null) {
|
if(w.getCurrentGrid() != null) {
|
||||||
return w.getCurrentGrid();
|
return w.getCurrentGrid();
|
||||||
} else {
|
} else {
|
||||||
@@ -80,11 +120,100 @@ public class GameManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onGameWon(Grid g) {
|
/**
|
||||||
World w = currentGrids.get(g.getId());
|
* UPDATE GAME CONFIG
|
||||||
if(w != null) {
|
*/
|
||||||
w.setWon(true);
|
public void unlockMutable(Game g, MutableOption opt, Object o) throws IllegalGameActionException {
|
||||||
|
Set<World> worlds = wRepository.findByGame(g);
|
||||||
|
for(World w : worlds) {
|
||||||
|
GridConfig initialConfig = w.getInitialConfig();
|
||||||
|
initialConfig.set(opt, o);
|
||||||
|
w.setInitialConfig(initialConfig);
|
||||||
|
wRepository.save(w);
|
||||||
|
Grid grid = w.getCurrentGrid();
|
||||||
|
if(grid != null) {
|
||||||
|
gm.unlock(grid, opt, o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unlockStructuredGrid(Game g) {
|
||||||
|
Set<World> worlds = wRepository.findByGame(g);
|
||||||
|
for(World w : worlds) {
|
||||||
|
GridConfig initialConfig = w.getInitialConfig();
|
||||||
|
initialConfig.setStructuredGrid(true);
|
||||||
|
w.setInitialConfig(initialConfig);
|
||||||
wRepository.save(w);
|
wRepository.save(w);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public void unlockAnyoneCanStart(Game g) {
|
||||||
|
Set<World> worlds = wRepository.findByGame(g);
|
||||||
|
for(World w : worlds) {
|
||||||
|
GridConfig initialConfig = w.getInitialConfig();
|
||||||
|
initialConfig.setAnyoneStarts(true);
|
||||||
|
w.setInitialConfig(initialConfig);
|
||||||
|
wRepository.save(w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unlockWorld(Game g, String name) throws IllegalGameActionException {
|
||||||
|
Optional<World> ow = wRepository.findByGameAndId_Name(g, name);
|
||||||
|
if(!ow.isPresent())
|
||||||
|
throw new IllegalGameActionException(null, "Could not find world with name %s".formatted(name));
|
||||||
|
World w = ow.get();
|
||||||
|
w.setLocked(false);
|
||||||
|
wRepository.save(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unlockMoreHintCount(Game game) throws IllegalGameActionException {
|
||||||
|
//TODO Store current hint count elsewhere
|
||||||
|
int currentHintCount = wRepository.findByGame(game).stream().findAny().get().getInitialConfig().getMaxHint();
|
||||||
|
unlockMutable(game, MutableOption.MAX_HINT, currentHintCount+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unlockMoreHintWordLengthCount(Game game) throws IllegalGameActionException {
|
||||||
|
//TODO Store current hint word length elsewhere
|
||||||
|
int currentHintWordLength = wRepository.findByGame(game).stream().findAny().get().getInitialConfig().getHintMaxLength();
|
||||||
|
unlockMutable(game, MutableOption.HINT_MAX_LENGTH, currentHintWordLength+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unlockMoreHintWordCount(Game game) throws IllegalGameActionException {
|
||||||
|
//TODO Store current hint word count elsewhere
|
||||||
|
int currentMaxWordCount = wRepository.findByGame(game).stream().findAny().get().getInitialConfig().getAvailableHintWordCount().stream().mapToInt(i -> i).max().getAsInt();
|
||||||
|
unlockHintWordCount(game, currentMaxWordCount+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unlockHintWordCount(Game game, int i) throws IllegalGameActionException {
|
||||||
|
Set<Integer> currentWordCounts = wRepository.findByGame(game).stream().findAny().get().getInitialConfig().getAvailableHintWordCount();
|
||||||
|
if(currentWordCounts.contains(i))
|
||||||
|
throw new IllegalGameActionException(null, "Word count already unlocked : %d".formatted(i));
|
||||||
|
Set<Integer> newWordCounts = new HashSet<>(currentWordCounts);
|
||||||
|
newWordCounts.add(i);
|
||||||
|
unlockMutable(game, MutableOption.AVAILABLE_HINT_WORD_COUNT, newWordCounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
private class GObserver implements GridObserver {
|
||||||
|
private Grid grid;
|
||||||
|
private World world;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onGridEnd(boolean win) {
|
||||||
|
if(win) {
|
||||||
|
world.setWon(true);
|
||||||
|
wRepository.save(world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPointCard(char player, Card card, Color color) {}
|
||||||
|
@Override
|
||||||
|
public void onSubmitHint(char player, Hint hint) {}
|
||||||
|
@Override
|
||||||
|
public void onGameEvent(GameEvent ge) {}
|
||||||
|
@Override
|
||||||
|
public void onNewPhase(Phase p) {}
|
||||||
|
@Override
|
||||||
|
public void onConfigChange(MutableOption cc, Object data) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,15 @@ package com.bernard.nodecames.game;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
@@ -14,10 +18,9 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import com.bernard.nodecames.frontend.WebSocketController;
|
|
||||||
import com.bernard.nodecames.model.Card;
|
import com.bernard.nodecames.model.Card;
|
||||||
import com.bernard.nodecames.model.GameEvent;
|
|
||||||
import com.bernard.nodecames.model.Card.Color;
|
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;
|
||||||
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;
|
||||||
@@ -25,21 +28,18 @@ import com.bernard.nodecames.model.GridConfig.CardColors;
|
|||||||
import com.bernard.nodecames.model.GridConfig.MutableOption;
|
import com.bernard.nodecames.model.GridConfig.MutableOption;
|
||||||
import com.bernard.nodecames.model.Hint;
|
import com.bernard.nodecames.model.Hint;
|
||||||
import com.bernard.nodecames.model.World;
|
import com.bernard.nodecames.model.World;
|
||||||
|
import com.bernard.nodecames.observer.GridObserver;
|
||||||
import com.bernard.nodecames.repository.CardRepository;
|
import com.bernard.nodecames.repository.CardRepository;
|
||||||
import com.bernard.nodecames.repository.GridRepository;
|
import com.bernard.nodecames.repository.GridRepository;
|
||||||
|
|
||||||
import jakarta.annotation.Nullable;
|
import jakarta.annotation.Nullable;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class GridManager {
|
public class GridManager {
|
||||||
|
|
||||||
@Setter
|
private Map<UUID, Set<GridObserver>> observers = new HashMap<>();
|
||||||
private WebSocketController wsc = null;
|
|
||||||
@Setter
|
|
||||||
private GameManager wm = null;
|
|
||||||
|
|
||||||
private final GridRepository gRepository;
|
private final GridRepository gRepository;
|
||||||
private final CardRepository cRepository;
|
private final CardRepository cRepository;
|
||||||
@@ -48,11 +48,22 @@ public class GridManager {
|
|||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(GridManager.class);
|
private static final Logger log = LoggerFactory.getLogger(GridManager.class);
|
||||||
|
|
||||||
|
public void addObserver(Grid g, GridObserver observer) {
|
||||||
|
if(!observers.containsKey(g.getId()))
|
||||||
|
observers.put(g.getId(), new CopyOnWriteArraySet<>());
|
||||||
|
observers.get(g.getId()).add(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void forEachObserver(Grid g, Consumer<GridObserver> action) {
|
||||||
|
log.debug("The current observers are : {}", observers.get(g.getId()));
|
||||||
|
observers.getOrDefault(g.getId(), Set.of()).forEach(action);
|
||||||
|
}
|
||||||
|
|
||||||
private void newGameEvent(Grid g, GameEvent ge) {
|
private void newGameEvent(Grid g, GameEvent ge) {
|
||||||
log.debug("New game event : {}", ge);
|
log.debug("New game event : {}", ge);
|
||||||
g.newGameEvent(ge);
|
g.newGameEvent(ge);
|
||||||
gRepository.save(g);
|
gRepository.save(g);
|
||||||
wsc.onNewEvent(g, ge);
|
forEachObserver(g, o -> o.onGameEvent(ge));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPhase(Grid g, Phase phase) {
|
private void setPhase(Grid g, Phase phase) {
|
||||||
@@ -62,33 +73,40 @@ public class GridManager {
|
|||||||
g.setCurrentHint(null);
|
g.setCurrentHint(null);
|
||||||
}
|
}
|
||||||
gRepository.save(g);
|
gRepository.save(g);
|
||||||
wsc.onNewPhase(g);
|
forEachObserver(g, o -> o.onNewPhase(phase));
|
||||||
}
|
}
|
||||||
private void setWon(Grid g, boolean won) {
|
private void setWon(Grid g, boolean won) {
|
||||||
log.debug("Game won: {}", won);
|
log.debug("Game won: {}", won);
|
||||||
g.setWonOpt(Optional.of(won));
|
g.setWonOpt(Optional.of(won));
|
||||||
gRepository.save(g);
|
gRepository.save(g);
|
||||||
|
forEachObserver(g, o -> o.onGridEnd(won));
|
||||||
|
// Grid will no longer be updated, therefore we stop observing it
|
||||||
|
observers.remove(g.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setCardPublic(Card c, char player) {
|
private void setCardPointed(Grid g, Card c, char player, Color col) {
|
||||||
log.debug("Card set public, index {}", c.getIndex());
|
log.debug("Card set public, index {}", c.getIndex());
|
||||||
c.setColorPublic(player, true);
|
c.setColorPublic(player, true);
|
||||||
cRepository.save(c);
|
cRepository.save(c);
|
||||||
|
forEachObserver(c.getGrid(), o -> o.onPointCard(player, c, col));
|
||||||
|
g.incrementCurrentGuessCount();
|
||||||
|
gRepository.save(g);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void newHint(Grid g, Hint h) {
|
private void newHint(Grid g, char player, Hint h) {
|
||||||
log.debug("New hint: {}", h);
|
log.debug("New hint: {}", h);
|
||||||
g.incrementHintCount();
|
g.incrementHintCount();
|
||||||
g.setCurrentHint(h);
|
g.setCurrentHint(h);
|
||||||
g.setCurrentGuessCount(0);
|
g.setCurrentGuessCount(0);
|
||||||
gRepository.save(g);
|
gRepository.save(g);
|
||||||
|
forEachObserver(g, o -> o.onSubmitHint(player, h));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void changeConfig(Grid g, MutableOption cc, Object data) {
|
private void changeConfig(Grid g, MutableOption cc, Object data) {
|
||||||
log.debug("Changed config option: {} -> new value: {}", cc.name(), data);
|
log.debug("Changed config option: {} -> new value: {}", cc.name(), data);
|
||||||
g.getConfig().set(cc, data);
|
g.getConfig().set(cc, data);
|
||||||
gRepository.save(g);
|
gRepository.save(g);
|
||||||
wsc.onConfigChange(g, cc, data);
|
forEachObserver(g, o -> o.onConfigChange(cc, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -169,12 +187,10 @@ public class GridManager {
|
|||||||
* GAME ACTIONS *
|
* GAME ACTIONS *
|
||||||
**********************************/
|
**********************************/
|
||||||
private void endGame(Grid g, boolean win) {
|
private void endGame(Grid g, boolean win) {
|
||||||
setWon(g, win);
|
|
||||||
setPhase(g, Phase.GAME_ENDED);
|
setPhase(g, Phase.GAME_ENDED);
|
||||||
newGameEvent(g, GameEvent.newGameEndEvent(win));
|
newGameEvent(g, GameEvent.newGameEndEvent(win));
|
||||||
if(win) {
|
// This must be the last step as it removes observers
|
||||||
wm.onGameWon(g);
|
setWon(g, win);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void proposeHint(Grid g, char player, String hint, int wordCount) throws IllegalGameActionException {
|
public void proposeHint(Grid g, char player, String hint, int wordCount) throws IllegalGameActionException {
|
||||||
@@ -189,7 +205,7 @@ public class GridManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Hint h = new Hint(hint, wordCount);
|
Hint h = new Hint(hint, wordCount);
|
||||||
newHint(g, h);
|
newHint(g, player, h);
|
||||||
newGameEvent(g, GameEvent.newHintEvent(h, player));
|
newGameEvent(g, GameEvent.newHintEvent(h, player));
|
||||||
setPhase(g, Phase.guessing(other(player)));
|
setPhase(g, Phase.guessing(other(player)));
|
||||||
}
|
}
|
||||||
@@ -264,8 +280,8 @@ public class GridManager {
|
|||||||
|
|
||||||
if(c.isColorPublic(other(player)) || (g.getConfig().isOnlyOneGreenGreen() && c.getColor(player) == Card.Color.GREEN && c.isColorPublic(player)))
|
if(c.isColorPublic(other(player)) || (g.getConfig().isOnlyOneGreenGreen() && c.getColor(player) == Card.Color.GREEN && c.isColorPublic(player)))
|
||||||
throw new IllegalGameActionException(g, "Card has already been revealed");
|
throw new IllegalGameActionException(g, "Card has already been revealed");
|
||||||
setCardPublic(c, other(player));
|
|
||||||
Card.Color guessedColor = c.getColor(other(player));
|
Card.Color guessedColor = c.getColor(other(player));
|
||||||
|
setCardPointed(g, c, other(player), guessedColor);
|
||||||
|
|
||||||
newGameEvent(g, GameEvent.newGuessEvent(c.getIndex(), player, c.getColor(other(player))));
|
newGameEvent(g, GameEvent.newGuessEvent(c.getIndex(), player, c.getColor(other(player))));
|
||||||
g.incrementCurrentGuessCount();
|
g.incrementCurrentGuessCount();
|
||||||
@@ -369,7 +385,7 @@ public class GridManager {
|
|||||||
}
|
}
|
||||||
public void unlockHintCount(Grid g, int newHintCount) throws IllegalGameActionException {
|
public void unlockHintCount(Grid g, int newHintCount) throws IllegalGameActionException {
|
||||||
if(newHintCount > g.getConfig().getMaxMaxHint()) {
|
if(newHintCount > g.getConfig().getMaxMaxHint()) {
|
||||||
throw new IllegalGameActionException(g, "Illegal hint count");
|
throw new IllegalGameActionException(g, "Hint count higher than highest hint count");
|
||||||
}
|
}
|
||||||
if(newHintCount <= g.getConfig().getMaxHint()) {
|
if(newHintCount <= g.getConfig().getMaxHint()) {
|
||||||
throw new IllegalGameActionException(g, "Cannot lower hint count");
|
throw new IllegalGameActionException(g, "Cannot lower hint count");
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.bernard.nodecames.model;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.hibernate.annotations.JdbcTypeCode;
|
import org.hibernate.annotations.JdbcTypeCode;
|
||||||
@@ -10,12 +11,15 @@ import org.hibernate.type.SqlTypes;
|
|||||||
|
|
||||||
import jakarta.annotation.Nullable;
|
import jakarta.annotation.Nullable;
|
||||||
import jakarta.persistence.AttributeConverter;
|
import jakarta.persistence.AttributeConverter;
|
||||||
|
import jakarta.persistence.CascadeType;
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.Convert;
|
import jakarta.persistence.Convert;
|
||||||
import jakarta.persistence.Embedded;
|
import jakarta.persistence.Embedded;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.Id;
|
||||||
import jakarta.persistence.ManyToOne;
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.OneToMany;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import jakarta.persistence.Embeddable;
|
|||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@@ -50,27 +51,24 @@ public class GridConfig {
|
|||||||
0,1,2,3,4,5,6,7,8,9,-1
|
0,1,2,3,4,5,6,7,8,9,-1
|
||||||
);
|
);
|
||||||
public static final GridConfig BIG_CLASSICAL = new GridConfig(
|
public static final GridConfig BIG_CLASSICAL = new GridConfig(
|
||||||
12, true, true, CUSTOM36, true,
|
12, CUSTOM36, true, true,
|
||||||
true, true, 12, BASE_HINTWORDCOUNT, true,
|
true, true, true, 12, BASE_HINTWORDCOUNT, true,
|
||||||
Integer.MAX_VALUE, true, true,
|
Integer.MAX_VALUE, true, true,
|
||||||
true, false);
|
true, false);
|
||||||
|
|
||||||
public static final GridConfig CODENAMES_CLASSICAL = new GridConfig(
|
public static final GridConfig CODENAMES_CLASSICAL = new GridConfig(
|
||||||
9, true, true, CLASSICAL25, true,
|
9, CLASSICAL25, true, true, true,
|
||||||
true, true, 9, BASE_HINTWORDCOUNT, true,
|
true, true, 9, BASE_HINTWORDCOUNT, true,
|
||||||
Integer.MAX_VALUE, true, true,
|
Integer.MAX_VALUE, true, true,
|
||||||
true, false);
|
true, false);
|
||||||
|
|
||||||
public static final GridConfig ZERO = new GridConfig(
|
public static final GridConfig ZERO = zeroWithSize(CLASSICAL25);
|
||||||
9, false, false, CLASSICAL25, true,
|
|
||||||
true, false, 1, Set.of(1), false,
|
|
||||||
3, false, false,
|
|
||||||
false, false);
|
|
||||||
|
|
||||||
|
//TODO this 9 should be config
|
||||||
public static final GridConfig zeroWithSize(Map<CardColors, Integer> colors) {
|
public static final GridConfig zeroWithSize(Map<CardColors, Integer> colors) {
|
||||||
return new GridConfig(
|
return new GridConfig(
|
||||||
9, false, false, colors, true,
|
9, colors, true, true,
|
||||||
true, false, 1, Set.of(1), false,
|
false, false, false, 1, Set.of(1), false,
|
||||||
3, false, false,
|
3, false, false,
|
||||||
false, false);
|
false, false);
|
||||||
}
|
}
|
||||||
@@ -79,8 +77,6 @@ public class GridConfig {
|
|||||||
// Maximum of maximom number of hints
|
// Maximum of maximom number of hints
|
||||||
// This is to trigger end of game
|
// This is to trigger end of game
|
||||||
private int maxMaxHint;
|
private int maxMaxHint;
|
||||||
private boolean structuredGrid;
|
|
||||||
private boolean anyoneStarts;
|
|
||||||
@Convert(converter = WordDistributionConverter.class)
|
@Convert(converter = WordDistributionConverter.class)
|
||||||
private Map<CardColors, Integer> wordDistribution;
|
private Map<CardColors, Integer> wordDistribution;
|
||||||
private boolean maybeSuddenDeath; //TODO
|
private boolean maybeSuddenDeath; //TODO
|
||||||
@@ -89,6 +85,11 @@ public class GridConfig {
|
|||||||
// prevend death and enter WIN_IF_GREEN_GREEN state, else just die
|
// prevend death and enter WIN_IF_GREEN_GREEN state, else just die
|
||||||
private boolean onlyOneGreenGreenPreventDeath;
|
private boolean onlyOneGreenGreenPreventDeath;
|
||||||
|
|
||||||
|
// Game mutable options
|
||||||
|
@Setter
|
||||||
|
private boolean structuredGrid;
|
||||||
|
@Setter
|
||||||
|
private boolean anyoneStarts;
|
||||||
|
|
||||||
// Mutable options
|
// Mutable options
|
||||||
private int maxHint;
|
private int maxHint;
|
||||||
|
|||||||
@@ -34,12 +34,15 @@ public class World {
|
|||||||
|
|
||||||
boolean won;
|
boolean won;
|
||||||
|
|
||||||
public World(Game g, String name, GridConfig initConfig, Grid currentGrid, boolean won) {
|
boolean locked;
|
||||||
|
|
||||||
|
public World(Game g, String name, GridConfig initConfig, Grid currentGrid, boolean won, boolean locked) {
|
||||||
this.id = new WorldId(g.getId(), name);
|
this.id = new WorldId(g.getId(), name);
|
||||||
this.game = g;
|
this.game = g;
|
||||||
this.initialConfig = initConfig;
|
this.initialConfig = initConfig;
|
||||||
this.currentGrid = currentGrid;
|
this.currentGrid = currentGrid;
|
||||||
this.won = won;
|
this.won = won;
|
||||||
|
this.locked = locked;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.bernard.nodecames.observer;
|
||||||
|
|
||||||
|
import com.bernard.nodecames.model.Card;
|
||||||
|
import com.bernard.nodecames.model.GameEvent;
|
||||||
|
import com.bernard.nodecames.model.Hint;
|
||||||
|
import com.bernard.nodecames.model.Card.Color;
|
||||||
|
import com.bernard.nodecames.model.Grid.Phase;
|
||||||
|
import com.bernard.nodecames.model.GridConfig.MutableOption;
|
||||||
|
|
||||||
|
public interface GridObserver {
|
||||||
|
|
||||||
|
public void onPointCard(char player, Card card, Color color);
|
||||||
|
public void onSubmitHint(char player, Hint hint);
|
||||||
|
|
||||||
|
public void onGridEnd(boolean won);
|
||||||
|
public void onGameEvent(GameEvent ge);
|
||||||
|
public void onNewPhase(Phase p);
|
||||||
|
public void onConfigChange(MutableOption cc, Object data);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.bernard.nodecames.observer;
|
||||||
|
|
||||||
|
public interface WorldObserver extends GridObserver {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
alter table if exists world add column locked boolean not null default false;
|
||||||
@@ -256,10 +256,10 @@ function selectPlayer(player) {
|
|||||||
|
|
||||||
socket = Stomp.over(new SockJS('/socket'));
|
socket = Stomp.over(new SockJS('/socket'));
|
||||||
socket.connect({}, function (frame) {
|
socket.connect({}, function (frame) {
|
||||||
socket.subscribe('/topic/new-phase', onNewPhase);
|
socket.subscribe('/topic/'+roomId+'/new-phase', onNewPhase);
|
||||||
socket.subscribe('/topic/new-event', onNewEvent);
|
socket.subscribe('/topic/'+roomId+'/new-event', onNewEvent);
|
||||||
socket.subscribe('/topic/update-card/' + player.toLowerCase(), onUpdateCard);
|
socket.subscribe('/topic/'+roomId+'/update-card/' + player.toLowerCase(), onUpdateCard);
|
||||||
socket.subscribe('/topic/config-change', onConfigChange)
|
socket.subscribe('/topic/'+roomId+'/config-change', onConfigChange)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,10 @@
|
|||||||
<main>
|
<main>
|
||||||
<ul>
|
<ul>
|
||||||
<li th:each="world : ${worlds}">
|
<li th:each="world : ${worlds}">
|
||||||
<a th:href="@{/game/{gid}/grid/{wname}?player={player}(gid=${game.id},wname=${world.name},player=${player})}">World <span th:text="${world.name}"/></a>
|
<!-- TODO remove this duplication of World span -->
|
||||||
<a th:href="@{/game/{gid}/reset/{wname}?player={player}(gid=${game.id},wname=${world.name},player=${player})}">reset</a>
|
<span th:if="${world.locked}"> World <span th:text="${world.name}"/> </span>
|
||||||
|
<a th:if="!${world.locked}" th:href="@{/game/{gid}/grid/{wname}?player={player}(gid=${game.id},wname=${world.name},player=${player})}">World <span th:text="${world.name}"/></a>
|
||||||
|
<a th:if="!${world.locked}" th:href="@{/game/{gid}/reset/{wname}?player={player}(gid=${game.id},wname=${world.name},player=${player})}">reset</a>
|
||||||
<span th:if="${world.won}">Gagné !</span>
|
<span th:if="${world.won}">Gagné !</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ version.com.fasterxml.jackson.core..jackson-databind=2.22.2
|
|||||||
|
|
||||||
version.com.fasterxml.jackson.datatype..jackson-datatype-hibernate5=2.22.2
|
version.com.fasterxml.jackson.datatype..jackson-datatype-hibernate5=2.22.2
|
||||||
|
|
||||||
|
version.io.github.archipelagomw..Java-Client=0.2.1
|
||||||
|
|
||||||
version.jakarta.validation..jakarta.validation-api=3.1.1
|
version.jakarta.validation..jakarta.validation-api=3.1.1
|
||||||
## # available=4.0.0-M1
|
## # available=4.0.0-M1
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user