@bendyline/gilde 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/data/project-types/ca/caregiving-binder/versions/1.0.1/about.md +8 -0
- package/data/project-types/ca/caregiving-binder/versions/1.0.1/care.json +4 -0
- package/data/project-types/ca/caregiving-binder/versions/1.0.1/manifest.json +177 -0
- package/data/project-types/ca/caregiving-binder/versions/1.0.1/meds.json +8 -0
- package/data/project-types/ca/caregiving-binder/versions/1.0.1/mission.md +8 -0
- package/data/project-types/ca/caregiving-binder/versions/1.0.1/pages/dashboard/index.html +167 -0
- package/data/project-types/ch/checkers/versions/1.1.2/about.md +9 -0
- package/data/project-types/ch/checkers/versions/1.1.2/game.json +77 -0
- package/data/project-types/ch/checkers/versions/1.1.2/manifest.json +166 -0
- package/data/project-types/ch/checkers/versions/1.1.2/mission.md +9 -0
- package/data/project-types/ch/checkers/versions/1.1.2/pages/board/index.html +1091 -0
- package/data/project-types/ch/chess/versions/1.0.2/about.md +9 -0
- package/data/project-types/ch/chess/versions/1.0.2/game.json +127 -0
- package/data/project-types/ch/chess/versions/1.0.2/manifest.json +180 -0
- package/data/project-types/ch/chess/versions/1.0.2/mission.md +8 -0
- package/data/project-types/ch/chess/versions/1.0.2/pages/board/index.html +627 -0
- package/data/project-types/ch/chess/versions/1.0.3/about.md +9 -0
- package/data/project-types/ch/chess/versions/1.0.3/game.json +127 -0
- package/data/project-types/ch/chess/versions/1.0.3/manifest.json +180 -0
- package/data/project-types/ch/chess/versions/1.0.3/mission.md +8 -0
- package/data/project-types/ch/chess/versions/1.0.3/pages/board/index.html +769 -0
- package/data/project-types/de/design-scheme/versions/1.0.1/about.md +6 -0
- package/data/project-types/de/design-scheme/versions/1.0.1/manifest.json +69 -0
- package/data/project-types/de/design-scheme/versions/1.0.1/mission.md +3 -0
- package/data/project-types/de/design-scheme/versions/1.0.1/pages/gallery/index.html +105 -0
- package/data/project-types/de/design-scheme/versions/1.0.1/schemes/index.json +9 -0
- package/data/project-types/de/design-scheme/versions/1.0.1/schemes/warm-dusk.json +4 -0
- package/data/project-types/ev/event-planner/versions/1.0.1/about.md +7 -0
- package/data/project-types/ev/event-planner/versions/1.0.1/budget.json +5 -0
- package/data/project-types/ev/event-planner/versions/1.0.1/guests.json +10 -0
- package/data/project-types/ev/event-planner/versions/1.0.1/manifest.json +170 -0
- package/data/project-types/ev/event-planner/versions/1.0.1/mission.md +6 -0
- package/data/project-types/ev/event-planner/versions/1.0.1/pages/dashboard/index.html +186 -0
- package/data/project-types/fi/fitness-coach/versions/1.0.1/about.md +7 -0
- package/data/project-types/fi/fitness-coach/versions/1.0.1/manifest.json +125 -0
- package/data/project-types/fi/fitness-coach/versions/1.0.1/mission.md +7 -0
- package/data/project-types/fi/fitness-coach/versions/1.0.1/pages/dashboard/index.html +172 -0
- package/data/project-types/fi/fitness-coach/versions/1.0.1/training.json +6 -0
- package/data/project-types/fl/flashcards/versions/1.0.1/about.md +8 -0
- package/data/project-types/fl/flashcards/versions/1.0.1/deck.json +7 -0
- package/data/project-types/fl/flashcards/versions/1.0.1/manifest.json +167 -0
- package/data/project-types/fl/flashcards/versions/1.0.1/mission.md +6 -0
- package/data/project-types/fl/flashcards/versions/1.0.1/pages/review/index.html +396 -0
- package/data/project-types/fr/freelance-office/versions/1.0.1/about.md +6 -0
- package/data/project-types/fr/freelance-office/versions/1.0.1/invoices.json +9 -0
- package/data/project-types/fr/freelance-office/versions/1.0.1/manifest.json +142 -0
- package/data/project-types/fr/freelance-office/versions/1.0.1/mission.md +6 -0
- package/data/project-types/fr/freelance-office/versions/1.0.1/pages/dashboard/index.html +157 -0
- package/data/project-types/fu/fundraiser-hq/versions/1.0.1/about.md +7 -0
- package/data/project-types/fu/fundraiser-hq/versions/1.0.1/donations.json +7 -0
- package/data/project-types/fu/fundraiser-hq/versions/1.0.1/manifest.json +126 -0
- package/data/project-types/fu/fundraiser-hq/versions/1.0.1/mission.md +6 -0
- package/data/project-types/fu/fundraiser-hq/versions/1.0.1/pages/dashboard/index.html +160 -0
- package/data/project-types/go/go/versions/1.0.1/about.md +9 -0
- package/data/project-types/go/go/versions/1.0.1/game.json +103 -0
- package/data/project-types/go/go/versions/1.0.1/manifest.json +166 -0
- package/data/project-types/go/go/versions/1.0.1/mission.md +8 -0
- package/data/project-types/go/go/versions/1.0.1/pages/board/index.html +778 -0
- package/data/project-types/ho/household-budget/versions/1.0.1/about.md +6 -0
- package/data/project-types/ho/household-budget/versions/1.0.1/ledger.json +5 -0
- package/data/project-types/ho/household-budget/versions/1.0.1/manifest.json +163 -0
- package/data/project-types/ho/household-budget/versions/1.0.1/mission.md +6 -0
- package/data/project-types/ho/household-budget/versions/1.0.1/pages/dashboard/index.html +233 -0
- package/data/project-types/ho/household-manual/versions/1.0.1/about.md +7 -0
- package/data/project-types/ho/household-manual/versions/1.0.1/manifest.json +152 -0
- package/data/project-types/ho/household-manual/versions/1.0.1/mission.md +8 -0
- package/data/project-types/ho/household-manual/versions/1.0.1/pages/dashboard/index.html +157 -0
- package/data/project-types/ho/household-manual/versions/1.0.1/systems.json +8 -0
- package/data/project-types/index.json +1 -1
- package/data/project-types/jo/job-hunt/versions/1.0.1/about.md +10 -0
- package/data/project-types/jo/job-hunt/versions/1.0.1/activity.json +4 -0
- package/data/project-types/jo/job-hunt/versions/1.0.1/manifest.json +212 -0
- package/data/project-types/jo/job-hunt/versions/1.0.1/mission.md +9 -0
- package/data/project-types/jo/job-hunt/versions/1.0.1/pages/dashboard/index.html +327 -0
- package/data/project-types/jo/job-hunt/versions/1.0.1/pipeline.json +6 -0
- package/data/project-types/la/language-trainer/versions/1.0.1/about.md +6 -0
- package/data/project-types/la/language-trainer/versions/1.0.1/manifest.json +79 -0
- package/data/project-types/la/language-trainer/versions/1.0.1/mission.md +4 -0
- package/data/project-types/la/language-trainer/versions/1.0.1/pages/dashboard/index.html +153 -0
- package/data/project-types/la/language-trainer/versions/1.0.1/progress.json +7 -0
- package/data/project-types/li/life-binder/versions/1.0.1/about.md +7 -0
- package/data/project-types/li/life-binder/versions/1.0.1/documents.json +145 -0
- package/data/project-types/li/life-binder/versions/1.0.1/manifest.json +137 -0
- package/data/project-types/li/life-binder/versions/1.0.1/mission.md +7 -0
- package/data/project-types/li/life-binder/versions/1.0.1/pages/dashboard/index.html +147 -0
- package/data/project-types/me/meal-planner/versions/1.0.1/about.md +6 -0
- package/data/project-types/me/meal-planner/versions/1.0.1/manifest.json +159 -0
- package/data/project-types/me/meal-planner/versions/1.0.1/menu.json +5 -0
- package/data/project-types/me/meal-planner/versions/1.0.1/mission.md +5 -0
- package/data/project-types/me/meal-planner/versions/1.0.1/pages/dashboard/index.html +152 -0
- package/data/project-types/me/meal-planner/versions/1.0.1/pantry.json +9 -0
- package/data/project-types/no/novel-writing-room/versions/1.0.1/about.md +7 -0
- package/data/project-types/no/novel-writing-room/versions/1.0.1/chapters.json +10 -0
- package/data/project-types/no/novel-writing-room/versions/1.0.1/manifest.json +157 -0
- package/data/project-types/no/novel-writing-room/versions/1.0.1/mission.md +7 -0
- package/data/project-types/no/novel-writing-room/versions/1.0.1/pages/dashboard/index.html +165 -0
- package/data/project-types/no/novel-writing-room/versions/1.0.1/sessions.json +6 -0
- package/data/project-types/tr/trip-planner/versions/1.0.1/about.md +7 -0
- package/data/project-types/tr/trip-planner/versions/1.0.1/journal.json +4 -0
- package/data/project-types/tr/trip-planner/versions/1.0.1/manifest.json +193 -0
- package/data/project-types/tr/trip-planner/versions/1.0.1/mission.md +4 -0
- package/data/project-types/tr/trip-planner/versions/1.0.1/packing.json +8 -0
- package/data/project-types/tr/trip-planner/versions/1.0.1/pages/dashboard/index.html +215 -0
- package/data/project-types/tr/trip-planner/versions/1.0.1/trip.json +7 -0
- package/package.json +4 -2
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"releasedAt": "2026-08-01T00:00:00Z",
|
|
5
|
+
"meesterManaged": false,
|
|
6
|
+
"mode": "solo",
|
|
7
|
+
"leadLabel": "Opponent",
|
|
8
|
+
"leanProfile": true,
|
|
9
|
+
"tabVisibility": {
|
|
10
|
+
"overview": false,
|
|
11
|
+
"tasks": false,
|
|
12
|
+
"approvals": false,
|
|
13
|
+
"workspace": false,
|
|
14
|
+
"artifacts": false,
|
|
15
|
+
"map": false
|
|
16
|
+
},
|
|
17
|
+
"params": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"properties": {
|
|
20
|
+
"personality": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"title": "Table-talk style",
|
|
23
|
+
"description": "How your opponent talks between moves.",
|
|
24
|
+
"enum": [
|
|
25
|
+
"peppy",
|
|
26
|
+
"cheeky",
|
|
27
|
+
"zen",
|
|
28
|
+
"gracious"
|
|
29
|
+
],
|
|
30
|
+
"default": "peppy"
|
|
31
|
+
},
|
|
32
|
+
"playStyle": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"title": "How should the Go-speler play?",
|
|
35
|
+
"description": "An opponent keeps their strategy to themselves; an instructor offers light teaching notes.",
|
|
36
|
+
"enum": [
|
|
37
|
+
"Opponent",
|
|
38
|
+
"Instructor"
|
|
39
|
+
],
|
|
40
|
+
"default": "Opponent"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"required": [
|
|
44
|
+
"personality",
|
|
45
|
+
"playStyle"
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
"nameTemplate": "Go vs the Go-speler",
|
|
49
|
+
"aboutTemplate": "about.md",
|
|
50
|
+
"missionTemplate": "mission.md",
|
|
51
|
+
"gezels": [
|
|
52
|
+
{
|
|
53
|
+
"templateId": "go-player",
|
|
54
|
+
"voorman": true
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
"scripts": {
|
|
58
|
+
"game-store": "import { type InferredInput, defineScript, gezel } from '@bendyline/gezel-sdk';\n\nexport const meta = defineScript({\n name: 'game-store',\n description:\n \"Read and update the Go game (game.json). Call action:'board' to see the position and every legal move; action:'ai_move' to play a coordinate such as D4 or 'pass' (optional moveThought); action:'state' for a light summary; action:'new_game' to reset with boardSize 9, 13, or 19 and optional komi. The engine enforces captures, suicide, simple ko, passing, and area scoring.\",\n inputs: {\n action: {\n type: 'string',\n description: \"One of 'state', 'board', 'user_move', 'ai_move', or 'new_game'.\",\n required: true,\n },\n move: {\n type: 'string',\n description: \"A board coordinate such as 'D4', or 'pass'.\",\n },\n boardSize: {\n type: 'number',\n description: 'For new_game: 9, 13, or 19. Defaults to 9.',\n },\n komi: {\n type: 'number',\n description: 'For new_game: points added to white. Defaults to 6.5.',\n },\n moveThought: {\n type: 'string',\n description: 'A short line of table talk to show on the board (max 140 chars).',\n },\n },\n outputs: {\n board: { type: 'string', description: 'ASCII board with coordinates.' },\n turn: { type: 'string', description: \"Whose move: 'user' (black) or 'ai' (white).\" },\n status: {\n type: 'string',\n description: 'playing, user_won, ai_won, or draw.',\n },\n lastMove: { type: 'string', description: \"The move just played, e.g. 'D4' or 'pass'.\" },\n legalMoves: { type: 'string', description: 'Legal placements plus pass.' },\n score: { type: 'string', description: 'Area score when the game has ended.' },\n summary: { type: 'string', description: 'Human-readable game summary.' },\n },\n requires: ['workspace.read', 'workspace.write'],\n} as const);\n\nconst input = gezel.input as InferredInput<typeof meta>;\n\n// ── go-rules-start ──\n// Pure, annotation-free Go engine. Supports 9x9, 13x13, and 19x19.\n// Squares are row-major, y0 at the top. X = black/user; O = white/AI.\n// Coordinates follow Go convention and skip the letter I.\n\nvar GO_FILES = 'ABCDEFGHJKLMNOPQRST';\n\nfunction buildInitial(size) {\n return '.'.repeat(size * size);\n}\n\nfunction inBounds(x, y, size) {\n return x >= 0 && x < size && y >= 0 && y < size;\n}\n\nfunction pointName(x, y, size) {\n return GO_FILES[x] + String(size - y);\n}\n\nfunction parsePoint(name, size) {\n if (typeof name !== 'string') return null;\n var text = name.trim().toUpperCase();\n var match = /^([A-HJ-T])(\\d{1,2})$/.exec(text);\n if (!match) return null;\n var x = GO_FILES.indexOf(match[1]);\n var rank = Number(match[2]);\n if (x < 0 || x >= size || rank < 1 || rank > size) return null;\n return { x: x, y: size - rank, index: (size - rank) * size + x };\n}\n\nfunction neighbors(index, size) {\n var x = index % size;\n var y = Math.floor(index / size);\n var out = [];\n if (x > 0) out.push(index - 1);\n if (x + 1 < size) out.push(index + 1);\n if (y > 0) out.push(index - size);\n if (y + 1 < size) out.push(index + size);\n return out;\n}\n\nfunction groupAt(squares, index, size) {\n var color = squares[index];\n var stones = [];\n var liberties = {};\n var seen = {};\n var queue = [index];\n seen[index] = true;\n while (queue.length) {\n var current = queue.pop();\n stones.push(current);\n for (var next of neighbors(current, size)) {\n if (squares[next] === '.') liberties[next] = true;\n else if (squares[next] === color && !seen[next]) {\n seen[next] = true;\n queue.push(next);\n }\n }\n }\n return { stones: stones, liberties: Object.keys(liberties).map(Number) };\n}\n\nfunction replaceAt(squares, index, ch) {\n return squares.slice(0, index) + ch + squares.slice(index + 1);\n}\n\nfunction applyPlacement(squares, size, side, name, previousPosition) {\n var point = parsePoint(name, size);\n if (!point) return { error: 'bad-coordinate' };\n if (squares[point.index] !== '.') return { error: 'occupied' };\n var stone = side === 'user' ? 'X' : 'O';\n var enemy = side === 'user' ? 'O' : 'X';\n var next = replaceAt(squares, point.index, stone);\n var captures = [];\n var checked = {};\n for (var adjacent of neighbors(point.index, size)) {\n if (next[adjacent] !== enemy || checked[adjacent]) continue;\n var enemyGroup = groupAt(next, adjacent, size);\n for (var member of enemyGroup.stones) checked[member] = true;\n if (enemyGroup.liberties.length === 0) {\n for (var captured of enemyGroup.stones) {\n next = replaceAt(next, captured, '.');\n captures.push(pointName(captured % size, Math.floor(captured / size), size));\n }\n }\n }\n if (groupAt(next, point.index, size).liberties.length === 0) return { error: 'suicide' };\n if (previousPosition && next === previousPosition) return { error: 'ko' };\n return { squares: next, captures: captures };\n}\n\nfunction legalMovesFor(squares, size, side, previousPosition) {\n var moves = [];\n for (var y = 0; y < size; y += 1) {\n for (var x = 0; x < size; x += 1) {\n if (squares[y * size + x] !== '.') continue;\n var name = pointName(x, y, size);\n var result = applyPlacement(squares, size, side, name, previousPosition);\n if (!result.error) moves.push(name);\n }\n }\n moves.push('pass');\n return moves;\n}\n\nfunction scoreArea(squares, size, komi) {\n var blackStones = 0;\n var whiteStones = 0;\n var blackTerritory = 0;\n var whiteTerritory = 0;\n var seen = {};\n for (var i = 0; i < squares.length; i += 1) {\n if (squares[i] === 'X') {\n blackStones += 1;\n continue;\n }\n if (squares[i] === 'O') {\n whiteStones += 1;\n continue;\n }\n if (seen[i]) continue;\n var region = [];\n var borders = {};\n var queue = [i];\n seen[i] = true;\n while (queue.length) {\n var current = queue.pop();\n region.push(current);\n for (var next of neighbors(current, size)) {\n if (squares[next] === '.' && !seen[next]) {\n seen[next] = true;\n queue.push(next);\n } else if (squares[next] === 'X' || squares[next] === 'O') {\n borders[squares[next]] = true;\n }\n }\n }\n if (borders.X && !borders.O) blackTerritory += region.length;\n if (borders.O && !borders.X) whiteTerritory += region.length;\n }\n var black = blackStones + blackTerritory;\n var white = whiteStones + whiteTerritory + komi;\n return {\n black: black,\n white: white,\n blackStones: blackStones,\n whiteStones: whiteStones,\n blackTerritory: blackTerritory,\n whiteTerritory: whiteTerritory,\n winner: black === white ? 'draw' : black > white ? 'user' : 'ai',\n margin: Math.abs(black - white),\n };\n}\n\nfunction statusAfter(squares, size, komi, consecutivePasses) {\n if (consecutivePasses < 2) return 'playing';\n var score = scoreArea(squares, size, komi);\n return score.winner === 'draw' ? 'draw' : score.winner === 'user' ? 'user_won' : 'ai_won';\n}\n\nfunction renderAscii(squares, size) {\n var labels = [];\n for (var x = 0; x < size; x += 1) labels.push(GO_FILES[x]);\n var pad = size >= 10 ? ' ' : ' ';\n var lines = [pad + labels.join(' ')];\n for (var y = 0; y < size; y += 1) {\n var rank = String(size - y).padStart(size >= 10 ? 2 : 1, ' ');\n var row = [];\n for (var x = 0; x < size; x += 1) row.push(squares[y * size + x]);\n lines.push(rank + ' ' + row.join(' ') + ' ' + rank);\n }\n lines.push(pad + labels.join(' '));\n lines.push('X = black (user). O = white (you).');\n return lines.join('\\n');\n}\n// ── go-rules-end ──\n\nconst STATE_FILE = 'game.json';\nconst SIDE_NAMES = { user: 'black', ai: 'white' };\n\ninterface GameState {\n version: number;\n boardSize: number;\n squares: string;\n turn: 'user' | 'ai';\n status: 'playing' | 'user_won' | 'ai_won' | 'draw';\n komi: number;\n consecutivePasses: number;\n previousPosition: string | null;\n legalMoves: string[];\n captures: { user: number; ai: number };\n score: ReturnType<typeof scoreArea> | null;\n moveLog: Array<{\n by: 'user' | 'ai';\n move: string;\n captures: string[];\n at: string;\n moveThought?: string;\n }>;\n lastMove: { by: 'user' | 'ai'; move: string; captures: string[] } | null;\n lastMoveThought: string;\n updatedAt: string;\n}\n\nfunction validSize(value: unknown): number {\n const size = Number(value);\n return size === 13 || size === 19 ? size : 9;\n}\n\nfunction validKomi(value: unknown): number {\n const komi = Number(value);\n return Number.isFinite(komi) && komi >= 0 && komi <= 20 ? komi : 6.5;\n}\n\nfunction freshState(boardSize = 9, komi = 6.5): GameState {\n const size = validSize(boardSize);\n const nextKomi = validKomi(komi);\n const squares = buildInitial(size);\n return {\n version: 1,\n boardSize: size,\n squares,\n turn: 'user',\n status: 'playing',\n komi: nextKomi,\n consecutivePasses: 0,\n previousPosition: null,\n legalMoves: legalMovesFor(squares, size, 'user', null),\n captures: { user: 0, ai: 0 },\n score: null,\n moveLog: [],\n lastMove: null,\n lastMoveThought: '',\n updatedAt: new Date().toISOString(),\n };\n}\n\nasync function load(): Promise<GameState> {\n let raw: string;\n try {\n raw = await gezel.fs.read(STATE_FILE);\n } catch {\n return freshState();\n }\n const parsed = JSON.parse(raw) as Partial<GameState>;\n const size = validSize(parsed.boardSize);\n if (typeof parsed.squares !== 'string' || parsed.squares.length !== size * size) {\n throw new Error(`${STATE_FILE} is corrupt; call action:'new_game' to reset.`);\n }\n return {\n ...freshState(size, parsed.komi),\n ...parsed,\n boardSize: size,\n komi: validKomi(parsed.komi),\n } as GameState;\n}\n\nasync function persist(state: GameState): Promise<GameState> {\n state.legalMoves =\n state.status === 'playing'\n ? legalMovesFor(\n state.squares,\n state.boardSize,\n state.turn,\n state.previousPosition,\n )\n : [];\n state.updatedAt = new Date().toISOString();\n await gezel.fs.write(STATE_FILE, `${JSON.stringify(state, null, 2)}\\n`);\n return state;\n}\n\nfunction renderLegal(state: GameState): string {\n if (state.status !== 'playing') return '(game over)';\n return state.legalMoves.join(', ');\n}\n\nfunction renderScore(state: GameState): string {\n if (!state.score) return '(not scored — play continues)';\n const result =\n state.score.winner === 'draw'\n ? 'draw'\n : `${SIDE_NAMES[state.score.winner]} wins by ${state.score.margin}`;\n return `Black ${state.score.black}; white ${state.score.white} (includes ${state.komi} komi): ${result}.`;\n}\n\nfunction summarize(state: GameState): string {\n if (state.status !== 'playing') return `Game over. ${renderScore(state)}`;\n return `Move ${state.moveLog.length + 1}: ${SIDE_NAMES[state.turn]} (${state.turn}) to move on ${state.boardSize}x${state.boardSize}. Captures — black ${state.captures.user}, white ${state.captures.ai}.`;\n}\n\nfunction applyTurn(\n state: GameState,\n by: 'user' | 'ai',\n moveThought?: string,\n): GameState {\n if (state.status !== 'playing') {\n throw new Error(`The game is over (${state.status}). Call action:'new_game' to start another.`);\n }\n if (state.turn !== by) {\n throw new Error(by === 'ai' ? \"It's not your turn yet.\" : 'The opponent is still to move.');\n }\n const move = input.move?.trim() || '';\n const normalized = move.toLowerCase() === 'pass' ? 'pass' : move.toUpperCase();\n if (!state.legalMoves.includes(normalized)) {\n throw new Error(\n `Illegal move '${move}'. Legal moves for ${SIDE_NAMES[by]}: ${renderLegal(state)}`,\n );\n }\n\n let captured: string[] = [];\n const before = state.squares;\n if (normalized === 'pass') {\n state.consecutivePasses += 1;\n } else {\n const applied = applyPlacement(\n state.squares,\n state.boardSize,\n by,\n normalized,\n state.previousPosition,\n );\n if (applied.error || !applied.squares) {\n throw new Error(`Illegal move '${move}' (${applied.error || 'unknown'}).`);\n }\n state.squares = applied.squares;\n captured = applied.captures || [];\n state.captures[by] += captured.length;\n state.consecutivePasses = 0;\n }\n state.previousPosition = before;\n state.turn = (by === 'user' ? 'ai' : 'user') as 'user' | 'ai';\n state.status = statusAfter(\n state.squares,\n state.boardSize,\n state.komi,\n state.consecutivePasses,\n ) as GameState['status'];\n if (state.status !== 'playing') {\n state.score = scoreArea(state.squares, state.boardSize, state.komi);\n }\n const entry = {\n by,\n move: normalized,\n captures: captured,\n at: new Date().toISOString(),\n ...(moveThought ? { moveThought } : {}),\n };\n state.moveLog.push(entry);\n state.lastMove = { by, move: normalized, captures: captured };\n if (by === 'ai' && moveThought) state.lastMoveThought = moveThought;\n return state;\n}\n\nlet state = await load();\nlet lastMoveText = state.lastMove?.move ?? '(none)';\n\nif (input.action === 'new_game') {\n state = await persist(freshState(input.boardSize, input.komi));\n lastMoveText = '(none)';\n} else if (input.action === 'user_move' || input.action === 'ai_move') {\n const by = input.action === 'user_move' ? 'user' : 'ai';\n const moveThought = input.moveThought?.trim().slice(0, 140);\n state = await persist(applyTurn(state, by, moveThought));\n lastMoveText = state.lastMove?.move ?? '(none)';\n} else if (input.action !== 'state' && input.action !== 'board') {\n throw new Error(`Unknown action '${input.action}'. Use state, board, user_move, ai_move, or new_game.`);\n}\n\ngezel.output({\n board:\n `${renderAscii(state.squares, state.boardSize)}\\nTurn: ${SIDE_NAMES[state.turn]} (${state.turn}).\\n` +\n `Komi: ${state.komi}. Consecutive passes: ${state.consecutivePasses}.\\n` +\n `Legal moves: ${renderLegal(state)}`,\n turn: state.turn,\n status: state.status,\n lastMove: lastMoveText,\n legalMoves: renderLegal(state),\n score: renderScore(state),\n summary: summarize(state),\n});\n"
|
|
59
|
+
},
|
|
60
|
+
"tools": [
|
|
61
|
+
{
|
|
62
|
+
"name": "get_board",
|
|
63
|
+
"description": "See the authoritative Go position, board size, captures, whose turn it is, and every legal move.",
|
|
64
|
+
"script": "game-store",
|
|
65
|
+
"inputs": {
|
|
66
|
+
"type": "object",
|
|
67
|
+
"properties": {}
|
|
68
|
+
},
|
|
69
|
+
"bind": {
|
|
70
|
+
"action": "board"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"name": "make_move",
|
|
75
|
+
"description": "Play your white move. Pass a coordinate such as 'D4', or 'pass', plus optional moveThought.",
|
|
76
|
+
"script": "game-store",
|
|
77
|
+
"inputs": {
|
|
78
|
+
"type": "object",
|
|
79
|
+
"properties": {
|
|
80
|
+
"move": {
|
|
81
|
+
"type": "string",
|
|
82
|
+
"description": "A legal coordinate such as 'D4', or 'pass'."
|
|
83
|
+
},
|
|
84
|
+
"moveThought": {
|
|
85
|
+
"type": "string",
|
|
86
|
+
"description": "A short line of table talk (max 140 chars)."
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"required": [
|
|
90
|
+
"move"
|
|
91
|
+
]
|
|
92
|
+
},
|
|
93
|
+
"bind": {
|
|
94
|
+
"action": "ai_move"
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"name": "user_move",
|
|
99
|
+
"description": "The user's black move or pass, played from the Go board.",
|
|
100
|
+
"script": "game-store",
|
|
101
|
+
"inputs": {
|
|
102
|
+
"type": "object",
|
|
103
|
+
"properties": {
|
|
104
|
+
"move": {
|
|
105
|
+
"type": "string"
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
"required": [
|
|
109
|
+
"move"
|
|
110
|
+
]
|
|
111
|
+
},
|
|
112
|
+
"bind": {
|
|
113
|
+
"action": "user_move"
|
|
114
|
+
},
|
|
115
|
+
"reaction": {
|
|
116
|
+
"gezel": "go-player",
|
|
117
|
+
"hideSeed": true,
|
|
118
|
+
"prompt": "Your opponent played {{output.lastMove}}. Status: {{output.status}}. Board now:\n{{output.board}}\nThe board and Legal moves line come directly from the Go engine and are always correct. Do not re-derive captures, liberties, ko, scoring, or legality, and do not invent a coordinate outside the legal list. Spend your thought on choosing among the listed moves.\nStyle: {{playStyle}}. In Opponent style, do not volunteer advice or reveal the user’s available moves, weak groups, threats, or your plan; comment only about your own move or the atmosphere. In Instructor style, offer at most one brief teaching observation without choosing the user’s move. Answer strategy questions when asked.\nAct on this latest position. If the game is over, do not move — give one gracious line about the result and offer a new game. Otherwise choose a strong legal move (or pass when strategically sound) and play it with make_move, including a short {{personality}} moveThought. If make_move rejects it, choose one move verbatim from the legal list in the error and retry. After moving, reply with ONE short line of table talk."
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"name": "new_game",
|
|
123
|
+
"description": "Reset the Go board. Choose 9, 13, or 19 lines and optional komi; defaults to 9×9 with 6.5 komi.",
|
|
124
|
+
"script": "game-store",
|
|
125
|
+
"inputs": {
|
|
126
|
+
"type": "object",
|
|
127
|
+
"properties": {
|
|
128
|
+
"boardSize": {
|
|
129
|
+
"type": "number",
|
|
130
|
+
"enum": [
|
|
131
|
+
9,
|
|
132
|
+
13,
|
|
133
|
+
19
|
|
134
|
+
]
|
|
135
|
+
},
|
|
136
|
+
"komi": {
|
|
137
|
+
"type": "number",
|
|
138
|
+
"minimum": 0,
|
|
139
|
+
"maximum": 20
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
"bind": {
|
|
144
|
+
"action": "new_game"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
],
|
|
148
|
+
"craftbooks": [],
|
|
149
|
+
"schedules": [],
|
|
150
|
+
"pages": {
|
|
151
|
+
"entry": "board/index.html",
|
|
152
|
+
"reads": [
|
|
153
|
+
{
|
|
154
|
+
"source": "workspace",
|
|
155
|
+
"path": "game.json"
|
|
156
|
+
}
|
|
157
|
+
],
|
|
158
|
+
"tools": [
|
|
159
|
+
"user_move",
|
|
160
|
+
"new_game"
|
|
161
|
+
]
|
|
162
|
+
},
|
|
163
|
+
"workspaceSeed": [
|
|
164
|
+
"game.json"
|
|
165
|
+
]
|
|
166
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
- Keep a Go board ready with black moving first, and make 9×9, 13×13, and
|
|
2
|
+
19×19 rematches easy to start.
|
|
3
|
+
- The Go-speler answers every user placement or pass with a legal white turn.
|
|
4
|
+
- Let the engine settle captures, suicide, simple ko, passing, komi, and
|
|
5
|
+
Chinese-style area scoring.
|
|
6
|
+
- Keep table talk {{personality}} and kind, matching the chosen {{playStyle}}:
|
|
7
|
+
Opponents do not leak reading or hints; Instructors teach lightly.
|
|
8
|
+
- Treat both passes and the final score calmly, then offer another game.
|