「ズンドコキヨシ」って、その昔に何度もこすり倒されたネタだから「今さら?」感は満載だけど、まぁそんな忘れたころにリバイバルする、みんな大好きズンドコキヨシだよね。
ランダム生成と明確な終了条件があるおかげで、ちょっとしたコードのサンプルにするにはちょうどいい題材だよね。
今回の記事では、F-Boxという「値を箱に詰める」コンセプトのライブラリを使って、このズンドコキヨシを TypeScript と React で淡々と再現してみるよ。
F-Box 自体は決して「React 専用の状態管理ライブラリ」ってわけじゃないんだけど、RBoxという仕組みを使えば状態管理ができるようになってて、さらにf-box-reactを組み合わせると React のコンポーネントと自然に連携できるようになるんだ。
ズンドコキヨシとは?
一応流れをおさらいすると、こんな感じだね。
- 「ズン」か「ドコ」をランダムで並べる
- 直近 5 回が「ズン」「ズン」「ズン」「ズン」「ドコ」になったら
- 「キ・ヨ・シ!」を出して終了
この単純なロジックがいいよね。
最後の「キ・ヨ・シ!」が出るまでひたすらズンドコし続ける感じが妙にクセになるんだ。
全体コード
では、まずは完成版のコードをまとめて載せるね。
TypeScript と React を前提にしていて、F-Box のRBox
と、その React 用のフックであるuseRBox
を使っているよ。
後の解説でポイントを補足するからサラッと眺めてみて。
import { RBox } from "f-box-core"
import { useRBox } from "f-box-react"
type ZUNDOKO = "ズン" | "ドコ" | "キ・ヨ・シ!"
const zun = "ズン"
const doko = "ドコ"
const kiyoshi = "キ・ヨ・シ!"
const zundokoBox = RBox.pack<ZUNDOKO[][]>([])
const rnd = () => Math.random() < 0.5
const zundoko = (s: ZUNDOKO[]): ZUNDOKO =>
s.slice(-5).join("") === zun.repeat(4) + doko ? kiyoshi : rnd() ? zun : doko
const next = (s: ZUNDOKO[]) => RBox.pack([...s, zundoko(s)])
const run = (s: ZUNDOKO[]): RBox<ZUNDOKO[]> =>
s.includes(kiyoshi)
? RBox.pack(s)
: RBox.pack(s)
[">>="](next)
[">>="](run)
const set = (s: ZUNDOKO[]) => {
zundokoBox.setValue((prev) => [...prev, s])
return zundokoBox
}
const start = () =>
RBox.pack([])
[">>="](run)
[">>="](set)
export default function ZunDokoKiyoshi() {
const [zundoko] = useRBox(zundokoBox)
return (
<>
<button onClick={start}>ズンドコキヨシ</button>
<ol>
{zundoko.map((v, i) => (
<li key={i}>{v.join(" ")}</li>
))}
</ol>
</>
)
}
デモ
コード解説
ZUNDOKO 型
type ZUNDOKO = "ズン" | "ドコ" | "キ・ヨ・シ!"
- まずは「ズン」か「ドコ」か「キ・ヨ・シ!」な文字列を表すユニオン型を定義しておくよ。
F-Box と RBox
import { RBox } from 'f-box-core';
const zundokoBox = RBox.pack<ZUNDOKO[][]>([])
この RBox
が F-Box の中心となる型で、「箱」の概念を扱えるようになるんだ。
-
RBox.pack
は、与えられた値を箱に詰めてRBox
を返す関数。 -
zundokoBox
はZUNDOKO[][]
を初期値にしたRBox
。ここに結果の値をセットしてコンポーネント上でリスト表示する。
F-Box 自体はただの箱を扱うだけなんだけど、RBox
を使うとリアクティブに値が変わる仕組みがあって、f-box-react の useRBox
と組み合わせると、React のコンポーネントでその値を購読できるようになるよ。
useRBox
import { useRBox } from "f-box-react"
export default function ZunDokoKiyoshi() {
const [zundoko] = useRBox(zundokoBox)
...
- Reactコンポーネントの中で
useRBox
を呼び出すと、zundokoBox
に詰めた配列の最新の状態を受け取れる。 - 値が更新されるとコンポーネントが再レンダリングされる。
ランダム判定と zundoko 関数
const rnd = () => Math.random() < 0.5
- シンプルに 1/2 の確率で
true
/false
。 - 「ズン」か「ドコ」をランダムで決めるのに使ってるだけ。
const zundoko = (s: ZUNDOKO[]): ZUNDOKO =>
s.slice(-5).join("") === zun.repeat(4) + doko ? kiyoshi : rnd() ? zun : doko
- もし直近 5 回が「ズン」「ズン」「ズン」「ズン」「ドコ」だったら「キ・ヨ・シ!」。
- そうでなければランダムに「ズン」か「ドコ」。
-
slice(-5).join('')
で末尾 5 つを文字列連結して判定してるだけだよ。
ループ処理
const next = (s: ZUNDOKO[]) => RBox.pack([...s, zundoko(s)])
- 配列
s
の末尾に「ズン」「ドコ」または「キ・ヨ・シ!」を 1 つ追加して、新しい配列を箱に詰める。 - “次のステップへ進む”って感じの処理だね。
const run = (s: ZUNDOKO[]): RBox<ZUNDOKO[]> =>
s.includes(kiyoshi)
? RBox.pack(s)
: RBox.pack(s)
[">>="](next)
[">>="](run)
- もし
s
にすでに「キ・ヨ・シ!」が含まれていたら終了。 - 含まれてなければ
next
で 1 ステップ進めて、その結果をさらにrun
に渡す。 - この
['>>='](...)
って書き方はちょっと特殊だけど、「箱の中身を次の関数に渡して、新しい箱を返す」ってイメージで読むとわかりやすいと思うよ。
結果のセットと実行
const set = (s: ZUNDOKO[]) => {
zundokoBox.setValue((prev) => [...prev, s])
return zundokoBox
}
-
run
の最終結果(ズンドコの流れが終わった配列)を、zundokoBox
にまとめて保存してる。 - ボタンを押すたびに結果をリストアップしていくために、履歴を蓄積してるんだ。
const start = () =>
RBox.pack([])
[">>="](run)
[">>="](set)
- ボタンが押されたらこの関数が呼ばれる。
- 空の配列を用意して
run
でズンドコを開始し、最終的にset
で完了。 - この一連の流れが
['>>=']
で繋がってるのが面白いところだね。
実行してみると
- 「ズンドコキヨシ」ボタンを押すと、画面に「ズン」「ドコ」とかがランダムで生成されて、最後に「キ・ヨ・シ!」が登場するまで続く。
- 終了すると、その結果がリストに追加されるので、何度でも「ズンドコ」できるよ。
おわりに
F-Box の RBoxを使うと、箱に詰めた値を「次の処理」「その次の処理」と渡していく仕組みがシンプルに書けるし、f-box-reactを併用すれば React コンポーネントとも簡単に連携できるよ。
興味を持ったら、ぜひあなたなりにズンドコをアレンジしてみたり、全然別のネタでも RBox を使って試してみると面白いと思う。
それじゃ、ズンドコライフを楽しんでね!
F-Box ドキュメント