図鑑の「発見済み」の管理:集合として扱う
遊びをつくる執筆にClaudeを使用しています。

同じ生きものを3回見つけたとき、図鑑のページは3枚に増えるでしょうか。増えないのが自然です。図鑑が知りたいのは「何回見たか」ではなく「見たことがあるかどうか」だからです。この性質をそのままプログラムの形にしたものが、集合(同じ要素を二重に持たず、順番も問わず、含まれているかどうかだけを答えるデータ)です。 「潮だまり」は、引き潮の浅瀬に海藻を3つ、砂地に小石を2つ置き、潮を切り替えて観察するゲームです。上下左右の隣接条件に合うと、イソギンチャクからタツノオトシゴまで8種の生きものが現れ、絵付きの図鑑と観察ノートに残ります。この記事では、その図鑑を「発見済みの集合」として考えると何が整理できるかを見ていきます。内部の実装は公開されていないため、集合はあくまで説明のための道具として使います。 後半では、自作のゲームに写せる最小のコードと、自分で作品を開いて確かめる手順を示します。
なぜ配列ではなく集合で考えるのか
発見済みの一覧は、配列(順番つきの並び)でも持てます。ただ、配列は「同じ値を何度でも入れられる」ので、追加のたびに「すでに入っているか」を確かめる処理を自分で書く必要があります。それを忘れると、同じ生きものが何行も並びます。
- 同じ値を入れたとき、配列:そのまま増える、集合:増えない
- 「含まれるか」の確認、配列:自分で探す処理を書く、集合:標準の問い合わせで済む
- 順番、配列:意味を持つ、集合:意味を持たない(見つけた順は別に持つ)
- 向いている用途、配列:棋譜、履歴、ランキング、集合:発見済み、取得済み、解放済み
見つけた順番を見せたいなら、順番を持つ別の記録(観察ノートのような時系列)を用意し、「発見済みかどうか」の判定は集合に任せる、という分担が考えられます。観察ノートがどんな形式で何を残すかは、この記事では断定しません。作品を開いて確かめてください。
潮だまりの図鑑に当てはめる
確認済みの事実を整理すると、次のとおりです。
- 生きものは全部で8種です。
- 現れるかどうかは、海藻3つと小石2つの置き方による上下左右の隣接条件で決まります。
- 図鑑は絵付きで、観察ノートもあります。
ここから集合の言葉で、次のように言い換えられます。
- 全体集合:8種すべて。発見の対象になりうるものの全体です。
- 発見済み集合:これまでに現れた生きものだけ。全体集合の部分集合です。
- 未発見:全体集合から発見済みを引いた差集合です。
- 進み具合:発見済みの要素数を8で割ったものです。
「現れる」という出来事は、盤面の配置ごとに何度でも起こります。同じ配置を作り直しても、同じ種類が現れるだけで、集合の大きさは変わりません。集合の大きさが増えるのは、まだ入っていない種類が現れた瞬間だけです。図鑑の数字が「出来事の回数」ではなく「種類の数」を表すのは、この仕組みのためと考えると自然です。
ここで注意したいのは、潮だまりが実際にこの形で作られているかどうかは、公開情報からは分からない点です。「設計から読み取れること」として言えるのは、8種という有限の全体があり、図鑑が種類ごとの記録であるなら、集合として扱う考え方がよく合う、というところまでです。
最小のコードで確かめる
次のHTMLを、ファイル名を問わず保存してブラウザで開くと単独で動きます。生きものの名前は使わず、IDを「A」〜「H」としています。
<!doctype html>
<meta charset="utf-8">
<title>発見済みの集合</title>
<div id="btns"></div>
<p id="out"></p>
<script>
const ALL = ["A","B","C","D","E","F","G","H"]; // 全体集合
const found = new Set(); // 発見済み集合
function discover(id) {
if (!ALL.includes(id)) return "unknown";
if (found.has(id)) return "seen";
found.add(id);
return "new";
}
const missing = () => ALL.filter(id => !found.has(id));
const save = () => JSON.stringify({ version: 2, found: [...found] });
function load(text) {
try {
const d = JSON.parse(text);
if (d.version !== 2 || !Array.isArray(d.found)) return;
d.found.filter(id => ALL.includes(id)).forEach(id => found.add(id));
} catch (e) { /* 壊れた保存は無視 */ }
}
const out = document.getElementById("out");
ALL.forEach(id => {
const b = document.createElement("button");
b.textContent = "種" + id + "を見つける";
b.onclick = () => {
const r = discover(id);
out.textContent = r + " / " + found.size + "/" + ALL.length +
" / 未発見: " + missing().join(",") + " / 保存形式: " + save();
};
document.getElementById("btns").appendChild(b);
});
</script>同じボタンを何度押しても、found.size は最初の1回しか増えません。discover が "new" か "seen" を返すので、「初めて見つけた」演出を出すかどうかの分岐にも使えます。
保存したいときの境界線
潮だまりを含む10作品では、記録や録音は画面を開いている間だけ残る、と確認されています。保存があるのは「余白の庭」「WAVE / WAVE」「かたちのあいだ」の3作品です。つまり潮だまりの図鑑は、画面を開いている間の集合だと読み取れます。
自作のゲームで発見済みを次回に持ち越したい場合、集合はそのままでは文字列にできないので、配列にしてJSONへ変換します。上のコードの save と load がその形です。いくつか押さえておきたい点があります。
- version を持たせる:公式作品の共通事項にも、古い保存(version1)を新しい形式(version2)へ移行する、とあります。形式は後から変わるものなので、自作でも最初から番号を入れておくと移行しやすくなります。
- 読み込み時に検証する:load が全体集合にない値を捨てているのは、後で種類を減らしたり改名したりしたときに、古い保存が悪さをしないようにするためです。
- 保存先の制限を確認する:アソバースでは、アップロード作品内の localStorage や sessionStorage は一時メモリーの互換処理とされています。作品ごとのクラウドセーブはJSONで128KiBまでです。発見済みのIDの配列は小さいので容量は問題になりにくいですが、保存の呼び出し方は現在の案内で確認してください。
- 個人情報を入れない:ゲームにFirebaseのUID、認証トークン、メールアドレスは渡さないことになっています。保存データにも含めないでください。
自分で確かめる手順と、起こりやすい誤り
潮だまりを開いて、次の順に確かめてみてください。結果は人や進め方で変わるので、ここには書きません。
- 図鑑を開き、現在の発見の表示(種類の数や絵)をメモします。
- 海藻3つと小石2つの置き方を一つ決め、潮を切り替えて観察します。
- 図鑑に変化があったかを確認します。
- まったく同じ配置を作り直し、もう一度観察します。図鑑の表示が増えるか、増えないかを見ます。
- 配置を少しずつ変え、新しい生きものが現れる条件をノートに書き出します。隣接のどの組み合わせで現れたかを、1配置につき1行で記録すると読み返しやすくなります。
- 観察ノートに何が残るかを見て、図鑑との役割の違いを書き出します。
- 画面を閉じて開き直し、図鑑と観察ノートがどうなるかを確認します。
4で増えなければ、種類単位の記録として働いていると読めます。7で消えるなら、保存がない10作品に当たるという確認済み事項と一致します。違う結果が出たら、現在の案内で仕様を確認するのが確実です。
自作の図鑑でよくある失敗は次のとおりです。
- 表示名で管理する:サイトは日本語と英語を切り替えられ、言語を変えても進行は保たれます。名前で記録すると、言語を切り替えたとたんに別の種類として数えてしまいます。内部ではIDで持ち、名前は表示のときだけ引きます。
- オブジェクトを入れる:集合は同じ参照かどうかで判定するので、中身が同じでも別のオブジェクトなら別物になります。文字列や数のIDを入れてください。
- 回数と種類を混ぜる:「何回見たか」も表示したいなら、集合とは別にIDごとの回数を持ちます。集合の大きさを回数の代わりにしてはいけません。
次の行動
上のコードを保存して開き、同じボタンを何度か押して、数が増える場面と増えない場面を見比べてみてください。次に潮だまりで前節の手順を行い、自作ゲームの収集要素に当てはめます。全体集合のIDを決め、discover を一つ置くところから始めると、無理なく組み込めます。

