概要
13ジャンルから興味のあるものをコインで解放し、3つのプレイモード(ランダム/順番/続きから)で問題を解いていく機能。
進捗・履歴は AppGroup に保存。
関連画面
13ジャンル
歴史 / 科学 / 地理 / 文学・言語 / 芸術・音楽 / スポーツ / 食・料理 / 自然・動植物 / 宇宙・天文 / テクノロジー・IT / 医学・健康 / 心理学 / 雑学・トリビア
API
@MainActor
final class GenreQuizService {
func unlockGenre(id: String) -> Bool
func isUnlocked(id: String) -> Bool
func loadQuestions(genreId: String) -> [GenreQuizQuestion]
func randomQuestion(genreId: String) -> GenreQuizQuestion?
}
final class GenreProgressService { // @unchecked Sendable
func getAnsweredCount(genreId: String) -> Int
func getCorrectCount(genreId: String) -> Int
func getPosition(genreId: String) -> Int
func recordAnswer(genreId:, questionId:, correct:)
}
プレイモード
| モード | 動作 |
| ランダム | 毎回 randomQuestion() で異なる問題 |
| 順番通り | position をリセットして1問目から |
| 続きから | 保存された position から再開 |
処理フロー(アンロック → プレイ)
- ユーザーがジャンルカードで「100コインで解放」タップ
- CoinService.spendCoins(100)
- GenreQuizService.unlockGenre(id:)
- AppGroup の
unlocked_genres に追加
- 「プレイ」ボタン → モード選択
- Interstitial広告表示(無料ユーザー)
- GenreQuizPlayView で問題解答
- 各回答ごとに GenreProgressService.recordAnswer()
ビジネスルール
- ジャンル解放は1ジャンル100コイン、買い切り(再購入不要)
- プレイ開始時にInterstitial広告(無料)
- 正解で streak +1、不正解で問題のみリセット
- 履歴は最新から順に保存(AppGroup)
外部連携
| 連携先 | 用途 |
| Bundle JSON | 各ジャンルの問題セット読込 |
| CoinService | 解放時のコイン消費 |
| InterstitialAdManager | プレイ開始時の広告 |
| AppGroup UserDefaults | 進捗・履歴・アンロック状態 |
エラー処理
| 発生条件 | 対応 |
| 残高不足 | 「コインが足りません」アラート |
| JSON 読込失敗 | 「問題を取得できませんでした」 + 空リスト |
| すでにアンロック済み | UI でブロック(unlock ボタン非表示) |
実装メモ
- GenreQuizService は @MainActor
- GenreProgressService は @unchecked Sendable
- AppGroup UserDefaults キー:
unlocked_genres, genre_progress_*
- SwiftUI Charts での進捗可視化(v1.1)
変更履歴
| バージョン | 日付 | 変更内容 |
| 1.0 | 2026-05-09 | 初版作成 |