ぷよぷよっぽいゲーム開発3日目(一応ひとくぎり)

今日やったこと

こんな感じのソースコードを書いた

#include "DxLib.h"
#include<iostream>
#include<vector>
#include<string>

typedef std::pair<int, int> pair;
std::vector<pair> pair_vec;

//連鎖関連の配列と変数、関数で呼び出すためグローバルにした
  int field[6][12];
  int rensaflag[6][12];
  int rensamemo[6][12];
  int sousapuyoflag;
  //ステージの各列の高さ
  int hi[6];
  //puyoの幅
  int puyow,puyoh;


//フラグを初期化する関数
void chaininit(){
  for(int k=0;k<6;k++){
    for(int l=0;l<12;l++){
      rensaflag[k][l]=0;
    }
  }
}


//フィールドを全探索し、自分自身と同色かつ縦横につながったぷよの数を計算する関数
void rensaf(int i,int j,int& n,int& chain){
  if(field[i][j]==0)return;
  rensaflag[i][j]=1;
  n++;
  if(i+1<6&&field[i][j]==field[i+1][j]&&rensaflag[i+1][j]==0)rensaf(i+1,j,n,chain);
  if(j+1<12&&field[i][j]==field[i][j+1]&&rensaflag[i][j+1]==0)rensaf(i,j+1,n,chain);
  if(i-1>-1&&field[i][j]==field[i-1][j]&&rensaflag[i-1][j]==0)rensaf(i-1,j,n,chain);
  if(j-1>-1&&field[i][j]==field[i][j-1]&&rensaflag[i][j-1]==0)rensaf(i,j-1,n,chain);  
  if(n>=4){chain=1;rensamemo[i][j]=n;}
  return;
}


