vol.12-2 アイテムの取得、使用

目次

はじめに

分割が終われば次はいよいよメニュー画面を作成していきます。メニュー画面では所持アイテム、装備品を確認できるようにして、アイテムも使用できるようにしましょう。また、ダンジョン内に落ちているアイテムの上に乗った時、そのアイテムを取得するかどうかを選択させるようにし、実際にそのアイテムを取得できるようにしましょう。

メニュー画面の作成

mキーを入力した時にメニュー画面を表示できるようにしましょう。メニュー画面ではいろいろなことを行えるようにしたいのでコードが長くなる可能性があります。そこで最初からメニュー画面に関する内容は関数として記述してやることにします。

void menuScreen(void);

でプロトタイプ宣言してから定義してやります。
メニュー画面をこのような設計にします
|――――――|
|1. アイテム |
|――――――|
|2. ステータス|
|――――――|

void menuScreen(void)
{
	printf("|――――――|\n");
	printf("|1.アイテム |\n");
	printf("|――――――|\n");
	printf("|2.ステータス|\n");
	printf("|――――――|\n");

	switch (_getch()) {
	case 'm':
		return;
	}

	return;
}

あとから色々機能を追加するので今はこれくらいです。一応mキーを押すとメニュー画面が閉じるようにはしました。

アイテムの取得

アイテムを取得するためにはそもそもプレイヤーがアイテムを持つという概念を追加しなければいけません。ローグライクゲームでは基本プレイヤーは20個しかアイテムは持てません。更に、重複して持つことも一部のアイテムを除き不可能です。限られたアイテムの中でゲームを攻略していくという意図があります。
今回はリスト構造というものを使って実装していきたいと思います。これはvol.12-1に挙がっているので細かいことはそっちを参考にして下さいねー。
セルをこのように宣言します。

typedef struct possession{
	int ID;
	struct possession *next;
}POSSESSION_t; //所持品

少し今までと違った書き方ですが、これはPOSSESSION_tの中に次のPOSSESSION_tのポインタを実装する際に今まで通りの書き方だとエラーが発生してしまうので、このようになります。
さて、実際にアイテムを取得するために準備をしていきましょう。vol.12-1に書かれたcreate関数を参考にして書いていきます。

POSSESSION_t *createData(int itemID)
{
	POSSESSION_t *pouch;
	pouch = (POSSESSION_t *)malloc(sizeof(POSSESSION_t)); //メモリの確保、ここが準備の中核
	(pouch->ID) = itemID, (pouch->next) = NULL;

	return pouch;
}

これをmain関数の最初にダミーセルとして

POSSESSION_t *itemPouch = createData(-1); //アイテムポーチの作成

このように準備してやります。

ちょっとだけ補足。
メモリの確保のためにmalloc関数というものを使っているのですが、これは配列などで予めメモリを確保しておくのではなく、状況に応じて必要分のメモリを動的に確保してくれるものです。
sizeof演算子は、カッコ内の変数やデータ型がどれくらいのメモリサイズを持っているかを教えてくれるものです。
(POSSESSION_t *)はただのキャストです。
なのでpouch = ...の記述は、POSSESSION_tのメモリ分だけpouchのメモリを確保しましょう、という意味になります。

次に、アイテムをポーチに追加するaddData関数(insert関数を参考に)を書いていきます。

//k番目にアイテムデータを追加
void addData(POSSESSION_t *list, int k, int itemID)
{
	POSSESSION_t *p;

	if (k > 1) addData(list->next, k - 1, itemID);
	else {
		p = createData(itemID);
		(p->next) = (list->next);
		(list->next) = p;
	}
}

次に、実際にアイテムを消費する関数consumeItem関数(delete関数を参考に)を書いていきます。(ここではまだアイテムの効果は発揮させません。)

//k番目のアイテムを消費
void consumeItem(POSSESSION_t *list, int k)
{
	POSSESSION_t *p;

	if (k > 1) useItem(list->next, k - 1);
	else {
		p = (list->next);
		(list->next) = (list->next->next); //listの次にあるデータを1つ飛ばした
		free(p); //メモリの開放、pは飛ばされたいらないやつなので
	}
}

ここではとりあえずアイテムを消費したということにして消しただけですので、実際にpの効果を発動させないといけません。そこで、pの内容に応じて効果を発動させる新たな関数activateItem関数を作成しましょう。

//アイテム発動
void activateItem(int itemID, PERSON_t *person)
{
	switch (itemID) {
	case CELL_TYPE_BREAD:
		(person->HUNGER) += 500; //最大満腹度1000の50%
		if ((person->HUNGER) >= 1000) (person->HUNGER) = 1000; //最大満腹度1000は越えてはいけない
		break;
	case CELL_TYPE_BIG_BREAD:
		(person->HUNGER) = 1000; //満腹度全回復
		break;
	}
}

