荷物運びゲームのプログラム

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
ねんどじん
記事: 23
登録日時: 9年前

荷物運びゲームのプログラム

#1

投稿記事 by ねんどじん » 6年前

こんにちは よろしくお願いします
書籍「ゲームプログラマになる前に覚えておきたい技術」第一章の課題について質問です。
もし書籍をお持ちの方がいらしたらそちらを合わせて参照なされると質問の意図がわかりやすいかと思います。

自分は、この本の最初の課題である「荷物運びゲーム」の作成でつまづいてしまいました。
ゲームの仕様をざっと説明しますと、
・以下の図のように壁を'#'、スペースを ' '、ゴール地点を'.'、プレイヤーを'p'、荷物を'o'、
 ゴール地点上のプレイヤーを'P'、ゴール地点上の荷物を'O'でcoutを使って描画する
・プレイヤーはcinで一文字入力を行い、入力された文字に応じてプレイヤーキャラが上下左右いずれかに1マス動く
・荷物を押すように移動すると、押された荷物もプレイヤーキャラと同方向に1マス動く
・全ての荷物をゴール地点に置ければゲームクリア

といった感じです。
自分は、入力を受け取ってプレイヤーキャラ等を動かすvoid C_map::update(const string& input)を書き始めるまでは
なんとか出来ていたつもりだったのですが、updateをうまく書けず、キャラクタが入力された値によっては移動しなかったり、
移動した際にマップを壊してしまったりとうまく日本語で説明できない変な挙動ばかりするようになってしまいました。
しかしブレークポイントを使って変数の中身をチェックしてみたり、書いたコードをお手本のコードと見比べてみても、
いまいちこの変な挙動の原因と解決策がわかりません。
以下にお手本を含めてコードを貼るので、もし良ければコンパイルしてなにが悪いのかチェックしていただけませんでしょうか。
そして自分にヒントでもいいので何が悪かったのか教えてください。 お願いします。

ねんどじん
記事: 23
登録日時: 9年前

Re: 荷物運びゲームのプログラム

#2

投稿記事 by ねんどじん » 6年前

マップの表示例です

########
# .. p #
# oo #
# #
########

どうも綺麗にかけませんが、お手本コードをコンパイルしてご確認ください。

ねんどじん
記事: 23
登録日時: 9年前

Re: 荷物運びゲームのプログラム

#3

投稿記事 by ねんどじん » 6年前

お手本コードです。

コード:

#include <iostream>
using namespace std;

//#壁 _空間 .ゴール oブロック p人
const char gStageData[] = "\
########\n\
# .. p #\n\
# oo   #\n\
#      #\n\
########";
const int gStageWidth = 8;
const int gStageHeight = 5;

enum Object{
	OBJ_SPACE,
	OBJ_WALL,
	OBJ_GOAL,
	OBJ_BLOCK,
	OBJ_BLOCK_ON_GOAL,
	OBJ_MAN,
	OBJ_MAN_ON_GOAL,

	OBJ_UNKNOWN,
};

//関数プロトタイプ
void initialize( Object* state, int w, int h, const char* stageData );
void draw( const Object* state, int w, int h );
void update( Object* state, char input, int w, int h );
bool checkClear( const Object* state, int w, int h );

int main(){
	//一次元配列である理由は本文参照
	Object* state = new Object[ gStageWidth * gStageHeight ]; //状態配列確保

	initialize( state, gStageWidth, gStageHeight, gStageData ); //ステージ初期化
	//メインループ
	while ( true ){
		//まず描画
		draw( state, gStageWidth, gStageHeight );
		//クリアチェック
		if ( checkClear(state, gStageWidth, gStageHeight ) ){
			break; //クリアチェック
		}
		//入力取得
		cout << "a:left s:right w:up z:down. command?" << endl; //操作説明
		char input;
		cin >> input;
		//更新
		update( state, input, gStageWidth, gStageHeight ); 	
	}
	//祝いのメッセージ
	cout << "Congratulation's! you won." << endl;
	//後始末
	delete[] state;
	state = 0;

	//Visual Studioから実行する人のために無限ループ。コマンドラインからはCtrl-Cで終えてください。
	while( true ){
		;
	} 
	return 0;
}

//---------------------以下関数定義------------------------------------------


