vol.5 戦闘シーンの実装

目次

はじめに

今回は戦闘シーンの実装をしていきます。
実装する内容としては、
こうげき、かいふく、ぼうぎょ、にげる
の4つを考えていきます。
構造体を意識してプログラムを書いていきますよー。

戦闘シーン

今回構造体に導入するキャラクターデータは
name(名前)、HP(体力)、MP(魔力)、ATK(攻撃力)、def_flag(防御判定)
の5つにします。
ダメージは攻撃力に対して最大10%の増減があるものとします。
回復基本量は30で固定して、実際に回復する量には3の増減があるものとします。
回復時にはMPを4消費させます。
ぼうぎょは防御判定(1なら防御している)があるなら被ダメージを半分にします。
DEF(防御力)などを実装してもよかったのですが、計算が多くなってしまうので
今回は割愛します。
にげるについては、1/4の確率で成功することとしましょう。
0~3までの乱数で、0が出ればにげられるようにします。

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

typedef struct{
	char name[80]; //名前
	int HP; //体力
	int MP; //魔力
	int ATK; //攻撃力
	int def_flag; //防御判定
}PERSON;


//数値の増減量を計算します
int rand_bonus(int);
//逃走判定
int isescape(void);

int main(void)
{
	//seed値初期化
	srand((unsigned int)time(NULL));

	int cmd;//コマンド入力用
	int escape=0; //1なら戦闘終了、0なら戦闘継続
	int turn = 0; // 0:player[0] 1:player[1]。 2で割った余りを使っていく
	

	PERSON player[2] =
	{ 
		{ "司温", 100, 20, 35 },
		{ "魔王", 150, 8, 30 }
	};

	//戦闘開始!
	while (1){
		//最初のターンを除いて、player[0]のターンになったら画面をリセットします
		if (turn !=0 && turn % 2 == 0){
			_getch();
			system("cls");
		}
		//turn%2でどちらのプレイヤーが行動しているのか表現しています
		//適切なコマンドが入力されるまで繰り返します
		do{
			printf("%sのターン、どうする?\n", player[turn%2].name);
			printf("こうげき(1)・かいふく(2)・ぼうぎょ(3)・にげる(4) :");
		
			scanf("%d", &cmd);
		} while (!(cmd == 1 || cmd == 2 || cmd == 3 || cmd == 4));
		//コマンドの場合分け
		switch (cmd){

		//こうげき
		case 1:
			printf("%sのこうげき!\n", player[turn % 2].name);
			
			//ダメージ増減分 ダメージボーナスの略
			int db = rand_bonus(player[turn % 2].ATK / 10); 
			if (player[(turn + 1) % 2].def_flag == 1) { //防御判定があれば
				player[(turn + 1) % 2].HP -= (player[turn % 2].ATK + db) / 2; //ダメージ半減
				player[(turn + 1) % 2].def_flag = 0; //防御判定リセット
			}
			else player[(turn + 1) % 2].HP -= player[turn % 2].ATK + db;
			//HPは非負整数なので、最小値は0
			if (player[(turn + 1) % 2].HP < 0) player[(turn + 1) % 2].HP = 0; 
			printf("%sの残りHP %d\n", player[(turn + 1) % 2].name, player[(turn + 1) % 2].HP);
			if (player[(turn + 1) % 2].HP == 0){
				printf("%sは死んでしまった...\n", player[(turn + 1) % 2].name);
				escape = 1;
			}
			break;

		//かいふく
		case 2:
			printf("%sは回復魔法を唱えた!\n", player[turn % 2].name);
			
			int heal_base = 30; //基本回復量
			if (player[turn % 2].MP < 4) printf("しかしMPが足りない!\n");
			else {
				player[turn % 2].MP -= 4; //MP減少
				player[turn % 2].HP += heal_base + rand_bonus(3); //体力回復
				printf("%sの体力は%dに回復した!\n", player[turn % 2].name, player[turn % 2].HP);
				printf("残りMPは%d\n", player[turn % 2].MP);
			}
			break;
			
		//ぼうぎょ
		case 3:
			printf("%sは防御した!\n", player[turn % 2].name);
			if (player[turn % 2].def_flag != 1)
				player[turn % 2].def_flag = 1;
			break;

		//にげる
		case 4:
			printf("%sはにげだした!\n", player[turn % 2].name);
			if (isescape() == 1){
				escape = 1;
				break;
			}
			printf("しかし、回り込まれてしまった!\n");
			break;
		}

		//見づらいので横線を描画
		for (int i = 0; i < 50; i++) printf("_");
		putchar('\n');

		//戦闘終了判定
		if (escape == 1) break;
		
		//ターン経過
		turn++;
	}
	printf("%s%sに勝利した!\n", player[turn % 2].name, player[(turn + 1) % 2].name);
	
	_getch();
	return 0;
}

int rand_bonus(int value)
{
	return rand() % (1+2 * value) - value;
}

int isescape(void)
{
	if (rand() % 4 == 0) return 1; //0,1,2,3の中で0が出る確率は1/4
	else return 0; //return 1: 逃走成功 return 0: 逃走失敗
}

構造体を用いて実装する際の注意

・構造体を使って様々なことを実装できるようになるのですが、例えばゲームの武器についての情報を構造体に入れたいと思い立った時に、一気に大量に武器についての情報を入れてしまうと、あとあと後悔することになる場合があります。
プログラムを書いているうえで機能を変更していきたいケースは少なくないので、そのたびに全ての情報を変更するのはとても面倒です。
なので、最初に構造体に情報を入れるときは必要最低限の量にすることを心がけましょう。
今回で言うなら登場人物の情報として2人分しか用意していませんが、配列で準備しているので後からいくらでも登場人物を増やすことが可能です。
このように発展可能なプログラムを書くことで、より一般的な内容に対して成立するものとなるので、そのことを意識しながらプログラムを書く習慣をつけると今後の進展に拍車をかけることができるはずです。

・また、今回は戦闘シーンと言うことなのでフィールドについて言及する必要はないのですが、フィールドの情報を構造体に入れてフィールドを実装するケースも少なくありません。その時、フィールドデータはたいてい沢山の情報を持っていて、それもいちいちプログラムに全て記述していたら大変な事です。この場合、別のファイルにフィールドデータを保存しておいて、それを引用して使うということで、また新しく「ファイルポインタ」という知識が必要になってくるので今回は控えることとします。