# 機能設計書 79-スコア保存

## 概要

プレイヤー別にスコアをファイルに保存する機能の設計書である。

### 本機能の処理概要

プレイ終了後、結果画面の初期化時にプレイヤーのスコアデータをファイル(.ksc)に保存する。既存のスコアファイルが存在する場合は同一設定条件のベストスコアを更新し、存在しない場合は新規作成する。スコアデータにはスコア値、クリア状態、グレード、ゲージパーセンテージ、最大チェーン数、プレイ回数、クリア回数、フルコンボ回数、パーフェクト回数が含まれる。

**業務上の目的・背景**：プレイヤーの成績を永続化し、過去のベストスコアとの比較(NEW RECORD判定)や選曲画面でのスコア表示を可能にする。設定条件(ゲージタイプ/TURN/ノートモード)ごとに独立してスコアを管理する。

**機能の利用シーン**：結果画面(SCENE_RESULT)の初期化処理(on_update_result)内で、auto=0かつadjmode=0かつiscmdline=0の場合に実行される。コースモードではない通常プレイ時に、プレイヤーディレクトリ配下にスコアファイルを保存する。

**主要な処理内容**：
1. 設定条件文字列(stat5_)を生成する(ゲージタイプ、TURN、ノートモードの組み合わせ)
2. 既存スコアファイル(.ksc)の存在確認と読み込みを行う
3. 同一設定条件のベストスコアと比較し、各フィールドの最大値を保持する
4. プレイ回数・クリア回数・フルコンボ回数・パーフェクト回数をインクリメントする
5. プレイヤーディレクトリが存在しない場合はmkdirで作成する
6. notesaveでスコアファイルを保存する

**関連システム・外部連携**：スコアファイル(.ksc)の読み書き。ファイルシステム操作(ディレクトリ作成)。

**権限による制御**：auto=1、adjmode=1、iscmdline=1の場合はスコア保存を行わない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 6 | リザルト画面 | 主画面 | プレイヤー別スコアファイル(.ksc)へのスコア・メダル・判定情報の保存 |

## 機能種別

ファイル入出力処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| totalscore | int | Yes | 今回の合計スコア | 0〜10,000,000 |
| stat6 | int | Yes | クリア状態(0:FAILED/1:CLEARED/2:FULLCOMBO/3:PERFECT) | 0-3 |
| stat1_ | int | Yes | グレード値 | 0-6 |
| gpercent | int | Yes | ゲージパーセンテージ | 0-100 |
| chainmax_song | int | Yes | 最大チェーン数 | 0以上 |
| effratetype | int | Yes | ゲージタイプ(-1:EASY/0:通常/1:HARD) | -1,0,1 |
| turn | int | Yes | TURN設定 | 0-4 |
| shortmode | int | Yes | ショートノートモード | 0-3 |
| longmode | int | Yes | ロングノートモード | 0-3 |
| analogmode | int | Yes | アナログノートモード | 0-3 |
| auto | int | Yes | オートプレイフラグ | 0,1 |
| adjmode | int | Yes | 調整モードフラグ | 0,1 |
| iscmdline | int | Yes | コマンドラインフラグ | 0,1 |
| tochu | int | Yes | 途中終了フラグ | 0,1 |
| iscourse | int | Yes | コースモードフラグ | 0,1 |
| players.cplayer | str | Yes | 現在のプレイヤー名 | - |
| csongdir | str | Yes | 楽曲ディレクトリパス | - |
| cnotesfile | str | Yes | 譜面ファイル名 | - |

### 入力データソース

既存スコアファイル(`score/プレイヤー名/楽曲ディレクトリ/譜面名.ksc`)から読み込まれる過去のスコアデータ。ファイルが存在しない場合は新規作成される。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| スコアファイル(.ksc) | File | 設定条件ごとのスコアデータを保持するテキストファイル |

### スコアファイルフォーマット

各行が「設定条件=スコアデータ」の形式で記録される。

**設定条件文字列(stat5_)**：
```
ゲージタイプ,TURN,ノートモード2,ショートモード,ロングモード,アナログモード
```
例：`normal,mirror,normal,on,on,on`

**スコアデータ**：
```
スコア,クリア状態,グレード,ゲージ%,最大チェーン,プレイ回数,クリア回数,フルコンボ回数,パーフェクト回数
```
例：`9500000,2,5,100,1234,10,8,3,0`

