STEP06:services層の実装(宿で全回復・教会で復活)

STEP06では、マップ上の施設イベントを担当する services層 を実装します。
ここでは 宿(INN)で全回復教会(CHURCH)で復活(戦闘不能から蘇生) を行えるようにします。

このSTEPのゴール

  • マップで I(宿)を踏む → パーティ全員のHP/MPが最大まで回復
  • マップで C(教会)を踏む → 戦闘不能(HP=0)の仲間を復活(HPを一定値まで回復)
  • UI層でイベント演出(メッセージ表示)を出す。
  • キャラの状態変更ロジックは services層に閉じ込める(main/map/uiに散らさない)。

今回追加するファイル

ファイル役割
services.hservices層の公開API(関数宣言・料金設定など)
services.c宿・教会の処理(回復・復活・料金処理)

※ 以降で characters.h の Party 構造や ui.h の表示関数を使う想定です。
(STEP04の UiStatusRow だけで進めている場合でも、最低限の構造体で動かせるように書きます)

services層の責務(ここが大事)

services層は「施設の効果」を提供する層です。

  • HP/MPの回復、戦闘不能の復活
  • お金(gold)の増減(料金)
  • 失敗条件(所持金不足、復活対象なし)を判定して返す。

逆に、次はやりません(UI層やmain側に任せる)

  • 施設画面の装飾やアニメ的演出(テキスト表示など)
  • キー入力待ちの処理

データ前提(最小構成)

キャラクター(例)

ここでは次のような最低限の要素があると想定します。

項目意味
name名前
hp / max_hp現在HP / 最大HP
mp / max_mp現在MP / 最大MP
alive判定hp > 0 を生存とみなす

パーティは配列+人数で渡します(「誰が何人いるか」をservices層が知りすぎないため)。

services.h(公開API)

/* services.h */
#ifndef SERVICES_H
#define SERVICES_H

#include "common.h"

/* 施設の料金(ゲームバランスは後で調整しやすいように定数化) */
#define INN_FEE_PER_PERSON     10
#define CHURCH_FEE_PER_REVIVE  30

/* 復活時のHP(最大HPの何%で起こすか) */
#define REVIVE_HP_PERCENT  50  /* 50% */

/* 結果コード(main側がUI表示を出し分けるため) */
typedef enum {
    SERVICE_OK = 0,
    SERVICE_NOT_ENOUGH_GOLD,
    SERVICE_NOTHING_TO_REVIVE
} ServiceResult;

/* 最小のキャラ型(既にcharacters.hがあるならそれを使ってOK)
   ここはあなたの既存設計に合わせて差し替えてください。 */
typedef struct Character {
    char name[32];
    int hp, max_hp;
    int mp, max_mp;
} Character;

/* 宿:全回復(パーティ全員)+料金徴収 */
ServiceResult service_inn(Character party[], int party_count, int *gold);

/* 教会:戦闘不能者を復活(全員)+料金徴収
   戦闘不能(HP=0)の人数ぶん課金、復活対象がいないなら SERVICE_NOTHING_TO_REVIVE */
ServiceResult service_church(Character party[], int party_count, int *gold);

/* 便利関数(UIで人数表示などに使える) */
int service_count_dead(const Character party[], int party_count);

#endif

すでに characters.h に Character がある場合:
services.h の typedef struct Character ... は削除して #include "characters.h" に置き換えてください。

services.c(宿・教会の実装)

/* services.c */
#include "services.h"

/* 内部ヘルパ */
static int clamp_min(int v, int minv) { return (v < minv) ? minv : v; }
static int calc_revive_hp(int max_hp) {
    int hp = (max_hp * REVIVE_HP_PERCENT) / 100;
    return clamp_min(hp, 1);
}

int service_count_dead(const Character party[], int party_count) {
    int dead = 0;
    for (int i = 0; i < party_count; i++) {
        if (party[i].hp <= 0) dead++;
    }
    return dead;
}

