
STEP06:services層の実装(宿で全回復・教会で復活)
STEP06では、マップ上の施設イベントを担当する services層 を実装します。
ここでは 宿(INN)で全回復、教会(CHURCH)で復活(戦闘不能から蘇生) を行えるようにします。
このSTEPのゴール
- マップで I(宿)を踏む → パーティ全員のHP/MPが最大まで回復
- マップで C(教会)を踏む → 戦闘不能(HP=0)の仲間を復活(HPを一定値まで回復)
- UI層でイベント演出(メッセージ表示)を出す。
- キャラの状態変更ロジックは services層に閉じ込める(main/map/uiに散らさない)。
今回追加するファイル
| ファイル | 役割 |
|---|---|
| services.h | services層の公開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層で持つ。
