C言語入門|STEP16:ターン制バトルの内部構造-コマンド入力と戦闘ループの流れ

はじめに:RPGの心臓部がバトル処理

STEP15では、敵キャラクターとAIの実装を解説しました。
STEP16では、いよいよ バトル全体を制御しているロジック を読み解いていきます。

ターン制バトルは、

  • 味方が行動
  • 敵が行動
  • 勝敗を判定

という流れを、
何度も繰り返す構造 になっています。

一見複雑そうに見えますが、
実はとても整理されたループ構造です。

バトル処理は battle 関数に集約されている

バトルは、battle 関数1つで完結しています。

static int battle(GameState* gs, Enemy enemy)

この関数の役割

内容説明
戦闘開始敵出現の演出
ターン制御味方→敵の行動
勝敗判定勝利・全滅
後処理経験値・画面遷移

戻り値は、

  • 1:勝利
  • 0:敗北

というシンプルな設計です。

戦闘ループの基本構造

バトルの中心となるのが、
次の while ループです。

while (enemy.hp > 0) {
    // 味方ターン
    // 敵ターン
}

この条件の意味

  • 敵のHPが0になるまで戦闘継続
  • 敵が倒れたらループ終了

勝利条件が明確なので、
処理の流れがとても分かりやすくなっています。

パーティ全滅チェックを最初に行う理由

ループの冒頭で、
パーティが全滅していないかを確認しています。

int alive_cnt = 0;
for (int i = 0; i < PARTY_SIZE; i++) {
    if (gs->party[i].alive) alive_cnt++;
}

なぜ最初に確認するのか

  • これ以上戦闘を続ける意味がない。
  • 無駄な処理を防ぐ。
  • 即ゲームオーバーへ移行できる。

失敗条件を早めに判定する のは、
安定したロジックの基本です。

ステータス表示は毎ターン行う

戦闘中は、
毎ターンごとに状態を表示しています。

print_party_status(gs->party);
print_enemy_status(&enemy);

これがあることで

  • 現在HP・MPが分かる。
  • 防御状態が確認できる。
  • 次の行動を考えやすい。

プレイヤーが
判断しながら戦える設計 になっています。

味方の行動順を固定している理由

味方の行動は、
配列の先頭から順番に処理されます。

for (int i = 0; i < PARTY_SIZE; i++) {
    Character* c = &gs->party[i];
    if (!c->alive) continue;

固定順のメリット

  • 実装がシンプル
  • 行動順が分かりやすい。
  • 学習用として理解しやすい。

速度や素早さを入れなくても、
十分RPGらしさは出せます。

コマンド選択の仕組み

各キャラのターンで、
次のような選択肢が表示されます。

printf("1:戦う  2:魔法  3:アイテム  4:防御\n");

コマンド設計のポイント

コマンド内容
戦う通常攻撃
魔法Spell 処理
アイテム使用・装備
防御ダメージ軽減

RPGの基本要素がすべて揃った構成 です。

入力が不正だった場合の保険処理

入力が不正だった場合は、
通常攻撃にフォールバックしています。

printf("迷っている…(通常攻撃)\n");

なぜこうしているのか

  • 入力ミスで進行不能にならない。
  • テストプレイが楽になる。
  • ゲームが止まらない。

学習用RPGとして、
多少のミスを許容する設計 になっています。

防御状態を毎ターン解除している理由

味方ターンの開始時に、
防御フラグをリセットしています。

c->defending = 0;

この処理の意味

  • 防御は次の攻撃1回分だけ。
  • 永続防御を防ぐ。
  • 状態管理を簡単にする。

状態効果の持続時間を
1ターンに限定 することで、
ロジックがとても分かりやすくなります。

敵ターン処理の流れ

味方全員の行動が終わったら、
敵の行動に移ります。

if (enemy_try_cast_spell(&enemy, gs->party)) {
    // 魔法行動
} else {
    // 通常攻撃
}

ここでやっていること

  • 魔法を使えるか判断
  • 使えなければ通常攻撃

敵の行動も、
1ターンに1回だけ 行われます。

ダメージ処理と戦闘不能判定

攻撃後には、
必ずHPと alive を更新しています。

if (gs->party[t].hp <= 0) {
    gs->party[t].hp = 0;
    gs->party[t].alive = 0;
}

なぜ alive フラグが必要か

  • HPが0でも復活前か判定できる。
  • 行動可否の判定に使える。

HPと状態を分けて管理することで、
処理が安定します。

バトル終了後の分岐処理

敵を倒したあとには、
勝利時専用の処理が行われます。

if (!enemy.is_boss) {
    add_exp_party(gs->party, enemy.exp);
    wait_enter_to_return();
}

ボス戦の場合は、
エンディング専用の流れに進みます。

ターン制バトルがきれいに動く理由

ここまで見てきたように、
このバトル処理は、

  • ループ
  • 分岐
  • 関数分割

がとても整理されています。

1ターンでやることが明確 なので、
コードを追いやすく、修正もしやすい構造です。

STEP16で押さえておきたいポイントまとめ

STEP16の重要ポイントはこちらです。

  • battle 関数が戦闘の司令塔
  • ターン制は while ループで表現する
  • 味方→敵の順で処理を進める
  • 入力ミスへの保険処理が重要
  • 防御や状態はフラグで管理する