| フィールド | インデックス | 型 | 説明 | 更新ルール |
|-----------|------------|-----|------|-----------|
| scorestat2.0 | 0 | int | ベストスコア | max(既存値, 今回スコア) |
| scorestat2.1 | 1 | int | クリア状態(_stat6) | max(既存値, 今回値) |
| scorestat2.2 | 2 | int | グレード(stat1_) | max(既存値, 今回値) |
| scorestat2.3 | 3 | int | ゲージパーセンテージ | max(既存値, 今回値) ※途中終了時は更新しない |
| scorestat2.4 | 4 | int | 最大チェーン数 | max(既存値, 今回値) |
| scorestat2.5 | 5 | int | プレイ回数 | max(既存値, 今回値) + 1 |
| scorestat2.6 | 6 | int | クリア回数 | max(既存値, 今回値) + (stat6>=1) |
| scorestat2.7 | 7 | int | フルコンボ回数 | max(既存値, 今回値) + (stat6>=2) |
| scorestat2.8 | 8 | int | パーフェクト回数 | max(既存値, 今回値) + (stat6>=3) |

### 出力先

`{dir_default}\score\{players.cplayer}\{csongdir}\{譜面名}.ksc`

## 処理フロー

### 処理シーケンス

```
1. スコア保存前チェック
   ├─ auto=0 かつ adjmode=0 かつ iscmdline=0 の場合のみ実行
   └─ 上記以外は保存処理をスキップ

2. 設定条件文字列の生成
   ├─ stat2: ゲージタイプ(easy/normal/hard)
   ├─ stat2_1: TURN(mirror/random/s-random/h-random)
   ├─ stat3_0: ショートモード(on/off/auto/hide)
   ├─ stat3: ロングモード(on/off/auto/hide)
   ├─ stat4: アナログモード(on/off/auto/hide)
   ├─ stat5_: 上記を","で連結した設定条件文字列
   └─ stat5_2: HARDゲージ時の通常ゲージ参照用条件文字列

3. スコアファイル読み込み
   ├─ exist でファイル存在確認
   ├─ 存在する場合: noteload でファイル内容を読み込み
   │   ├─ 各行をパース(設定条件=スコアデータ)
   │   ├─ stat5_と一致する行: ベストスコア比較・各フィールド更新
   │   ├─ stat5_2と一致する行: ベストスコア参照のみ(内容は書き戻す)
   │   └─ それ以外の行: そのまま書き戻す
   └─ 存在しない場合: 新規データ行を生成

4. 新規レコード生成(既存に同一条件がない場合: f=1)
   └─ stat5_+"="+totalscore+","+_stat6+","+stat1_+","+gpercent*(tochu=0)+","+chainmax+",1,"+(stat6>=1)+","+(stat6>=2)+","+(stat6>=3)

5. ディレクトリ作成
   ├─ score/ 直下にプレイヤーディレクトリの確認・作成
   ├─ プレイヤーディレクトリ配下に楽曲ディレクトリの確認・作成
   └─ 楽曲ディレクトリがネスト(最大3階層)している場合は順に作成

6. ファイル保存
   └─ notesave でbuf変数の内容をスコアファイルに書き込み
```

### フローチャート

