Skip to content

Commit 2e151b7

Browse files
authored
Merge pull request #1 from faluciano/chore/ci-and-automation
chore: add CI, APK release, couch-kit auto-update workflow, and MIT license
2 parents 347d13a + 2c20eb6 commit 2e151b7

33 files changed

Lines changed: 1613 additions & 679 deletions

.github/copilot-instructions.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Card Game Engine — Copilot Instructions
2+
3+
A customizable card game engine driven by declarative JSON rulesets, with multi-device gameplay over local WiFi.
4+
5+
## Project Structure
6+
7+
This is a Bun monorepo with four packages:
8+
9+
| Package | Purpose |
10+
| ----------------- | ------------------------------------------------------------------------------- |
11+
| `packages/schema` | Zod validation schemas and shared types for card game rulesets |
12+
| `packages/shared` | Pure TypeScript game engine — expression evaluator, interpreter, builtins, PRNG |
13+
| `packages/host` | Expo React Native TV app — CouchKit host + expo-file-system storage |
14+
| `packages/client` | Vite + React web app — phone controller UI via CouchKit client |
15+
16+
## Key Commands
17+
18+
```bash
19+
bun run dev:client # Start Vite dev server with HMR
20+
bun run build:client # TypeScript check + Vite production build
21+
bun run bundle:client # Bundle client dist into host Android assets
22+
bun run build:android # Bundle + Expo Android build
23+
bun run typecheck # Type-check shared and client packages
24+
bun run validate # Validate all rulesets against schema
25+
bun run catalog # Generate catalog.json from rulesets
26+
```
27+
28+
## Testing
29+
30+
Tests use Vitest and live in shared, schema, and host packages:
31+
32+
```bash
33+
cd packages/shared && bunx vitest run # Engine core tests
34+
cd packages/schema && bunx vitest run # Schema validation tests
35+
cd packages/host && bunx vitest run # Host storage/importer tests
36+
```
37+
38+
## Updating @couch-kit Dependencies
39+
40+
This project depends on @couch-kit/\* packages from npm:
41+
42+
- @couch-kit/core in packages/shared and packages/host and packages/client
43+
- @couch-kit/client in packages/client
44+
- @couch-kit/host in packages/host
45+
- @couch-kit/cli in packages/host (dev dependency)
46+
47+
When updating @couch-kit packages:
48+
49+
1. Update versions in the relevant packages/\*/package.json files
50+
2. Run `bun install` to update bun.lock
51+
3. Run `bun run typecheck` to verify compatibility
52+
4. Run `bun run build:client` to verify the client build

.github/workflows/ci.yml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
jobs:
9+
typecheck:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
- uses: oven-sh/setup-bun@v2
14+
with:
15+
bun-version: "1.2.19"
16+
- run: bun install --frozen-lockfile
17+
- run: bun run typecheck
18+
19+
build:
20+
runs-on: ubuntu-latest
21+
needs: typecheck
22+
steps:
23+
- uses: actions/checkout@v4
24+
- uses: oven-sh/setup-bun@v2
25+
with:
26+
bun-version: "1.2.19"
27+
- run: bun install --frozen-lockfile
28+
- run: bun run build:client
29+
30+
test:
31+
runs-on: ubuntu-latest
32+
steps:
33+
- uses: actions/checkout@v4
34+
- uses: oven-sh/setup-bun@v2
35+
with:
36+
bun-version: "1.2.19"
37+
- run: bun install --frozen-lockfile
38+
- name: Test shared
39+
run: bunx vitest run
40+
working-directory: packages/shared
41+
- name: Test schema
42+
run: bunx vitest run
43+
working-directory: packages/schema
44+
- name: Test host
45+
run: bunx vitest run
46+
working-directory: packages/host
47+
48+
validate:
49+
runs-on: ubuntu-latest
50+
steps:
51+
- uses: actions/checkout@v4
52+
- uses: oven-sh/setup-bun@v2
53+
with:
54+
bun-version: "1.2.19"
55+
- run: bun install --frozen-lockfile
56+
- run: bun run validate

