STEP02:platform層の実装(画面クリア・効果音・色表現の土台)

STEP02では、ゲーム全体で共通して使う「環境依存の処理」を platform 層として切り出します。
今回のRPGは Visual Studio(Windows)で動かすのが前提ですが、元の rpg1.c では Linux/Mac でも動くように #ifdef _WIN32 で分岐していましたよね。

このSTEPで作る platform 層は、ざっくり言うと次の2つを担当します。

  • 画面クリア(Windowsなら cls / Linuxなら clear)
  • 効果音(Windowsなら Beep / それ以外なら端末ベル)

そして「色表現(ANSIカラー)」は platform.c に実装するのではなく、common.h 側で定義して全モジュールから使える土台にしておきます。
(なので STEP02 では “platform層 + 色表現の土台(common.h)” という位置づけです)

platform層でやりたいこと(役割の整理)

platform層に置く処理の特徴はこれです。

種類なぜplatform層?
OS依存の処理system("cls") / system("clear")OSでコマンドが違う
OS依存のAPIBeep(周波数, ミリ秒)Windows固有の関数だから
環境によって動かない可能性printf("\a") のベル端末設定で鳴らないこともある

つまり、ゲームロジック(battle/map/itemsなど)がOS差分を気にしないようにするのがplatform層の価値です。

STEP02で登場するファイル

このSTEPで扱うのはこの3点です。

ファイル役割
common.h色コード(ANSIカラー)や共通定義、Windowsヘッダの取り込み
platform.hplatform層の公開関数(宣言)
platform.cplatform層の実装(画面クリア・効果音)

common.h:色表現の「土台」をここに置く理由

色(ANSIカラー)は、ui.c / map.c / battle.c など、あちこちで使います
だから platform.c に置くより、共通ヘッダ common.h に置いてしまうのがラクです。

掲載:common.h(該当箇所)

/* common.h */
#ifndef COMMON_H
#define COMMON_H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#ifdef _WIN32
#include <windows.h>
#endif

/* =========================================================
   ANSIカラー(対応していない端末ではそのまま文字列になります)
   ========================================================= */
#define RESET   "\x1b[0m"
#define RED     "\x1b[31m"
#define GREEN   "\x1b[32m"
#define YELLOW  "\x1b[33m"
#define BLUE    "\x1b[34m"
#define MAGENTA "\x1b[35m"
#define CYAN    "\x1b[36m"
#define WHITE   "\x1b[37m"

#endif

ここで出てくる命令・要素の解説

要素何をしている?ポイント
#ifdef _WIN32Windows環境のときだけ中を有効にするVisual Studio では基本 _WIN32 が定義されます
#include <windows.h>Beep などWindows APIの宣言を使えるようにするBeep を呼ぶなら必要
#define RED "\x1b[31m"文字列マクロの定義printfに混ぜて色を付ける

ANSIカラーの使い方(例)

printf(RED "赤い文字\n" RESET); のように、色の開始コード→文字→RESET の順に出します。

platform.h:外に見せるのは2関数だけ

platform層の関数は、他のモジュールから呼ばれるので platform.h で宣言します。

掲載:platform.h

/* platform.h */
#ifndef PLATFORM_H
#define PLATFORM_H

#include "common.h"

void clear_screen(void);
void sfx_beep(int kind);

#endif

関数プロトタイプ(宣言)の意味

宣言意味
void clear_screen(void);引数なしで画面を消す処理
void sfx_beep(int kind);kind(効果音タイプ)に応じてビープを鳴らす

※ void を引数に書くのは「引数を取らない」を明示したいときに使います(C言語の書き方として丁寧)。

platform.c:画面クリアと効果音を実装する

ここがSTEP02のメインです。
OS差分を #ifdef _WIN32 で吸収します。

掲載:platform.c

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

void clear_screen(void) {
#ifdef _WIN32
    system("cls");
#else
    system("clear");
#endif
}

void sfx_beep(int kind) {
#ifdef _WIN32
    switch (kind) {
    case 0: Beep(880, 60); break;
    case 1: Beep(660, 60); break;
    case 2: Beep(988, 60); break;
    case 3: Beep(523, 80); Beep(659, 80); Beep(784, 120); break;
    default: Beep(200, 120); break;
    }
#else
    (void)kind;
    printf("\a");
    fflush(stdout);
#endif
}

clear_screen のロジック解説

system がやっていること

system("cls") は、OSのコマンドを“外部コマンドとして実行する”関数です。

命令何をする?
system("cls")Windowsの画面クリアコマンド cls を実行
system("clear")Linux/Macの画面クリアコマンド clear を実行

注意点(設計の考え方)

system は便利ですが、外部コマンド実行なので「本格的なゲーム」では避けたくなることもあります。
でもこの教材の目的は 仕組みを理解しながら作れることなので、まずはこれでOKです。

sfx_beep のロジック解説

kind で効果音パターンを切り替える

kind は「何の効果音か」を表す番号です(ゲーム側から種類を指定して鳴らす)。

kind意味(想定)Windows側の音
0決定Beep(880,60)
1攻撃Beep(660,60)
2回復Beep(988,60)
3勝利3回鳴らしてそれっぽく
その他警告など低い音

switch の意味

switch (kind) は、if (kind == 0) ... else if ... を読みやすくした分岐です。

命令何をする?
switch (kind)kind の値で分岐
case 0:kind が0のとき
break;switch を抜ける(これがないと次のcaseまで流れる)

Beep(周波数, 時間)

Windowsの Beep(frequency, duration) はこういう意味です。

引数意味
frequency周波数(Hz)
durationミリ秒

Linux/Mac側の printf("\a") と fflush(stdout)

命令何をする?
printf("\a");端末ベル文字(BEL)を出す
fflush(stdout);すぐ表示(出力バッファを吐き出す)

(void)kind; は「kind未使用の警告を消す」ための書き方です。
Linux/Mac側では kind を使わないので、コンパイラが “未使用変数” 警告を出すのを防いでいます。

platform層を導入すると何が嬉しい?

ここが一番大事なところです。

もしplatform層が無いと…platform層があると…
battle.c や ui.c の中に #ifdef _WIN32 が散らばるOS差分は platform.c に集中する
あちこちで Beep や system を直接呼び始める呼び出しは clear_screen / sfx_beep に統一
改修が面倒(修正漏れが起きやすい)直す場所がplatform.cだけで済む

つまり 見通しが良くなって、保守がラクになるってわけです。

次のSTEPへのつながり(STEP03の予告)

次の STEP03 では input 層として、

  • 行入力 read_line
  • 安全な整数入力 read_int_safely

を切り出して、battle/map/ui から「入力まわりの共通処理」を消していきます。