```mermaid
flowchart TD
    A{auto=0 かつ adjmode=0 かつ iscmdline=0?} -->|No| Z[保存スキップ]
    A -->|Yes| B[設定条件文字列生成]
    B --> C{スコアファイル存在?}
    C -->|Yes| D[noteloadでファイル読み込み]
    C -->|No| E[diff=totalscore]
    D --> F[各行をパース]
    F --> G{stat5_と一致?}
    G -->|Yes| H[ベストスコア比較・フィールド更新]
    G -->|No| I{stat5_2と一致?}
    I -->|Yes| J[ベストスコア参照・行保持]
    I -->|No| K[行をそのまま保持]
    H --> L{f=1? 新規条件?}
    J --> L
    K --> L
    E --> L
    L -->|Yes| M[新規レコード生成]
    L -->|No| N[ディレクトリ確認・作成]
    M --> N
    N --> O[notesaveで保存]
    O --> P[完了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-79-01 | auto/adjmode除外 | auto=1またはadjmode=1またはiscmdline=1の場合はスコア保存を行わない | 常時 |
| BR-79-02 | 設定条件別保存 | ゲージタイプ+TURN+ノートモードの組み合わせごとに独立したスコア行として保存する | 常時 |
| BR-79-03 | ベスト値保持 | 各フィールドは既存値と今回値のmax値を保持する(劣化しない) | 既存ファイルあり時 |
| BR-79-04 | 途中終了時ゲージ | 途中終了(tochu=1)の場合、ゲージパーセンテージは0として保存される | 新規レコード生成時 |
| BR-79-05 | コースモード補正 | コースモード(iscourse=1)の場合、stat6が2未満であれば_stat6=0(FAILED扱い)となる | コースモード時 |
| BR-79-06 | HARDゲージ参照 | HARDゲージプレイ時、通常ゲージ条件(stat5_2)のベストスコアも差分計算に使用される | effratetype=1時 |
| BR-79-07 | プレイ回数インクリメント | プレイ回数は既存最大値+1で更新される | 常時 |
| BR-79-08 | クリア回数条件 | stat6>=1の場合にクリア回数がインクリメントされる | 常時 |
| BR-79-09 | ディレクトリ自動作成 | プレイヤーディレクトリ・楽曲ディレクトリが存在しない場合はmkdirで自動作成する | 常時 |

### 計算ロジック

- 設定条件文字列: `stat5_ = ゲージタイプ + "," + TURN + "," + ノートモード2 + "," + ショートモード + "," + ロングモード + "," + アナログモード`
- HARDゲージ参照用: `stat5_2 = "normal" + "," + TURN + "," + 他同一条件` (effratetype=1の場合)
- 通常ゲージ参照用: `stat5_2 = "hard" + "," + TURN + "," + 他同一条件` (effratetype=0の場合)
- 新規レコードのゲージ%: `gpercent * (tochu=0)` (途中終了時は0)
- コースモード補正: `_stat6 = stat6` (ただし iscourse=1 かつ stat6<2 の場合 _stat6=0)

## データベース操作仕様

該当なし（ファイルベースのスコアデータ操作）。

## エラー処理

特になし。スコアファイルが存在しない場合は新規作成として処理される。ディレクトリが存在しない場合はmkdirで自動作成される。

### リトライ仕様

リトライ処理は不要。

## トランザクション仕様

該当なし。ファイル書き込みはnotesaveによる一括書き込みで行われる。

## パフォーマンス要件

特になし。

## セキュリティ考慮事項

- スコアファイルはプレイヤー名でディレクトリを分離している
- ファイル操作後にchdir dir_stat1で元のディレクトリに復帰する

## 備考

- スコアファイルのパス: `{dir_default}\score\{プレイヤー名}\{楽曲ディレクトリ}\{譜面名}.ksc`
- 楽曲ディレクトリは最大3階層のネストに対応している(sndirstat配列で分割し、各階層を順にmkdir)
- HARDゲージ(effratetype=1)プレイ時は通常ゲージ条件のスコアも参照してベストスコア比較を行う(stat5_2)
- 通常ゲージ(effratetype=0)プレイ時はHARDゲージ条件のスコアも参照してベストスコア比較を行う(stat5_2)
- scorestat2配列は16バイト x 9要素のsdim配列として初期化される
- 既存ファイルの全行を読み込み、更新後の内容をbuf変数に蓄積してから一括保存する

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: 前提条件を理解する

スコア保存の実行条件を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | result.hsp | `src/scene/result.hsp` | 341行目の条件分岐 `if((auto=0)&(adjmode=0)&(iscmdline=0))` を確認する |

**読解のコツ**: auto/adjmode/iscmdlineのいずれかが1の場合、スコア保存処理全体がスキップされる。この条件は341行目のifブロックで囲まれている。

#### Step 2: 設定条件文字列の生成を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | result.hsp | `src/scene/result.hsp` | 351-376行目の設定条件文字列(stat5_, stat5_2)の生成を理解する |

**主要処理フロー**:
1. **351-355行目**: ゲージタイプ(stat2: easy/normal/hard)の文字列設定
2. **356-360行目**: TURN設定(stat2_1: mirror/random/s-random/h-random)の文字列設定
3. **361-372行目**: ノートモード(stat3_0, stat3, stat4: on/off/auto/hide)の文字列設定
4. **373行目**: stat5_の連結 `stat5_=stat2+","+stat2_1+","+stat2_2+","+stat3_0+","+stat3+","+stat4`
5. **374-376行目**: stat5_2(HARDゲージ時の相互参照用条件文字列)の生成

#### Step 3: スコアファイルの読み込みと更新を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | result.hsp | `src/scene/result.hsp` | 377-435行目のスコアファイル読み込み・比較・更新を理解する |

**主要処理フロー**:
1. **377行目**: `exist` でスコアファイルの存在確認
2. **383-385行目**: `noteload` でファイル内容読み込み
3. **388-430行目**: 全行ループで設定条件マッチングと各フィールド更新
4. **392行目**: `if(scorestat.0=stat5_)` で同一設定条件の行を特定
5. **396-422行目**: 各フィールドのベスト値更新(max比較)
6. **423行目**: 更新済みデータ行をbufに追加
7. **425-429行目**: stat5_2(相互参照条件)一致時はベストスコア参照のみ
8. **432-435行目**: diff値の計算(NEW RECORD判定用)

#### Step 4: 新規レコードの生成を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | result.hsp | `src/scene/result.hsp` | 436-438行目の新規レコード生成を理解する |

**読解のコツ**: f変数は既存ファイルに同一条件のレコードが見つかったかを示すフラグ(初期値1、見つかれば0)。f=1の場合、437行目で新規レコード行が生成される。

#### Step 5: ディレクトリ作成とファイル保存を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | result.hsp | `src/scene/result.hsp` | 439-469行目のディレクトリ作成・ファイル保存を理解する |

**主要処理フロー**:
1. **440-451行目**: score/ディレクトリ内のプレイヤーディレクトリ確認・作成
2. **452-466行目**: 楽曲ディレクトリの階層的作成(最大3階層)
3. **467-468行目**: `notesel buf` + `notesave` によるファイル保存
4. **469行目**: `chdir dir_stat1` で元のディレクトリに復帰

### プログラム呼び出し階層図

```
on_update_result (結果画面更新)
    │
    ├─ 前提条件チェック (341行目)
    │      └─ auto=0 & adjmode=0 & iscmdline=0
    │
    ├─ ゲージパーセンテージ計算 (342-350行目)
    │
    ├─ 設定条件文字列生成 (351-376行目)
    │      ├─ stat5_: メイン条件
    │      └─ stat5_2: 相互参照条件
    │
    ├─ スコアファイル読み込み (377-435行目)
    │      ├─ exist (ファイル存在確認)
    │      ├─ notesel + noteload (内容読み込み)
    │      ├─ 全行ループ (388-430行目)
    │      │      ├─ stat5_一致: フィールド更新 (396-423行目)
    │      │      ├─ stat5_2一致: ベストスコア参照 (425-429行目)
    │      │      └─ その他: そのまま保持 (430行目)
    │      └─ diff計算 (432-435行目)
    │
    ├─ 新規レコード生成 (436-438行目)
    │      └─ f=1の場合に新規行追加
    │
    ├─ ディレクトリ作成 (439-466行目)
    │      ├─ プレイヤーディレクトリ確認・作成 (440-451行目)
    │      └─ 楽曲ディレクトリ階層作成 (452-466行目)
    │
    └─ ファイル保存 (467-469行目)
           ├─ notesel buf
           ├─ notesave (スコアファイル書き込み)
           └─ chdir dir_stat1 (ディレクトリ復帰)
```

### データフロー図

```
[入力]                    [処理]                         [出力]

effratetype/turn       ──▶ 設定条件文字列生成        ──▶ stat5_, stat5_2
shortmode/longmode          (351-376行目)
analogmode

既存.kscファイル       ──▶ noteload読み込み           ──▶ scorestat2配列
                            (383-385行目)

scorestat2 + 今回結果  ──▶ ベスト値更新               ──▶ buf(更新済み行)
(totalscore,stat6等)        (396-423行目)

buf                    ──▶ notesave保存               ──▶ .kscファイル
                            (467-468行目)

totalscore -           ──▶ diff計算                    ──▶ diff
totalscorep                 (432-435行目)                   (NEW RECORD用)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| result.hsp | `src/scene/result.hsp` | ソース | スコア保存の主要処理(341-469行目) |
| kshootmania.hsp | `kshootmania.hsp` | ソース | 定数・変数の定義 |
| score/*.ksc | `score/{プレイヤー名}/{楽曲ディレクトリ}/{譜面名}.ksc` | データ | スコアデータファイル(入出力) |
