Skip to content

Commit

Permalink
feat: Variants finish (#104)
Browse files Browse the repository at this point in the history
* feat: variants setup on game start

* feat: variants progress

* fix: fix examples, and rename con to test
  • Loading branch information
Zielak authored May 17, 2024
1 parent 19951e6 commit ba97781
Show file tree
Hide file tree
Showing 57 changed files with 620 additions and 355 deletions.
14 changes: 14 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@
"program": "${workspaceFolder}/examples/server-js/app.js",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
},
{
"type": "node",
"request": "launch",
"name": "Example - server-ts",
"env": {
"NODE_ENV": "development",
"LOGS": "verbose"
},
"runtimeExecutable": "node",
"sourceMaps": false,
"program": "${workspaceFolder}/examples/server-ts/dist/app.js",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
}
]
}
16 changes: 8 additions & 8 deletions docs/docs/getting-started/server-side/action-templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ export const TakeOneCard = defineEntityAction<MyGameState>({

interaction: () => [{ type: "deck" }],

conditions: (con) => {
con("It's not your turn yet!").itsPlayersTurn()
conditions: (test) => {
test("It's not your turn yet!").itsPlayersTurn()
},

command: ({ state, player }) => {
Expand Down Expand Up @@ -134,12 +134,12 @@ Use [`conditions` framework](./conditions.md), passed as first argument of `cond
```ts
// Example for card interaction
// You can name it `con` for short.
conditions: (con, { player }) => {
con().itsPlayersTurn()
conditions: (test, { player }) => {
test().itsPlayersTurn()

// Grab current player's `hand` and remember it
// under alias "chosenCards"
con().remember("chosenCards", {
test().remember("chosenCards", {
type: "hand",
parent: {
owner: player,
Expand All @@ -149,14 +149,14 @@ conditions: (con, { player }) => {

// Change subject to previously remembered "chosenCards"
// and ensure its got nothing inside.
con().get("chosenCards").children.is.not.empty()
test().get("chosenCards").children.is.not.empty()
}

// Example for custom command with expected additional data
conditions: (con) => {
con().itsPlayersTurn()
test().itsPlayersTurn()
// When client sent data as `{ "suits": "S" }`
con().subject.data.its("suit").equals("S")
test().subject.data.its("suit").equals("S")
}
```

Expand Down
54 changes: 27 additions & 27 deletions docs/docs/getting-started/server-side/conditions.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ sidebar_position: 4
Conditions are inspired by Chai Assertion Library. It is provided in [Action Templates](./action-templates) and [Bot's Neuron](./bots#botneuron) `conditions` methods.

```ts
conditions: (con: Conditions) => {
con().itsPlayersTurn();
conditions: (test: Conditions) => {
test().itsPlayersTurn();
},
```

Expand All @@ -18,16 +18,16 @@ Given player's interaction and current state of the game, **conditions** help th

With each statement you're going to check if _something_ matches your expectations/game rules. You can do that by switching **current subject** to some game element, player's status, value of some prop on game state or anything else.

Default **subject** is always the root game state, and it is reset every time you begin with `con()`.
Default **subject** is always the root game state, and it is reset every time you begin with `test()`.

You can change current **subject** by using many props and functions:

```ts title="Checking player's name is 'Bob'"
con().get("player").its("name").equals("Bob")
test().get("player").its("name").equals("Bob")

// explained:

con()
test()
// changes subject to something by alias "player"
.get("player")
// changes subject to a prop "name" of previous subject, effectively: player['name']
Expand All @@ -37,11 +37,11 @@ con()
```

```ts title="Checking the topmost card on deck is of rank Spades or Clubs"
con().get({ type: "deck" }).top.its("rank").is.oneOf(["S", "C"])
test().get({ type: "deck" }).top.its("rank").is.oneOf(["S", "C"])

// explained:

con()
test()
// changes subject to entity matching the "type" by querying whole game state
.get({ type: "deck" })
// changes subject to the last/topmost child of previously found entity
Expand All @@ -56,9 +56,9 @@ con()
There are quick references for couple of objects you can use to start construction your assertions. You can access them with `subject` or even shorter with `$`.

```ts
con().subject.entity.its("name").equals("mainDeck")
test().subject.entity.its("name").equals("mainDeck")
// This is the same
con().$.entity.its("name").equals("mainDeck")
test().$.entity.its("name").equals("mainDeck")
```

#### `entity`
Expand All @@ -76,7 +76,7 @@ Changes subject to extra data sent with an event.
You may have a UI interaction which would send extra data:

```ts
con().subject.data.oneOf(["5", "6", "7", "8", "9", "K", "A"])
test().subject.data.oneOf(["5", "6", "7", "8", "9", "K", "A"])
```

### Changing subjects
Expand Down Expand Up @@ -108,9 +108,9 @@ Remembers subject found by [`QuerableProps`](/api/server/interfaces/QuerableProp

```ts
// First call, queries state for entity named "deck"
con().remember("aliasToDeck", { name: "deck" })
test().remember("aliasToDeck", { name: "deck" })
// Won't perform the lookup again, as we already remember "aliasToDeck"
con().remember("aliasToDeck", { name: "deck" })
test().remember("aliasToDeck", { name: "deck" })
```

#### `get(alias: string)`
Expand All @@ -123,13 +123,13 @@ Changes subject to current subject's key/prop value.

```typescript
// On state
con().its("round").above(10)
test().its("round").above(10)

// On an entity
con({ type: "deck" }).its("angle").equals(90)
test({ type: "deck" }).its("angle").equals(90)

// Or anything else
con()
test()
.set({
propA: "foo",
propB: "bar",
Expand Down Expand Up @@ -255,9 +255,9 @@ You can continue your assertions using chain words, to construct neat, human-rea
`has`, `to`, `is`, `can`, `be`, `and`.

```ts
con().has.not.revealedUI()
test().has.not.revealedUI()

con()
test()
.subject.entity.its("rank")
.equals("K")
.and.its("suit")
Expand All @@ -279,11 +279,11 @@ import type { ClientMessageConditions } from "@cardsgame/server"
import type { MakaoState } from "../state.js"

export const matchesWithPile = (
con: ClientMessageConditions<MakaoState>
test: ClientMessageConditions<MakaoState>,
): void => {
con("Card must match with the one on the pile").either(
() => con().subject.entity.its("rank").matchesPropOf("pileTop"),
() => con().subject.entity.its("suit").matchesPropOf("pileTop")
test("Card must match with the one on the pile").either(
() => test().subject.entity.its("rank").matchesPropOf("pileTop"),
() => test().subject.entity.its("suit").matchesPropOf("pileTop"),
)
}
```
Expand All @@ -293,8 +293,8 @@ Remember to pass reference to `con` down to the other function.
```ts title="src/actions/selectCard.ts"
// ...
conditions: (con, { player }) => {
con().itsPlayersTurn()
con().remember("hand", {
test().itsPlayersTurn()
test().remember("hand", {
type: "hand",
owner: player,
})
Expand All @@ -311,7 +311,7 @@ Sometimes you need to grab a direct reference to an object and for example decid
You can use `grab<T>()`, which will return current subject as direct value reference. Provide `T` with the expected type to have nicer coding experience - your value will be typed as whatever you provide as `T`

```ts
const currentlySelected = con().get({ type: "hand" }).grab<ClassicCard>()
const currentlySelected = test().get({ type: "hand" }).grab<ClassicCard>()

if (isCardAttack(currentlySelected)) {
// Flow A, if selected card is of "attack" type
Expand All @@ -327,13 +327,13 @@ if (isCardAttack(currentlySelected)) {
In below example we use it to navigate condition checks flow. Assume `isCardAttack`, `isCardSkip` and `matchesWithPile` are defined elsewhere.

```ts
const state = con().grabState()
const state = test().grabState()

if (state.attackPoints > 0) {
con("Can only play 'attack' cards now").subject.entity.test(isCardAttack)
test("Can only play 'attack' cards now").subject.entity.test(isCardAttack)
matchesWithPile(con)
}
if (state.skipPoints > 0) {
con("Can only play 'skip turn' cards now").subject.entity.test(isCardSkip)
test("Can only play 'skip turn' cards now").subject.entity.test(isCardSkip)
}
```
2 changes: 1 addition & 1 deletion examples/client-svelte/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"license": "MIT",
"main": "index.html",
"scripts": {
"start": "sirv dist --http2 --key node_modules/@cardsgame/example-certs/localhost.key --cert node_modules/@cardsgame/example-certs/localhost.crt",
"start": "npm run build && sirv dist --http2 --key ../../node_modules/@cardsgame/example-certs/localhost.key --cert ../../node_modules/@cardsgame/example-certs/localhost.crt",
"build": "rollup --config rollup.config.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
Expand Down
34 changes: 31 additions & 3 deletions examples/client-svelte/src/components/Menu.svelte
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
<script lang="ts">
import { game } from "../gameHandler";
import { clientJoined, gameStarted, readyToStart } from "../stores";
import { game } from "../gameHandler";
import { clientJoined, gameStarted, readyToStart } from "../stores";
let anteStart = 0
let anteRatio = 0.5
const handleQuickJoin = () => {
game.quickJoin()
}
const handleStart = () => {
game.room.send("start")
game.room.send("start", {variantData: {anteRatio, anteStart}})
}
const handleAddBot = () => {
game.room.send("bot_add")
Expand All @@ -33,6 +36,31 @@ import { clientJoined, gameStarted, readyToStart } from "../stores";
Add Bot
</button>
</li>
<li>
Configure the game:
<label for="variant_anteStart">
Starting Ante:
<input
name="variant_anteStart"
type="range"
min="0"
max="10"
step="1"
bind:value={anteStart}
/>
</label>
<label for="variant_anteRatio">
Ante Ratio:
<input
name="variant_anteRatio"
type="range"
min="0.1"
max="3"
step="0.1"
bind:value={anteRatio}
/>
</label>
</li>
<li>
<button type="button"
class="StartButton"
Expand Down
4 changes: 2 additions & 2 deletions examples/client-svelte/src/gameHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
export class GameHandler {
game = new Game({
wss: {
port: 443,
port: 3033,
},
})
room: Room<WarState, WarMessageTypes>
Expand Down Expand Up @@ -142,7 +142,7 @@ export class GameHandler {
console.log(
"Card updated post-creation:",
change.field,
change.value
change.value,
)
players.update(($players) => {
const player = $players.get(ownerID)
Expand Down
25 changes: 25 additions & 0 deletions examples/client-vanilla/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,31 @@ <h1>@cardsgame/example</h1>
Wait fot other player to join, or
<button id="addBot_btn" type="button" disabled>Add Bot</button>
</li>
<li>
Configure the game:
<label for="variant_anteStart">
Starting Ante:
<input
name="variant_anteStart"
type="range"
min="0"
max="10"
step="1"
value="0"
/>
</label>
<label for="variant_anteRatio">
Ante Ratio:
<input
name="variant_anteRatio"
type="range"
min="0.1"
max="3"
step="0.1"
value="0.5"
/>
</label>
</li>
<li>
<button id="start_btn" type="button" disabled>Start game</button>
</li>
Expand Down
16 changes: 14 additions & 2 deletions examples/client-vanilla/main.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ const EL = {
gameView: document.querySelector(".gameView"),
start_btn: document.getElementById("start_btn"),
addBot_btn: document.getElementById("addBot_btn"),
variantFields: {
anteStart: document.getElementsByName("variant_anteStart")[0],
anteRatio: document.getElementsByName("variant_anteRatio")[0],
},
quickJoin_btn: document.getElementById("quickJoin_btn"),
player: {
container: document.getElementById("player"),
Expand Down Expand Up @@ -55,6 +59,9 @@ const UI = {
console.log("isGameStarted = true")
EL.addBot_btn.disabled = true
EL.start_btn.disabled = true

EL.variantFields.anteStart.disabled = true
EL.variantFields.anteRatio.disabled = true
},
deckCountUpdated: (isPlayer, cardsCount) => {
EL[isPlayer ? "player" : "opponent"].deckCount.innerHTML = cardsCount
Expand Down Expand Up @@ -122,7 +129,7 @@ class GameHandler {
this.joined = false
this.game = new Game({
wss: {
port: 443,
port: 3033,
},
})

Expand All @@ -141,7 +148,12 @@ class GameHandler {
const clientID = room.sessionID

EL.start_btn.addEventListener("click", () => {
room.send("start")
room.send("start", {
variantData: {
anteStart: Number(EL.variantFields.anteStart.value),
anteRatio: Number(EL.variantFields.anteRatio.value),
},
})
})

EL.addBot_btn.addEventListener("click", () => {
Expand Down
2 changes: 1 addition & 1 deletion examples/client-vanilla/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"license": "MIT",
"main": "index.html",
"scripts": {
"start": "sirv --dev --http2 --key node_modules/@cardsgame/example-certs/localhost.key --cert node_modules/@cardsgame/example-certs/localhost.crt",
"start": "npm run build && sirv --dev --http2 --key ../../node_modules/@cardsgame/example-certs/localhost.key --cert ../../node_modules/@cardsgame/example-certs/localhost.crt",
"build": "rollup -c",
"test": "echo \"Error: no test specified\" && exit 1"
},
Expand Down
Loading

0 comments on commit ba97781

Please sign in to comment.