UI State Tic Tac Toe
A full game in three stores, with no JavaScript on the page. $cells holds nine numbers: 1 for X, -1 for O, and 0 for free. The square is an sv-template stamped nine times.
A line sums to 3 or -3 only when one player holds all of it, so a $peak of 3 means someone has won.
Code
The following example assumes that you have imported the component and set up the theme. This one needs sv-template as well.
<!-- One square, stamped nine times. [[ i ]] is its seat in the board array --><sv-template id="ttt-square">10 collapsed lines
<template> <button type="button" bind-attr-toggle="disabled:$cells.at([[ i ]]) !== 0 || $winner !== 0" bind-class-toggle="is-o:$cells.at([[ i ]]) === -1" bind-event="click:$cells($cells.with([[ i ]], $turn)), $turn(-$turn), $moves($moves + 1)" bind-text="$cells.at([[ i ]]) === 0 ? '' : $cells.at([[ i ]]) === 1 ? 'X' : 'O'" template-for="i of 9" ></button> </template></sv-template>
<sv-ui-state>49 collapsed lines
<!-- X plays 1, O plays -1, an empty square is 0 --> <sv-store type="json" name="$cells" value="[0,0,0, 0,0,0, 0,0,0]"></sv-store> <sv-store type="number" name="$turn" value="1"></sv-store> <sv-store type="number" name="$moves" value="0"></sv-store>
<!-- A line can only reach 3 or -3 when one player holds all three of it --> <sv-store derive=" Math.max( Math.abs($cells[0] + $cells[1] + $cells[2]), Math.abs($cells[3] + $cells[4] + $cells[5]), Math.abs($cells[6] + $cells[7] + $cells[8]), Math.abs($cells[0] + $cells[3] + $cells[6]), Math.abs($cells[1] + $cells[4] + $cells[7]), Math.abs($cells[2] + $cells[5] + $cells[8]), Math.abs($cells[0] + $cells[4] + $cells[8]), Math.abs($cells[2] + $cells[4] + $cells[6]) ) " name="$peak" ></sv-store>
<!-- Only the player who just moved can have won, and $turn has already flipped --> <sv-store derive="$peak === 3 ? -$turn : 0" name="$winner"></sv-store> <sv-store derive=" $winner !== 0 ? ($winner === 1 ? 'X' : 'O') + ' wins' : $moves === 9 ? 'Draw' : ($turn === 1 ? 'X' : 'O') + ' to play' " name="$status" ></sv-store>
<div class="game"> <span class="game-status" bind-class-toggle="is-over:$winner !== 0 || $moves === 9" bind-text="$status"></span>
<div class="game-board"> <sv-template use="ttt-square"></sv-template> </div>
<button type="button" class="game-reset" bind-attr-toggle="disabled:$moves === 0" bind-event="click:$cells(new Array(9).fill(0)), $turn(1), $moves(0)" > New game </button> </div></sv-ui-state>
<style>69 collapsed lines
.game { display: grid; gap: 0.75em; justify-items: center; inline-size: min(15rem, 100%); padding-block: 1em; margin-inline: auto;
.game-status { font-size: 0.9em; color: var(--sv-gray-3); white-space: nowrap;
&.is-over { color: var(--sv-accent); } }
.game-board { display: grid; grid-template-rows: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.3em; inline-size: 100%; aspect-ratio: 1;
button { box-sizing: border-box; padding: 0; font: inherit; font-size: 1.6em; line-height: 1; color: var(--sv-accent); cursor: pointer; background-color: var(--sv-gray-6); border: none; border-radius: var(--sv-bdr-rad-sm);
&.is-o { color: var(--sv-gray-2); }
&:disabled { cursor: default; }
&:hover:not(:disabled) { background-color: var(--sv-gray-5); } } }
.game-reset { padding: 0.4em 1.1em; font: inherit; font-size: 0.85em; color: var(--sv-gray-1); cursor: pointer; background-color: var(--sv-gray-5); border: none; border-radius: var(--sv-bdr-rad-sm);
&:disabled { color: var(--sv-gray-4); cursor: not-allowed; background-color: transparent; } } }</style>