ゲームについて

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

ゲームについて

#1

投稿記事 by K_Tarou » 12年前

お久しぶりにこちらの掲示板に書き込みさせていただきます。K_Tarouと申します。

ある書籍を参考にc++のゲーム製作について勉強しておりまして、本当に初歩の初歩ですが、一応動くようになりました。
しかし、意図していない挙動をしていて、その原因もいまいち分かりません。具体的には
ユーザーから、cinで入力を受け取る部分で、複数回の入力( 例えば、"aaa" など )を一度に受け付けてしまう状態になっています。
本当は一度の入力( エンターキー )に対して、1マスだけ動いてほしいのです。

どうか原因をヒントだけでも結構ですので、教えていただけませんでしょうか?
それとよろしければ、構造やコードの書き方などで「 もっとこうした方がよい 」という点がありましたら、ご指摘していただければ嬉しいです。
下記は、拙いですが、書籍を参考に私が書いたコードです。よろしくお願い致します。


コード:

#include <iostream>
using namespace std;


/*   
    仕様 :
             ユーザーの入力を受け取り、ステージ上の ' p ' を動かす。
             最終的にブロック( ' o ' )をゴールである ' . ' の位置に2つ共全て動かせば、ゲームクリアとなる
             ゴール地点にある ' p ' や ' o ' は判別の為、大文字になる
*/


/* グローバル変数群 */

struct StgMaterial{
	static const int width = 9;
	static const int height = 5;
	static const char wall = '#'; //壁
	static const char space = ' '; //空き場所
	static const char goal = '.'; //ゴール地点
	static const char block = 'o'; //荷物
	static const char blockIn = 'O'; //ゴール上の荷物
	static const char player = 'p'; //プレイヤー
	static const char playerIn = 'P'; //ゴール上のプレイヤー
	const char stgData[ width * height ]; //読み取り用のステージデータ
};

static const StgMaterial stObj = {"\
########\n\
# .. p #\n\
# oo   #\n\
#      #\n\
########"
};

const enum ComFlag{ QUIT = 0, ENIT, LEFT, RIGHT, UP, DOWN, MISS }; //ユーザーのコマンド(QUITは終了用)



/* 関数プロトタイプ群 */

void initStg(char* const stgArr, const char* gStgData); // ステージデータの初期化
void disp(char* stgArr); // ゲーム画面出力
ComFlag getInput(char* stgArr, const StgMaterial* obj); // ユーザー入力を受け取る
void update(ComFlag flg, char* stgArr, const StgMaterial* obj); // ゲームの状態更新
bool checkClear(const char* stage, const StgMaterial* obj); // ゲームクリアしたかをチェック



/* 関数群 */

int main(){
	char* stage = new char[ stObj.width * stObj.height ];
	ComFlag flg = ENIT;   //最初の初期化用

	do{
		if(flg != MISS){
			update(flg, stage, &stObj);
			disp(stage);
			checkClear(stage, &stObj);
		}
	}while( flg = getInput(stage, &stObj) );  //QUITだったら抜ける

	delete[] stage;
	stage = NULL;
	return 0;
}


void initStg(char* stgArr, const char* gStgData){
	while( (*stgArr++) = (*gStgData++) ) ;
}


void disp(char* stgArr){
	cout << stgArr << endl;
	cout << "\na:left  s:right  w:up  z:down  q:end  e:enitialize    command?" << endl;
}


ComFlag getInput(char* stgArr, const StgMaterial* obj){
	char command = '\0';
	cin >> command;
	switch( command ){
		case 'a' : return LEFT;  
		case 's' : return RIGHT; 
		case 'w' : return UP;    
		case 'z' : return DOWN;  
		case 'e' : return ENIT;  
		case 'q' : return QUIT;  

		default:
		cout << "入力データが不正です! a,s,w,z,e,q のいずれかを押してください\n" << endl;
		return MISS;
	}
}


void update(ComFlag flg, char* stgArr, const StgMaterial* obj){

	if( flg == ENIT ){   //初期化コマンドが入力されていたら初期化する
		initStg(stgArr, obj->stgData);
		return;
	}

	int moveD = 0;   //移動する方向
	int pPos = 0;   //プレイヤーの位置
	int fPos = 0;   //プレイヤーの移動先
	char* dummy = stgArr;

	for(; (*dummy != obj->player) && (*dummy != obj->playerIn); ++dummy){ pPos++; }

	switch( flg ){
	    case LEFT  : moveD = -1; break;   //左のインデックスへ向ける
	    case RIGHT : moveD = 1; break;    //右のインデックスへ向ける
		case UP    : moveD = -(obj->width); break;   //上のインデックス(+9)へ向ける
		case DOWN  : moveD =  obj->width ; break;    //下のインデックス(-9)へ向ける
	}
	fPos = ( pPos + moveD );

	if( (fPos >= 0) && (fPos <= (obj->width * obj->height)) ){ //はみ出し確認

		if( (stgArr[ fPos ] == obj->space) || (stgArr[ fPos ] == obj->goal) ){ //移動先が空きスペースか、ゴールの場合

			stgArr[ fPos ] = (stgArr[ fPos ] == obj->goal) ? obj->playerIn : obj->player; //移動先がゴールならゴール上のプレイヤーに(P)
			stgArr[ pPos ] = (stgArr[ pPos ] == obj->playerIn) ? obj->goal : obj->space; //現在地がゴール(P)ならゴール(.)に

		}else if( (stgArr[ fPos ] == obj->block) || (stgArr[ fPos ] == obj->blockIn) ){ //移動先が荷物の場合

			if( (stgArr[ fPos + moveD ] == obj->wall) || (stgArr[ fPos + moveD ] == obj->block) || (stgArr[ fPos + moveD ] == obj->blockIn) ){
				return; //荷物の先が壁か、荷物(ゴール上の荷物)の場合は何もせず抜ける
			}

			stgArr[ fPos + moveD ] = (stgArr[ fPos + moveD ] == obj->goal) ? obj->blockIn : obj->block; //荷物の先がゴールならゴール上の荷物(o → O)に
			stgArr[ fPos ] = (stgArr[ fPos ] == obj->blockIn) ? obj->playerIn : obj->player; //荷物があった位置がゴールだった場合、ゴール上のプレイヤーに
			stgArr[ pPos ] = (stgArr[ pPos ] == obj->playerIn) ? obj->goal : obj->space;

		}else{ ;/* 壁なので何もしない */ }
	}
}