//DXライブラリのメイン関数呼び出し
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
			 LPSTR lpCmdLine, int nCmdShow )
{
	//ログファイルを吐かない
	SetOutApplicationLogValidFlag(false) ;
	
	//フルスクリーンの表示をやめる
	ChangeWindowMode(true);
	
	
	// DXライブラリ初期化
	if( DxLib_Init() == -1 ) return -1 ;
	
	//ウィンドウに表示される名前
	SetMainWindowText( "かちかち" ) ;
	
	//画面の解像度などを設定
	SetGraphMode( 640 , 480 , 16 ) ;
	
	
	//画像ハンドルの宣言
	int StageHandle,KiroHandle,AkaHandle,AoHandle,MidoriHandle,BatuHandle;
	int GOHandle;
	
	//ステージ画像の読み込み
	StageHandle=LoadGraph("sozai/stage2.png");
	//ぷよの画像の読み込み
	KiroHandle=LoadGraph("sozai/kiro.png");
	AkaHandle=LoadGraph("sozai/aka.png");
	AoHandle=LoadGraph("sozai/ao.png");
	MidoriHandle=LoadGraph("sozai/midori.png");
	//その他画像の読み込み
	BatuHandle=LoadGraph("sozai/batu.png");
	GOHandle=LoadGraph("sozai/gameover.png");
	
	//ステージ画像のサイズを取得
	int stagew,stageh;
	GetGraphSize(StageHandle,&stagew,&stageh);
	//ステージ画像を拡大
	double stager=(double)480.0/stageh;
	stagew=stagew*stager;
	stageh=stageh*stager;
	//ステージの左端の位置
	int a=150;
	
	
	//ぷよ画像のサイズを取得
	//int puyow,puyoh;
	GetGraphSize(AkaHandle,&puyow,&puyoh);
	//ぷよ画像を拡大
	double puyor=(double)stagew/6.0/puyow;
	puyow=puyow*puyor;
	puyor=(double)stageh/12.0/puyoh;
	puyoh=puyoh*puyor;
	
	
	//その他の画像の処理
	int batuw,batuh;
	GetGraphSize(BatuHandle,&batuw,&batuh);
	double batur=(double)puyow/batuw;
	batuw=batur*batuw;
	batuh=batur*batuh;
	
	
	//音声ファイルの読み込み
	int gameover_sou,ready_sou,go_sou,bgm_sou,idou_sou,kieru_sou;
        gameover_sou=LoadSoundMem("sozai/gameover.mp3");
	go_sou=LoadSoundMem("sozai/go.mp3");
	ready_sou=LoadSoundMem("sozai/ready.mp3");
	bgm_sou=LoadSoundMem("sozai/bgm.mp3");
	idou_sou=LoadSoundMem("sozai/idou.wav");
	kieru_sou=LoadSoundMem("sozai/kieru.wav");

	
	
	//ステージの各列の高さを初期化
	//int hi[6];
	for(int i=0;i<6;i++)
	  hi[i]=11*puyoh;
	
	
	//ぷよの初期位置
	int inix=a+2*puyow;
	int iniy=-puyoh;
	
	//連鎖関連の変数
	int chain_max=0;
	int chain_num=0;
	
	//操作中のぷよの位置の変数
	int x,y;
	
	//ぷよの色
	int c;
	int nexc;
	srand((unsigned int)time(NULL));
	
	//ぷよに関するフラグ
	int puyoflag=0;
	int puyoxflag=0;
	int chainflag;
	
	
	//フィールドの状態を保存する配列の初期化
	for(int i=0;i<6;i++){
	  for(int j=0;j<12;j++){
	    field[i][j]=0;
	  }
	}
	
	//描画先を裏画面に設定
	SetDrawScreen( DX_SCREEN_BACK ) ;
	
	//bgmを鳴らす
	PlaySoundMem(bgm_sou,DX_PLAYTYPE_LOOP,false) ;
	
	//Nextに表示されるぷよの色を乱数で指定
	nexc=rand()%4;
	switch(nexc){                    
	         case 0:nexc=AoHandle;break;
		 case 1:nexc=AkaHandle;break;
		 case 2:nexc=KiroHandle;break;
		 case 3:nexc=MidoriHandle;break;
	       }
	
	
	//ゲームスタート画面の描画
	  ClearDrawScreen() ;
	  //ステージ画像の処理
	  {
	    //ステージ画像を拡大して表示
	    DrawExtendGraph(a,0,stagew+a-0.5,stageh,StageHandle,false);
	    DrawExtendGraph(a+2*puyow,0,a+2*puyow+batuw,batuh,BatuHandle,true);
	    DrawExtendGraph(stagew+a+5,0,stagew+a+5+puyow,puyoh,nexc,true);
	    DrawString(stagew+a+5+puyow+3,0,"Next",GetColor( 255 , 0 , 0 ));
	    DrawString(a+100,200,"レディ",GetColor( 255 , 0 , 0 )) ;
	    DrawFormatString(stagew+a+5+puyow+3,150,GetColor( 255 , 0 , 0 ),"MAX %d 連鎖",chain_max);
	    
          }
	  ScreenFlip();
	  PlaySoundMem(ready_sou,DX_PLAYTYPE_NORMAL,false) ;
	  WaitTimer(1000);
	//start!
	  ClearDrawScreen() ;
	  //ステージ画像の処理
	  {
	    //ステージ画像を拡大して表示
	    DrawFormatString(stagew+a+5+puyow+3,150,GetColor( 255 , 0 , 0 ),"MAX %d 連鎖",chain_max);
	    DrawExtendGraph(a,0,stagew+a-0.5,stageh,StageHandle,false);
	    DrawExtendGraph(a+2*puyow,0,a+2*puyow+batuw,batuh,BatuHandle,true);
	    DrawExtendGraph(stagew+a+5,0,stagew+a+5+puyow,puyoh,nexc,true);
	    DrawString(stagew+a+5+puyow+3,0,"Next",GetColor( 255 , 0 , 0 ));
	    DrawString(a+100,200,"ゴー!!",GetColor( 255 , 0 , 0 )) ;
          }
	  ScreenFlip();
	  PlaySoundMem(go_sou,DX_PLAYTYPE_NORMAL,false) ;
	  WaitTimer(1000);
	
	
	//ゲームの開始、ゲームオーバーまたはESCキーが推されるまで繰り返し
	while(1){
	  //画面をクリア
	  ClearDrawScreen() ;
	  DrawExtendGraph(stagew+a+5,0,stagew+a+5+puyow,puyoh,nexc,true);
	  DrawString(stagew+a+5+puyow+3,0,"Next",GetColor( 255 , 0 , 0 ));
	  DrawString(0,0,"終了:ESCキー",GetColor( 255 , 0 , 0 )) ;
	  sousapuyoflag=0;
	  //ステージ画像の処理
	  {
	    //ステージ画像を拡大して表示
	    DrawExtendGraph(a,0,stagew+a-0.5,stageh,StageHandle,false);
	    DrawExtendGraph(a+2*puyow,0,a+2*puyow+batuw,batuh,BatuHandle,true);
	    DrawFormatString(stagew+a+5+puyow+3,150,GetColor( 255 , 0 , 0 ),"MAX %d 連鎖",chain_max);
          }
	  
	  //ぷよの処理
	  {
	     //ぷよを操作し終わったら新しいぷよを降らせる
	     if(puyoflag==0){
	       x=inix;
	       y=iniy; 
	       //ぷよの色をランダムに決定
	       c=nexc;
	       nexc=rand()%4;
	       switch(nexc){                    
	         case 0:nexc=AoHandle;break;
		 case 1:nexc=AkaHandle;break;
		 case 2:nexc=KiroHandle;break;
		 case 3:nexc=MidoriHandle;break;
	       }
	       DrawExtendGraph(x,y,x+puyow,y+puyoh,c,true);
	       puyoflag=1;
	     }
	     
	     else{
	       //キーボードから入力に応じてぷよを動かす
	       if( CheckHitKey( KEY_INPUT_LEFT ) == 1 ){
	         if(puyoxflag==0){
		   if(hi[(x-puyow-a)/puyow]<y)x=x;
		   else{
	             x-=puyow;
		     puyoxflag=7;
		   }
		 }
	       }
	       if( CheckHitKey( KEY_INPUT_RIGHT ) == 1 ){
	         if(puyoxflag==0){
		   if(hi[(x+puyow-a)/puyow]<y)x=x;
		   else{
	             x+=puyow;
		     puyoxflag=7;
		   }
		 }
	       }
	       if( CheckHitKey( KEY_INPUT_DOWN ) == 1 ) y += 12 ;
	       //壁際の処理
	       if(x<a)x=a;
	       if(x>a+5*puyow)x=a+5*puyow;
	       
	       //着地したら操作フラグをしまう
	       if(y>hi[(x-a)/puyow]){
	         y=hi[(x-a)/puyow];
		 field[(x-a)/puyow][y/puyoh]=c;
		 hi[(x-a)/puyow]-=puyoh;
		 puyoflag=0;
		 
		 
		 //連鎖関連の処理
		 chainflag=1;
		 chain_num=0;
		 int test=0;
		 while(chainflag){
		   //フラグを0にする
		   chainflag=0;
		   //連鎖があるかどうかメモする配列の初期化
		   for(int i=0;i<6;i++){
		     for(int j=0;j<12;j++){
		       rensamemo[i][j]=0;
		     }
		   }
		   //連鎖関数の呼び出し
		   for(int i=0;i<6;i++){
		     for(int j=0;j<12;j++){
		       chaininit();
		       rensaf(i,j,0,chainflag);
		     }
		   }
		   
		   //連鎖があれば処理を行う
		   if(chainflag==0)break;
		   chain_num++;
		   //画面をクリア
	           ClearDrawScreen() ;
	           //ステージ画像の処理
	           {
	           //ステージ画像を拡大して表示
		        DrawFormatString(stagew+a+5+puyow+3,150,GetColor( 255 , 0 , 0 ),"MAX %d 連鎖",chain_max);
	    		DrawExtendGraph(a,0,stagew+a-0.5,stageh,StageHandle,false);
	   		DrawExtendGraph(a+2*puyow,0,a+2*puyow+batuw,batuh,BatuHandle,true);
			DrawExtendGraph(stagew+a+5,0,stagew+a+5+puyow,puyoh,nexc,true);
	    		DrawString(stagew+a+5+puyow+3,0,"Next",GetColor( 255 , 0 , 0 ));
			int color= GetColor( 255 , 0 , 0 );
			DrawFormatString(0,0,color,"連鎖中",chain_num) ;
          	   }
		   //ぷよの表示
		   for(int i=0;i<6;i++){
		     for(int j=0;j<12;j++){
		       if(field[i][j]==0)continue;
	               DrawExtendGraph(i*puyow+a,puyoh*j,i*puyow+a+puyow,puyoh*j+puyoh,field[i][j],false);
		     }
		   }
		   ScreenFlip();
		   WaitTimer( 250 );
		                                   
		   
		   sousapuyoflag=1;
		   for(int i=0;i<6;i++){
		     for(int j=0;j<12;j++){
		       if(rensamemo[i][j]>=4){
		         field[i][j]=0;
			 hi[i]+=puyoh;
		       }
		     }
		   }
		   //画面をクリア
	           ClearDrawScreen() ;
	           //ステージ画像の処理
	           {
	           //ステージ画像を拡大して表示
		   	DrawFormatString(stagew+a+5+puyow+3,150,GetColor( 255 , 0 , 0 ),"MAX %d 連鎖",chain_max);
	    		DrawExtendGraph(a,0,stagew+a-0.5,stageh,StageHandle,false);
	   		DrawExtendGraph(a+2*puyow,0,a+2*puyow+batuw,batuh,BatuHandle,true);
			DrawExtendGraph(stagew+a+5,0,stagew+a+5+puyow,puyoh,nexc,true);
	    		DrawString(stagew+a+5+puyow+3,0,"Next",GetColor( 255 , 0 , 0 ));
			int color= GetColor( 255 , 0 , 0 );
			DrawFormatString(0,0,color,"%d連鎖",chain_num) ;
          	   }
		   //ぷよの表示
		   for(int i=0;i<6;i++){
		     for(int j=0;j<12;j++){
		       if(field[i][j]==0)continue;
	               DrawExtendGraph(i*puyow+a,puyoh*j,i*puyow+a+puyow,puyoh*j+puyoh,field[i][j],false);
		     }
		   }
		   ScreenFlip();
		   PlaySoundMem(kieru_sou,DX_PLAYTYPE_NORMAL,true) ;
		   WaitTimer( 250 );
		   
		   //画面をクリア
	           ClearDrawScreen() ;
	           //ステージ画像の処理
	           {
	           //ステージ画像を拡大して表示
		   	DrawFormatString(stagew+a+5+puyow+3,150,GetColor( 255 , 0 , 0 ),"MAX %d 連鎖",chain_max);
	    		DrawExtendGraph(a,0,stagew+a-0.5,stageh,StageHandle,false);
	   		DrawExtendGraph(a+2*puyow,0,a+2*puyow+batuw,batuh,BatuHandle,true);
			DrawString(0,0,"終了:ESCキー",GetColor( 255 , 0 , 0 )) ;
          	   }
		   int rakkaflag=1;
		   while(rakkaflag){
		     rakkaflag=0;
		     for(int i=0;i<6;i++){
		       for(int j=0;j<12;j++){
		         if(field[i][j]==0&&j!=0){
	                   field[i][j]=field[i][j-1];
			   field[i][j-1]=0;
		         }
		       }
		     }
		     for(int i=0;i<6;i++){
		       for(int j=0;j<12;j++){
		         if(field[i][j]!=0&&field[i][j+1]==0&&j+1<12)
			   rakkaflag=1;
		       }
		     }
		   }
		   }
		//連鎖関連の処理の終了
		 
	       }
	       
	       
	       //ぷよを横方向に小刻みに動かすための処理
	       if(puyoxflag!=0)
	         puyoxflag--;
		 
	     //操作中のぷよの描画
	     if(sousapuyoflag==0) 
	       DrawExtendGraph(x,y,x+puyow,y+puyoh,c,false);

	     //堆積したぷよの描画
	       for(int i=0;i<6;i++){
	         for(int j=0;j<12;j++){
		   if(field[i][j]==0)continue;
	             DrawExtendGraph(i*puyow+a,puyoh*j,i*puyow+a+puyow,puyoh*j+puyoh,field[i][j],false);
	         }
	       }
	       
	       
	    //表画面への描画
	      ScreenFlip();
	       
	    }
	     
	     //自由落下
	     y+=1;
	   } 
	  
	  if(chain_max<chain_num)chain_max=chain_num;
	  //ゲームオーバー
	  if(hi[2]==-puyoh){
	    DrawGraph(a,150,GOHandle,false);
	    ScreenFlip();
	    StopSoundMem(bgm_sou) ;
	    PlaySoundMem(gameover_sou,DX_PLAYTYPE_NORMAL,false) ;
	    WaitKey();
	    break;
	  }  
	  sousapuyoflag=0;
	  
	  
	  //仮の終了処理
	  {
	    // Windows特有の面倒な処理をDXライブラリにやらせる
	    // -1 が返ってきたらループを抜ける
	    if( ProcessMessage() < 0 ) break ;
	    // もしESCキーが押されていたらループから抜ける
	    if( CheckHitKey( KEY_INPUT_ESCAPE ) ) break ;
	  }
	  
	}
	
	//ゲームオーバーの処理
	DrawString(0,20,"ゲーム終了",GetColor( 255 , 0 , 0 )) ;
	ScreenFlip();
	StopSoundMem(bgm_sou) ;
	WaitTimer(5*100);

	// DXライブラリ使用の終了
	DxLib_End() ;

	// ソフトの終了
	return 0 ;
}

進捗

今後の展望

  • 連結しているぷよの形は変えたほうがフィールドが見やすくなる気がする
  • ぷよを動かす操作に適当なSEをつけたい
  • 本物のぷよぷよのように消したぷよの数に応じたスコアをつけたい
  • 連鎖に伴った硬直時間が適切か見直したい
  • ぷよが消えるときのアニメーションを追加したい
  • メニューを充実させたい
  • ぷよを接地したことが分かるようなエフェクトをつけたい

今思いつくのはこれくらいか。

感想

DXライブラリのおかげで初心者でも割と手軽にゲームプログラミングができたと思う。ゲーム特有(?)の画像の表示や音楽を鳴らす処理はDXライブラリのおかげで苦労しなかった。一方で連鎖をどう実装するかなど純粋なアルゴリズム的な部分で苦労した。趣味の競技プログラミングでしばしば出てくる再帰関数がゲームのプログラミングに役立っているのを見て再帰関数は偉いなと思った。

実行ファイル

release.zip - Google ドライブ

windows用のexeファイルが格納されたzipファイルがダウンロードできます。一応完成版ということにします。