Re: B-070調査依頼: ゲームデータのレジストリパターン統一
調査結果: B-070 ゲームデータのレジストリパターン統一
1. ハードコード箇所の完全なリスト
ゲームデータ(ゲーム名・slug・パス・説明・アイコン等)がハードコードされている箇所を以下に整理した。「ゲームリスト」として一覧を定義している主要箇所は 7箇所 あり、さらに個別ゲームページ内で各ゲーム固有のメタデータ・SEOデータがハードコードされている箇所がある。
A. ゲームリストを定義している主要箇所(7箇所)
箇所1: ゲーム一覧ページ(games/page.tsx)
- ファイル:
/mnt/data/yolo-web/src/app/games/page.tsx行6-43 - 定数名:
GAMES - フィールド: slug, title, description, icon, accentColor, difficulty
- 4ゲーム全て含む
箇所2: トップページ(app/page.tsx)
- ファイル:
/mnt/data/yolo-web/src/app/page.tsx行17-46 - 定数名:
DAILY_GAMES - フィールド: slug, title, description, icon, accentColor
- 4ゲーム全て含む
- 差異: descriptionが箇所1と異なる(短縮版)。difficultyフィールドなし。
箇所3: 検索インデックス(build-index.ts)
- ファイル:
/mnt/data/yolo-web/src/lib/search/build-index.ts行18-51 - 定数名:
GAMES_FOR_SEARCH - フィールド: slug, title, description, difficulty, keywords
- 4ゲーム全て含む
- 差異: icon・accentColorなし。keywords追加。descriptionは箇所1と同じ。
箇所4: クロスゲーム進捗(crossGameProgress.ts)
- ファイル:
/mnt/data/yolo-web/src/lib/games/shared/crossGameProgress.ts行13-38 - 定数名:
ALL_GAMES - フィールド: slug, title, path, statsKey
- 4ゲーム全て含む
- 差異: description・icon・accentColor・difficulty・keywordsなし。statsKey追加。
箇所5: フッター(Footer.tsx)
- ファイル:
/mnt/data/yolo-web/src/components/common/Footer.tsx行14-19 SECTION_LINKS[1].links内にインライン定義- フィールド: href, label(= title)
- 4ゲーム全て含む
- 差異: hrefとlabelのみ。
箇所6: サイトマップ(sitemap.ts)
- ファイル:
/mnt/data/yolo-web/src/app/sitemap.ts行59-81 - URLがハードコードされたオブジェクトリテラルとして定義
- フィールド: url, lastModified, changeFrequency, priority
- 注意: 3ゲームのみ(kanji-kanaru, yoji-kimeru, nakamawake)。irodoriが欠落している
箇所7: OGP画像生成(各opengraph-image.tsx)
/mnt/data/yolo-web/src/app/games/kanji-kanaru/opengraph-image.tsx行12-17/mnt/data/yolo-web/src/app/games/yoji-kimeru/opengraph-image.tsx行12-17/mnt/data/yolo-web/src/app/games/nakamawake/opengraph-image.tsx行12-17/mnt/data/yolo-web/src/app/games/irodori/opengraph-image.tsx行12-17- フィールド: title, subtitle, accentColor, icon
- 差異: accentColorの値が箇所1・箇所2と異なる(後述)。iconも一部異なる。
B. 個別ゲームページ内のメタデータ(4箇所)
各ゲームのpage.tsxファイル内にNext.js Metadata + JSON-LDが定義されている。
/mnt/data/yolo-web/src/app/games/kanji-kanaru/page.tsx行8-34/mnt/data/yolo-web/src/app/games/yoji-kimeru/page.tsx行8-45/mnt/data/yolo-web/src/app/games/nakamawake/page.tsx行7-43/mnt/data/yolo-web/src/app/games/irodori/page.tsx行7-47
フィールド: title(長い形式), description(長い形式), keywords, OG/Twitterメタ, JSON-LD(name, description, url, genre, inLanguage, numberOfPlayers)
C. その他のハードコード箇所
share.ts(各ゲーム) - ゲーム名とURLがシェアテキスト内にハードコード
/mnt/data/yolo-web/src/lib/games/kanji-kanaru/share.ts行39,41/mnt/data/yolo-web/src/lib/games/yoji-kimeru/share.ts行35,37/mnt/data/yolo-web/src/lib/games/nakamawake/share.ts行36,38/mnt/data/yolo-web/src/lib/games/irodori/share.ts行20,22(+ 行183 画像フッター)
ShareButtons.tsx - ゲーム名とURLがハードコード
/mnt/data/yolo-web/src/components/games/kanji-kanaru/ShareButtons.tsx行33,41,43/mnt/data/yolo-web/src/components/games/yoji-kimeru/ShareButtons.tsx行33,41,43
GameHeader.tsx(各ゲーム) - ゲーム名がh1要素にハードコード
/mnt/data/yolo-web/src/components/games/kanji-kanaru/GameHeader.tsx行24/mnt/data/yolo-web/src/components/games/yoji-kimeru/GameHeader.tsx行24/mnt/data/yolo-web/src/components/games/nakamawake/GameHeader.tsx行22/mnt/data/yolo-web/src/components/games/irodori/GameHeader.tsx行22
辞典の関連ゲームリンク - 特定ゲームへのリンクがハードコード
/mnt/data/yolo-web/src/components/dictionary/kanji/KanjiDetail.tsx行91-93/mnt/data/yolo-web/src/components/dictionary/yoji/YojiDetail.tsx行89-91
storage.ts(各ゲーム) - localStorage キーがハードコード
/mnt/data/yolo-web/src/lib/games/kanji-kanaru/storage.ts行3-4/mnt/data/yolo-web/src/lib/games/yoji-kimeru/storage.ts行3-4/mnt/data/yolo-web/src/lib/games/nakamawake/storage.ts行3-4/mnt/data/yolo-web/src/lib/games/irodori/storage.ts行3-4
2. データの差異分析
accentColorの不一致
| ゲーム | games/page.tsx / page.tsx | opengraph-image.tsx |
|---|---|---|
| kanji-kanaru | #4d8c3f (緑) | #1e40af (青) |
| yoji-kimeru | #9a8533 (金) | #dc2626 (赤) |
| nakamawake | #8a5a9a (紫) | #059669 (緑) |
| irodori | #e91e63 (ピンク) | #e11d48 (ほぼ同じだが微妙に異なるピンク) |
全ゲームでアクセントカラーがページ表示用とOGP画像用で異なっている。
iconの不一致
| ゲーム | games/page.tsx / page.tsx | opengraph-image.tsx |
|---|---|---|
| kanji-kanaru | U+1F4DA (本) | U+1F4D6 (開いた本) |
| yoji-kimeru | U+1F3AF (的) | U+1F4AE (花飾り) |
| nakamawake | U+1F9E9 (パズル) | U+1F9E9 (パズル) - 一致 |
| irodori | U+1F3A8 (パレット) | U+1F3A8 (パレット) - 一致 |
descriptionの不一致
トップページ(page.tsx)のDESCRIPTIONは短縮版:
- kanji-kanaru: "毎日1つの漢字を推理するパズル" (トップ) vs "毎日1つの漢字を当てるパズルゲーム。部首・画数・読みのヒントで推理しよう!" (ゲーム一覧)
- yoji-kimeru: "毎日1つの四字熟語を当てるパズル" (トップ) vs "毎日1つの四字熟語を当てるパズルゲーム。4文字の漢字を推理しよう!" (ゲーム一覧)
- nakamawake: "16個の言葉を4グループに分けるパズル" (トップ) vs "16個の言葉を4つのグループに分けるパズルゲーム。共通テーマを見つけて仲間分けしよう!" (ゲーム一覧)
- irodori: "毎日5つの色を作って色彩感覚を鍛えよう" (トップ) vs "毎日5つの色を作って色彩感覚を鍛えよう! ターゲットカラーにどれだけ近づけるかチャレンジ!" (ゲーム一覧)
sitemapでのirodori欠落
sitemap.tsでは kanji-kanaru, yoji-kimeru, nakamawake の3ゲームのみが記載されており、irodoriのエントリがない。これはバグと考えられる。
3. 推奨するレジストリの型定義とファイル配置
推奨ファイル配置
既存のレジストリパターン(tools/registry.ts, cheatsheets/registry.ts, lib/quiz/registry.ts)に倣い、以下を推奨する。
src/lib/games/registry.ts # レジストリ本体
src/lib/games/types.ts # 型定義(新規、または既存のgames/shared/に配置)
ゲームは既にsrc/lib/games/以下にサブディレクトリ構造があるため、そこにregistry.tsを配置するのが自然。
推奨する型定義
// src/lib/games/types.ts (新規ファイル、または共通型として)
/** ゲームのメタ情報 - 全箇所の統合元 */
export interface GameMeta {
/** URL用スラッグ (例: "kanji-kanaru") */
slug: string;
/** 日本語タイトル (例: "漢字カナール") */
title: string;
/** カード用短い説明 (トップページ用、~30文字) */
shortDescription: string;
/** 詳細な説明 (ゲーム一覧ページ・検索用、~60文字) */
description: string;
/** SEO用の長い説明 (meta description用、120-160文字) */
longDescription: string;
/** アイコン絵文字 */
icon: string;
/** テーマカラー (CSS hex) */
accentColor: string;
/** OGP画像用アクセントカラー(意図的に異なる場合) */
ogpAccentColor: string;
/** OGP画像用アイコン(意図的に異なる場合) */
ogpIcon: string;
/** OGP画像用サブタイトル */
ogpSubtitle: string;
/** 難易度表示 */
difficulty: string;
/** 検索用キーワード */
keywords: string[];
/** localStorage統計キー */
statsKey: string;
/** ゲームパス (例: "/games/kanji-kanaru") */
path: string;
/** SEO keywords(ページメタデータ用) */
seoKeywords?: string[];
/** JSON-LD genre */
genre?: string;
/** サイトマップ設定 */
sitemap: {
changeFrequency: "daily" | "weekly" | "monthly";
priority: number;
};
}
ただし、実装の簡潔さを優先するなら、OGP関連の差異は意図的でない可能性が高いため統一し、以下のより簡潔な定義も検討に値する。
export interface GameMeta {
slug: string;
title: string;
shortDescription: string;
description: string;
longDescription: string;
icon: string;
accentColor: string;
difficulty: string;
keywords: string[];
statsKey: string;
ogpSubtitle: string;
}
この簡潔版では、path は "/games/${slug}" で自動導出、statsKey は "${slug}-stats" で自動導出できるため、フィールドとして持たない選択肢もある。ただし crossGameProgress.ts の既存GameInfo型との互換性を考えると、明示的にpathとstatsKeyを持つ方が安全。
レジストリの構造(案)
// src/lib/games/registry.ts
import type { GameMeta } from "./types";
const gameEntries: GameMeta[] = [
{
slug: "kanji-kanaru",
title: "漢字カナール",
shortDescription: "毎日1つの漢字を推理するパズル",
description: "毎日1つの漢字を当てるパズルゲーム。部首・画数・読みのヒントで推理しよう!",
longDescription: "毎日1つの漢字を当てるパズルゲーム。6回以内に正解を見つけよう!部首・画数・読みなどのヒントを頼りに推理する、新感覚の漢字クイズです。",
icon: "\u{1F4DA}",
accentColor: "#4d8c3f",
difficulty: "初級〜中級",
keywords: ["漢字", "パズル", "デイリー", "推理"],
statsKey: "kanji-kanaru-stats",
ogpSubtitle: "毎日の漢字パズル",
},
// ... 他ゲーム
];
/** slug -> GameMeta の O(1) ルックアップ */
export const gameBySlug: Map<string, GameMeta> = new Map(
gameEntries.map((g) => [g.slug, g]),
);
/** 全ゲームのメタ情報 */
export const allGameMetas: GameMeta[] = gameEntries;
/** 全ゲームのslugリスト */
export function getAllGameSlugs(): string[] {
return gameEntries.map((g) => g.slug);
}
4. レジストリ統一の適用範囲と注意点
高い効果が見込める箇所(統一すべき)
- games/page.tsx の GAMES 定数 -> レジストリから参照
- page.tsx の DAILY_GAMES 定数 -> レジストリから参照
- build-index.ts の GAMES_FOR_SEARCH 定数 -> レジストリから参照(TODO コメントも既に存在)
- crossGameProgress.ts の ALL_GAMES 定数 -> レジストリから参照
- Footer.tsx の SECTION_LINKS ゲーム部分 -> レジストリから参照
- sitemap.ts のゲームURL -> レジストリから動的生成(irodori欠落バグも修正される)
- opengraph-image.tsx(各ゲーム) -> レジストリから参照
個別コンポーネント内のハードコード(統一の効果が限定的)
以下はゲーム固有のコンポーネント内部にあり、レジストリ化しても各コンポーネントが「自分のslug」を知る必要があるため、効果が限定的。ただしpropsとして渡す設計にすれば統一は可能。
- GameHeader.tsx(各ゲームのh1タイトル)
- ShareButtons.tsx(各ゲームのシェアURL・タイトル)
- share.ts(各ゲームのシェアテキスト生成)
- storage.ts(各ゲームのlocalStorageキー)
辞典の関連リンク(別途対応)
- KanjiDetail.tsx, YojiDetail.tsx 内のゲームリンクは、特定ゲームへの固定リンクであり、レジストリとは別の課題。
5. 既存レジストリパターンとの整合性
tools/registry.ts と cheatsheets/registry.ts は「メタ + コンポーネントインポート」のペアで管理しているが、ゲームは各ゲームが独立したNext.jsページ(app/games/[slug]/page.tsx)として存在しており、動的インポートのパターンは不要。quiz/registry.ts のようにメタ情報のみを管理する形が適切。