//いつか使う日も来るだろうと高さも渡す仕様にしたが、現状使っていないので名前だけ(height)コメントアウトしてある。
void initialize( Object* state, int width, int /* height */, const char* stageData ){	
	const char* d = stageData; //読み込みポインタ
	int x = 0;
	int y = 0;
	while ( *d != '\0' ){ //NULL文字でない間
		Object t; //特に意味はないが使う回数が多い変数に私は良くtを使う。temporaryの略。たぶんよくない習慣だが、無駄に長い名前にして読みにくいのも困り物だろう。
		switch ( *d ){
			case '#': t = OBJ_WALL; break;
			case ' ': t = OBJ_SPACE; break;
			case 'o': t = OBJ_BLOCK; break;
			case 'O': t = OBJ_BLOCK_ON_GOAL; break;
			case '.': t = OBJ_GOAL; break;
			case 'p': t = OBJ_MAN; break;
			case 'P': t = OBJ_MAN_ON_GOAL; break;
			case '\n': x = 0; ++y; t = OBJ_UNKNOWN; break; //改行処理
			default: t = OBJ_UNKNOWN; break;
		}
		++d;
		if ( t != OBJ_UNKNOWN ){ //知らない文字なら無視するのでこのif文がある
			state[ y*width + x ] = t; //書き込み
			++x;
		}
	}
}

void draw( const Object* state, int width, int height ){
	const char font[] = {' ', '#', '.', 'o', 'O', 'p', 'P'}; //Object列挙の順
	for ( int y = 0; y < height; ++y ){
		for ( int x=0; x < width; ++x ){
			Object o = state[ y*width + x ];
			cout << font[ o ];
		}
		cout << endl;
	}
}

//第一引数はほかの関数ではstateとしているが、あまりに頻繁に使うので
//短いsで済ませている。w,hもそれぞれwidth,heightである。
void update( Object* s, char input, int w, int h ){
	//移動差分に変換(dはdifferenceでもdeltaでもお好きな方の略だと思って欲しい)
	int dx = 0; 
	int dy = 0;
	switch ( input ){
		case 'a': dx = -1; break; //左
		case 's': dx = 1; break; //右
		case 'w': dy = -1; break; //上。Yは下がプラス
		case 'z': dy = 1; break; //下。
	}
	//人座標を検索
	int i = -1;
	for ( i = 0; i < w * h; ++i ){
		if ( s[ i ] == OBJ_MAN || s[ i ] == OBJ_MAN_ON_GOAL ){
			break;
		}
	}
	int x = i % w; //xは幅で割ったあまり
	int y = i / w; //yは幅で割った商

	//移動
	//移動後座標(tに意味はない。ごめんなさい)
	int tx = x + dx;
	int ty = y + dy;
	//座標の最大最小チェック。外れていれば不許可
	if ( tx < 0 || ty < 0 || tx >= w || ty >= h ){
		return;
	}
	//A.その方向が空白またはゴール。人が移動。
	int p = y*w + x; //人位置
	int tp = ty*w + tx; //ターゲット位置(TargetPosition)
	if ( s[ tp ] == OBJ_SPACE || s[ tp ] == OBJ_GOAL ){
		s[ tp ] = ( s[ tp ] == OBJ_GOAL ) ? OBJ_MAN_ON_GOAL : OBJ_MAN; //ゴールならゴール上の人に
		s[ p ] = ( s[ p ] == OBJ_MAN_ON_GOAL ) ? OBJ_GOAL : OBJ_SPACE; //もともとゴール上ならゴールに
	//B.その方向が箱。その方向の次のマスが空白またはゴールであれば移動。
	}else if ( s[ tp ] == OBJ_BLOCK || s[ tp ] == OBJ_BLOCK_ON_GOAL ){
		//2マス先が範囲内かチェック
		int tx2 = tx + dx;
		int ty2 = ty + dy; 
		if ( tx2 < 0 || ty2 < 0 || tx2 >= w || ty2 >= h ){ //押せない
			return;
		}

		int tp2 = ( ty + dy )*w + ( tx + dx ); //2マス先
		if ( s[ tp2 ] == OBJ_SPACE || s[ tp2 ] == OBJ_GOAL ){
			//順次入れ替え
			s[ tp2 ] = ( s[ tp2 ] == OBJ_GOAL ) ? OBJ_BLOCK_ON_GOAL : OBJ_BLOCK;
			s[ tp ] = ( s[ tp ] == OBJ_BLOCK_ON_GOAL ) ? OBJ_MAN_ON_GOAL : OBJ_MAN;
			s[ p ] = ( s[ p ] == OBJ_MAN_ON_GOAL ) ? OBJ_GOAL : OBJ_SPACE;
		}
	}
}