.github/workflows/release-apk.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: Build APK
2+
3+
on:
4+
workflow_dispatch: {}
5+
release:
6+
types: [published]
7+
8+
jobs:
9+
build-apk:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
14+
- uses: oven-sh/setup-bun@v2
15+
with:
16+
bun-version: "1.2.19"
17+
18+
- uses: actions/setup-java@v4
19+
with:
20+
distribution: "temurin"
21+
java-version: "17"
22+
23+
- uses: android-actions/setup-android@v3
24+
25+
- run: bun install
26+
27+
- name: Build client
28+
run: bun run build:client
29+
30+
- name: Bundle client for host
31+
run: bun run bundle:client
32+
33+
- name: Generate native project
34+
run: bunx expo prebuild --clean --platform android
35+
working-directory: packages/host
36+
37+
- name: Fix Gradle JDK path
38+
run: |
39+
echo "org.gradle.java.home=$JAVA_HOME" >> packages/host/android/gradle.properties
40+
41+
- name: Build APK
42+
run: ./gradlew assembleRelease
43+
working-directory: packages/host/android
44+
45+
- name: Upload APK artifact
46+
uses: actions/upload-artifact@v4
47+
with:
48+
name: card-game-engine-release
49+
path: packages/host/android/app/build/outputs/apk/release/*.apk
50+
51+
- name: Upload APK to release
52+
if: github.event_name == 'release'
53+
env:
54+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
55+
run: |
56+
APK=$(find packages/host/android/app/build/outputs/apk/release -name "*.apk" | head -1)
57+
gh release upload "${{ github.event.release.tag_name }}" "$APK" --clobber
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Consumer repo workflow: .github/workflows/update-couch-kit.yml
2+
# This workflow is triggered by the couch-kit library when new versions are published.
3+
# It automatically updates @couch-kit/* dependencies and creates a PR.
4+
5+
name: Update @couch-kit
6+
7+
on:
8+
repository_dispatch:
9+
types: [couch-kit-update]
10+
11+
permissions:
12+
contents: write
13+
pull-requests: write
14+
15+
jobs:
16+
update:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- uses: oven-sh/setup-bun@v2
22+
with:
23+
bun-version: "1.2.19"
24+
25+
- name: Install dependencies
26+
run: bun install
27+
28+
- name: Update @couch-kit packages
29+
run: |
30+
PACKAGES='${{ toJson(github.event.client_payload.packages) }}'
31+
32+
echo "$PACKAGES" | jq -c '.[]' | while read -r pkg; do
33+
NAME=$(echo "$pkg" | jq -r '.name')
34+
VERSION=$(echo "$pkg" | jq -r '.version')
35+
36+
echo "Updating $NAME to ^$VERSION"
37+
38+
# Update in all sub-packages that have this dependency
39+
for PKG_JSON in packages/*/package.json; do
40+
if jq -e "(.dependencies[\"$NAME\"] // .devDependencies[\"$NAME\"] // .peerDependencies[\"$NAME\"])" "$PKG_JSON" > /dev/null 2>&1; then
41+
echo " → $PKG_JSON"
42+
TMP=$(mktemp)
43+
jq "
44+
if .dependencies[\"$NAME\"] then .dependencies[\"$NAME\"] = \"^$VERSION\" else . end |
45+
if .devDependencies[\"$NAME\"] then .devDependencies[\"$NAME\"] = \"^$VERSION\" else . end |
46+
if .peerDependencies[\"$NAME\"] then .peerDependencies[\"$NAME\"] = \"^$VERSION\" else . end
47+
" "$PKG_JSON" > "$TMP" && mv "$TMP" "$PKG_JSON"
48+
fi
49+
done
50+
done
51+
52+
bun install
53+
54+
- name: Verify
55+
run: |
56+
bun run typecheck
57+
bun run build:client
58+
59+
- name: Generate PR body
60+
run: |
61+
CHANGELOG='${{ toJson(github.event.client_payload.changelog) }}'
62+
HAS_BREAKING=$(echo "$CHANGELOG" | jq -r '.hasBreaking')
63+
64+
{
65+
echo "## 📦 @couch-kit Update"
66+
echo ""
67+
68+
echo "$CHANGELOG" | jq -r '.packages[] | "### \(.name) → \(.version) (\(.bump))\n\n\(.fullChangelog)\n"'
69+
70+
if [ "$HAS_BREAKING" = "true" ]; then
71+
echo "## ⚠️ Breaking Changes"
72+
echo ""
73+
echo "$CHANGELOG" | jq -r '.packages[] | select(.breaking | length > 0) | .breaking[] | "- \(.)"'
74+
echo ""
75+
fi
76+
77+
MIGRATIONS=$(echo "$CHANGELOG" | jq -r '[.packages[] | select(.migration != null)] | length')
78+
if [ "$MIGRATIONS" -gt "0" ]; then
79+
echo "## 🔄 Migration Guide"
80+
echo ""
81+
echo "$CHANGELOG" | jq -r '.packages[] | select(.migration != null) | "### \(.name)\n\n\(.migration)\n"'
82+
fi
83+
} > /tmp/pr-body.md
84+
85+
- name: Create Pull Request
86+
uses: peter-evans/create-pull-request@v7
87+
id: create-pr
88+
with:
89+
title: "chore(deps): update @couch-kit packages"
90+
body-path: /tmp/pr-body.md
91+
branch: chore/update-couch-kit
92+
labels: couch-kit-update
93+
commit-message: "chore(deps): update @couch-kit packages"
94+
delete-branch: true
95+
96+
- name: Auto-merge non-breaking updates
97+
if: steps.create-pr.outputs.pull-request-number && !fromJson(toJson(github.event.client_payload.changelog)).hasBreaking
98+
env:
99+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
100+
run: |
101+
gh pr merge ${{ steps.create-pr.outputs.pull-request-number }} --auto --squash

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Felix Luciano
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ The TV runs the authoritative game engine: it loads the ruleset, advances the FS
3030
- **41 query builtins + 23 effect builtins** — covering common card game mechanics (draw, discard, shuffle, score, card matching, pattern matching, turn order, trick-taking, string variables, etc.)
3131
- **Phase-based FSM** — supports automatic, player_action, and simultaneous phase types
3232
- **Turn order mechanics** — clockwise/counterclockwise direction, reverse, skip, and set-next-player effects
33-
- **Seeded PRNG** — mulberry32 enables deterministic replay from an action log
34-
- **Hidden information** — per-player state filtering via `createPlayerView`
33+
- **Seeded PRNG** — mulberry32 with `crypto.getRandomValues` seed hardening enables deterministic replay from an action log
34+
- **Hidden information** — per-player state filtering via `createPlayerView` with `publicVariables` support
35+
- **Security hardening** — internal actions (`advance_phase`, `reset_round`) blocked from client submissions, action log capped at 500 entries
3536
- **Zod schema validation** — rulesets are validated against a strict schema at load time
3637
- **2 deck presets + custom decks**`standard52`, `standard54`, plus fully custom card lists
3738

@@ -93,7 +94,7 @@ cd packages/shared
9394
bunx vitest run
9495
```
9596

96-
853 tests across the shared (744), schema (15), and host (94) packages cover the engine core (expression evaluator, builtins, interpreter, PRNG, schema validation, player views, game phases, integration scenarios), schema meta fields, and the host package (storage, importers). The client package is verified via `tsc` type-checking and Vite production build.
97+
883 tests across the shared (770), schema (19), and host (94) packages cover the engine core (expression evaluator, builtins, interpreter, PRNG, schema validation, player views, game phases, integration scenarios), schema meta fields, and the host package (storage, importers). The client package is verified via `tsc` type-checking and Vite production build.
9798

9899
### Build and Deploy
99100

@@ -143,15 +144,15 @@ See the [Ruleset Authoring Guide](docs/ruleset-authoring.md) for the full format
143144

144145
## Project Status
145146

146-
All four implementation phases are **complete** with **853 passing tests** across shared (744), schema (15), and host (94) packages.
147+
All four implementation phases are **complete** with **883 passing tests** across shared (770), schema (19), and host (94) packages.
147148

148149
| Phase | Status | Tests |
149150
|-------|--------|-------|
150-
| Phase 1 — Engine Core | ✅ Complete | 744 |
151+
| Phase 1 — Engine Core | ✅ Complete | 770 |
151152
| Phase 1.5 — Documentation | ✅ Complete ||
152153
| Phase 2 — Storage & Import | ✅ Complete | 94 |
153154
| Phase 3 — Host Screens & CouchKit Integration | ✅ Complete ||
154-
| Phase 3.4 — Schema Package & Catalog | ✅ Complete | 15 |
155+
| Phase 3.4 — Schema Package & Catalog | ✅ Complete | 19 |
155156
| Phase 4 — Client Controller App | ✅ Complete ||
156157

157158
The app builds and deploys to Android TV via `bun run build:android`. The host runs an HTTP+WebSocket server via CouchKit; phones connect by scanning a QR code displayed on the TV.

docs/ruleset-authoring.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ sections:
7979
"zones": ["...array of zone definitions"],
8080
"initialVariables": { "...optional name-to-number mappings" },
8181
"initialStringVariables": { "...optional name-to-string mappings" },
82+
"publicVariables": ["...optional list of variable names exposed to clients"],
8283
"phases": ["...array of phase definitions (the FSM)"],
8384
"scoring": {
8485
"method": "expression",
@@ -538,7 +539,7 @@ sandboxed evaluator.
538539

539540
| Category | Operators |
540541
|---|---|
541-
| Arithmetic | `+`, `-`, `*`, `/` |
542+
| Arithmetic | `+`, `-`, `*`, `/`, `%` |
542543
| Comparison | `==`, `!=`, `<`, `>`, `<=`, `>=` |
543544
| Logic | `&&`, `\|\|`, `!` |
544545
| Unary | `-` (negation), `!` (logical not) |
@@ -816,11 +817,23 @@ Variables can appear in scoring expressions:
816817
}
817818
```
818819

820+
### Public Variables
821+
822+
By default, all variables and string variables are exposed to clients in the `PlayerView`. To restrict which variables are visible, add a `publicVariables` array to the ruleset:
823+
824+
```json
825+
{
826+
"publicVariables": ["score", "round"]
827+
}
828+
```
829+
830+
When specified, only the named variables (both numeric and string) are included in the client-facing `PlayerView`. Variables not listed are hidden from all players. If omitted, all variables are exposed (backward compatible).
831+
819832
### Variables in Player Views
820833

821-
All variables are included in `PlayerView` — they are global game state visible
834+
All variables are included in `PlayerView` by default — they are global game state visible
822835
to all players. The client can display them (e.g., showing the running total on
823-
the TV screen).
836+
the TV screen). Use `publicVariables` (see above) to restrict which variables are sent to clients.
824837

825838
### Reset Behavior
826839

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
{
22
"name": "card-game-engine",
33
"private": true,
4+
"license": "MIT",
45
"packageManager": "bun@1.2.19",
5-
"workspaces": ["packages/*"],
6+
"workspaces": [
7+
"packages/*"
8+
],
69
"scripts": {
710
"dev:client": "cd packages/client && bun run dev",
811
"build:client": "cd packages/client && bun run build",
912
"bundle:client": "cd packages/host && bunx couch-kit bundle --source ../client --output ./android/app/src/main/assets/www --manifest ./src/www-manifest.json",
1013
"build:android": "bun run bundle:client && cd packages/host && npx expo run:android",
11-
"typecheck": "cd packages/shared && bunx tsc && cd ../client && bunx tsc --noEmit",
14+
"typecheck": "cd packages/schema && bunx tsc && cd ../shared && bunx tsc && cd ../client && bunx tsc --noEmit",
1215
"build": "bun run typecheck && bun run build:client && bun run bundle:client",
1316
"validate": "bun run scripts/validate-rulesets.ts",
1417
"catalog": "bun run scripts/build-catalog.ts"

0 commit comments

Comments
 (0)