Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0bb880d434 | ||
|
|
fa82900770 | ||
|
|
89cac6a0c6 |
@@ -15,6 +15,11 @@
|
||||
gradle = pkgs.gradle;
|
||||
version = "1.0";
|
||||
|
||||
nodecames-apworld = pkgs.runCommand "nodecames.apworld" {} ''
|
||||
cp -r ${./src/main/apworld} ./nodecames/
|
||||
${pkgs.zip}/bin/zip -r $out ./nodecames/
|
||||
'';
|
||||
|
||||
nodecames = pkgs.buildGradleArtifact {
|
||||
pname = "nodecames";
|
||||
src = ./.;
|
||||
@@ -38,10 +43,11 @@
|
||||
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 &
|
||||
'';
|
||||
|
||||
in {
|
||||
packages.${system} = {
|
||||
default = self.packages.${system}.nodecames;
|
||||
inherit nodecames;
|
||||
inherit nodecames nodecames-apworld;
|
||||
};
|
||||
|
||||
apps.${system} = {
|
||||
@@ -59,6 +65,7 @@
|
||||
vscode = pkgs.vscodium;
|
||||
vscodeExtensions = with pkgs.vscode-extensions; [
|
||||
redhat.java
|
||||
ms-python.python
|
||||
vscjava.vscode-java-debug
|
||||
vscjava.vscode-java-test
|
||||
vscjava.vscode-gradle
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from .world import NodecamesWorld
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"game": "NodeCames",
|
||||
"minimum_ap_version": "0.6.8",
|
||||
"world_version": "1.0.0",
|
||||
"authors": ["Mysaa"]
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from BaseClasses import Item, ItemClassification
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .world import NodecamesWorld
|
||||
|
||||
ITEM_NAME_TO_ID = {
|
||||
"progressive_hint_count" : 1,
|
||||
"zero_hint" : 2,
|
||||
"infinity_hint" : 3,
|
||||
"one_more_guess" : 4,
|
||||
"hint_max_length" : 5,
|
||||
"end_guessing" : 6,
|
||||
"only_one_green_green" : 7,
|
||||
"sudden_death" : 8,
|
||||
"structured_grid" : 9,
|
||||
"hints_in_a_row" : 10,
|
||||
"anyone_starts" : 11,
|
||||
"filler": 12
|
||||
}
|
||||
for w in range(1,12+1):
|
||||
ITEM_NAME_TO_ID[f"unlock_world_{w}"] = 100+w
|
||||
|
||||
|
||||
class NodecamesItem(Item):
|
||||
game = "Nodecames"
|
||||
|
||||
def get_random_filler_item_name(world: NodecamesWorld) -> str:
|
||||
return "filler"
|
||||
|
||||
|
||||
def create_item_with_correct_classification(
|
||||
world: NodecamesWorld, name: str
|
||||
) -> NodecamesItem:
|
||||
return NodecamesItem(name, ItemClassification.progression, ITEM_NAME_TO_ID[name], world.player)
|
||||
|
||||
|
||||
def create_all_items(world: NodecamesWorld) -> None:
|
||||
itempool: list[Item] = []
|
||||
|
||||
hintItemCount = world.options.max_hint_count - world.options.max_hint_length
|
||||
itempool += [world.create_item("progressive_hint_count") for _ in range(hintItemCount)]
|
||||
itempool += [
|
||||
world.create_item("zero_hint"),
|
||||
world.create_item("infinity_hint"),
|
||||
world.create_item("one_more_guess"),
|
||||
world.create_item("hint_max_length"),
|
||||
world.create_item("end_guessing"),
|
||||
world.create_item("only_one_green_green"),
|
||||
world.create_item("sudden_death"),
|
||||
world.create_item("structured_grid"),
|
||||
world.create_item("hints_in_a_row"),
|
||||
world.create_item("anyone_starts"),
|
||||
]
|
||||
|
||||
to_complete = len(world.multiworld.get_unfilled_locations(world.player)) - len(itempool)
|
||||
|
||||
itempool += [world.create_filler() for _ in range(to_complete)]
|
||||
|
||||
world.multiworld.itempool += itempool
|
||||
@@ -0,0 +1,67 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from BaseClasses import ItemClassification, Location
|
||||
|
||||
from . import items
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .world import NodecamesWorld
|
||||
|
||||
|
||||
GREEN_COUNT_PER_WORLD = [6, 9, 9, 12, 16, 16, 20, 25, 25, 30, 36, 36]
|
||||
|
||||
def worldName(index):
|
||||
return f"{(index//3)+1}-{(index%3)+1}"
|
||||
|
||||
LOCATION_NAME_TO_ID = {}
|
||||
LOCATION_NAME_FROM_WORLD = {}
|
||||
id = 0
|
||||
for w in range(len(GREEN_COUNT_PER_WORLD)):
|
||||
LOCATION_NAME_FROM_WORLD[w] = []
|
||||
for i in range(1, GREEN_COUNT_PER_WORLD[w]):
|
||||
name = f"World {worldName(w)} word {i}"
|
||||
id += 1
|
||||
LOCATION_NAME_FROM_WORLD[w].append(name)
|
||||
LOCATION_NAME_TO_ID[name] = id
|
||||
name = f"Complete {worldName(w)}"
|
||||
id += 1
|
||||
LOCATION_NAME_FROM_WORLD[w].append(name)
|
||||
LOCATION_NAME_TO_ID[name] = id
|
||||
|
||||
|
||||
class NodecamesLocation(Location):
|
||||
game = "Nodecames"
|
||||
|
||||
|
||||
def create_all_locations(world: NodecamesWorld) -> None:
|
||||
create_world_locations(world)
|
||||
create_events(world)
|
||||
|
||||
|
||||
def create_world_locations(world: NodecamesWorld) -> None:
|
||||
overworld = world.get_region("Overworld")
|
||||
|
||||
for w in LOCATION_NAME_FROM_WORLD:
|
||||
for l in range(len(LOCATION_NAME_FROM_WORLD[w])-1):
|
||||
name = LOCATION_NAME_FROM_WORLD[w][l]
|
||||
loc = NodecamesLocation(
|
||||
world.player,
|
||||
name,
|
||||
LOCATION_NAME_TO_ID[name],
|
||||
overworld,
|
||||
)
|
||||
overworld.locations.append(loc)
|
||||
|
||||
def all_completion_items(world: NodecamesWorld) -> list[str]:
|
||||
return [LOCATION_NAME_FROM_WORLD[w][-1]+" item" for w in LOCATION_NAME_FROM_WORLD]
|
||||
|
||||
def create_events(world: NodecamesWorld) -> None:
|
||||
overworld = world.get_region("Overworld")
|
||||
for w in LOCATION_NAME_FROM_WORLD:
|
||||
overworld.add_event(
|
||||
LOCATION_NAME_FROM_WORLD[w][-1],
|
||||
LOCATION_NAME_FROM_WORLD[w][-1]+" item",
|
||||
location_type = NodecamesLocation,
|
||||
item_type = items.NodecamesItem)
|
||||
@@ -0,0 +1,233 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from Options import Choice, OptionGroup, PerGameCommonOptions, Range, DefaultOnToggle
|
||||
|
||||
|
||||
class WorldsLocked(DefaultOnToggle):
|
||||
"""
|
||||
Access to worlds need to be unlocked by an item
|
||||
"""
|
||||
|
||||
display_name = "Worlds locked"
|
||||
|
||||
|
||||
class StartHintCount(Range):
|
||||
"""
|
||||
Hint count at the beginning of the game
|
||||
"""
|
||||
|
||||
display_name = "Start hint count"
|
||||
|
||||
range_start = 1
|
||||
range_end = 20
|
||||
default = 1
|
||||
|
||||
|
||||
class MaxHintCount(Range):
|
||||
"""
|
||||
Maximum hint count we can get
|
||||
"""
|
||||
|
||||
display_name = "Max hint count"
|
||||
|
||||
range_start = 1
|
||||
range_end = 20
|
||||
default = 10
|
||||
|
||||
|
||||
class StartHintLength(Range):
|
||||
"""
|
||||
Maximal Hint length at the beginning of the game
|
||||
"""
|
||||
|
||||
display_name = "Start Hint Length"
|
||||
|
||||
range_start = 3
|
||||
range_end = 30
|
||||
default = 3
|
||||
|
||||
|
||||
class MaxHintLength(Range):
|
||||
"""
|
||||
Maximal maximum hint length we can get. Once it is reached, maximal length is now infinity.
|
||||
"""
|
||||
|
||||
display_name = "Max Hint Length"
|
||||
|
||||
range_start = 3
|
||||
range_end = 30
|
||||
default = 10
|
||||
|
||||
|
||||
class OnOffDefault(Choice):
|
||||
option_Off = 0
|
||||
option_Item = 1
|
||||
option_On = 2
|
||||
|
||||
default = 1
|
||||
|
||||
|
||||
class ZeroHint(OnOffDefault):
|
||||
"""
|
||||
Can give zero as a hint
|
||||
"""
|
||||
|
||||
display_name = "Hint Count 0"
|
||||
|
||||
|
||||
class InfinityHint(OnOffDefault):
|
||||
"""
|
||||
Can give infinity as a hint
|
||||
"""
|
||||
|
||||
display_name = "Hint Count infty"
|
||||
|
||||
|
||||
class OneMoreGuess(OnOffDefault):
|
||||
"""
|
||||
Can guess one word more than hint count
|
||||
"""
|
||||
|
||||
display_name = "One More Guess"
|
||||
|
||||
|
||||
class EndGuessing(OnOffDefault):
|
||||
"""
|
||||
Can end guessing before touching the given hint count
|
||||
"""
|
||||
|
||||
display_name = "End Guessing"
|
||||
|
||||
|
||||
class OnlyOneGreenGreen(OnOffDefault):
|
||||
"""
|
||||
Green-Green cards only have to be pointed by one of the players
|
||||
"""
|
||||
|
||||
display_name = "Only One Green Green"
|
||||
|
||||
|
||||
class SuddenDeath(OnOffDefault):
|
||||
"""
|
||||
Sudden death triggered when no more hints can be given
|
||||
"""
|
||||
|
||||
display_name = "Sudden Death"
|
||||
|
||||
|
||||
class StructuredGrid(OnOffDefault):
|
||||
"""
|
||||
Are front and back colors of the cards linked
|
||||
"""
|
||||
|
||||
display_name = "Structured Grid"
|
||||
|
||||
|
||||
class HintsInARow(OnOffDefault):
|
||||
"""
|
||||
Players can give multiple hints in a row
|
||||
"""
|
||||
|
||||
display_name = "HintsInARow"
|
||||
|
||||
|
||||
class AnyoneStarts(OnOffDefault):
|
||||
"""
|
||||
Both players can start the game
|
||||
"""
|
||||
|
||||
display_name = "AnyoneStarts"
|
||||
|
||||
|
||||
class WorldCount(Range):
|
||||
"""
|
||||
How many worlds to generate
|
||||
"""
|
||||
|
||||
display_name = "World Count"
|
||||
|
||||
range_start = 1
|
||||
range_end = 12
|
||||
default = 12
|
||||
|
||||
|
||||
class VictoryCondition(Choice):
|
||||
"""
|
||||
What is needed to complete game
|
||||
"""
|
||||
|
||||
option_AllWorlds = 1
|
||||
option_LastWorld = 2
|
||||
default = 2
|
||||
|
||||
|
||||
# We must now define a dataclass inheriting from PerGameCommonOptions that we put all our options in.
|
||||
# This is in the format "option_name_in_snake_case: OptionClassName".
|
||||
@dataclass
|
||||
class NodecamesOptions(PerGameCommonOptions):
|
||||
worlds_locked: WorldsLocked
|
||||
victory_condition: VictoryCondition
|
||||
start_hint_count: StartHintCount
|
||||
max_hint_count: MaxHintCount
|
||||
start_hint_length: StartHintLength
|
||||
max_hint_length: MaxHintLength
|
||||
zero_hint: ZeroHint
|
||||
infinity_hint: InfinityHint
|
||||
one_more_guess: OneMoreGuess
|
||||
end_guessing: EndGuessing
|
||||
only_one_green_green: OnlyOneGreenGreen
|
||||
sudden_death: SuddenDeath
|
||||
structured_grid: StructuredGrid
|
||||
hints_in_a_row: HintsInARow
|
||||
anyone_starts: AnyoneStarts
|
||||
world_count: WorldCount
|
||||
|
||||
|
||||
# If we want to group our options by similar type, we can do so as well. This looks nice on the website.
|
||||
option_groups = [
|
||||
OptionGroup(
|
||||
"General Options",
|
||||
[WorldsLocked, VictoryCondition],
|
||||
),
|
||||
OptionGroup(
|
||||
"Grid Options",
|
||||
[
|
||||
StartHintCount,
|
||||
MaxHintCount,
|
||||
StartHintLength,
|
||||
MaxHintLength,
|
||||
ZeroHint,
|
||||
InfinityHint,
|
||||
OneMoreGuess,
|
||||
EndGuessing,
|
||||
OnlyOneGreenGreen,
|
||||
SuddenDeath,
|
||||
StructuredGrid,
|
||||
HintsInARow,
|
||||
AnyoneStarts,
|
||||
],
|
||||
),
|
||||
OptionGroup("Worlds Options", [WorldCount]),
|
||||
]
|
||||
|
||||
# Finally, we can define some option presets if we want the player to be able to quickly choose a specific "mode".
|
||||
option_presets = {
|
||||
"default": {
|
||||
"worlds_locked": True,
|
||||
"start_hint_count": 1,
|
||||
"max_hint_count": 10,
|
||||
"start_hint_length": 3,
|
||||
"max_hint_length": 12,
|
||||
"zero_hint": 1,
|
||||
"infinity_hint": 1,
|
||||
"one_more_guess": 1,
|
||||
"end_guessing": 1,
|
||||
"only_one_green_green": 1,
|
||||
"sudden_death": 1,
|
||||
"structured_grid": 1,
|
||||
"hints_in_a_row": 1,
|
||||
"anyone_starts": 1,
|
||||
"world_count": 12,
|
||||
"victory_condition": 2,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from BaseClasses import Entrance, Region
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .world import NodecamesWorld
|
||||
|
||||
|
||||
def create_and_connect_regions(world: NodecamesWorld) -> None:
|
||||
create_all_regions(world)
|
||||
connect_regions(world)
|
||||
|
||||
|
||||
def create_all_regions(world: NodecamesWorld) -> None:
|
||||
overworld = Region("Overworld", world.player, world.multiworld)
|
||||
world.multiworld.regions += [overworld]
|
||||
|
||||
|
||||
def connect_regions(world: NodecamesWorld) -> None:
|
||||
overworld = world.get_region("Overworld")
|
||||
@@ -0,0 +1,31 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from rule_builder.options import OptionFilter
|
||||
from rule_builder.rules import Has, HasAll, Rule
|
||||
|
||||
from . import locations
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .world import NodecamesWorld
|
||||
|
||||
|
||||
def set_all_rules(world: NodecamesWorld) -> None:
|
||||
set_all_entrance_rules(world)
|
||||
set_all_location_rules(world)
|
||||
set_completion_condition(world)
|
||||
|
||||
|
||||
def set_all_entrance_rules(world: NodecamesWorld) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def set_all_location_rules(world: NodecamesWorld) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def set_completion_condition(world: NodecamesWorld) -> None:
|
||||
|
||||
worldLocations = locations.all_completion_items(world)
|
||||
world.set_completion_rule(HasAll(*worldLocations))
|
||||
@@ -0,0 +1,62 @@
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
from worlds.AutoWorld import World
|
||||
|
||||
from . import items, locations, regions, rules
|
||||
from . import options as nodecames_options
|
||||
|
||||
|
||||
class NodecamesWorld(World):
|
||||
"""
|
||||
Nodecames is a word game, inspired by Code Names by Vlaada Chvátil
|
||||
"""
|
||||
|
||||
game = "Nodecames"
|
||||
|
||||
options_dataclass = nodecames_options.NodecamesOptions
|
||||
options: nodecames_options.NodecamesOptions
|
||||
|
||||
location_name_to_id = locations.LOCATION_NAME_TO_ID
|
||||
item_name_to_id = items.ITEM_NAME_TO_ID
|
||||
|
||||
origin_region_name = "Overworld"
|
||||
|
||||
def create_regions(self) -> None:
|
||||
regions.create_and_connect_regions(self)
|
||||
locations.create_all_locations(self)
|
||||
|
||||
def set_rules(self) -> None:
|
||||
rules.set_all_rules(self)
|
||||
|
||||
def create_items(self) -> None:
|
||||
items.create_all_items(self)
|
||||
|
||||
# Create an item from name
|
||||
def create_item(self, name: str) -> items.NodecamesItem:
|
||||
return items.create_item_with_correct_classification(self, name)
|
||||
|
||||
# Create one filler item
|
||||
def get_filler_item_name(self) -> str:
|
||||
return items.get_random_filler_item_name(self)
|
||||
|
||||
# Data sent to the client
|
||||
def fill_slot_data(self) -> Mapping[str, Any]:
|
||||
return self.options.as_dict(
|
||||
"worlds_locked",
|
||||
"start_hint_count",
|
||||
"max_hint_count",
|
||||
"start_hint_length",
|
||||
"max_hint_length",
|
||||
"zero_hint",
|
||||
"infinity_hint",
|
||||
"one_more_guess",
|
||||
"end_guessing",
|
||||
"only_one_green_green",
|
||||
"sudden_death",
|
||||
"structured_grid",
|
||||
"hints_in_a_row",
|
||||
"anyone_starts",
|
||||
"world_count",
|
||||
"victory_condition",
|
||||
)
|
||||
@@ -100,9 +100,15 @@ public class HttpController {
|
||||
}
|
||||
|
||||
@GetMapping("/create-room")
|
||||
public RedirectView createRoom() {
|
||||
public RedirectView createRoom(@RequestParam("type") String type) {
|
||||
GridConfig cfg = switch(type) {
|
||||
case "zero" -> GridConfig.ZERO;
|
||||
case "base" -> GridConfig.CODENAMES_CLASSICAL;
|
||||
case "big" -> GridConfig.BIG_CLASSICAL;
|
||||
default -> GridConfig.CODENAMES_CLASSICAL;
|
||||
};
|
||||
Random r = new Random(rand.nextLong());
|
||||
Grid g = gm.newGrid(null, GridConfig.ZERO, r);
|
||||
Grid g = gm.newGrid(null, cfg, r);
|
||||
return new RedirectView("/room/"+g.getId()+"/grid");
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,12 @@ public class GridManager {
|
||||
gRepository.save(g);
|
||||
}
|
||||
|
||||
private void setCardPublic(Card c, char player) {
|
||||
log.debug("Card set public, index {}", c.getIndex());
|
||||
c.setColorPublic(player, true);
|
||||
cRepository.save(c);
|
||||
}
|
||||
|
||||
private void newHint(Grid g, Hint h) {
|
||||
log.debug("New hint: {}", h);
|
||||
g.incrementHintCount();
|
||||
@@ -258,7 +264,7 @@ public class GridManager {
|
||||
|
||||
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");
|
||||
c.setColorPublic(other(player), true);//TODO XXX BDD
|
||||
setCardPublic(c, other(player));
|
||||
Card.Color guessedColor = c.getColor(other(player));
|
||||
|
||||
newGameEvent(g, GameEvent.newGuessEvent(c.getIndex(), player, c.getColor(other(player))));
|
||||
|
||||
@@ -49,6 +49,12 @@ public class GridConfig {
|
||||
public static final Set<Integer> BASE_HINTWORDCOUNT = Set.of(
|
||||
0,1,2,3,4,5,6,7,8,9,-1
|
||||
);
|
||||
public static final GridConfig BIG_CLASSICAL = new GridConfig(
|
||||
12, true, true, CUSTOM36, true,
|
||||
true, true, 12, BASE_HINTWORDCOUNT, true,
|
||||
Integer.MAX_VALUE, true, true,
|
||||
true, false);
|
||||
|
||||
public static final GridConfig CODENAMES_CLASSICAL = new GridConfig(
|
||||
9, true, true, CLASSICAL25, true,
|
||||
true, true, 9, BASE_HINTWORDCOUNT, true,
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
|
||||
<body>
|
||||
<main>
|
||||
Vous voulez jouer à Node Cames ? Ben c'est pas encore prêt :/
|
||||
Vous voulez jouer à Node Cames ? Ben c'est là.
|
||||
<br/>
|
||||
<a href="/create-room">(Bon, en vrai, vous pouvez créer une room en cliquant ici)</a>
|
||||
<a href="/create-room?type=zero">Grille Zéro</a>
|
||||
<a href="/create-room?type=base">Grille Normale</a>
|
||||
<a href="/create-room?type=big">Grille Grosse</a>
|
||||
|
||||
<br/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user