with one click
new-game
Scaffold all files needed for a new card game in delcard_games
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
Scaffold all files needed for a new card game in delcard_games
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
| name | new-game |
| description | Scaffold all files needed for a new card game in delcard_games |
| disable-model-invocation | true |
Scaffold a new card game named $ARGUMENTS.
src/lib/games/<name>.tsGame logic. Use war.ts as the simplest reference.
Required GameDefinition<State> fields:
id โ string matching <name>name โ display namedeckType โ one of 'FrenchDeckWithJoker' | 'FrenchDeckWithoutJoker' | 'ColorDeck'minPlayers / maxPlayerssetup(players, options?) โ returns initial state, must include { players, zones, turnPlayerId, phase, activeGameId: '<name>' }getValidActions(state, playerId) โ returns Action[], never null/undefinedapplyAction(state, action) โ returns NEW state, never mutatesisOver(state) โ returns true iff phase === 'gameover' (or equivalent)getWinner(state) โ returns player ID or null; must return null if !isOver(state)onPlayerDisconnect(state, playerId) โ optional but recommended; set phase to 'gameover'State type must extend GameStateGeneric (imported from $lib/core/types).
src/lib/games/<name>.test.tsVitest tests. Pattern from war.test.ts:
import { describe, expect, it } from 'vitest'
import { <name> } from './<name>'
const P1 = 'p1'
const P2 = 'p2'
const PLAYERS = [P1, P2]
function setup() { return <name>.setup(PLAYERS) }
describe('<name>.setup', () => {
it('initialises with correct player count', () => { ... })
it('starts in correct phase', () => { ... })
})
describe('<name>.getValidActions', () => {
it('returns empty array when not player turn', () => { ... })
})
describe('<name>.applyAction', () => {
it('does not mutate input state', () => {
const state = setup()
const frozen = Object.freeze({ ...state })
expect(() => <name>.applyAction(frozen, ...)).not.toThrow()
})
})
describe('<name>.isOver / getWinner', () => {
it('getWinner returns null while game in progress', () => {
expect(<name>.getWinner(setup())).toBeNull()
})
})
src/lib/games/rules/<name>.json{ "en": "Rules in English.", "fr": "Rรจgles en franรงais." }
src/lib/games/index.tsAdd alongside existing imports:
import <name>Rules from './rules/<name>.json'
import { <name> } from './<name>'
Add to games object and gameRules record.
src/lib/components/games/<Name>View.svelteUse the frontend-design skill to create this component. Invoke it before writing any UI code.
The component receives these props โ pass them as design context to the skill:
let {
state: gameState,
myPlayerId,
players,
validActions,
onAction,
isSpectator = false,
deckSlug
}: {
state: GameStateGeneric
myPlayerId: string
players: LobbyPlayer[]
validActions: Action[]
onAction: (action: Action) => void
isSpectator?: boolean
deckSlug?: string
} = $props()
If the game has opponents arrayed around a shared table/board (3+ players, spatial layout), use GameLayout:
import GameLayout from '$lib/components/games/GameLayout.svelte'
const opponents = $derived(gs.players.filter((p) => p !== myPlayerId))
{#snippet opponentTile(pid: string)}
<!-- opponent card, hand preview, status, etc. -->
{/snippet}
{#snippet center()}
<!-- draw pile, discard, dice tray, phase icon, etc. -->
{/snippet}
<GameLayout {opponents} {opponentTile} {center} />
GameLayout renders a vertical list on mobile and an ellipse arc on desktop automatically. See docs/game-layout.md for full details. Skip it for 2-player or non-spatial games (like WarView).
Constraints to give the skill:
$props, $derived, $effect)shadcn-svelte (bits-ui) for interactive elements when availablelucide-svelte for any iconsWarView.svelte as reference for 2-player prop wiring; use FightView.svelte as reference for multi-player arc layoutapplyAction uses spread ({ ...state }) not mutationgetValidActions returns [] (not null) for inactive playersgetWinner returns null when !isOver(state)activeGameId set to '<name>' in setupfrontend-design skill invoked for the View componentGameLayout (not a hand-rolled arc)