
C言語で関数ポインタを使って、キャラクタごとの行動と関数を紐づけしておき、 順番に各キャラクタが行動をすることで、自動的に紐づいた関数が実行されます。
この仕組みを使って、簡単なターン制バトルの動きを確認してみます。
まずはキャラクタの構造体を定義し、 キャラクタがとれる動作を、関数で定義しておきます。
次に、キャラクタごとに、各ターンでどの行動(関数)をとるのかの紐づけを定義しておくことで、 簡単にターン制のバトルを実現できます。
#include <stdio.h>
struct Character {
char name[64];
int hp;
void (*action)(struct Character* src, struct Character* dest, int val);
};
typedef struct Character Ch;
void punch(Ch* src, Ch* dest, int val) {
printf("%s のパンチ! %s に %d のダメージ!\n", src->name, dest->name, val);
dest->hp -= val;
}
void magic(Ch* src, Ch* dest, int val) {
int damage = (dest->hp * 4) / 10; // 3割ダメージ
printf("%s の火炎魔法! %s は %d のダメージ!\n", src->name, dest->name, damage);
dest->hp -= damage;
}
int main() {
Ch planc = { "ぷらんく", 17, punch };
Ch firerat = { "炎のネズミ", 13, magic };
for (int i = 0; i < 20; i++) {
printf("=== %d ターン目 ===\n", i);
planc.action( &planc, &firerat, 5);
firerat.action( &firerat, &planc, 3);
printf("%s の体力は %d\n", planc.name, planc.hp);
printf("%s の体力は %d\n", firerat.name, firerat.hp);
if (planc.hp <= 0 || firerat.hp <= 0) break; // END
}
}
このコードを実行するとしたのような結果になります。
=== 1 ターン目 ===
ぷらんく のパンチ! 炎のネズミ に 5 のダメージ!
炎のネズミ の火炎魔法! ぷらんく は 6 のダメージ!
ぷらんく の体力は 11
炎のネズミ の体力は 8
=== 2 ターン目 ===
ぷらんく のパンチ! 炎のネズミ に 5 のダメージ!
炎のネズミ の火炎魔法! ぷらんく は 4 のダメージ!
ぷらんく の体力は 7
炎のネズミ の体力は 3
=== 3 ターン目 ===
ぷらんく のパンチ! 炎のネズミ に 5 のダメージ!
炎のネズミ の火炎魔法! ぷらんく は 2 のダメージ!
ぷらんく の体力は 5
炎のネズミ の体力は -2
勝負ありです!
では、コードの中身を確認していきます。 2~7行目でキャラクタの構造体を定義しており、そのメンバとして action という関数ポインタを持たせています。
struct Character {
char name[64];
int hp;
void (*action)(struct Character* src, struct Character* dest, int val);
};
typedef struct Character Ch;
この action には、キャラクタごとのデータを定義するときに、あわせて行動(関数)を設定しておきます。
続いて、8 ~ 16 行目ではキャラクタが実行できる行動を関数で定義しています。
void punch(Ch* src, Ch* dest, int val) {
printf("%s のパンチ! %s に %d のダメージ!\n", src->name, dest->name, val);
dest->hp -= val;
}
void magic(Ch* src, Ch* dest, int val) {
int damage = (dest->hp * 4) / 10; // 3割ダメージ
printf("%s の火炎魔法! %s は %d のダメージ!\n", src->name, dest->name, damage);
dest->hp -= damage;
}
今回は punch 関数と、magic 関数の2つをキャラクタが選択できる行動(関数)として定義しています。 なお、punch は固定ダメージ、magic は指定した相手の HP を 4 割削るダメージです。
続いて、main 関数です。 18行目、19行目では キャラクタの名前、体力、ターンごとの行動 を定義しています。
int main() {
Ch planc = { "ぷらんく", 17, punch };
Ch firerat = { "炎のネズミ", 13, magic };
今回は main 関数の中で、キャラクタ「ぷらんく」は punch 関数を実行するように定義し、 キャラクタ「炎のネズミ」は magic 関数を実行するように定義しました。
このようにキャラクタの行動する内容を事前に定義してしまえば、 後は構造体の中の action メンバを呼び出せば最初に定義しておいた、それぞれの行動(関数)が実行される仕組みです。
今回のプログラムでは、「ぷらんく」か「炎のネズミ」のどちらかが、 体力(hp)が 0 以下となったとき、for ループから抜けて勝負終了となります。
オブジェクト指向プログラミング
今回のプログラムのように、ある特定のデータの集まりと、そのデータの振る舞い
をまとめるようなプログラムは、「オブジェクト指向プログラミング」にある考え方の一つです。
キャラクタというデータがどんな風に行動をするか、というオブジェクトという単位で
定義していくことで、非常にプログラムが設計しやすくなります。
オブジェクト指向プログラミングは非常に奥が深く、とても少しでは書き表せないため、
今回は ”そういったプログラミングの考え方があるよ” というご紹介までに留めます。
今回はキャラクタの行動に着目して、関数ポインタを利用しましたが、 もっと幅広く、様々な定型的な処理を関数にわけておき、 ゲームのシナリオなどの順番で関数を読み込ませてから、順番に実行していくことで、 簡易的な紙芝居型の ADV のスクリプト処理などを自作できます。
関数を実行する順番を別のテキストファイルなどで定義しておき、 プログラムからそのテキストファイルを読込んで、該当する関数を実行していくような仕組みを インタプリタと呼び、ゲームシナリオをインタプリタの仕組みを使って、シナリオエディターなどで 作成し、プログラム本体とは分けて作っていく方法は良くあります。
コメント、ありがとうございます。
ごめんなさい。エラーでうまく送信できませんでした。ご迷惑をおかけします。しばらくおいてから再度送信を試していただくか、以下から DM などでご連絡頂ければと思います。
Twitter:@NodachiSoft_jpお名前:以下の内容でコメントを送信します。よろしければ、「送信」を押してください。修正する場合は「戻る」を押してください
お名前: