
STEP14:全プログラム完全掲載と総合解説(main.c?battle.cまで一式)
STEP14では、これまでSTEP01〜STEP13で作ってきた 分割済みマップ探索RPG を「プロジェクト一式として完全掲載」します。
Visual Studio(C言語)でそのままビルドできるように、Source Files / Header Files すべて載せます。
プロジェクト構成(Visual Studio)
Source Files
- main.c
- platform.c
- input.c
- ui.c
- map.c
- services.c
- items.c
- characters.c
- enemies.c
- battle.c
Header Files
- common.h
- platform.h
- input.h
- ui.h
- map.h
- services.h
- items.h
- characters.h
- enemies.h
- battle.h
全体の流れ(ゲームループ概要)
| 処理 | 担当 | 内容 |
|---|---|---|
| タイトル表示 | ui | スタート/遊び方/終了 |
| 初期化 | characters/items/map | パーティ生成、初期装備、マップ生成、宝箱固定配置 |
| 探索ループ | map/main | マップ描画→入力→移動→マス判定(敵/宝箱/施設/ボス) |
| 戦闘 | battle/enemies/characters/items | ターン制、行動選択、敵AI、勝利処理、経験値・お金 |
| 施設 | services | 宿で全回復、教会で復活 |
| エンディング | ui | ラストボス撃破後に導入→終了 |
Visual Studioでの実行手順(最短)
- Visual Studio → 新規作成 → 空のプロジェクト(C++)
- プロジェクトに .c と .h を全部追加
- Cとしてコンパイルされるように
それぞれのファイル拡張子が .c になっていることを確認 - 実行(Ctrl + F5)
全プログラム完全掲載
以下、コピペでそのまま動く「完成版一式」です。
(難しそうに見えますが、各層が役割分担しているので、修正もしやすい構造になっています)
common.h
#ifndef COMMON_H
#define COMMON_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#ifdef _WIN32
#include <windows.h>
#endif
#define MAP_W 25
#define MAP_H 25
#define PARTY_SIZE 3
#define MAX_ENEMIES 3
#define NAME_LEN 32
#define MAX_ITEMS 16
#define MAX_INV_SLOTS 32
#define MAX_SPELLS 8
#define CLAMP(x, lo, hi) ((x) < (lo) ? (lo) : ((x) > (hi) ? (hi) : (x)))
typedef enum { false = 0, true = 1 } bool;
#endifplatform.h
#ifndef PLATFORM_H
#define PLATFORM_H
#include "common.h"
typedef enum {
COL_DEFAULT = 0,
COL_TITLE,
COL_HP,
COL_MP,
COL_WARN,
COL_GOOD
} ColorKind;
void platform_init(void);
void clear_screen(void);
void sleep_ms(int ms);
void sfx_beep(int kind);
void set_color(ColorKind c);
void reset_color(void);
#endifplatform.c
#include "platform.h"
static int g_ansi_ok = 0;
void platform_init(void) {
#ifdef _WIN32
/* WindowsでANSIカラーを有効化(可能なら) */
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hOut != INVALID_HANDLE_VALUE) {
DWORD mode = 0;
if (GetConsoleMode(hOut, &mode)) {
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
if (SetConsoleMode(hOut, mode)) {
g_ansi_ok = 1;
}
}
}
#else
g_ansi_ok = 1;
#endif
}
void clear_screen(void) {
#ifdef _WIN32
system("cls");
#else
system("clear");
#endif
}
void sleep_ms(int ms) {
#ifdef _WIN32
Sleep((DWORD)ms);
#else
struct timespec ts;
ts.tv_sec = ms / 1000;
ts.tv_nsec = (ms % 1000) * 1000000L;
nanosleep(&ts, NULL);
#endif
}
void sfx_beep(int kind) {
#ifdef _WIN32
switch (kind) {
case 0: Beep(880, 50); break; /* 決定 */
case 1: Beep(660, 60); break; /* 攻撃 */
case 2: Beep(988, 60); break; /* 回復 */
case 3: Beep(1046, 80); break;/* 勝利 */
case 4: Beep(220, 120); break;/* 警告 */
default: Beep(440, 50); break;
}
#else
(void)kind;
putchar('\a');
#endif
}
static const char* ansi_of(ColorKind c) {
switch (c) {
case COL_TITLE: return "\x1b[1;36m";
case COL_HP: return "\x1b[1;31m";
case COL_MP: return "\x1b[1;34m";
case COL_WARN: return "\x1b[1;33m";
case COL_GOOD: return "\x1b[1;32m";
default: return "\x1b[0m";
}
}
void set_color(ColorKind c) {
if (!g_ansi_ok) return;
fputs(ansi_of(c), stdout);
}
void reset_color(void) {
if (!g_ansi_ok) return;
fputs("\x1b[0m", stdout);
}input.h
#ifndef INPUT_H
#define INPUT_H
#include "common.h"
int input_read_int_range(const char* prompt, int minv, int maxv);
char input_read_command(const char* prompt, const char* validChars);
#endifinput.c
#include "input.h"
static void trim_newline(char* s) {
size_t n = strlen(s);
if (n > 0 && (s[n - 1] == '\n' || s[n - 1] == '\r')) s[n - 1] = '\0';
n = strlen(s);
if (n > 0 && (s[n - 1] == '\n' || s[n - 1] == '\r')) s[n - 1] = '\0';
}
int input_read_int_range(const char* prompt, int minv, int maxv) {
char buf[128];
for (;;) {
printf("%s (%d-%d): ", prompt, minv, maxv);
if (!fgets(buf, (int)sizeof(buf), stdin)) {
clearerr(stdin);
continue;
}
trim_newline(buf);
char* p = buf;
while (*p && isspace((unsigned char)*p)) p++;
if (*p == '\0') continue;
char* end = NULL;
long v = strtol(p, &end, 10);
if (end == p) continue;
while (*end && isspace((unsigned char)*end)) end++;
if (*end != '\0') continue;
if (v < minv || v > maxv) continue;
return (int)v;
}
}
char input_read_command(const char* prompt, const char* validChars) {
char buf[128];
for (;;) {
printf("%s [%s]: ", prompt, validChars);
if (!fgets(buf, (int)sizeof(buf), stdin)) {
clearerr(stdin);
continue;
}
char c = '\0';
for (int i = 0; buf[i]; i++) {
if (!isspace((unsigned char)buf[i])) {
c = (char)toupper((unsigned char)buf[i]);
break;
}
}
if (c == '\0') continue;
for (int i = 0; validChars[i]; i++) {
if (c == (char)toupper((unsigned char)validChars[i])) return c;
}
}
}items.h
#ifndef ITEMS_H
#define ITEMS_H
#include "common.h"
typedef enum {
IT_NONE = 0,
IT_POTION,
IT_HIPOTION,
IT_ETHER,
IT_SWORD,
IT_STAFF,
IT_SHIELD
} ItemId;
typedef enum {
ITK_CONSUME = 0,
ITK_EQUIP
} ItemKind;
typedef struct {
ItemId id;
char name[NAME_LEN];
ItemKind kind;
int price;
int power; /* 回復量や装備補正 */
int magpow; /* 魔力補正など */
} ItemDef;
typedef struct {
ItemId id;
int count;
} InvSlot;
typedef struct {
InvSlot slots[MAX_INV_SLOTS];
int gold;
} Inventory;
const ItemDef* items_get_def(ItemId id);
void items_init_inventory(Inventory* inv);
bool items_add(Inventory* inv, ItemId id, int count);
bool items_remove(Inventory* inv, ItemId id, int count);
int items_count(const Inventory* inv, ItemId id);
void items_print_inventory(const Inventory* inv);
#endifitems.c
#include "items.h"
#include "platform.h"
static ItemDef g_defs[] = {
{ IT_NONE, "None", ITK_CONSUME, 0, 0, 0 },
{ IT_POTION, "Potion", ITK_CONSUME, 30, 80, 0 },
{ IT_HIPOTION,"HiPotion",ITK_CONSUME, 90, 200, 0 },
{ IT_ETHER, "Ether", ITK_CONSUME, 80, 0, 60 },
{ IT_SWORD, "Sword", ITK_EQUIP, 150, 25, 0 },
{ IT_STAFF, "Staff", ITK_EQUIP, 150, 0, 25 },
{ IT_SHIELD, "Shield", ITK_EQUIP, 120, 12, 0 }
};
const ItemDef* items_get_def(ItemId id) {
for (int i = 0; i < (int)(sizeof(g_defs)/sizeof(g_defs[0])); i++) {
if (g_defs[i].id == id) return &g_defs[i];
}
return &g_defs[0];
}
void items_init_inventory(Inventory* inv) {
memset(inv, 0, sizeof(*inv));
inv->gold = 100;
items_add(inv, IT_POTION, 2);
}
static int find_slot(const Inventory* inv, ItemId id) {
for (int i = 0; i < MAX_INV_SLOTS; i++) {
if (inv->slots[i].id == id) return i;
}
return -1;
}
static int find_empty(const Inventory* inv) {
for (int i = 0; i < MAX_INV_SLOTS; i++) {
if (inv->slots[i].id == IT_NONE || inv->slots[i].count <= 0) return i;
}
return -1;
}
bool items_add(Inventory* inv, ItemId id, int count) {
if (id == IT_NONE || count <= 0) return false;
int idx = find_slot(inv, id);
if (idx < 0) idx = find_empty(inv);
if (idx < 0) return false;
inv->slots[idx].id = id;
inv->slots[idx].count += count;
return true;
}
bool items_remove(Inventory* inv, ItemId id, int count) {
if (id == IT_NONE || count <= 0) return false;
int idx = find_slot(inv, id);
if (idx < 0) return false;
if (inv->slots[idx].count < count) return false;
inv->slots[idx].count -= count;
if (inv->slots[idx].count <= 0) {
inv->slots[idx].id = IT_NONE;
inv->slots[idx].count = 0;
}
return true;
}
int items_count(const Inventory* inv, ItemId id) {
int idx = find_slot(inv, id);
if (idx < 0) return 0;
return inv->slots[idx].count;
}
void items_print_inventory(const Inventory* inv) {
printf("Gold: %d\n", inv->gold);
printf("Inventory:\n");
int shown = 0;
for (int i = 0; i < MAX_INV_SLOTS; i++) {
if (inv->slots[i].id != IT_NONE && inv->slots[i].count > 0) {
const ItemDef* d = items_get_def(inv->slots[i].id);
printf(" %2d) %-10s x%d\n", i + 1, d->name, inv->slots[i].count);
shown++;
}
}
if (!shown) printf(" (empty)\n");
}characters.h
#ifndef CHARACTERS_H
#define CHARACTERS_H
#include "common.h"
#include "items.h"
typedef enum {
JOB_HERO = 0,
JOB_WARRIOR,
JOB_MAGE
} JobId;
typedef enum {
SPELL_DMG = 0,
SPELL_HEAL,
SPELL_BUFF_ATK,
SPELL_BUFF_DEF
} SpellType;
typedef struct {
char name[NAME_LEN];
SpellType type;
int mp_cost;
int power; /* dmg/heal/buff量の基準 */
bool target_all;/* 全体対象かどうか(味方魔法用) */
} Spell;
typedef struct {
char name[NAME_LEN];
JobId job;
int level;
int exp;
int max_hp, hp;
int max_mp, mp;
int atk, def, mag, mdef, spd;
bool alive;
bool defending;
int buff_atk_turns;
int buff_def_turns;
int buff_atk_rate;
int buff_def_rate;
ItemId weapon;
ItemId shield;
Spell spells[MAX_SPELLS];
int spell_count;
} Character;
void characters_init_party(Character party[PARTY_SIZE], Inventory* inv);
void characters_full_heal(Character party[PARTY_SIZE]);
void characters_revive(Character* c, int hp_percent);
void characters_print_status(const Character party[PARTY_SIZE], const Inventory* inv);
int characters_gain_exp(Character* c, int exp);
void characters_apply_equipment(Character* c);
#endifcharacters.c
#include "characters.h"
#include "platform.h"
static void add_spell(Character* c, const char* name, SpellType type, int mp, int power, bool all) {
if (c->spell_count >= MAX_SPELLS) return;
Spell* s = &c->spells[c->spell_count++];
strncpy(s->name, name, sizeof(s->name)-1);
s->name[sizeof(s->name)-1] = '\0';
s->type = type;
s->mp_cost = mp;
s->power = power;
s->target_all = all;
}
static void init_char(Character* c, const char* name, JobId job) {
memset(c, 0, sizeof(*c));
strncpy(c->name, name, sizeof(c->name)-1);
c->job = job;
c->level = 1;
c->exp = 0;
c->alive = true;
c->buff_atk_rate = 130;
c->buff_def_rate = 140;
if (job == JOB_HERO) {
c->max_hp = 240; c->max_mp = 60;
c->atk = 24; c->def = 16; c->mag = 14; c->mdef = 12; c->spd = 14;
add_spell(c, "Fire", SPELL_DMG, 8, 18, false);
add_spell(c, "Heal", SPELL_HEAL, 10, 90, false);
add_spell(c, "Brave", SPELL_BUFF_ATK, 12, 0, false);
} else if (job == JOB_WARRIOR) {
c->max_hp = 320; c->max_mp = 30;
c->atk = 30; c->def = 22; c->mag = 6; c->mdef = 10; c->spd = 10;
add_spell(c, "WarCry", SPELL_BUFF_ATK, 10, 0, true);
} else {
c->max_hp = 200; c->max_mp = 120;
c->atk = 10; c->def = 12; c->mag = 30; c->mdef = 18; c->spd = 16;
add_spell(c, "Ice", SPELL_DMG, 8, 16, false);
add_spell(c, "MegaHeal", SPELL_HEAL, 16, 160, true);
add_spell(c, "Barrier", SPELL_BUFF_DEF, 14, 0, true);
}
c->hp = c->max_hp;
c->mp = c->max_mp;
c->weapon = IT_NONE;
c->shield = IT_NONE;
}
void characters_apply_equipment(Character* c) {
/* 装備は「ステータスに加算」方式ではなく「都度参照」方式にしたい場合があるので、
ここでは簡易に、装備アイテムのpower/magpowを反映する形にする */
/* 本サンプルでは base stat を直接上書きしない。
battle側で武器補正を参照する方針に合わせるため、ここは空でも動きます。 */
(void)c;
}
void characters_init_party(Character party[PARTY_SIZE], Inventory* inv) {
init_char(&party[0], "Hero", JOB_HERO);
init_char(&party[1], "Warrior", JOB_WARRIOR);
init_char(&party[2], "Mage", JOB_MAGE);
/* 初期装備を配布して装備 */
items_add(inv, IT_SWORD, 1);
items_add(inv, IT_SHIELD, 1);
items_add(inv, IT_STAFF, 1);
party[0].weapon = IT_SWORD;
party[1].weapon = IT_SWORD;
party[1].shield = IT_SHIELD;
party[2].weapon = IT_STAFF;
}
void characters_full_heal(Character party[PARTY_SIZE]) {
for (int i = 0; i < PARTY_SIZE; i++) {
party[i].hp = party[i].max_hp;
party[i].mp = party[i].max_mp;
party[i].alive = true;
party[i].defending = false;
party[i].buff_atk_turns = 0;
party[i].buff_def_turns = 0;
}
}
void characters_revive(Character* c, int hp_percent) {
if (!c) return;
if (c->alive) return;
c->alive = true;
c->hp = (c->max_hp * hp_percent) / 100;
if (c->hp < 1) c->hp = 1;
c->mp = c->max_mp / 2;
}
static int exp_to_next(int level) {
return 60 + (level - 1) * 40;
}
static void level_up(Character* c) {
c->level++;
if (c->job == JOB_HERO) {
c->max_hp += 30; c->max_mp += 10;
c->atk += 4; c->def += 3; c->mag += 3; c->mdef += 2; c->spd += 1;
if (c->level == 4) add_spell(c, "HealAll", SPELL_HEAL, 20, 120, true);
} else if (c->job == JOB_WARRIOR) {
c->max_hp += 45; c->max_mp += 6;
c->atk += 5; c->def += 4; c->mag += 1; c->mdef += 2; c->spd += 1;
} else {
c->max_hp += 22; c->max_mp += 18;
c->atk += 2; c->def += 2; c->mag += 6; c->mdef += 3; c->spd += 2;
if (c->level == 5) add_spell(c, "Blizzard", SPELL_DMG, 20, 22, true);
}
c->hp = c->max_hp;
c->mp = c->max_mp;
set_color(COL_GOOD);
printf("%s leveled up! -> Lv %d\n", c->name, c->level);
reset_color();
sfx_beep(3);
}
int characters_gain_exp(Character* c, int exp) {
if (!c || !c->alive) return 0;
c->exp += exp;
int ups = 0;
while (c->exp >= exp_to_next(c->level)) {
c->exp -= exp_to_next(c->level);
level_up(c);
ups++;
}
return ups;
}
void characters_print_status(const Character party[PARTY_SIZE], const Inventory* inv) {
printf("Gold: %d\n", inv->gold);
for (int i = 0; i < PARTY_SIZE; i++) {
const Character* c = &party[i];
printf("%d) %-8s Lv%2d ", i + 1, c->name, c->level);
set_color(COL_HP);
printf("HP %3d/%3d ", c->hp, c->max_hp);
reset_color();
set_color(COL_MP);
printf("MP %3d/%3d ", c->mp, c->max_mp);
reset_color();
printf("ATK %2d DEF %2d MAG %2d MDF %2d SPD %2d ",
c->atk, c->def, c->mag, c->mdef, c->spd);
if (!c->alive) {
set_color(COL_WARN);
printf("[DEAD]");
reset_color();
}
printf("\n");
}
}enemies.h
#ifndef ENEMIES_H
#define ENEMIES_H
#include "common.h"
typedef enum {
ENEMY_SLIME = 0,
ENEMY_GOBLIN,
ENEMY_WOLF,
ENEMY_MAGE,
ENEMY_GOLEM,
ENEMY_MASTER_DRAGON
} EnemyId;
typedef struct {
char name[NAME_LEN];
int max_hp, hp;
int max_mp, mp;
int atk, def, mag, mdef, spd;
bool alive;
bool defending;
int buff_atk_turns;
int buff_def_turns;
int buff_atk_rate;
int buff_def_rate;
EnemyId kind;
/* 敵専用魔法 */
struct {
char name[NAME_LEN];
int type; /* SPELL_DMG / SPELL_HEAL / BUFF_ATK / BUFF_DEF */
int mp_cost;
int power;
bool target_all;
} spells[MAX_SPELLS];
int spell_count;
int reward_exp;
int reward_gold;
} Enemy;
Enemy enemies_create(EnemyId id);
Enemy enemies_create_random_by_floor(int floor);
Enemy enemies_create_boss_master_dragon(void);
#endifenemies.c
#include "enemies.h"
#include <string.h>
static void add_spell(Enemy* e, const char* name, int type, int mp, int power, bool all) {
if (e->spell_count >= MAX_SPELLS) return;
int i = e->spell_count++;
strncpy(e->spells[i].name, name, sizeof(e->spells[i].name)-1);
e->spells[i].name[sizeof(e->spells[i].name)-1] = '\0';
e->spells[i].type = type;
e->spells[i].mp_cost = mp;
e->spells[i].power = power;
e->spells[i].target_all = all;
}
static Enemy make_enemy(const char* name, EnemyId id,
int hp, int mp, int atk, int def, int mag, int mdef, int spd,
int exp, int gold) {
Enemy e;
memset(&e, 0, sizeof(e));
strncpy(e.name, name, sizeof(e.name)-1);
e.kind = id;
e.max_hp = hp; e.hp = hp;
e.max_mp = mp; e.mp = mp;
e.atk = atk; e.def = def;
e.mag = mag; e.mdef = mdef;
e.spd = spd;
e.alive = true;
e.buff_atk_rate = 130;
e.buff_def_rate = 140;
e.reward_exp = exp;
e.reward_gold = gold;
return e;
}
Enemy enemies_create(EnemyId id) {
Enemy e;
switch (id) {
case ENEMY_SLIME:
e = make_enemy("Slime", id, 90, 20, 10, 6, 8, 5, 10, 20, 15);
add_spell(&e, "TinyHeal", 1, 10, 40, false);
break;
case ENEMY_GOBLIN:
e = make_enemy("Goblin", id, 140, 20, 16, 10, 6, 6, 12, 35, 25);
break;
case ENEMY_WOLF:
e = make_enemy("Wolf", id, 120, 10, 18, 8, 4, 6, 18, 38, 28);
break;
case ENEMY_MAGE:
e = make_enemy("DarkMage", id, 110, 60, 10, 8, 22, 14, 14, 45, 35);
add_spell(&e, "DarkBolt", 0, 12, 18, false);
add_spell(&e, "Heal", 1, 14, 70, false);
add_spell(&e, "Hex", 2, 12, 0, false); /* atk buff */
break;
case ENEMY_GOLEM:
e = make_enemy("Golem", id, 210, 20, 22, 18, 8, 12, 8, 62, 50);
add_spell(&e, "StoneSkin", 3, 14, 0, false); /* def buff */
break;
case ENEMY_MASTER_DRAGON:
e = enemies_create_boss_master_dragon();
break;
default:
e = make_enemy("???", ENEMY_SLIME, 50, 0, 8, 6, 0, 0, 8, 10, 10);
break;
}
return e;
}
Enemy enemies_create_random_by_floor(int floor) {
/* floorは探索進行度っぽい値として使う(0〜) */
int r = rand() % 100;
if (floor <= 1) {
if (r < 50) return enemies_create(ENEMY_SLIME);
if (r < 80) return enemies_create(ENEMY_GOBLIN);
return enemies_create(ENEMY_WOLF);
} else if (floor <= 3) {
if (r < 35) return enemies_create(ENEMY_GOBLIN);
if (r < 65) return enemies_create(ENEMY_WOLF);
if (r < 85) return enemies_create(ENEMY_MAGE);
return enemies_create(ENEMY_GOLEM);
} else {
if (r < 25) return enemies_create(ENEMY_WOLF);
if (r < 55) return enemies_create(ENEMY_MAGE);
return enemies_create(ENEMY_GOLEM);
}
}
Enemy enemies_create_boss_master_dragon(void) {
Enemy e = make_enemy("MasterDragon", ENEMY_MASTER_DRAGON,
900, 180, 55, 30, 60, 25, 14, 420, 350);
add_spell(&e, "DragonBreath", 0, 25, 22, true); /* 全体ダメ */
add_spell(&e, "AncientRoar", 2, 18, 0, false); /* atk buff */
add_spell(&e, "DarkRegen", 1, 20, 180,false); /* heal */
return e;
}services.h
#ifndef SERVICES_H
#define SERVICES_H
#include "common.h"
#include "characters.h"
#include "items.h"
void services_inn(Character party[PARTY_SIZE], Inventory* inv);
void services_church(Character party[PARTY_SIZE], Inventory* inv);
#endifservices.c
#include "services.h"
#include "platform.h"
#include "input.h"
void services_inn(Character party[PARTY_SIZE], Inventory* inv) {
const int fee = 40;
clear_screen();
set_color(COL_TITLE);
printf("INN\n");
reset_color();
printf("Welcome! Full recovery costs %d gold.\n", fee);
printf("Your gold: %d\n", inv->gold);
if (inv->gold < fee) {
set_color(COL_WARN);
printf("Not enough gold...\n");
reset_color();
sfx_beep(4);
printf("Press Enter...\n");
getchar();
return;
}
char c = input_read_command("Stay at the inn?", "Y N");
if (c == 'Y') {
inv->gold -= fee;
characters_full_heal(party);
set_color(COL_GOOD);
printf("Fully recovered!\n");
reset_color();
sfx_beep(2);
sleep_ms(600);
}
}
void services_church(Character party[PARTY_SIZE], Inventory* inv) {
const int fee = 60;
clear_screen();
set_color(COL_TITLE);
printf("CHURCH\n");
reset_color();
printf("Revival service costs %d gold (revive all dead with 50%% HP).\n", fee);
printf("Your gold: %d\n", inv->gold);
bool anyDead = false;
for (int i = 0; i < PARTY_SIZE; i++) {
if (!party[i].alive) anyDead = true;
}
if (!anyDead) {
printf("No one needs revival.\n");
printf("Press Enter...\n");
getchar();
return;
}
if (inv->gold < fee) {
set_color(COL_WARN);
printf("Not enough gold...\n");
reset_color();
sfx_beep(4);
printf("Press Enter...\n");
getchar();
return;
}
char c = input_read_command("Receive revival?", "Y N");
if (c == 'Y') {
inv->gold -= fee;
for (int i = 0; i < PARTY_SIZE; i++) {
characters_revive(&party[i], 50);
}
set_color(COL_GOOD);
printf("Blessings granted. The fallen return...\n");
reset_color();
sfx_beep(2);
sleep_ms(600);
}
}ui.h
#ifndef UI_H
#define UI_H
#include "common.h"
#include "characters.h"
#include "items.h"
int ui_title_menu(void);
void ui_show_help(void);
void ui_show_status(const Character party[PARTY_SIZE], const Inventory* inv);
void ui_show_ending_intro(void);
void ui_show_ending(void);
#endifui.c
#include "ui.h"
#include "platform.h"
#include "input.h"
int ui_title_menu(void) {
clear_screen();
set_color(COL_TITLE);
printf("========================================\n");
printf(" MAP RPG (C / Console)\n");
printf("========================================\n");
reset_color();
printf("1) Start\n");
printf("2) How to play\n");
printf("3) Exit\n");
return input_read_int_range("Select", 1, 3);
}
void ui_show_help(void) {
clear_screen();
set_color(COL_TITLE);
printf("HOW TO PLAY\n");
reset_color();
printf("Move: 8=Up 2=Down 4=Left 6=Right\n");
printf("Tiles:\n");
printf(" # Wall\n");
printf(" . Floor\n");
printf(" C Chest (fixed items)\n");
printf(" E Enemy (battle)\n");
printf(" I Inn (full heal)\n");
printf(" H Church (revive)\n");
printf(" B Boss room (Master Dragon)\n");
printf("\nBattle:\n");
printf(" Attack / Skill / Item / Defend / Run (not for boss)\n");
printf("\nPress Enter...\n");
getchar();
}
void ui_show_status(const Character party[PARTY_SIZE], const Inventory* inv) {
clear_screen();
set_color(COL_TITLE);
printf("STATUS\n");
reset_color();
characters_print_status(party, inv);
printf("\nPress Enter...\n");
getchar();
}
void ui_show_ending_intro(void) {
clear_screen();
set_color(COL_TITLE);
printf("VICTORY\n");
reset_color();
printf("The Master Dragon has fallen...\n");
printf("A warm light spreads across the land.\n");
printf("Your journey is finally over.\n");
printf("\nPress Enter...\n");
getchar();
}
void ui_show_ending(void) {
clear_screen();
set_color(COL_TITLE);
printf("THE END\n");
reset_color();
printf("Thank you for playing!\n");
printf("\nPress Enter...\n");
getchar();
}map.h
#ifndef MAP_H
#define MAP_H
#include "common.h"
#include "items.h"
typedef enum {
TILE_FLOOR = 0,
TILE_WALL,
TILE_ENEMY,
TILE_CHEST,
TILE_INN,
TILE_CHURCH,
TILE_BOSS
} TileKind;
typedef struct {
int x, y;
} Pos;
typedef struct {
TileKind tiles[MAP_H][MAP_W];
bool chest_opened[MAP_H][MAP_W];
} Map;
void map_init(Map* m);
void map_draw(const Map* m, const Pos* player);
bool map_in_bounds(int x, int y);
TileKind map_get(const Map* m, int x, int y);
void map_set(Map* m, int x, int y, TileKind t);
ItemId map_open_chest(Map* m, int x, int y);
int map_calc_floor(const Pos* player);
#endifmap.c
#include "map.h"
#include "platform.h"
static void fill(Map* m, TileKind t) {
for (int y = 0; y < MAP_H; y++) {
for (int x = 0; x < MAP_W; x++) {
m->tiles[y][x] = t;
m->chest_opened[y][x] = false;
}
}
}
bool map_in_bounds(int x, int y) {
return (x >= 0 && x < MAP_W && y >= 0 && y < MAP_H);
}
TileKind map_get(const Map* m, int x, int y) {
if (!map_in_bounds(x, y)) return TILE_WALL;
return m->tiles[y][x];
}
void map_set(Map* m, int x, int y, TileKind t) {
if (!map_in_bounds(x, y)) return;
m->tiles[y][x] = t;
}
static char tile_char(TileKind t, bool opened) {
switch (t) {
case TILE_WALL: return '#';
case TILE_FLOOR: return '.';
case TILE_ENEMY: return 'E';
case TILE_CHEST: return opened ? 'o' : 'C';
case TILE_INN: return 'I';
case TILE_CHURCH:return 'H';
case TILE_BOSS: return 'B';
default: return '?';
}
}
void map_draw(const Map* m, const Pos* player) {
for (int y = 0; y < MAP_H; y++) {
for (int x = 0; x < MAP_W; x++) {
if (player && player->x == x && player->y == y) {
putchar('@');
} else {
putchar(tile_char(m->tiles[y][x], m->chest_opened[y][x]));
}
}
putchar('\n');
}
}
static void make_walls(Map* m) {
for (int y = 0; y < MAP_H; y++) {
for (int x = 0; x < MAP_W; x++) {
if (x == 0 || y == 0 || x == MAP_W - 1 || y == MAP_H - 1) {
m->tiles[y][x] = TILE_WALL;
}
}
}
/* 中央に迷路っぽい壁 */
for (int y = 4; y < MAP_H - 4; y += 2) {
for (int x = 3; x < MAP_W - 3; x++) {
if (rand() % 100 < 35) m->tiles[y][x] = TILE_WALL;
}
}
}
static void place_fixed(Map* m) {
/* 施設 */
m->tiles[2][2] = TILE_INN;
m->tiles[2][3] = TILE_FLOOR;
m->tiles[3][2] = TILE_FLOOR;
m->tiles[2][MAP_W - 3] = TILE_CHURCH;
/* ボス部屋(最深部) */
m->tiles[MAP_H - 3][MAP_W - 3] = TILE_BOSS;
/* 宝箱(固定配置) */
m->tiles[5][5] = TILE_CHEST;
m->tiles[8][12] = TILE_CHEST;
m->tiles[15][7] = TILE_CHEST;
m->tiles[18][18] = TILE_CHEST;
/* 敵(固定配置) */
m->tiles[6][10] = TILE_ENEMY;
m->tiles[10][6] = TILE_ENEMY;
m->tiles[12][14] = TILE_ENEMY;
m->tiles[20][10] = TILE_ENEMY;
}
void map_init(Map* m) {
fill(m, TILE_FLOOR);
make_walls(m);
place_fixed(m);
}
ItemId map_open_chest(Map* m, int x, int y) {
if (!map_in_bounds(x, y)) return IT_NONE;
if (m->tiles[y][x] != TILE_CHEST) return IT_NONE;
if (m->chest_opened[y][x]) return IT_NONE;
m->chest_opened[y][x] = true;
/* 固定配置アイテム(場所で決める) */
if (x == 5 && y == 5) return IT_POTION;
if (x == 12 && y == 8) return IT_HIPOTION;
if (x == 7 && y == 15) return IT_ETHER;
if (x == 18 && y == 18) return (rand() % 2 == 0) ? IT_SWORD : IT_STAFF;
return IT_POTION;
}
int map_calc_floor(const Pos* player) {
/* 探索進行度っぽく座標から算出(簡易) */
int d = player->x + player->y;
if (d < 15) return 1;
if (d < 28) return 2;
if (d < 38) return 3;
return 4;
}battle.h
#ifndef BATTLE_H
#define BATTLE_H
#include "common.h"
#include "characters.h"
#include "enemies.h"
#include "items.h"
int battle_random_encounter(Character party[PARTY_SIZE], Inventory* inv, int floor);
int battle_boss_master_dragon(Character party[PARTY_SIZE], Inventory* inv);
#endifbattle.c
#include "battle.h"
#include "platform.h"
#include "input.h"
static int alive_party_count(const Character party[PARTY_SIZE]) {
int n = 0;
for (int i = 0; i < PARTY_SIZE; i++) if (party[i].alive) n++;
return n;
}
static int alive_enemy_count(const Enemy enemies[], int n) {
int c = 0;
for (int i = 0; i < n; i++) if (enemies[i].alive) c++;
return c;
}
static int variance(int base) {
int v = base + (rand() % 7) - 3;
if (v < 1) v = 1;
return v;
}
static int char_atk_value(const Character* c) {
int atk = c->atk;
if (c->buff_atk_turns > 0) atk = atk * c->buff_atk_rate / 100;
const ItemDef* w = items_get_def(c->weapon);
if (w->kind == ITK_EQUIP) atk += w->power;
return atk;
}
static int char_def_value(const Character* c) {
int def = c->def;
if (c->buff_def_turns > 0) def = def * c->buff_def_rate / 100;
const ItemDef* sh = items_get_def(c->shield);
if (sh->kind == ITK_EQUIP) def += sh->power;
return def;
}
static int char_mag_value(const Character* c) {
int mag = c->mag;
const ItemDef* w = items_get_def(c->weapon);
if (w->kind == ITK_EQUIP) mag += w->magpow;
return mag;
}
static int enemy_atk_value(const Enemy* e) {
int atk = e->atk;
if (e->buff_atk_turns > 0) atk = atk * e->buff_atk_rate / 100;
return atk;
}
static int enemy_def_value(const Enemy* e) {
int def = e->def;
if (e->buff_def_turns > 0) def = def * e->buff_def_rate / 100;
return def;
}
static void apply_damage_char(Character* c, int dmg) {
if (!c->alive) return;
if (c->defending) dmg = dmg * 65 / 100;
c->hp -= dmg;
if (c->hp <= 0) {
c->hp = 0;
c->alive = false;
}
}
static void apply_damage_enemy(Enemy* e, int dmg) {
if (!e->alive) return;
if (e->defending) dmg = dmg * 65 / 100;
e->hp -= dmg;
if (e->hp <= 0) {
e->hp = 0;
e->alive = false;
}
}
static void battle_print_party(const Character party[PARTY_SIZE]) {
for (int i = 0; i < PARTY_SIZE; i++) {
const Character* c = &party[i];
printf("%d) %-8s ", i + 1, c->name);
if (!c->alive) {
set_color(COL_WARN);
printf("DEAD\n");
reset_color();
continue;
}
set_color(COL_HP);
printf("HP %3d/%3d ", c->hp, c->max_hp);
reset_color();
set_color(COL_MP);
printf("MP %3d/%3d", c->mp, c->max_mp);
reset_color();
if (c->defending) printf(" [DEF]");
if (c->buff_atk_turns > 0) printf(" [ATK+%d]", c->buff_atk_turns);
if (c->buff_def_turns > 0) printf(" [DEF+%d]", c->buff_def_turns);
printf("\n");
}
}
static void battle_print_enemies(const Enemy enemies[], int n) {
for (int i = 0; i < n; i++) {
const Enemy* e = &enemies[i];
printf("%d) %-12s ", i + 1, e->name);
if (!e->alive) {
set_color(COL_WARN);
printf("DEAD\n");
reset_color();
continue;
}
set_color(COL_HP);
printf("HP %3d/%3d ", e->hp, e->max_hp);
reset_color();
set_color(COL_MP);
printf("MP %3d/%3d", e->mp, e->max_mp);
reset_color();
if (e->defending) printf(" [DEF]");
if (e->buff_atk_turns > 0) printf(" [ATK+%d]", e->buff_atk_turns);
if (e->buff_def_turns > 0) printf(" [DEF+%d]", e->buff_def_turns);
printf("\n");
}
}
static int pick_alive_enemy(const Enemy enemies[], int n) {
for (;;) {
int t = input_read_int_range("Target enemy", 1, n) - 1;
if (enemies[t].alive) return t;
}
}
static int pick_alive_ally(const Character party[PARTY_SIZE], const char* prompt) {
for (;;) {
int t = input_read_int_range(prompt, 1, PARTY_SIZE) - 1;
if (party[t].alive) return t;
printf("That ally is down.\n");
}
}
static void tick_buffs_party(Character party[PARTY_SIZE]) {
for (int i = 0; i < PARTY_SIZE; i++) {
if (party[i].buff_atk_turns > 0) party[i].buff_atk_turns--;
if (party[i].buff_def_turns > 0) party[i].buff_def_turns--;
party[i].defending = false;
}
}
static void tick_buffs_enemies(Enemy enemies[], int n) {
for (int i = 0; i < n; i++) {
if (enemies[i].buff_atk_turns > 0) enemies[i].buff_atk_turns--;
if (enemies[i].buff_def_turns > 0) enemies[i].buff_def_turns--;
enemies[i].defending = false;
}
}
static void do_attack_char_to_enemy(Character* c, Enemy* e) {
int atk = char_atk_value(c);
int def = enemy_def_value(e);
int base = atk - def / 2;
int dmg = variance(base);
apply_damage_enemy(e, dmg);
printf("%s attacks %s for %d damage!\n", c->name, e->name, dmg);
sfx_beep(1);
}
static void do_attack_enemy_to_char(Enemy* e, Character* c) {
int atk = enemy_atk_value(e);
int def = char_def_value(c);
int base = atk - def / 2;
int dmg = variance(base);
apply_damage_char(c, dmg);
printf("%s attacks %s for %d damage!\n", e->name, c->name, dmg);
sfx_beep(1);
}
static void cast_spell_char(Character* c, Character party[PARTY_SIZE], Enemy enemies[], int en, const Spell* sp) {
if (c->mp < sp->mp_cost) {
set_color(COL_WARN);
printf("Not enough MP!\n");
reset_color();
sfx_beep(4);
return;
}
c->mp -= sp->mp_cost;
printf("%s casts %s!\n", c->name, sp->name);
sfx_beep(0);
if (sp->type == SPELL_DMG) {
if (sp->target_all) {
for (int i = 0; i < en; i++) {
if (!enemies[i].alive) continue;
int base = char_mag_value(c) * sp->power / 10 - enemies[i].mdef;
int dmg = variance(base);
apply_damage_enemy(&enemies[i], dmg);
printf(" -> %s takes %d damage!\n", enemies[i].name, dmg);
}
} else {
int t = pick_alive_enemy(enemies, en);
int base = char_mag_value(c) * sp->power / 10 - enemies[t].mdef;
int dmg = variance(base);
apply_damage_enemy(&enemies[t], dmg);
printf(" -> %s takes %d damage!\n", enemies[t].name, dmg);
}
} else if (sp->type == SPELL_HEAL) {
if (sp->target_all) {
for (int i = 0; i < PARTY_SIZE; i++) {
if (!party[i].alive) continue;
int heal = variance(sp->power);
party[i].hp = CLAMP(party[i].hp + heal, 0, party[i].max_hp);
printf(" -> %s recovers %d HP!\n", party[i].name, heal);
}
} else {
int t = pick_alive_ally(party, "Heal target");
int heal = variance(sp->power);
party[t].hp = CLAMP(party[t].hp + heal, 0, party[t].max_hp);
printf(" -> %s recovers %d HP!\n", party[t].name, heal);
}
sfx_beep(2);
} else if (sp->type == SPELL_BUFF_ATK) {
if (sp->target_all) {
for (int i = 0; i < PARTY_SIZE; i++) {
if (!party[i].alive) continue;
party[i].buff_atk_turns = 3;
}
printf(" -> Party ATK up (3 turns)\n");
} else {
int t = pick_alive_ally(party, "Buff target");
party[t].buff_atk_turns = 3;
printf(" -> %s ATK up (3 turns)\n", party[t].name);
}
} else if (sp->type == SPELL_BUFF_DEF) {
if (sp->target_all) {
for (int i = 0; i < PARTY_SIZE; i++) {
if (!party[i].alive) continue;
party[i].buff_def_turns = 3;
}
printf(" -> Party DEF up (3 turns)\n");
} else {
int t = pick_alive_ally(party, "Buff target");
party[t].buff_def_turns = 3;
printf(" -> %s DEF up (3 turns)\n", party[t].name);
}
}
}
static void use_item_in_battle(Character party[PARTY_SIZE], Inventory* inv) {
clear_screen();
items_print_inventory(inv);
printf("\nUse which item?\n");
printf("0) Cancel\n");
int sel = input_read_int_range("Select slot number (0 to cancel)", 0, MAX_INV_SLOTS);
if (sel == 0) return;
int idx = sel - 1;
if (idx < 0 || idx >= MAX_INV_SLOTS) return;
if (inv->slots[idx].id == IT_NONE || inv->slots[idx].count <= 0) return;
ItemId id = inv->slots[idx].id;
const ItemDef* d = items_get_def(id);
if (d->kind != ITK_CONSUME) {
printf("That item cannot be used now.\n");
getchar();
return;
}
if (id == IT_POTION || id == IT_HIPOTION) {
int t = pick_alive_ally(party, "Use on");
int heal = d->power;
party[t].hp = CLAMP(party[t].hp + heal, 0, party[t].max_hp);
items_remove(inv, id, 1);
printf("%s used %s. %s recovers %d HP.\n", party[t].name, d->name, party[t].name, heal);
sfx_beep(2);
sleep_ms(400);
} else if (id == IT_ETHER) {
int t = pick_alive_ally(party, "Use on");
int mp = d->magpow;
party[t].mp = CLAMP(party[t].mp + mp, 0, party[t].max_mp);
items_remove(inv, id, 1);
printf("%s used %s. %s recovers %d MP.\n", party[t].name, d->name, party[t].name, mp);
sfx_beep(2);
sleep_ms(400);
}
}
static int enemy_hp_percent(const Enemy* e) {
return (e->hp * 100) / e->max_hp;
}
static int char_hp_percent(const Character* c) {
return (c->hp * 100) / c->max_hp;
}
static int pick_random_alive_party(Character party[PARTY_SIZE]) {
int alive[PARTY_SIZE];
int n = 0;
for (int i = 0; i < PARTY_SIZE; i++) if (party[i].alive) alive[n++] = i;
if (n <= 0) return 0;
return alive[rand() % n];
}
static void enemy_cast_spell(Enemy* e, Character party[PARTY_SIZE], Enemy enemies[], int en, int si) {
(void)enemies; (void)en;
int type = e->spells[si].type;
int mp_cost = e->spells[si].mp_cost;
int power = e->spells[si].power;
bool all = e->spells[si].target_all;
if (e->mp < mp_cost) return;
e->mp -= mp_cost;
printf("%s uses %s!\n", e->name, e->spells[si].name);
sfx_beep(0);
if (type == SPELL_DMG) {
if (all) {
for (int i = 0; i < PARTY_SIZE; i++) {
if (!party[i].alive) continue;
int base = e->mag * power / 10 - party[i].mdef;
int dmg = variance(base);
apply_damage_char(&party[i], dmg);
printf(" -> %s takes %d damage!\n", party[i].name, dmg);
}
} else {
int t = pick_random_alive_party(party);
int base = e->mag * power / 10 - party[t].mdef;
int dmg = variance(base);
apply_damage_char(&party[t], dmg);
printf(" -> %s takes %d damage!\n", party[t].name, dmg);
}
} else if (type == SPELL_HEAL) {
int heal = variance(power);
e->hp = CLAMP(e->hp + heal, 0, e->max_hp);
printf(" -> %s recovers %d HP!\n", e->name, heal);
sfx_beep(2);
} else if (type == SPELL_BUFF_ATK) {
e->buff_atk_turns = 3;
printf(" -> %s ATK up (3 turns)\n", e->name);
} else if (type == SPELL_BUFF_DEF) {
e->buff_def_turns = 3;
printf(" -> %s DEF up (3 turns)\n", e->name);
}
}
static void enemy_ai_take_turn(Enemy* e, Character party[PARTY_SIZE], bool isBoss, int turn) {
if (!e->alive) return;
int hp = enemy_hp_percent(e);
/* ボス:周期ブレス + 条件強化 + 回復 */
if (isBoss) {
/* 3ターンごとに全体攻撃(MPあれば) */
if (turn % 3 == 0 && e->spell_count >= 1 && e->mp >= e->spells[0].mp_cost) {
enemy_cast_spell(e, party, NULL, 0, 0);
return;
}
/* 25%以下は怒りモード:50%でブレス */
if (hp <= 25 && e->spell_count >= 1 && e->mp >= e->spells[0].mp_cost) {
if ((rand() % 100) < 50) {
enemy_cast_spell(e, party, NULL, 0, 0);
return;
}
}
/* 強化が切れていたら20%で強化 */
if (e->buff_atk_turns <= 0 && e->spell_count >= 2 && e->mp >= e->spells[1].mp_cost) {
if ((rand() % 100) < 20) {
enemy_cast_spell(e, party, NULL, 0, 1);
return;
}
}
/* HP 50%以下は35%で回復 */
if (hp <= 50 && e->spell_count >= 3 && e->mp >= e->spells[2].mp_cost) {
if ((rand() % 100) < 35) {
enemy_cast_spell(e, party, NULL, 0, 2);
return;
}
}
}
/* 通常敵AI:HP割合で回復優先 + ランダム性 + 防御 */
if (hp <= 35) {
/* 回復魔法があるなら優先(50%) */
for (int i = 0; i < e->spell_count; i++) {
if (e->spells[i].type == SPELL_HEAL && e->mp >= e->spells[i].mp_cost) {
if ((rand() % 100) < 50) {
enemy_cast_spell(e, party, NULL, 0, i);
return;
}
}
}
}
/* 防御(15%) */
if ((rand() % 100) < 15) {
e->defending = true;
printf("%s defends.\n", e->name);
return;
}
/* 攻撃魔法があるなら20%で撃つ */
for (int i = 0; i < e->spell_count; i++) {
if (e->spells[i].type == SPELL_DMG && e->mp >= e->spells[i].mp_cost) {
if ((rand() % 100) < 20) {
enemy_cast_spell(e, party, NULL, 0, i);
return;
}
}
}
/* 通常攻撃 */
int t = pick_random_alive_party(party);
do_attack_enemy_to_char(e, &party[t]);
}
static void player_turn(Character party[PARTY_SIZE], Inventory* inv, Enemy enemies[], int en, int actor) {
Character* c = &party[actor];
if (!c->alive) return;
clear_screen();
set_color(COL_TITLE);
printf("BATTLE\n");
reset_color();
battle_print_party(party);
printf("\n");
battle_print_enemies(enemies, en);
printf("\n");
printf("%s's turn\n", c->name);
printf("1) Attack\n");
printf("2) Skill\n");
printf("3) Item\n");
printf("4) Defend\n");
printf("5) Run\n");
int sel = input_read_int_range("Select", 1, 5);
if (sel == 1) {
int t = pick_alive_enemy(enemies, en);
do_attack_char_to_enemy(c, &enemies[t]);
sleep_ms(400);
return;
}
if (sel == 2) {
if (c->spell_count <= 0) {
printf("No skills.\n");
sleep_ms(400);
return;
}
printf("\nSkills:\n");
for (int i = 0; i < c->spell_count; i++) {
printf("%d) %-10s MP %d\n", i + 1, c->spells[i].name, c->spells[i].mp_cost);
}
int s = input_read_int_range("Select skill", 1, c->spell_count) - 1;
cast_spell_char(c, party, enemies, en, &c->spells[s]);
sleep_ms(500);
return;
}
if (sel == 3) {
use_item_in_battle(party, inv);
return;
}
if (sel == 4) {
c->defending = true;
printf("%s defends.\n", c->name);
sleep_ms(300);
return;
}
if (sel == 5) {
/* 逃走は敵次第。ここでは50%成功 */
if ((rand() % 100) < 50) {
set_color(COL_GOOD);
printf("You escaped!\n");
reset_color();
sleep_ms(500);
/* 逃走成功を示す特殊値を使う */
c->hp = c->hp; /* 何もしない */
/* run成功は battle_main が扱う */
} else {
set_color(COL_WARN);
printf("Failed to escape!\n");
reset_color();
sfx_beep(4);
sleep_ms(500);
}
return;
}
}
static void distribute_rewards(Character party[PARTY_SIZE], Inventory* inv, int totalExp, int totalGold) {
inv->gold += totalGold;
set_color(COL_GOOD);
printf("Victory! +%d EXP, +%d gold\n", totalExp, totalGold);
reset_color();
sfx_beep(3);
for (int i = 0; i < PARTY_SIZE; i++) {
if (!party[i].alive) continue;
characters_gain_exp(&party[i], totalExp);
}
sleep_ms(800);
}
static int battle_core(Character party[PARTY_SIZE], Inventory* inv, Enemy enemies[], int en, bool isBoss) {
int turn = 1;
for (;;) {
if (alive_party_count(party) <= 0) {
set_color(COL_WARN);
printf("Party defeated...\n");
reset_color();
sfx_beep(4);
sleep_ms(800);
return 0;
}
if (alive_enemy_count(enemies, en) <= 0) {
int exp = 0, gold = 0;
for (int i = 0; i < en; i++) {
exp += enemies[i].reward_exp;
gold += enemies[i].reward_gold;
}
distribute_rewards(party, inv, exp, gold);
return 1;
}
/* ターン開始時:防御解除はターン終了時にまとめてやる */
/* 行動順:SPD順の簡易(味方→敵でループし、速度で乱数補正) */
int orderCount = PARTY_SIZE + en;
int orderIdx[PARTY_SIZE + MAX_ENEMIES];
int orderIsEnemy[PARTY_SIZE + MAX_ENEMIES];
int k = 0;
for (int i = 0; i < PARTY_SIZE; i++) { orderIdx[k] = i; orderIsEnemy[k] = 0; k++; }
for (int i = 0; i < en; i++) { orderIdx[k] = i; orderIsEnemy[k] = 1; k++; }
/* ソート(単純バブル) */
for (int i = 0; i < orderCount; i++) {
for (int j = 0; j < orderCount - 1; j++) {
int spA = orderIsEnemy[j] ? enemies[orderIdx[j]].spd : party[orderIdx[j]].spd;
int spB = orderIsEnemy[j+1] ? enemies[orderIdx[j+1]].spd : party[orderIdx[j+1]].spd;
spA += rand() % 3;
spB += rand() % 3;
if (spA < spB) {
int ti = orderIdx[j]; orderIdx[j] = orderIdx[j+1]; orderIdx[j+1] = ti;
int te = orderIsEnemy[j]; orderIsEnemy[j] = orderIsEnemy[j+1]; orderIsEnemy[j+1] = te;
}
}
}
/* 行動 */
for (int i = 0; i < orderCount; i++) {
if (alive_party_count(party) <= 0) break;
if (alive_enemy_count(enemies, en) <= 0) break;
if (orderIsEnemy[i] == 0) {
int actor = orderIdx[i];
if (!party[actor].alive) continue;
/* ボス戦は逃走不可 */
player_turn(party, inv, enemies, en, actor);
/* 逃走の判定:ここでは「Runを選んだ時に50%」だったので、簡易で再チェック */
/* 実装を簡単にするため:Runは成功したら即終了扱いにする */
/* ただしボスでは不可 */
/* このサンプルではUI上Runは出るが、ボスで選ぶと無効化したいなら改善ポイント */
if (!isBoss) {
/* 直前のターンで「You escaped!」を表示したら終了にしたいが、
ここでは簡略化し、行動選択でRunを選んだ場合はこの場で再抽選して終了 */
/* 改善するなら、player_turnに戻り値を持たせる */
}
} else {
int ei = orderIdx[i];
if (!enemies[ei].alive) continue;
enemy_ai_take_turn(&enemies[ei], party, isBoss, turn);
sleep_ms(450);
}
}
tick_buffs_party(party);
tick_buffs_enemies(enemies, en);
turn++;
}
}
int battle_random_encounter(Character party[PARTY_SIZE], Inventory* inv, int floor) {
int en = 1 + (rand() % 3);
if (floor >= 3 && en < 2) en = 2;
Enemy enemies[MAX_ENEMIES];
for (int i = 0; i < en; i++) {
enemies[i] = enemies_create_random_by_floor(floor);
}
return battle_core(party, inv, enemies, en, false);
}
int battle_boss_master_dragon(Character party[PARTY_SIZE], Inventory* inv) {
Enemy boss[1];
boss[0] = enemies_create_boss_master_dragon();
return battle_core(party, inv, boss, 1, true);
}main.c
#include "common.h"
#include "platform.h"
#include "input.h"
#include "ui.h"
#include "map.h"
#include "items.h"
#include "characters.h"
#include "services.h"
#include "battle.h"
static void press_enter(void) {
printf("\nPress Enter...\n");
getchar();
}
int main(void) {
srand((unsigned)time(NULL));
platform_init();
for (;;) {
int menu = ui_title_menu();
if (menu == 2) {
ui_show_help();
continue;
}
if (menu == 3) {
break;
}
Inventory inv;
items_init_inventory(&inv);
Character party[PARTY_SIZE];
characters_init_party(party, &inv);
Map m;
map_init(&m);
Pos player = { 1, 1 };
bool gameClear = false;
for (;;) {
clear_screen();
set_color(COL_TITLE);
printf("MAP RPG (Move: 8/2/4/6) Menu: M Quit: Q\n");
reset_color();
map_draw(&m, &player);
printf("\n");
characters_print_status(party, &inv);
printf("\nCommand: 8/2/4/6 move, M status, I inventory, Q quit\n");
char cmd = input_read_command("Input", "8 2 4 6 M I Q");
if (cmd == 'Q') {
break;
}
if (cmd == 'M') {
ui_show_status(party, &inv);
continue;
}
if (cmd == 'I') {
clear_screen();
items_print_inventory(&inv);
press_enter();
continue;
}
int nx = player.x;
int ny = player.y;
if (cmd == '8') ny--;
if (cmd == '2') ny++;
if (cmd == '4') nx--;
if (cmd == '6') nx++;
if (!map_in_bounds(nx, ny) || map_get(&m, nx, ny) == TILE_WALL) {
sfx_beep(4);
continue;
}
player.x = nx;
player.y = ny;
TileKind t = map_get(&m, nx, ny);
if (t == TILE_INN) {
services_inn(party, &inv);
continue;
}
if (t == TILE_CHURCH) {
services_church(party, &inv);
continue;
}
if (t == TILE_CHEST) {
clear_screen();
set_color(COL_TITLE);
printf("CHEST\n");
reset_color();
ItemId it = map_open_chest(&m, nx, ny);
if (it == IT_NONE) {
printf("The chest is empty.\n");
} else {
const ItemDef* d = items_get_def(it);
printf("You got %s!\n", d->name);
items_add(&inv, it, 1);
sfx_beep(0);
}
press_enter();
continue;
}
if (t == TILE_ENEMY) {
int floor = map_calc_floor(&player);
clear_screen();
printf("An enemy appears!\n");
sleep_ms(350);
int win = battle_random_encounter(party, &inv, floor);
if (!win) {
clear_screen();
set_color(COL_WARN);
printf("You were defeated...\n");
reset_color();
printf("Return to the church for revival.\n");
press_enter();
} else {
/* 勝利後:敵マスは床に */
map_set(&m, nx, ny, TILE_FLOOR);
}
continue;
}
if (t == TILE_BOSS) {
clear_screen();
set_color(COL_WARN);
printf("A terrifying presence fills the air...\n");
reset_color();
printf("The Master Dragon descends!\n");
sleep_ms(900);
int win = battle_boss_master_dragon(party, &inv);
if (win) {
gameClear = true;
break;
} else {
clear_screen();
set_color(COL_WARN);
printf("You were defeated by the Master Dragon...\n");
reset_color();
press_enter();
}
continue;
}
}
if (gameClear) {
ui_show_ending_intro();
ui_show_ending();
}
}
return 0;
}総合解説(完成版を理解するコツ)
「層構造」を意識すると読みやすい
| 層 | 代表関数 | 役割 |
|---|---|---|
| platform | clear_screen / sfx_beep / set_color | 端末依存の吸収 |
| input | input_read_int_range / input_read_command | 安全入力 |
| ui | ui_title_menu / ui_show_ending | 画面演出 |
| map | map_init / map_draw / map_open_chest | 25x25探索とイベント判定 |
| services | services_inn / services_church | 宿・教会 |
| items | items_add / items_print_inventory | インベントリとアイテム定義 |
| characters | characters_init_party / characters_gain_exp | 職業・成長・レベル |
| enemies | enemies_create_random_by_floor | 敵テンプレ・生成 |
| battle | battle_core | ターン制戦闘・AI・勝利処理 |
改造ポイント(次STEPへの布石)
- 「逃走」を正しく扱う(player_turnに戻り値を持たせて run 成功を battle_core に返す)
- 装備を「インベントリ→装備画面」で付け替えできるようにする(items層+ui層)
- 宝箱の中身を「テーブル化」して拡張しやすくする(map層で固定表を用意)
- 戦闘UIを強化(行動ログ、状態異常、ターゲット選択を見やすく)
