【svelte】svelteで3目並べ

はじめに
---
こんにちは。こまつです。

svelteに可能性を感じる今日この頃。
reactチュートリアルの3目並べをsvelte(kit)で記述してみました。

![3目並べ.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1342736/b060746a-02e3-ac91-5457-81470bf14989.gif)


感想(結論)
---
記述量が少なくサクサク作れる(動作面でもサクサク)
書き方はvueと似てる
typescriptは問題なく対応していた
storeはよくできていて関数化するとそれぞれのstore、関数化しなければglobalなstoreになるらしい。

結果、どこにでもある感想になりました。流行ってほしい！

コード
---
```sanmoku.svelte(page)
<script lang="ts">
	import Game from '$lib/Game.svelte';
</script>

<Game />
```

```Game.svelte(Organisms)
<script lang="ts">
	import Ban from '$lib/Ban.svelte';
	import { useBan, usePlayer } from './sanmoku_store';

	const player = usePlayer();
	const ban = useBan();
	let isGamePlaing: boolean = true;

	// 初期化する
	const init = () => {
		ban.reset();
		player.reset();
		isGamePlaing = true;
	};

	// 盤がクリックされた
	const onBanClick = (event: CustomEvent) => {
		if (!isGamePlaing) {
			return;
		}
		const index: number = event.detail;
		if (ban.put(index, $player)) {
			if (ban.isWon($player) || ban.isDraw()) {
				isGamePlaing = false;
				return;
			}
			player.change();
		}
	};
</script>

{#if isGamePlaing}
	<div>Next player: {$player}</div>
{:else if ban.isWon($player)}
	<div>Player {$player} won.</div>
{:else}
	<div>Game draw</div>
{/if}
<Ban ban={$ban} on:click={onBanClick} />

<button on:click={init}>clear</button>
```

```Ban.svelte(Molecules) -> 盤
<script lang="ts">
	import { createEventDispatcher } from 'svelte';
	import type { MasuValue } from 'src/types/type';
	import Masu from '../lib/Masu.svelte';

	export let ban: MasuValue[];
	const dispatch = createEventDispatcher<{ click: number }>();
	const onClick = (event: CustomEvent<number>) => dispatch('click', event.detail);
</script>

<div class="ban">
	{#each ban as masu, index}
		<Masu value={masu} {index} on:click={onClick} />
	{/each}
</div>

<style>
	.ban {
		display: flex;
		width: 300px;
		flex-wrap: wrap;
	}
</style>

```


```Masu.svelte(Atoms) -> マス
<script lang="ts">
	import { createEventDispatcher } from 'svelte';
	import type { MasuValue } from 'src/types/type';

	export let index: number;
	export let value: MasuValue;
	const dispatch = createEventDispatcher<{ click: number }>();
	const onClick = () => dispatch('click', index);
</script>

<button class="masu" on:click={onClick}>{value}</button>

<style>
	.masu {
		width: 100px;
		height: 100px;
		background-color: white;
		border: solid gray 1px;
		font-size: 80px;
		align-content: center;
		margin: 0 0;
	}
</style>
```

```sanmoku_store.ts(store) -> 非global
import type { MasuValue, Player } from 'src/types/type';
import { get, writable } from 'svelte/store';

export const usePlayer = () => {
	const initial: Player = 'O';
	const { subscribe, set, update } = writable<Player>(initial);
	const reset = () => set(initial);
	const change = () => update((p) => (p == 'O' ? 'X' : 'O'));
	return { subscribe, reset, change };
};

export const useBan = () => {
	const initialValues = (): MasuValue[] => ['', '', '', '', '', '', '', '', ''];
	const { subscribe, set } = writable<MasuValue[]>(initialValues());
	const reset = () => set(initialValues());

	// 盤がクリックされた
	const put = (index: number, player: Player): boolean => {
		const ban = get({ subscribe });
		if (ban[index] !== '') {
			return false;
		}
		ban[index] = player;
		set(ban);
		return true;
	};

	// 勝ちを判定する
	const isWon = (player: Player): boolean => {
		const positions = [
			[0, 1, 2],
			[3, 4, 5],
			[6, 7, 8],
			[0, 3, 6],
			[1, 4, 7],
			[2, 5, 8],
			[0, 4, 8],
			[2, 4, 6]
		];
		const ban: MasuValue[] = get({ subscribe });
		return (
			positions.find((p) => {
				return p.map((px) => ban[px]).every((masu) => masu === player);
			}) !== undefined
		);
	};
	// 引き分けを判定する
	const isDraw = (): boolean => {
		return get({subscribe}).every((masu) => masu !== '');
	};

	return { subscribe, reset, put, isWon, isDraw };
};
```