アイテムはまだ2つしかないのでこんな感じになります。アイテムが増えてき次第この関数は更新していきます。
では、activateItem関数をconsumeItem関数に組み込んでいきましょう。ここで、consumeItem関数の中からPERSON_tのものを呼び出すことになるので、consumeItem関数の引数にPERSON_tを追加します。

//k番目のアイテムを消費
void consumeItem(PERSON_t *person, POSSESSION_t *list, int k)
{
	POSSESSION_t *p;

	if (k > 1) useItem(list->next, k - 1);
	else {
		p = (list->next);
		activateItem(p->ID, person);
		(list->next) = (list->next->next); //listの次にあるデータを1つ飛ばした
		free(p); //メモリの開放、pは飛ばされたいらないやつなので
	}
}

アイテムが使えるようになったということでようやくアイテムを取得する段階になりました。プレイヤーがアイテムの上に立った時に拾うか拾わないかを選択し、拾う場合アイテムデータとして追加してやりましょう。アイテムの上に立った後で行う処理なので、main関数のswitch文内で記述してやりましょう。
(注:int pouchItemCount=0; をmain関数の最初に記述してあります。)

int searchItemID; //落ちていたアイテムのIDを検索し、結果をここに代入

switch (_getch()) {
case 'w':
	if (field[cursorY - 1][cursorX] == CELL_TYPE_WALL) break;
		
	if (field[cursorY - 1][cursorX] >= CELL_TYPE_BREAD) {
		printf("このアイテムを拾いますか?\n");
		printf("1:はい 2(またはその他のキー):いいえ\n");
		if (_getch() != '1') break;
		else {
			if (pouchItemCount == 20) {
				printf("これ以上アイテムは持てません。");
				_getch();
				break;
			}
			//落ちているアイテムのIDを検索
			for (i = 0; i < itemCount; i++)
				if (item[i].x == cursorX && item[i].y == cursorY - 1) {
					searchItemID = item[i].ID;
					break;
				}

			addData(itemPouch, pouchItemCount, searchItemID);
			pouchItemCount++;
			printf("アイテムを入手した!\n");
			_getch();
		}
	}

	field[cursorY][cursorX] = CELL_TYPE_NONE;
	cursorY--;
	field[cursorY][cursorX] = CELL_TYPE_PLAYER;
	player.HUNGER--;
	break;
case 's':
	if (field[cursorY + 1][cursorX] == CELL_TYPE_WALL) break;
	if (field[cursorY + 1][cursorX] >= CELL_TYPE_BREAD) {
		printf("このアイテムを拾いますか?\n");
		printf("1:はい 2(またはその他のキー):いいえ\n");
		if (_getch() != '1') break;
		else {
			if (pouchItemCount == 20) {
				printf("これ以上アイテムは持てません。");
				_getch();
				break;
			}
				//落ちているアイテムのIDを検索
			for (i = 0; i < itemCount; i++)
				if (item[i].x == cursorX && item[i].y == cursorY + 1) {
					searchItemID = item[i].ID;
					break;
				}

			addData(itemPouch, pouchItemCount, searchItemID);
			pouchItemCount++;
			printf("アイテムを入手した!\n");
			_getch();
		}
	}

	field[cursorY][cursorX] = CELL_TYPE_NONE;
	cursorY++;
	field[cursorY][cursorX] = CELL_TYPE_PLAYER;
	player.HUNGER--;
	break;
case 'a':
	if (field[cursorY][cursorX - 1] == CELL_TYPE_WALL) break;
	if (field[cursorY][cursorX - 1] >= CELL_TYPE_BREAD) {
		printf("このアイテムを拾いますか?\n");
		printf("1:はい 2(またはその他のキー):いいえ\n");
		if (_getch() != '1') break;
		else {
			if (pouchItemCount == 20) {
				printf("これ以上アイテムは持てません。");
				_getch();
				break;
			}
			//落ちているアイテムのIDを検索
			for (i = 0; i < itemCount; i++)
				if (item[i].x == cursorX - 1 && item[i].y == cursorY) {
					searchItemID = item[i].ID;
					break;
				}

			addData(itemPouch, pouchItemCount, searchItemID);
			pouchItemCount++;
			printf("アイテムを入手した!\n");
			_getch();
		}
	}

	field[cursorY][cursorX] = CELL_TYPE_NONE;
	cursorX--;
	field[cursorY][cursorX] = CELL_TYPE_PLAYER;
	player.HUNGER--;
	break;
case 'd':
	if (field[cursorY][cursorX + 1] == CELL_TYPE_WALL) break;
	if (field[cursorY][cursorX + 1] >= CELL_TYPE_BREAD) {
		printf("このアイテムを拾いますか?\n");
		printf("1:はい 2(またはその他のキー):いいえ\n");
		if (_getch() != '1') break;
		else {
			if (pouchItemCount == 20) {
				printf("これ以上アイテムは持てません。");
				_getch();
				break;
			}
			//落ちているアイテムのIDを検索
			for (i = 0; i < itemCount; i++)
				if (item[i].x == cursorX + 1 && item[i].y == cursorY) {
					searchItemID = item[i].ID;
					break;
				}

			addData(itemPouch, pouchItemCount, searchItemID);
			pouchItemCount++;
			printf("アイテムを入手した!\n");
			_getch();
		}
	}

	field[cursorY][cursorX] = CELL_TYPE_NONE;
	cursorX++;
	field[cursorY][cursorX] = CELL_TYPE_PLAYER;
	player.HUNGER--;
	break;
case 'm':
	menuScreen(itemPouch);
	break;
default:break;
}