//ブロックのみがなければクリアしている。
bool checkClear( const Object* s, int width, int height ){
	for ( int i = 0; i < width*height; ++i ){
		if ( s[ i ] == OBJ_BLOCK ){
			return false;
		}
	}
	return true;
}


ねんどじん
記事: 23
登録日時: 9年前

Re: 荷物運びゲームのプログラム

#4

投稿記事 by ねんどじん » 6年前

以下、自分の書いたコードです。
main.cpp

コード:

#include"main.h"

void G_getinput(string& str);

int main(){

	string inputstr;	//入力格納用
	C_map Map("dat/map/map.txt");

	while(true){
		Map.draw();				//描画
		G_getinput(inputstr);	//入力受付
		Map.update(inputstr);	//入力情報の反映
	}
	
}

void G_getinput(string& str){
	cout<<"a:left s:right w:up z:down. command?"<<endl;
	cin>>str;
}

ねんどじん
記事: 23
登録日時: 9年前

Re: 荷物運びゲームのプログラム

#5

投稿記事 by ねんどじん » 6年前

main.hです

コード:

#pragma once

#include<iostream>
#include<string>
using namespace std;

#include"../classes/map/map.h"

ねんどじん
記事: 23
登録日時: 9年前

Re: 荷物運びゲームのプログラム

#6

投稿記事 by ねんどじん » 6年前

block.hです

コード:

#pragma once

#include<iostream>
using namespace std;

enum E_blockflag{
	WALL		=1<<0,	//壁
	SPACE		=1<<1,	//スペース
	GOAL		=1<<2,	//ゴール
	PLAYER		=1<<3,	//プレイヤー
	NIMOTSU		=1<<4,	//荷物
};

class C_block{
private:
	unsigned int mFlags;
public:
	C_block();

	//フラグがオンかどうか調べる
	bool check(unsigned int f)const;

	//フラグのオンオフを設定する
	void set(bool true_or_false,unsigned int f);
	
	//mFlagsをそのままget,setしたいときに。
	unsigned int getflag()const{return mFlags;}
	void setflag(unsigned int src){mFlags=src;}

	//ブロックの描画
	void draw()const;
};

ねんどじん
記事: 23
登録日時: 9年前

Re: 荷物運びゲームのプログラム

#7

投稿記事 by ねんどじん » 6年前

block.cppです

コード:

#include"block.h"

C_block::C_block(){
	mFlags=0;
}

bool C_block::check(unsigned int f)const{
	return ((mFlags&f)!=0);
}

void C_block::set(bool true_or_false,unsigned int f){
	if(true_or_false==true){
		mFlags|=f;
	}
	else if(true_or_false==false){
		mFlags&=~f;
	}
}

void C_block::draw()const{
	if(check(WALL))	{
		cout<<'#'<<flush;
	}
	if(check(SPACE)){
		cout<<' '<<flush;
	}
	if(check(GOAL)){
		if(check(PLAYER)){
			cout<<'P'<<flush;
		}
		else if(check(NIMOTSU)){
			cout<<'O'<<flush;
		}
		else{
			cout<<'.'<<flush;
		}
	}
	else if(check(PLAYER)){
		cout<<'p'<<flush;
	}
	else if(check(NIMOTSU)){
		cout<<'o'<<flush;
	}
}

ねんどじん
記事: 23
登録日時: 9年前

Re: 荷物運びゲームのプログラム

#8

投稿記事 by ねんどじん » 6年前

map.hです

コード:

#pragma once

#include<iostream>
#include<fstream>
#include<string>
using namespace std;

#include"../block/block.h"

class C_map{
private:
	int mSizeX,mSizeY;	//マップの横幅、縦幅
	C_block* mBlocks;	//ブロックの配列

	int mPlayerX,mPlayerY;	//プレイヤーの座標

	bool isInsideofMap(int x,int y);	//渡した座標がマップ内なら真を、マップ外なら偽を返す

	C_map(const C_map& src);
	C_map& operator=(const C_map& src);
public:
	C_map(const string& mapsrc);
	~C_map();

	//入力情報の反映
	void update(const string& input);

	//マップの描画
	void draw()const;
};

ねんどじん
記事: 23
登録日時: 9年前

Re: 荷物運びゲームのプログラム

#9

投稿記事 by ねんどじん » 6年前

map.cppです

コード:

#include"map.h"

