VanJSでスロットゲームを作ってみた

VanJSの勉強のために、VanJSでスロットゲームを作ってみました。Reactのカスタムフックのようなものを使ったり、コンポーネントを作って再利用できるようにしてます。ゲームとリポジトリは公開しているので、VanJSを勉強したい方は是非参考にしてください。

## 作成したゲーム

同じ絵柄が３つ揃ったら成功です。

https://vanjs-slot-game.vercel.app/

## リポジトリ

https://github.com/mori-goq/vanjs-slot-game

## コード

```typescript:src/main.ts
import van from 'vanjs-core'

import { Reel } from './Reel'

import { useIntervalChanger } from './useIntervalChanger'

const symbols = ['🍦', '🔔', '🍒', '🍉', '💎', '🍇', '💰', '🍀', '🍋']

const SlotGame = () => {
  const { h1, div, span, button } = van.tags

  const [intervalChanger, handler] = useIntervalChanger(symbols)
  const [intervalChanger2, handler2] = useIntervalChanger(symbols)
  const [intervalChanger3, handler3] = useIntervalChanger(symbols)

  // 絵柄の変更を開始する
  const reset = () => {
    handler.start()
    handler2.start()
    handler3.start()
  }

  // init
  reset()

  van.derive(() => {
    // どれか１つでも変更中の場合、リターン
    if (
      intervalChanger.isChanging.val ||
      intervalChanger2.isChanging.val ||
      intervalChanger3.isChanging.val
    ) {
      return
    }

    // 絵柄が全て揃った場合、アラートを表示
    if (
      intervalChanger.currentValue.val === intervalChanger2.currentValue.val &&
      intervalChanger2.currentValue.val === intervalChanger3.currentValue.val
    ) {
      alert('Congratulations!')
    }
  })

  return div(
    { class: 'slot-game' },
    h1({ class: 'title' }, 'VanJS Slot Game'),
    div(
      { class: 'order' },
      symbols.map((symbol) => span(symbol)),
    ),
    div(
      { class: 'reels' },
      Reel({
        symbol: intervalChanger.currentValue,
        stop: () => handler.stop(),
      }),
      Reel({
        symbol: intervalChanger2.currentValue,
        stop: () => handler2.stop(),
      }),
      Reel({
        symbol: intervalChanger3.currentValue,
        stop: () => handler3.stop(),
      }),
    ),
    button({ class: 'reset-btn', onclick: () => reset() }, 'RESET'),
  )
}

van.add(document.getElementById('app')!, SlotGame())
```

絵柄の表示とストップボタンは３つ同じなので、コンポーネントを作って再利用できるようにしました。

```typescript:src/Reel.ts
import van from 'vanjs-core'
import type { State } from 'vanjs-core'

type Props = {
  symbol: State<string>
  stop: VoidFunction
}

export const Reel = ({ symbol, stop }: Props) => {
  const { div, span, button } = van.tags

  return div(
    { class: 'reel' },
    span({ class: 'reel-symbol' }, symbol),
    button(
      {
        type: 'button',
        class: 'reel-btn',
        onclick: () => stop(),
      },
      'STOP',
    ),
  )
}
```

絵柄の変更処理は`useIntervalChanger`というReactのカスタムフックのようなものを作って、処理を再利用できるようにしました。これについては前回の記事でも取り上げているので、是非参考にしてください。

https://qiita.com/mori_goq/items/099093d85c4fc71eba20

```typescript:src/useIntervalChanger.ts
import van from 'vanjs-core'

import { createRandomNumber } from './utils'

export const useIntervalChanger = (items: string[]) => {
  const currentIndex = van.state(0)

  // 現在の値（絵柄）
  const currentValue = van.derive(() => items[currentIndex.val])

  // 変更中かどうか
  const isChanging = van.state(false)

  const intervalId = van.state(0)

  // 変更を開始する
  const start = () => {
    if (isChanging.val) return

    currentIndex.val = createRandomNumber(items.length)

    // 100ミリ秒毎にindexを足して値を変更
    intervalId.val = setInterval(() => {
      currentIndex.val++

      if (currentIndex.val > items.length - 1) {
        currentIndex.val = 0
      }
    }, 100)

    isChanging.val = true
  }

  // 停止する
  const stop = () => {
    clearInterval(intervalId.val)
    isChanging.val = false
  }

  return [
    { currentValue, isChanging },
    { start, stop },
  ] as const
}
```

## まとめ

今回はVanJSでスロットゲームを作ってみました。今までVanJSでは簡単なカスタムフックしか作ったこと無かったので良い勉強になりました。コンポーネントの使いまわしで全体のコード量も減らせたので、メンテナンスしやすい形にできたのも良かったと思います。

## 最後に

### GoQSystemでは一緒に働いてくれる仲間を募集中です！

ご興味がある方は以下リンクよりご確認ください。

https://goqsystem.recruit.site?qiita=eab72f7e406337c3b7bf