norns-demo

Tic-tac-toe

Component composition, Svelte stores in a Civet module, $effect, a toggle group and a small AI.

src/routes/examples/norns/tic-tac-toe/ has no server side; it is a client-side game built from Game.n, Board.n, Cell.n, store.c and ai.c.

#The store

store.c uses Svelte stores from a Civet module. writable, derived and get are auto-imported from svelte/store:

mode := writable '1v1'
board := writable newBoard()
turn := writable 'X'
stats := writable { X: 0, O: 0, draw: 0 }

result := derived board, ($b) =>
	for [a, x, y] of LINES
		if $b[a]? and $b[a] is $b[x] and $b[x] is $b[y]
			return { winner: $b[a], line: [a, x, y] }
	if $b.every((c) => c?) then { winner: null, line: [] } else null

play := (idx) =>
	return if get(result)?
	$b := get board
	return if $b[idx]?
	...

Note the Civet idioms: is for ===, x? existence checks, return if cond, and if ... then ... else as an expression.

#The component

Game.n subscribes with the $store syntax in the template ({$stats.X}, $board), uses a ToggleButtonGroup for the mode, and a $effect that increments the stats once per finished game:

notified .= $state false

$effect =>
	if $result?
		unless notified
			incrementStats $result.winner
			notified = true
	else
		notified = false

notified is declared with .= because it is reassigned; := would make it a const and norns lint would flag it.

Board.n and Cell.n are colocated components picked up by the auto-importer because src/routes is in componentDirs; ai.c exports scheduleAiMove, imported explicitly.

#The vanilla twin

src/routes/examples/svelte/tic-tac-toe/ implements the same game in .svelte / .ts.