メニュー画面の改良

アイテムを取得できるようになったので、実際に何を持っているのか確認でき、使えるようにしていきます。これはアイテムの取得の確認も兼ねています。メニュー画面で'1'を入力したら所持しているアイテムを全て表示するようにします。これは過去のノートに載っているprint関数を参考にしましょう。
問題は、何を表示するかです。今アイテムIDを取得することはできましたが、肝心の名前が定められていません。IDに応じてアイテム名を返すidToName関数をまずは作成します。

char *idToName(int itemID)
{
	switch (itemID) {
	case CELL_TYPE_BREAD:
		return "パン";
	case CELL_TYPE_BIG_BREAD:
		return "大きなパン";
	}
}

アイテムが増えて行く度にここを更新していきましょう。

さあ、いよいよ所持アイテムの表示になります。menu画面で'1'を入力したときに所持しているアイテムを全て表示するようにします。もしアイテムを持っていなかった場合、0を返すようにします。以前のノートにあるprint関数を参考にしてshowPouchItem関数も同時に作成して使います。

int showPouchItem(POSSESSION_t *list, int *count)
{
	list = list->next;
	if(list == NULL) return 0;
	while (list != NULL) {
		*count += 1;
		printf("%d.%s\n", *count, idToName(list->ID));
		list = list->next;
	}
	return 1;
}

そして最後に、使えるようにしましょう。所持アイテムを表示した後で再び入力を待機させ、使うかどうかを選択させます。使うなら効果を発動させましょう!
(他にも本来ならありますが今回はこれだけにしておきましょう。)

所持アイテムの選択時に、例えば、
1.大きなパン
2.パン
3.大きなパン
とあったとき、パンを選びたいときは2と選択するようにしたいのですが、所持アイテムが増えた時、所持アイテム数が二桁になることがあります。ここではscanf_s関数を用いて入力しますが、scanf_s関数は数字だけでなく他の文字も入力できてしまうので、数字だけ入力する機構を作成することによってコマンド入力をします。

int selectItem; //アイテムの選択用
char buf[64]; //scanf_s関数の後処理用
while(1){
	if(scanf_s("%d",&selectItem) == 1) break;
	printf("数字で入力してください。\n");
	scanf_s("%s",buf,63); //数字以外が入力された場合、入力した文字をここで処理する
}

このような機構で数字のみを入力します。
補足:scanf_s関数は入力に成功した個数を返してくれるので、もし%dに対して文字列が入力されればそれは入力失敗と判定され、戻り値のカウントには入りません。この時scanf_s関数の特性で、入力に失敗した場合その入力した文字はプログラムの中で漂ってしまいます。なので、char buf[64];を準備してそこに吸収してあげたわけです。(別にchar型ならなんでもOK)
以下、case 1:の内容

case 1:{
	int count = 0; //所持アイテムの番号
	int selectItem; //アイテムの選択用
	char buf[64]; //scanf_s関数の後処理用

	if (showPouchItem(list, &count) == 0) {
		printf("アイテムを持っていません。\n");
		_getch();
		return;
	}
		
	//数字しか入力を受け付けない機構
	while (1) {
		if (scanf_s("%d", &selectItem) == 1) break;
		printf("数字で入力してください。\n");
		scanf_s("%s", buf, 63); //数字以外が入力された場合、入力された文字をここで処理する
		}
		
		if (1 <= selectItem && selectItem <= count) {
			printf("%d番のアイテムを使いますか?\n", selectItem);
			printf("1:はい 2(またはその他のキー):いいえ\n");
			if (_getch() != '1') break;
			else {
			printf("アイテムを使用した!\n");
			consumeItem(person, list, selectItem);
			_getch();
			}
		}
		break;
	}
}

おわりに

なんとかアイテム関連を実装することができました。下準備が大変でしたね。次回は敵の概念を追加してみようと思います。お疲れ様でした。