bool C_map::isInsideofMap(int x,int y){
	if(x<0||mSizeX<=x||y<0||mSizeY<=y){
		return false;
	}
	return true;
}

C_map::C_map(const string& mapsrc){	//マップの読み込み
	mSizeX=0,mSizeY=0;
	mBlocks=0;
	mPlayerX=0,mPlayerY=0;
	int cnt=0;

	ifstream map(mapsrc);
	if(map.fail()){
		cerr<<"file open error"<<endl;
		exit(EXIT_FAILURE);
	}
	string str1,str2;
	while(getline(map,str1)){
		str2+=str1;
		mSizeY++;
	}
	str2+='\0';
	mSizeX=str1.size();

	mBlocks=new C_block[mSizeY*mSizeX];	//ブロックの配列としてマップ情報を確保 以下代入していく

	while(str2[cnt]){
		switch(str2[cnt]){
		case '#':
			mBlocks[cnt].set(true,WALL);
			break;
		case ' ':
			mBlocks[cnt].set(true,SPACE);
			break;
		case '.':
			mBlocks[cnt].set(true,GOAL);
			break;
		case 'p':
			mBlocks[cnt].set(true,PLAYER);
			//プレイヤーの初期座標の獲得
			mPlayerY=cnt/mSizeX;
			mPlayerX=cnt%mSizeX;
			break;
		case 'P':
			mBlocks[cnt].set(true,PLAYER|GOAL);
			break;
		case 'o':
			mBlocks[cnt].set(true,NIMOTSU);
			break;
		case 'O':
			mBlocks[cnt].set(true,NIMOTSU|GOAL);
			break;
		default:
			cerr<<"unknown block type"<<endl;
			exit(EXIT_FAILURE);
			break;
		}

		cnt++;
	}
}

C_map::~C_map(){
	delete[] mBlocks;
	mBlocks=0;
}

void C_map::update(const string& input){
	
	int mem_x1=0,mem_y1=0;		//進行方向にひとつ進んだとした際の座標
	C_block mem_block1;			//進行方向にひとつ進んたとした際のブロック情報
	mem_block1.setflag(0);

	int mem_x2=0,mem_y2=0;		//進行方向にふたつ進んだとした際の座標
	C_block mem_block2;			//進行方向にふたつ進んたとした際のブロック情報
	mem_block2.setflag(0);


	switch(input[0]){
	case 'a':
		mem_x1=mPlayerX-1,mem_x2=mPlayerX-2;
		mem_y1=mPlayerY,mem_y2=mPlayerY;
		break;
	case 's':
		mem_x1=mPlayerX+1,mem_x2=mPlayerX+2;
		mem_y1=mPlayerY,mem_y2=mPlayerY;
		break;
	case 'w':
		mem_x1=mPlayerX,mem_x2=mPlayerX;
		mem_y1=mPlayerY-1,mem_y2=mPlayerY-2;
		break;
	case 'z':
		mem_x1=mPlayerX,mem_x2=mPlayerX;
		mem_y1=mPlayerY+1,mem_y2=mPlayerY+2;
		break;
	default:
		cerr<<"unacceptable command"<<endl;
		return;
		break;
	}
	//進行先がマップ外なら戻る
	if(isInsideofMap(mem_x1,mem_y1)==false||isInsideofMap(mem_x2,mem_y2)==false){
		return;
	}
	//マップ内なら進行先のマップ情報を得る
	mem_block1.setflag(mBlocks[mem_y1*mSizeX+mem_x1].getflag());
	mem_block2.setflag(mBlocks[mem_y2*mSizeX+mem_x2].getflag());

	//進行先が壁の場合
	if(mem_block1.check(WALL)){return;}//戻る

	//進行先がスペースか、荷物の載っていないゴールの場合
	if(mem_block1.check(SPACE)||(mem_block1.check(GOAL)&&!mem_block1.check(NIMOTSU))){
		//まず足元の情報を変更
		mBlocks[mPlayerY*mSizeX+mPlayerX].set(false,PLAYER);
		//プレイヤーを移動
		mPlayerX=mem_x1,mPlayerY=mem_y1;
		mBlocks[mPlayerY*mSizeX+mPlayerX].set(true,PLAYER);
	}
}

void C_map::draw()const{
	for(int i=0;i<mSizeY;i++){
		for(int j=0;j<mSizeX;j++){
			mBlocks[i*mSizeX+j].draw();
		}
		cout<<endl;
	}
}

ねんどじん
記事: 23
登録日時: 9年前