bool checkClear(const char* stgArr, const StgMaterial* obj){
	const char* check = stgArr;
	while(*check++){
		if( *check == obj->block ){
			return false;
		}
	}
	cout << "\nCongratulation's!!" << endl;
	return true;
}

アバター
みけCAT
記事: 6734
登録日時: 15年前
住所: 千葉県
連絡を取る:

Re: ゲームについて

#2

投稿記事 by みけCAT » 12年前

getlineで読み込み、最初の1文字をコマンドとして処理するといいと思います。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

K_Tarou
記事: 22
登録日時: 12年前

Re: ゲームについて

#3

投稿記事 by K_Tarou » 12年前

みけCAT さん、ご返答ありがとうございます。
教えて頂いた、getline関数や他の関数を調べて、総当たり的にデバッグして入力を受け取る getInput 関数の部分を以下のように書いたところ、
無事に複数の文字列を入力するなどしても、意図した通りの動きをしてくれるようになりました。

なので、これにてこのトピックは解決としたいのですが、デバッグしていく中で新たな疑問が生じました。
もし差し支えなければ教えて頂きたいのですが、


当初は cin.fail() などのエラー処理をせずに、getlineなどを使ってテストをしていたのですが
確保しておいたcommand配列のサイズを超過した入力があったときに以降、エラーによって読み込みできなくなることは分かったのです。

しかし、配列を例えば、command[2]という小さなサイズにしたときは、下記のコードと全く同様のエラー処理を行っても
ストリーム上(?)の文字が読み捨てされずに配列に格納されてしまい、ユーザーの入力タイミングが失われてしまう結果になりました。
同じエラー処理を行っている上に、この配列のサイズは終端子を除いて、最低1文字文確保していれば良いと思っていたので、この挙動が全く理解できません。
この挙動はどういった仕組みに基づくものなのでしょうか?

※ 追記・訂正
意図した通りの動きをするようになったと思っていたのですが、様々なパターンの入力を与えていくうちに
特定の入力によっては、やはり二回別の方向にプレイヤーが移動してしまうことが分かりました。
しかし、このプログラムについては解決としたいと思います。
上記の内容について理解が曖昧な故にこういう状態になっているように感じられますので...


本来は別トピックを立てるべきかと迷ったのですが、この動きの流れが理解できなかったのでお聞きしてしまいました。
迷惑でしたら、新しくトピックを立てさせていただきます。
すみません。

コード:

ComFlag getInput(char* stgArr, const StgMaterial* obj){
	char command[128] = { '\0' };
	cin.getline( command, sizeof(command) );
	
	if( cin.fail() ){   //エラーフラグが設定されたら、クリアして残された文字を全て読み捨てる(つもり)
		cin.clear();
		cin.ignore( cin.gcount(), '\n' );
	}

	switch( command[0] ){
		case 'a' : return LEFT;  
		case 's' : return RIGHT; 
		case 'w' : return UP;    
		case 'z' : return DOWN;  
		case 'e' : return ENIT;  
		case 'q' : return QUIT;  

		default:
		cout << "入力データが不正です! a,s,w,z,e,q のいずれかを押してください\n" << endl;
		return MISS;
	}
}

ISLe
記事: 2650
登録日時: 15年前
連絡を取る:

Re: ゲームについて

#4

投稿記事 by ISLe » 12年前

標準入力は、読み出した分だけが消費され、残った分は次の読み出し操作に繰り越されます。

アバター
みけCAT
記事: 6734
登録日時: 15年前
住所: 千葉県
連絡を取る:

Re: ゲームについて

#5

投稿記事 by みけCAT » 12年前

getlineでstd::string型の変数に読み込むようにすれば、バッファサイズを気にしなくてすみます。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

K_Tarou
記事: 22
登録日時: 12年前

Re: ゲームについて

#6

投稿記事 by K_Tarou » 12年前

ISLeさん、みけCAT さん
ご指摘ありがとうございます。
char型配列をstring型に変更して、getline関数で読み込むようにしたところ問題無く動くようになりました。
初歩的な事柄をお聞きしてしまい、すいませんでした。解決とさせていただきます。
お二方共、ありがとうございました。

閉鎖

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