ServiceResult service_inn(Character party[], int party_count, int *gold) {
    int fee = INN_FEE_PER_PERSON * party_count;
    if (*gold < fee) return SERVICE_NOT_ENOUGH_GOLD;

    *gold -= fee;

    for (int i = 0; i < party_count; i++) {
        party[i].hp = party[i].max_hp;
        party[i].mp = party[i].max_mp;
    }
    return SERVICE_OK;
}

ServiceResult service_church(Character party[], int party_count, int *gold) {
    int dead = service_count_dead(party, party_count);
    if (dead == 0) return SERVICE_NOTHING_TO_REVIVE;

    int fee = CHURCH_FEE_PER_REVIVE * dead;
    if (*gold < fee) return SERVICE_NOT_ENOUGH_GOLD;

    *gold -= fee;

    for (int i = 0; i < party_count; i++) {
        if (party[i].hp <= 0) {
            party[i].hp = calc_revive_hp(party[i].max_hp);
            /* MPはそのまま(好みで 0 にしてもOK) */
        }
    }
    return SERVICE_OK;
}

main側の組み込み(map層イベントと接続)

STEP05の map_try_move() が返すタイルを使って、I と C を踏んだとき services層を呼びます。

例:イベント処理(差し替えポイント)

#include "services.h"
#include "map.h"
#include "ui.h"
/* STEP02 */
void sfx_beep(int kind);

static void on_inn(Character party[], int n, int *gold) {
    ServiceResult r = service_inn(party, n, gold);

    ui_print_header("INN");
    if (r == SERVICE_OK) {
        sfx_beep(2);
        printf("You fully recovered!  (Fee: %dG)\n\n", INN_FEE_PER_PERSON * n);
    } else {
        sfx_beep(4);
        printf("Not enough gold...  (Need: %dG)\n\n", INN_FEE_PER_PERSON * n);
    }
    ui_wait_enter("Press Enter...");
}

static void on_church(Character party[], int n, int *gold) {
    int dead = service_count_dead(party, n);
    ServiceResult r = service_church(party, n, gold);

    ui_print_header("CHURCH");
    if (r == SERVICE_OK) {
        sfx_beep(0);
        printf("Revived %d member(s)!  (Fee: %dG)\n\n", dead, CHURCH_FEE_PER_REVIVE * dead);
    } else if (r == SERVICE_NOTHING_TO_REVIVE) {
        sfx_beep(4);
        printf("No one needs revival.\n\n");
    } else {
        sfx_beep(4);
        printf("Not enough gold...  (Need: %dG)\n\n", CHURCH_FEE_PER_REVIVE * dead);
    }
    ui_wait_enter("Press Enter...");
}

そして移動後の判定を以下のようにします。

char stepped = map_try_move(&m, &px, &py, dx, dy);

if (stepped == TILE_INN) {
    on_inn(party, party_count, &gold);
} else if (stepped == TILE_CHURCH) {
    on_church(party, party_count, &gold);
}

動作確認のための小テスト(戦闘不能を作る)

教会の復活を確認したいので、テスト用に「誰かをHP=0にする」コマンドを一時的に作ると早いです。

例:9 を押すと戦闘不能者を作る(デバッグ)

if (cmd == 9) {
    party[1].hp = 0; /* Mage を戦闘不能にする */
    ui_print_header("DEBUG");
    printf("%s is now down (HP=0).\n\n", party[1].name);
    ui_wait_enter("Press Enter...");
    continue;
}

このSTEPのまとめ

  • services層は「施設の効果」を担当し、UIとは分離できた。
  • 宿は「全員全回復+人数課金」
  • 教会は「戦闘不能者の復活+復活人数課金」
  • mainは「踏んだタイル → services呼び出し → ui表示」の接続だけを担当

次のSTEP07(予告)

次は items層 に進めるのが自然です。

  • 宝箱(T)を踏んだらアイテム獲得
  • 武器・杖などの装備で攻撃力/魔力が上がる。
  • 取得・所持・装備の管理を items層で持つ。