Re: 荷物運びゲームのプログラム

#10

投稿記事 by ねんどじん » 6年前

長々とすみません。最後に、自分の書いたソリューションをそのまま上げておきますので必要ならばご活用ください。
添付ファイル
GameLearning.zip
(3.09 MiB) ダウンロード数: 22 回

アバター
usao
記事: 1569
登録日時: 6年前

Re: 荷物運びゲームのプログラム

#11

投稿記事 by usao » 6年前

見た感じ↓この判定はおかしいように見える.
座標(mem_x2,mem_y2)をチェックする必要はないのでは?

コード:

//進行先がマップ外なら戻る
if(isInsideofMap(mem_x1,mem_y1)==false||isInsideofMap(mem_x2,mem_y2)==false){
    return;
}
あと,荷物を押して移動するパターンの記述がないような.

ねんどじん
記事: 23
登録日時: 9年前

Re: 荷物運びゲームのプログラム

#12

投稿記事 by ねんどじん » 6年前

>座標(mem_x2,mem_y2)をチェックする必要はないのでは?

たしかにそうでした。修正させていただきました。しかし問題の解決とはいかなかったようです。

>あと,荷物を押して移動するパターンの記述がないような.

すみません、荷物を押さない移動がうまくいかないので、まだ書いていないのです。

M.R

Re: 荷物運びゲームのプログラム

#13

投稿記事 by M.R » 6年前

コード:

void C_block::draw()const{
	if(check(WALL))	{
		cout<<'#'<<flush;
	}
	else if(check(GOAL)){
		if(check(PLAYER)){
			cout<<'P'<<flush;
		}
		else if(check(NIMOTSU)){
			cout<<'O'<<flush;
		}
		else{
			cout<<'.'<<flush;
		}
	}
	else if(check(PLAYER)){
		cout<<'p'<<flush;
	}
	else if(check(NIMOTSU)){
		cout<<'o'<<flush;
	}
	else if(check(SPACE)){
		cout<<' '<<flush;
	}
	else{
		cout<<'*'<<flush;
	}
}
とすればmapが壊れる事はなくなります。

'*'はエラーの代わりに表示させたものです。
これは
map.cpp(119): mBlocks[mPlayerY*mSizeX+mPlayerX].set(false,PLAYER);
でプレイヤー情報をクリアしただけで代わりの情報が入れられていないためです。

アバター
usao
記事: 1569
登録日時: 6年前

Re: 荷物運びゲームのプログラム

#14

投稿記事 by usao » 6年前

>map.cpp(119): mBlocks[mPlayerY*mSizeX+mPlayerX].set(false,PLAYER);
>でプレイヤー情報をクリアしただけで代わりの情報が入れられていないためです。
(実際に動かしてみているわけではないのでコードの見た目からの判断ですが)
フラグをビットの組み合わせで表現しているように見えるのでこの点については問題ないように見えます.

M.R

Re: 荷物運びゲームのプログラム

#15

投稿記事 by M.R » 6年前

>フラグをビットの組み合わせで表現しているように見えるのでこの点については問題ないように見えます.
そうですね。この箇所が問題なわけではないです。
ここでクリアされた後、SPACE なり何なりをセットする箇所がないためです。

ねんどじん
記事: 23
登録日時: 9年前

Re: 荷物運びゲームのプログラム

#16

投稿記事 by ねんどじん » 6年前

>map.cpp(119): mBlocks[mPlayerY*mSizeX+mPlayerX].set(false,PLAYER);
>でプレイヤー情報をクリアしただけで代わりの情報が入れられていないためです。

なるほど。それでmBlocks[mPlayerY*mSizeX+mPlayerX].mFlagsに未定義の値が入ってしまった結果今回の問題が起きたのですね。
試しにC_map::C_map内のswitchでいままでmBlocks[cnt].set(true,PLAYER)と書いていたのを
mBlocks[cnt].set(true,PLAYER|SPACE)と書いて、set(false,PLAYER)しても中身がSPACEになるようにしたところ、
仕様通りに動くようになりました。 本当にありがとうございました。

残るは荷物を押す処理だけですが、ひとまず当初の問題は解決したのでこのトピックは解決扱いにしようと思います

ねんどじん
記事: 23
登録日時: 9年前

Re: 荷物運びゲームのプログラム

#17

投稿記事 by ねんどじん » 6年前

解決にし忘れてました。
皆さん本当にありがとうございました。

閉鎖

“C言語何でも質問掲示板” へ戻る