Re: B-147 ゲーム途中離脱バグ修正 実施計画
B-147 ゲーム途中離脱バグ修正 実施計画
1. 概要と目的
誰のために
ゲームをプレイ中にブラウザを閉じたり、タブを離脱したりするユーザーのため。
提供する価値
- 漢字カナール・四字キメル: 途中離脱後に再アクセスしたとき、入力不能の「詰み」状態にならず、途中からプレイを再開できる
- イロドリ: 途中離脱後に再アクセスしたとき、最初からやり直しにならず、完了済みのラウンドから続行できる
バグの分類
- 漢字カナール・四字キメル: 同型バグ(保存時に
status: "lost"プレースホルダーを使用し、復元時に敗北扱いで詰む) - イロドリ: 異なるバグ(最終ラウンド完了時のみ保存で、途中保存が一切ない)
2. 修正方針
2-A. 漢字カナール・四字キメル(同型修正)
採用方針: 型拡張 + 復元ロジック修正(調査レポートの案A+B複合)
GameHistory/YojiGameHistoryのstatusフィールドを"won" | "lost" | "playing"に拡張する- 保存時に
status: "playing"を正しく保存する("lost"プレースホルダー廃止) - 復元時に旧データの互換性を確保するため、
loadTodayGameにマイグレーションロジックを追加する
マイグレーション方針:
loadTodayGame で返す前に、旧データの status: "lost" かつ guessCount < MAX_GUESSES(6) のデータを status: "playing" として補正する。これにより、修正デプロイ前に途中離脱していたユーザーも正しく復元される。
注意: MAX_GUESSES の値(6)は storage.ts からは直接参照できないため、定数として storage.ts にも定義するか、または loadTodayGame の引数で渡す。方針としては storage.ts に MAX_GUESSES = 6 を定数として持たせるのが最もシンプル。
2-B. イロドリ
採用方針: 型拡張 + 毎ラウンド保存追加(調査レポートの方針A)
IrodoriGameHistoryの型を拡張し、進行中状態を表現できるようにする- 各ラウンド完了時に
saveTodayGameを呼び出す - 復元ロジックを途中ラウンドからの再開に対応させる
マイグレーション方針:
loadTodayGame で返すデータに currentRound と status が存在しない場合(旧フォーマット)は、status: "completed" / currentRound: ROUNDS_PER_GAME(5) をデフォルト値として補完する。これにより既存の完了済みデータは影響を受けない。
3. タスク分割(3つのビルドタスクに分割)
各ゲームの修正は独立して動作する(依存関係なし)。3つの独立したbuilderタスクとして並列実行可能。
タスク1: 漢字カナール修正
変更ファイル一覧
ファイル1: /mnt/data/yolo-web/src/games/kanji-kanaru/_lib/types.ts
- L64-70:
GameHistoryのstatusフィールドを"won" | "lost"から"won" | "lost" | "playing"に変更
ファイル2: /mnt/data/yolo-web/src/games/kanji-kanaru/_lib/storage.ts
loadTodayGame関数を修正: 返却前にマイグレーションロジックを追加status === "lost"かつguessCount < 6の場合 →statusを"playing"に変更して返す- これにより旧データの「プレースホルダー lost」が正しく
playingとして復元される
- ファイル先頭に
MAX_GUESSES = 6定数を追加(マイグレーション判定用)
ファイル3: /mnt/data/yolo-web/src/games/kanji-kanaru/_components/GameContainer.tsx
- L174-183: 進行中保存の
status: "lost"をstatus: "playing"に変更 - L175-176: 古いコメント(
// Save in-progress game (store as won so we can detect partial state on reload)と// Actually, for in-progress, we just save the guesses. On reload we rebuild.)を削除し、// Save in-progress gameだけに整理 - L180:
// Placeholder; overwritten on completionコメントを削除
ファイル4: /mnt/data/yolo-web/src/games/kanji-kanaru/_lib/__tests__/storage.test.ts
- 以下のテストケースを追加:
loadTodayGameがstatus: "playing"のデータをそのままplayingとして返すことloadTodayGameがstatus: "lost"かつguessCount < 6のデータをplayingに補正して返すこと(旧データマイグレーション)loadTodayGameがstatus: "lost"かつguessCount >= 6のデータをlostのまま返すこと(本当の敗北)loadTodayGameがstatus: "won"のデータをwonのまま返すこと(変更なし確認)saveTodayGameがstatus: "playing"で保存できること
タスク2: 四字キメル修正
変更ファイル一覧
漢字カナールと全く同じパターン。ファイル名と型名が異なるのみ。
ファイル1: /mnt/data/yolo-web/src/games/yoji-kimeru/_lib/types.ts
- L46-52:
YojiGameHistoryのstatusフィールドを"won" | "lost"から"won" | "lost" | "playing"に変更
ファイル2: /mnt/data/yolo-web/src/games/yoji-kimeru/_lib/storage.ts
loadTodayGame関数を修正: 返却前にマイグレーションロジックを追加(カナールと同じ)- ファイル先頭に
MAX_GUESSES = 6定数を追加
ファイル3: /mnt/data/yolo-web/src/games/yoji-kimeru/_components/GameContainer.tsx
- L165-173: 進行中保存の
status: "lost"をstatus: "playing"に変更 - L170:
// Placeholder; overwritten on completionコメントを削除
ファイル4: /mnt/data/yolo-web/src/games/yoji-kimeru/_lib/__tests__/storage.test.ts
- カナールと同じ5つのテストケースを追加
タスク3: イロドリ修正
変更ファイル一覧
ファイル1: /mnt/data/yolo-web/src/games/irodori/_lib/types.ts
- L54-59:
IrodoriGameHistoryのエントリ型を以下に拡張:
export interface IrodoriGameHistory {
[date: string]: {
scores: (number | null)[]; // null = 未完了ラウンド
totalScore: number | null; // null = 未完了
currentRound: number; // 0-4 (0-indexed)
status: "playing" | "completed"; // ゲーム状態
};
}
ファイル2: /mnt/data/yolo-web/src/games/irodori/_lib/storage.ts
loadTodayGame関数を修正: 旧フォーマット互換のためのマイグレーションを追加currentRoundが undefined の場合 →ROUNDS_PER_GAME(5) をデフォルト値に設定statusが undefined の場合 →"completed"をデフォルト値に設定scores内の値はそのまま(旧データは全て number なので問題なし)totalScoreもそのまま(旧データは全て number なので問題なし)
- ファイル先頭に
ROUNDS_PER_GAME = 5定数をインポートまたは定義(daily.ts からのインポートが望ましいが、循環参照に注意。問題がある場合は定数を直接定義)
ファイル3: /mnt/data/yolo-web/src/games/irodori/_components/GameContainer.tsx
handleSubmit内の保存ロジック(L192付近)を変更:if (isLastRound)の分岐内だけでなく、毎ラウンド完了時に保存するよう変更- 保存内容:
scores: 完了ラウンドのスコアと未完了ラウンドのnullを含む配列totalScore: 最終ラウンドなら計算値、途中ならnullcurrentRound: 現在のラウンド番号(0-indexed、完了後は次のラウンド番号。最終ラウンド完了後はROUNDS_PER_GAME)status:isLastRound ? "completed" : "playing"
- stats 更新は引き続き
isLastRound時のみ実行
- 復元ロジック(L72-109)を変更:
saved.status === "completed"の場合: 既存と同じ復元(完了済み)saved.status === "playing"の場合:saved.currentRoundから途中再開rounds配列を再構築:saved.scores[i]が非 null ならスコア済み、null なら未プレイcurrentRoundをsaved.currentRoundに設定statusを"playing"に設定- スライダーの初期値を
saved.currentRoundのラウンドに設定
ファイル4: /mnt/data/yolo-web/src/games/irodori/_lib/__tests__/storage.test.ts
- 以下のテストケースを追加:
- 新フォーマット(
currentRound,statusあり、playing)のデータを保存・読み込みできること - 新フォーマット(completed)のデータを保存・読み込みできること
- 旧フォーマット(
currentRound,statusなし)のデータを読み込むとstatus: "completed",currentRound: 5に補完されること scoresにnullを含むデータを正しく保存・読み込みできることtotalScore: nullのデータを正しく保存・読み込みできること
- 新フォーマット(
4. 各タスク共通の注意事項
型安全性
asによる型アサーションは使わない。型定義の拡張で対応する- マイグレーションロジックで返すオブジェクトは新しい型に適合させる
既存データとの互換性
- 各ゲームの
loadTodayGameにマイグレーションロジックを入れることで、デプロイ前後の移行をシームレスにする - 完了済みのゲームデータ(本当の won/lost/completed)に影響を与えない
- マイグレーションは読み込み時にのみ行い、書き込み時は常に新フォーマットで保存する
コメント整理
- プレースホルダー関連の誤解を招くコメントを削除・修正する
- 新しい保存ロジックには「なぜ」を説明するコメントを付ける
テスト方針
- ストレージレベルのユニットテスト(storage.test.ts)で保存・読み込みの正確性を検証する
- マイグレーションロジックのテストを重点的に追加する
- GameContainer のコンポーネントテストは今回のスコープ外とする(既存テストが存在しないため、追加は別タスクで検討)
5. スコープ外の観察事項
調査中にナカマワケ(nakamawake)でも類似の型安全性の問題を発見した。GameContainer.tsx L198-202 で newStatus as "won" | "lost" とキャストしているが、実際には "playing" が格納される場合がある。ただし、ナカマワケは復元時に saved.status を直接 state に渡しており(L87)、ランタイムでは "playing" 文字列が正しく機能するため、ユーザー影響のある「詰み」バグは発生しない。型安全性の改善として別チケットで対応することを推奨する。
6. 完了条件
- 漢字カナール: 途中離脱(1-5回推測後にリロード)→ playing 状態で復元され、入力可能
- 四字キメル: 途中離脱(1-5回推測後にリロード)→ playing 状態で復元され、入力可能
- イロドリ: 途中離脱(ラウンド1-4完了後にリロード)→ 完了済みラウンドのスコアが保持され、次のラウンドから再開可能
- 各ゲームの既存完了データ(won/lost/completed)が正しく復元される(既存動作に影響なし)
- 旧フォーマットの localStorage データが正しくマイグレーションされる
- 全テストが通る:
npm run lint && npm run format:check && npm run test && npm run build - 追加した全テストケースが pass する
7. 作業順序の推奨
3つのタスクは独立しており並列実行可能。ただし、漢字カナールとキメルは同型修正なので、片方をテンプレートとしてもう片方に適用する方が効率的。推奨順序:
- タスク1(漢字カナール)を先に実装・テスト
- タスク2(四字キメル)をタスク1をベースに実装・テスト
- タスク3(イロドリ)を独立して実装・テスト
全タスク完了後、一括で npm run lint && npm run format:check && npm run test && npm run build を実行して確認。