/*
 *	game.c
 *
 *	ぐるりんもどき
 *
 *	* Tue Apr 25 21:31:44 JST 2017 Naoyuki Sawa
 *	- 1st リリース。
 */
#include "app.h"
/****************************************************************************
 *	
 ****************************************************************************/
extern /*const*/ unsigned char MAIN_BGM[];
/****************************************************************************
 *	
 ****************************************************************************/
#define STAT_READY		0			//ゲーム開始前
#define STAT_APPEAR		1			//ブロック出現中
#define STAT_MOVE		2			//ブロック操作中
#define STAT_GAMEOVER		3			//ゲームオーバー
/*--------------------------------------------------------------------------*/
#define FIELD_COLS		6			//列数
#define FIELD_ROWS		(6+1)			//行数					最上段はゲームオーバー判定用です。
/*--------------------------------------------------------------------------*/
#define BLOCK_TYPE_MAX		13			//ブロックの種類の最大値		0=空き,1〜BLOCK_TYPE_MAX
/*--------------------------------------------------------------------------*/
#define FALL_SPEED		(H__CELL/2)		//落下速度
/*--------------------------------------------------------------------------*/
#define MOVE_WAIT		SEC(2)			//落下開始前ウェイト
#define MOVE_SPD_INIT		64			//初期落下速度
/*--------------------------------------------------------------------------*/
#define EFFECT_ERASE		1			//消滅エフェクト
#define EFFECT_GAMEOVER		2			//ゲームオーバーエフェクト
/*--------------------------------------------------------------------------*/
#define LEVUP_CNT		5			//何回消す度に早くなるか。
#define MOVESPD_UPRATE		1.2			//何倍に早くなるか。
/****************************************************************************
 *	
 ****************************************************************************/
typedef struct _ST_Cell {
	uint8_t			type;			//0=空き,1〜BLOCK_TYPE_MAX
	uint8_t			rot;			//回転(0〜3)				演出上の見た目だけの回転です。
	uint8_t			erase;			//消去マーク
	uint8_t			fall;			//落下マーク
} ST_Cell;
/*--------------------------------------------------------------------------*/
typedef struct _ST_Effect {
	uint8_t			mode;			//エフェクトの種類			0=空き,1〜EFFECT_*
	uint8_t			type;			//1〜BLOCK_TYPE_MAX
	uint8_t			anmCnt;			//アニメーションカウンタ
	short			x, y;			//位置
	short			vx, vy;			//速度
	short			ax, ay;			//加速度
} ST_Effect;
/*--------------------------------------------------------------------------*/
typedef struct _ST_Game {
	uint16_t		score;			//スコア
	uint8_t			colors;			//ブロックの種類数
	uint8_t			stat;			//状態
	uint8_t			levUp;			//レベルアップカウンタ
	//
	 int8_t			nextPosX;		//NEXTブロックの位置			-(W__CELL*2)⇒X__NEXT⇒X__CELL
	uint8_t			TBL_Next[2];		//NEXTブロックの種類			1〜BLOCK_TYPE_MAX
	//
	uint8_t			movePosX;		//┬出現中のブロックの位置		X__NEXT⇒X__CELL+W__CELL*2
	uint8_t			movePosY;		//┘					Y__NEXT⇒Y__NEXT+H__CELL
	uint8_t			movePosYi;		//出現中のブロックの階段を下りるアニメーション用のカウンタ
	//
	uint8_t			moveWait;		//出現後、落下開始までのウェイト	MOVE_WAIT⇒0
	uint8_t			moveCol;		//操作ブロックの列			0〜FIELD_COLS
	uint8_t			moveRow;		//操作ブロックの行			0〜FIELD_ROWS
	 int16_t		moveOfs;		//操作ブロックの行オフセット		(?<<8)⇒(0<<8)
	uint16_t		moveSpd;		//操作ブロックの落下速度		(?<<8)
	uint8_t			TBL_Move[2];		//操作ブロックの種類			0〜BLOCK_TYPE_MAX	右側のブロックが接地したら{TBL_Move[1]=0}とする。左側のブロックが接地したら右ブロックが有れば{TBL_Move[0]=TBL_Move[1];moveCol++}とする。(TBL_Move[0]==0)になったら両方のブロックが接地完了したと判断出来る。
	//
	 int8_t			fieldRot;		//フィールド回転アニメーションカウンタ		-16=左90度〜0=0度〜+16=右90度
	uint8_t			fieldFall;		//フィールド落下アニメーションカウンタ		H__CELL⇒0
	ST_Cell			TBL_Field[FIELD_ROWS][FIELD_COLS];	//フィールド
	//
	ST_Effect		TBL_Effect[FIELD_ROWS*FIELD_COLS];	//エフェクト
} ST_Game;
ST_Game stGame;		//唯一のインスタンス
/****************************************************************************
 *	next_init
 ****************************************************************************/
void next_init() {
	stGame.movePosX = stGame.nextPosX;
	stGame.movePosY = Y__NEXT;
	stGame.movePosYi = 0;
	stGame.TBL_Move[0] = stGame.TBL_Next[0];
	stGame.TBL_Move[1] = stGame.TBL_Next[1];
	//↑↓この順序を逆にしないように注意しろ。
	stGame.nextPosX = -(W__CELL*2);
	stGame.TBL_Next[0] = RND32_RANGE(seed, 1, stGame.colors + 1);
	stGame.TBL_Next[1] = RND32_RANGE(seed, 1, stGame.colors + 1);
}
/****************************************************************************
 *	field_rot_r
 ****************************************************************************/
void field_rot_r() {
	ST_Cell TBL_New[FIELD_ROWS][FIELD_COLS], cell;
	int col, row, new_col, new_row;
	for(row = 0; row < FIELD_ROWS; row++) {
		for(col = 0; col < FIELD_COLS; col++) {
			cell = stGame.TBL_Field[row][col];
			if(!row) {
				new_col = col;
				new_row = row;
			} else {
				new_col = FIELD_COLS - row;
				new_row = col + 1;
				if(cell.type) { cell.rot = (cell.rot + 1) & 3; }
				if((new_col < 0) || (new_col > FIELD_COLS - 1)) { DIE(); }
				if((new_row < 1) || (new_row > FIELD_ROWS - 1)) { DIE(); }
			}
			TBL_New[new_row][new_col] = cell;
		}
	}
	memcpy(stGame.TBL_Field, TBL_New, sizeof TBL_New);
}
/****************************************************************************
 *	field_rot_l
 ****************************************************************************/
void field_rot_l() {
	ST_Cell TBL_New[FIELD_ROWS][FIELD_COLS], cell;
	int col, row, new_col, new_row;
	for(row = 0; row < FIELD_ROWS; row++) {
		for(col = 0; col < FIELD_COLS; col++) {
			cell = stGame.TBL_Field[row][col];
			if(!row) {
				new_col = col;
				new_row = row;
			} else {
				new_col = row - 1;
				new_row = FIELD_ROWS - col - 1;
				if(cell.type) { cell.rot = (cell.rot - 1) & 3; }
				if((new_col < 0) || (new_col > FIELD_COLS - 1)) { DIE(); }
				if((new_row < 1) || (new_row > FIELD_ROWS - 1)) { DIE(); }
			}
			TBL_New[new_row][new_col] = cell;
		}
	}
	memcpy(stGame.TBL_Field, TBL_New, sizeof TBL_New);
}
/****************************************************************************
 *	check_fall
 ****************************************************************************/
void check_fall() {
	int col, row;
	for(row = 0; row < FIELD_ROWS; row++) {
		for(col = 0; col < FIELD_COLS; col++) {
			stGame.TBL_Field[row][col].fall = 0;
		}
	}
	for(row = FIELD_ROWS - 1; row > 0; row--) {
		for(col = 0; col < FIELD_COLS; col++) {
			ST_Cell* cell1 = &stGame.TBL_Field[row][col];
			if(!cell1->type) {
				ST_Cell* cell2 = &stGame.TBL_Field[row - 1][col];
				if(cell2->type) {
					memcpy(cell1, cell2, sizeof(ST_Cell));
					memset(cell2,     0, sizeof(ST_Cell));
					cell1->fall = 1;
					stGame.fieldFall = H__CELL;
				}
			}
		}
	}
}
/****************************************************************************
 *	effect_gen
 ****************************************************************************/
void effect_gen(int mode, int type, int col, int row) {
	int i;
	for(i = 0; i < ARRAY_SIZE(stGame.TBL_Effect); i++) {
		ST_Effect* effect = &stGame.TBL_Effect[i];
		if(!effect->mode) {
			memset(effect, 0, sizeof(ST_Effect));
			effect->mode	= mode;
			effect->type	= type;
			effect->x	= (X__CELL + (W__CELL * col)) << 6;
			effect->y	= (Y__CELL + (H__CELL * row)) << 6;
			switch(effect->mode) {
			default:DIE();
			case EFFECT_ERASE:
				{
					double r = RND32_RANGE(seed, 0, 8) * (M_PI * 2 / 8);
					double dSin = sin(r);
					double dCos = cos(r);
					effect->ax = dCos * (3<<2);
					effect->ay = dSin * (3<<2);
				}
				break;
			case EFFECT_GAMEOVER:
				{
					effect->vy = -9.0 * (3<<2);
					effect->ay =  0.9 * (3<<2);
					if(!(col & 1)) {
						effect->vx = 5.0 * (3<<2);
						if((col ^ row) & 1) {
							effect->vx = -effect->vx;
						}
					}
				}
				break;
			}
			return;	//ここまで。
		}
	}
}
/****************************************************************************
 *	check_erase
 ****************************************************************************/
static int check_erase_subr1(int col, int row, int type, int dir) {
	ST_Cell* cell;
	if((unsigned)col >= FIELD_COLS) { return 0; }
	if((unsigned)row >= FIELD_ROWS) { return 0; }
	cell = &stGame.TBL_Field[row][col];
	if(cell->type != type) { return 0; }
	col += (dir == 0);
	row += (dir == 1);
	return check_erase_subr1(col, row, type, dir) + 1;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
static void check_erase_subr2(int col, int row, int type, int dir) {
	ST_Cell* cell;
	if((unsigned)col >= FIELD_COLS) { return; }
	if((unsigned)row >= FIELD_ROWS) { return; }
	cell = &stGame.TBL_Field[row][col];
	if(cell->type != type) { return; }
	if(cell->erase &  (1<<dir)) { DIE(); }	//バグ
	   cell->erase |= (1<<dir);
	col += (dir == 0);
	row += (dir == 1);
	check_erase_subr2(col, row, type, dir);
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
void check_erase() {
	int col, row;
	//各セルについて…
	for(row = 0; row < FIELD_ROWS; row++) {
		for(col = 0; col < FIELD_COLS; col++) {
			ST_Cell* cell = &stGame.TBL_Field[row][col];
			//空セルでなければ…
			if(cell->type) {
				int dir, len;
				//横方向と縦方向について…
				for(dir = 0; dir <= 1; dir++) {
					//既に左側(又は上側)のセルの消滅に含まれていなければ…
					if(!(cell->erase & (1<<dir))) {
						//同じ種類のブロックの並び数を数える。
						len = check_erase_subr1(col, row, cell->type, dir);
						//3個以上並んでいたら…
						if(len >= 3) {
							sound_play(3, sound_table[SND_SE_ERASE], 1);
							//並んでいるセルに消滅フラグをセットする。
							check_erase_subr2(col, row, cell->type, dir);
							//スコアを増やす。
							UVAR_ADD(stGame.score, 10 * len);
							//ハイスコアを更新する。
							if(stGame.score > hiscore) { hiscore = stGame.score; }
							//レベルアップカウンタを増やして、一定回数以上になったら…
							if(++stGame.levUp >= LEVUP_CNT) {
								//レベルアップカウンタをクリアする。
								stGame.levUp = 0;
								//操作ブロックの落下速度を早くする。
								stGame.moveSpd *= MOVESPD_UPRATE;
							}
							//ここではまだ消さないように注意!!
							//一つのセルが複数方向の並びに含まれている可能性があるから。
						}
					}
				}
			}
		}
	}
	//消滅フラグをセットしたブロックを消す処理。
	for(row = 0; row < FIELD_ROWS; row++) {
		for(col = 0; col < FIELD_COLS; col++) {
			ST_Cell* cell = &stGame.TBL_Field[row][col];
			//消滅フラグをセットされていたら…
			if(cell->erase) {
				//消滅エフェクトを生成する。
				effect_gen(EFFECT_ERASE, cell->type, col, row);
				//セルをクリアする。
				memset(cell, 0, sizeof(ST_Cell));
			}
		}
	}
}
/****************************************************************************
 *	game_init
 ****************************************************************************/
int check_gameover() {
	//最上段にブロックが残っていたらゲームオーバー。
	int col;
	for(col = 0; col < FIELD_COLS; col++) {
		ST_Cell* cell = &stGame.TBL_Field[0][col];
		if(cell->type) { return 1; }
	}
	return 0;
}
/****************************************************************************
 *	game_init
 ****************************************************************************/
void game_init() {
	//構造体をクリアする。
	memset(&stGame, 0, sizeof stGame);
	//ブロックの種類数を設定する。
	stGame.colors = clamp(colors_sel, 2, BLOCK_TYPE_MAX + 1);
	//初期落下速度を設定する。
	stGame.moveSpd = MOVE_SPD_INIT;
	//最初のNEXTブロックを生成する。
	//この時点ではまだ、操作ブロックは未出現です。
	next_init();
}
/****************************************************************************
 *	game_exec
 ****************************************************************************/
void game_exec() {
	//NEXTブロック
	{
		//NEXTブロックは、ゲーム状態に関係無く、画面上部で左から出現して待機位置まで移動する動作を行う。
		//ただし、ゲームオーバーになったら、その瞬間の位置で停止して移動しない。(自分の判断でそうしました)
		//ゲームオーバー中はNEXTブロックを上下反転して描くので、移動すると不自然だからです。
		if(!stGame.TBL_Next[0] || !stGame.TBL_Next[1]) { DIE(); }	//バグ。[0]も[1]も有るはず。
		if(stGame.stat != STAT_GAMEOVER) {	//ゲームオーバー中でなければ…
			if(stGame.nextPosX < X__NEXT) {	//待機位置に到達していなければ…
				//1/1の速度で右へ進む。
				stGame.nextPosX++;
			}
		}
	}
	//操作ブロック
	{
		//出現中ならば…
		if(stGame.stat == STAT_APPEAR) {
			if(!stGame.TBL_Move[0] || !stGame.TBL_Move[1]) { DIE(); }	//バグ。[0]も[1]も有るはず。
			//待機位置から、列2のX座標の位置まで、2/1の速度で移動する。
			if(stGame.movePosX < (X__CELL+W__CELL*2)) {
				stGame.movePosX += 2;
				if(stGame.movePosX > (X__CELL+W__CELL*2)) { stGame.movePosX = (X__CELL+W__CELL*2); }
			//列2のX座標に到達したら…
			} else {
				//階段を下りるアニメーションを行う。
				static const int TBL_y[]={
					-2, 0, 1, 3,
					 1, 3, 4, 6,
					 4, 6, 7, 9,
					 7, 9,10,12};
				if((unsigned)stGame.movePosYi >= ARRAY_SIZE(TBL_y)) { DIE(); }	//バグ。stGame.movePosYiがARRAY_SIZE(TBL_y)に到達した後、next_init()にてstGame.movePosYi=0に戻る前に、再びこの処理へ来る事は無いはず。
				stGame.movePosY = Y__NEXT + TBL_y[stGame.movePosYi];
				//階段を下り切ったら…
				if(++stGame.movePosYi >= ARRAY_SIZE(TBL_y)) {
					//操作中へ移行する。
					stGame.stat = STAT_MOVE;
					stGame.moveWait = MOVE_WAIT;
					stGame.moveCol = 2;
					stGame.moveRow = 0;
					stGame.moveOfs = (Y__CELL - stGame.movePosY) << 8;
					if(stGame.moveOfs <= 0) { DIE(); }	//バグ。階段を下り切った位置はフィールドの最上段よりも上であるはず。
				}
			}
		//操作中ならば…
		} else if(stGame.stat == STAT_MOVE) {
			//両方実体化済みでなければ…
			if(stGame.TBL_Move[0]) {
				//フィールド落下中でなければ…
				// - オリジナル版は落下が早過ぎてフィールド落下中の左右移動が可能なのかどうかよくわからなかった。
				//   直観的には、フィールド落下中に左右操作出来てしまうと落下中のブロックの下に潜り込めてしまう恐れが有るので良くないと思うので、当実装ではフィールド落下中は操作不可とする事にした。
				//   (現実的には、元々、下に潜り込めるような消し方は組めないとは思うが…)
				if(!stGame.fieldFall ||
				   !stGame.moveRow) {	//ただし、たとえフィールド落下中でも、操作ブロックが最上段ならば左右移動は影響しないので、左右移動可とする事にした。
					//左ボタンの押下トリガならば…
					if(joy & TRG_LF) {
						//左ブロックの左に空きが有れば…
						if((stGame.moveCol > 0) &&
						   !stGame.TBL_Field[stGame.moveRow][stGame.moveCol - 1].type) {
							//左へ移動する。
							stGame.moveCol--;
							sound_play(0, sound_table[SND_SE_IDOU], 1);
						}
					//右ボタンの押下トリガならば…
					} else if(joy & TRG_RI) {
						//2ブロック操作中ならば…
						if(stGame.TBL_Move[1]) {
							//右ブロックの右に空きが有れば…
							if((stGame.moveCol < (FIELD_COLS - 2)) &&
							   !stGame.TBL_Field[stGame.moveRow][stGame.moveCol + 2].type) {
								//右へ移動する。
								stGame.moveCol++;
								sound_play(0, sound_table[SND_SE_IDOU], 1);
							}
						//1ブロック操作中ならば…
						} else {
							//左ブロックの右に空きが有れば…
							if((stGame.moveCol < (FIELD_COLS - 1)) &&
							   !stGame.TBL_Field[stGame.moveRow][stGame.moveCol + 1].type) {
								//右へ移動する。
								stGame.moveCol++;
								sound_play(0, sound_table[SND_SE_IDOU], 1);
							}
						}
					}
				}
				//落下開始までのウェイトが残っていたら…
				if(stGame.moveWait) {
					//ウェイトを減らす。
					stGame.moveWait--;
				#if 0
					//下ボタンの押下トリガならば、ウェイトをクリアする。
					if(joy & TRG_DN) { stGame.moveWait = 0; }
				#else//↓操作ミスを避けるために上のようにしていたのだが、押しっぱなしで早く進めたい時に不便なので、下のように変更する事にした。
					//下ボタンが押されていたら、ウェイトをクリアする。
					if(joy & PAD_DN) { stGame.moveWait = 0; }
				#endif
					//ウェイトが無くなった瞬間に落下音を鳴らす。
					if(!stGame.moveWait) {
						sound_play(1, sound_table[SND_SE_RAKKA], 1);
					}
				//落下開始までのウェイトが残っていなければ…
				} else {
					//操作ブロックの行オフセットを減らす。
					stGame.moveOfs -= (joy & PAD_DN) ? max((int)stGame.moveSpd, (int)(H__CELL<<8))	//下ボタンが押されていたら高速落下する。
					                                 :     (int)stGame.moveSpd;
					//行オフセットが無くなったら…(既にstGame.moveOfs==0だった場合もマイナスになるのでこの条件に含む)
					while(stGame.moveOfs <= 0) {	//もし(stGame.moveSpd>H__CELL)でも対応出来るようにifではなくwhileにした。
						//回転中,又は,落下中ならば、それらが完了するのを待つ。
						// - 実際には、回転待ちは最上段でしか起きないはずです。
						//   落下中は、片方のブロックだけが先に接地した場合に、どの行でも起こり得ます。
						if(stGame.fieldRot || stGame.fieldFall) {
							//現在の行で行オフセット0を維持する。
							stGame.moveOfs = 0;
							//ループを抜ける。
							break;
						//回転中でも落下中でもなければ…
						} else {
							int bLeft = 0, bRight = 0;
							//左右ブロックの接地判定を行う。
							if(stGame.moveRow < (FIELD_ROWS - 1)) {	//最下段でなければ…
								if(                      stGame.TBL_Field[stGame.moveRow + 1][stGame.moveCol + 0].type) { bLeft  = 1; }	//左ブロックの接地判定
								if(stGame.TBL_Move[1] && stGame.TBL_Field[stGame.moveRow + 1][stGame.moveCol + 1].type) { bRight = 1; }	//右ブロック(有れば)の接地判定
							} else {	//最下段ならば…
								                         bLeft  = 1;	//左ブロックの接地判定
								if(stGame.TBL_Move[1]) { bRight = 1; }	//右ブロック(有れば)の接地判定
							}
							//左右ブロックの少なくとも一方が接地していたら…
							if(bLeft || bRight) {
								sound_play(2, sound_table[SND_SE_SETTI], 1);
								//右ブロックが接地していたら…
								if(bRight) {
									//右ブロックを実体化する。
									ST_Cell* cell = &stGame.TBL_Field[stGame.moveRow][stGame.moveCol + 1];
									memset(cell, 0, sizeof(ST_Cell));
									cell->type = stGame.TBL_Move[1];
									stGame.TBL_Move[1] = 0;
								}
								//↑↓この順序を逆にしないように注意しろ。
								//左ブロックが接地していたら…
								if(bLeft) {
									//左ブロックを実体化する。
									ST_Cell* cell = &stGame.TBL_Field[stGame.moveRow][stGame.moveCol + 0];
									memset(cell, 0, sizeof(ST_Cell));
									cell->type = stGame.TBL_Move[0];
									stGame.TBL_Move[0] = 0;
									//右ブロックが残っていたら…
									if(stGame.TBL_Move[1]) {
										//右ブロックを左ブロックへ移す。
										stGame.TBL_Move[0] = stGame.TBL_Move[1];
										stGame.TBL_Move[1] = 0;
										if(stGame.moveCol > (FIELD_COLS - 2)) { DIE(); }	//バグ
										stGame.moveCol++;
									}
								}
								//両方のブロックが接地した瞬間に落下音を消す。
								if(!stGame.TBL_Move[0]) {
									sound_stop(1);
								}
								//フィールドの消滅を検査する。
								check_erase();
								//消滅した(かも知れない)事によって、再度、フィールドの落下を開始する。(有れば)
								// - 消滅⇒即落下という仕様です。(オリジナル版でも)
								check_fall();
								//現在の行で行オフセット0を維持する。
								stGame.moveOfs = 0;
								//ループを抜ける。
								break;
							//左右ブロックのどちらも接地していなければ…
							} else {
								//下へ移動する。
								stGame.moveRow++;
								stGame.moveOfs += (H__CELL<<8);
							}
						}
					}
				}
			}
		}
	}
	//フィールド
	{
		//出現中,又は,移動中で最上段でオフセットが残っていれば…
		if((stGame.stat == STAT_APPEAR) ||
		  ((stGame.stat == STAT_MOVE) && !stGame.moveRow && stGame.moveOfs)) {
			//既に回転中でなく、フィールド落下中でもなければ…
			if(!stGame.fieldRot && !stGame.fieldFall) {
				//Aボタンの押下トリガならば…
				if(joy & TRG_A) {
					sound_play(2, sound_table[SND_SE_KAITEN], 1);
					//右回転を開始する。
					stGame.fieldRot = -16;
					field_rot_r();
				//Bボタンの押下トリガならば…
				} else if(joy & TRG_B) {
					sound_play(2, sound_table[SND_SE_KAITEN], 1);
					//左回転を開始する。
					stGame.fieldRot =  16;
					field_rot_l();
				}
			}
		}
		//回転中ならば…
		if(stGame.fieldRot) {
			//残り回転角度を減らす。
			if(stGame.fieldRot < 0) { stGame.fieldRot++; }
			if(stGame.fieldRot > 0) { stGame.fieldRot--; }
			//現在の90度分の回転が完了したら…
			if(!stGame.fieldRot) {
				//出現中,又は,移動中で最上段でオフセットが残っていれば…
				if((stGame.stat == STAT_APPEAR) ||
				  ((stGame.stat == STAT_MOVE) && !stGame.moveRow && stGame.moveOfs)) {
					//Aボタンが押され続けていたら…
					if(joy & PAD_A) {
						//フィールドの落下を開始せずに(←重要!!。オリジナル版でもそういう仕様です。)、右回転を開始する。
						stGame.fieldRot = -16;
						field_rot_r();
					//Bボタンが押され続けていたら…
					} else if(joy & PAD_B) {
						//フィールドの落下を開始せずに(←重要!!。オリジナル版でもそういう仕様です。)、左回転を開始する。
						stGame.fieldRot =  16;
						field_rot_l();
					}
				}
				//新たな回転が開始していなければ…
				if(!stGame.fieldRot) {
					//フィールドの落下を開始する。(有れば)
					check_fall();
					//フィールドの落下が開始しなければ…
					if(!stGame.fieldFall) {
						//フィールドの消滅を検査する。
						check_erase();
						//消滅した(かも知れない)事によって、再度、フィールドの落下を開始する。(有れば)
						// - 消滅⇒即落下という仕様です。(オリジナル版でも)
						check_fall();
					}
				}
			}
		}
		//落下中ならば…
		if(stGame.fieldFall) {
			stGame.fieldFall -= FALL_SPEED;
			if(!stGame.fieldFall) {
				check_fall();
				if(!stGame.fieldFall) {
					//フィールドの消滅を検査する。
					check_erase();
					//消滅した(かも知れない)事によって、再度、フィールドの落下を開始する。(有れば)
					// - 消滅⇒即落下という仕様です。(オリジナル版でも)
					check_fall();
				}
			}
		}
	}
	//エフェクト
	{
		int i;
		for(i = 0; i < ARRAY_SIZE(stGame.TBL_Effect); i++) {
			ST_Effect* effect = &stGame.TBL_Effect[i];
			if(effect->mode) {
				switch(effect->mode) {
				default:DIE();
				case EFFECT_ERASE:
					UVAR_INC(effect->anmCnt);
					effect->vx += effect->ax;
					effect->vy += effect->ay;
					effect->x  += effect->vx;
					effect->y  += effect->vy;
					if((effect->x < (0<<6)) || (effect->x >= (DISP_X<<6)) ||
					   (effect->y < (0<<6)) || (effect->y >= (DISP_Y<<6))) {
						memset(effect, 0, sizeof(ST_Effect));
					}
					break;
				case EFFECT_GAMEOVER:
					effect->vx += effect->ax;
					effect->vy += effect->ay;
					effect->x  += effect->vx;
					effect->y  += effect->vy;
					if((effect->x < (0<<6)) ||   (effect->x >= (DISP_X<<6)) ||
					 /*(effect->y < (0<<6)) ||*/ (effect->y >= (DISP_Y<<6))) {
						memset(effect, 0, sizeof(ST_Effect));
					}
					break;
				}
			}
		}
	}
}
/****************************************************************************
 *	score_draw
 ****************************************************************************/
void score_draw() {
	int iPri, x, y;
	//1P
	iPri	= PRI__TEXT;
	x	=   X__TEXT__1P;
	y	=   Y__TEXT__1P;
	CbkQue_render_printf(pCbkQue,
		iPri,
		&render,
		x,
		y,
		2,
		0,
		"%5d", stGame.score);
	//HI
	iPri	= PRI__TEXT;
	x	=   X__TEXT__HI;
	y	=   Y__TEXT__HI;
	CbkQue_render_printf(pCbkQue,
		iPri,
		&render,
		x,
		y,
		2,
		0,
		"%5d", hiscore);
}
/****************************************************************************
 *	game_draw
 ****************************************************************************/
void game_draw() {
	int iPri, x, y, iSprNo, iParam;
	//背景
	{
		iPri	= PRI__BG;
		x	= 0;
		y	= 0;
		iSprNo	= SPR_BG;
		iParam	= DRW_NOMAL;
		CbkQue_sprite_draw(pCbkQue, iPri, x, y, iSprNo, iParam);
	}
	//NEXTブロック
	{
		int i;
		for(i = 0; i < 2; i++) {
			iPri	= PRI__NEXT;
			x	= stGame.nextPosX + (W__CELL * i);
			y	= Y__NEXT;
			iSprNo	= SPR_BLOCK_1_0_0 + (((32 * 2) + 8) * (stGame.TBL_Next[i] - 1));
			iParam	= DRW_NOMAL;
			if(stGame.stat == STAT_GAMEOVER) {
				iParam = DRW_REVY;	//ゲームオーバー中はNEXTブロックを上下反転して描く。180度回転ではない事に注意しろ。
			} else if(stGame.nextPosX != X__NEXT) {
				if(now & 2) { y--; }	//歩いている感じを出すために上下に揺らす。
			}
			CbkQue_sprite_draw(pCbkQue, iPri, x, y, iSprNo, iParam);
		}
	}
	if(stGame.stat != STAT_GAMEOVER) {	//ゲームオーバー中は操作ブロック,フィールドブロック,歯車,枠は絵が描かない。
		//操作ブロック
		{
			int i;
			if(stGame.stat == STAT_APPEAR) {
				for(i = 0; i < 2; i++) {
					iPri	= PRI__MOVE;
					x	= stGame.movePosX + (W__CELL * i);
					y	= stGame.movePosY;
					iSprNo	= SPR_BLOCK_1_0_0 + (((32 * 2) + 8) * (stGame.TBL_Move[i] - 1));
					iParam	= DRW_NOMAL;
					if(stGame.movePosX != (X__CELL+W__CELL*2)) {
						if(now & 2) { y--; }	//歩いている感じを出すために上下に揺らす。
					}
					CbkQue_sprite_draw(pCbkQue, iPri, x, y, iSprNo, iParam);
				}
			} else if(stGame.stat == STAT_MOVE) {
				for(i = 0; i < 2; i++) {
					if(stGame.TBL_Move[i]) {
						iPri	= PRI__MOVE;
						x	= X__CELL + (W__CELL * (stGame.moveCol + i));
						y	= Y__CELL + (H__CELL *  stGame.moveRow     );
						iSprNo	= SPR_BLOCK_1_0_0 + (((32 * 2) + 8) * (stGame.TBL_Move[i] - 1));
						iParam	= DRW_NOMAL;
						y -= (stGame.moveOfs + 255) >> 8;
						if(stGame.moveOfs) {
							if(now & 2) { iSprNo += (32 * 1); }	//落下中はジタバタする。
						}
						CbkQue_sprite_draw(pCbkQue, iPri, x, y, iSprNo, iParam);
					}
				}
				//落下開始までのウェイトが残っていたら、メーターを描くことにした。(オリジナル版には無い、独自要素です。)
				if(stGame.moveWait) {
					iPri	= PRI__MOVE;
					x	= X__CELL + (W__CELL * stGame.moveCol);
					y	= Y__CELL + (H__CELL * stGame.moveRow);
					y -= (stGame.moveOfs + 255) >> 8;
					x -= (W__CELL / 2);
					y += (H__CELL / 2) + 2;
					CbkQue_render_rectangle(pCbkQue, iPri, &render, x, y,
						W__CELL * 2 + 1,
						4, 3);
					CbkQue_render_rectangle_fill(pCbkQue, iPri, &render, x + 1, y + 1,
						(W__CELL * 2 - 1),
						2, 2);
					CbkQue_render_rectangle_fill(pCbkQue, iPri, &render, x + 1, y + 1,
						(W__CELL * 2 - 1) * stGame.moveWait / MOVE_WAIT,
						2, 0);
				}
			}
		}
		//フィールド
		{
			int col, row;
			int r = stGame.fieldRot & 62/*63*/;	//枠の精度に合わせるために63ではなく62とした。
			double dSin = 0.0, dCos = 0.0;	//警告抑制
			if(r) {
				double d = (M_PI / 32) * r;
				dSin = sin(d);
				dCos = cos(d);
			}
			for(row = 0; row < FIELD_ROWS; row++) {
				for(col = 0; col < FIELD_COLS; col++) {
					ST_Cell* cell = &stGame.TBL_Field[row][col];
					if(cell->type) {
						iPri	= PRI__CELL;
						x	= X__CELL + (W__CELL * col);
						y	= Y__CELL + (H__CELL * row);
						iSprNo	= SPR_BLOCK_1_0_0 + (((32 * 2) + 8) * (cell->type - 1));
						iParam	= DRW_NOMAL;
						if(cell->fall) { y -= stGame.fieldFall; }
						if(r) {
							int dx = x - X__BORDER;
							int dy = y - Y__BORDER;
							x = (int)(dx * dCos - dy * dSin) + X__BORDER;
							y = (int)(dx * dSin + dy * dCos) + Y__BORDER;
						}
						iSprNo += ((cell->rot << 3) + (stGame.fieldRot >> 1)) & 31;
						CbkQue_sprite_draw(pCbkQue, iPri, x, y, iSprNo, iParam);
					}
				}
			}
		}
		//歯車
		{
			//左
			iPri	= PRI__GEAR;
			x	= X__GEAR__L;
			y	= Y__GEAR__L;
			iSprNo	= SPR_GEAR_0;
			iParam	= DRW_NOMAL;
			iSprNo += (-stGame.fieldRot & 7);
			CbkQue_sprite_draw(pCbkQue, iPri, x, y, iSprNo, iParam);
			//右
			iPri	= PRI__GEAR;
			x	= X__GEAR__R;
			y	= Y__GEAR__R;
			iSprNo	= SPR_GEAR_0;
			iParam	= DRW_NOMAL;
			iSprNo += (-stGame.fieldRot & 7);
			CbkQue_sprite_draw(pCbkQue, iPri, x, y, iSprNo, iParam);
		}
		//枠
		{
			iPri	= PRI__BORDER;
			x	= X__BORDER;
			y	= Y__BORDER;
			iSprNo	= SPR_BORDER_0;
			iParam	= DRW_NOMAL;
			iSprNo += ((stGame.fieldRot >> 1) & 7);
			CbkQue_sprite_draw(pCbkQue, iPri, x, y, iSprNo, iParam);
		}
	}
	//エフェクト
	{
		int i;
		for(i = 0; i < ARRAY_SIZE(stGame.TBL_Effect); i++) {
			ST_Effect* effect = &stGame.TBL_Effect[i];
			if(effect->mode) {
				switch(effect->mode) {
				default:DIE();
				case EFFECT_ERASE:
					iPri	= PRI__EFFECT;
					x	= effect->x >> 6;
					y	= effect->y >> 6;
					iSprNo	= SPR_BLOCK_1_0_0 + (((32 * 2) + 8) * (effect->type - 1));
					iParam	= DRW_NOMAL;
					iSprNo += (32 * 2) + min((effect->anmCnt / 4), 7);	//拡大
					CbkQue_sprite_draw(pCbkQue, iPri, x, y, iSprNo, iParam);
					break;
				case EFFECT_GAMEOVER:
					iPri	= PRI__EFFECT;
					x	= effect->x >> 6;
					y	= effect->y >> 6;
					iSprNo	= SPR_BLOCK_1_0_0 + (((32 * 2) + 8) * (effect->type - 1));
					iParam	= DRW_NOMAL;
					CbkQue_sprite_draw(pCbkQue, iPri, x, y, iSprNo, iParam);
					break;
				}
			}
		}
	}
	//スコア;
	score_draw();
}
/****************************************************************************
 *	game_run
 ****************************************************************************/
void game_run() {
	int i;
	//初期化
	game_init();
	//--- STAT_READY ---
//不要	stGame.stat = STAT_READY;
	i = 0;
	do {
		schedule();
		game_draw();
		game_exec();
		if(i++ < SEC(0.5)) {
			CbkQue_render_printf_framed(pCbkQue,
				PRI__TEXT,
				&render,
				(DISP_X / 2) - 10,
				(DISP_Y / 2) - 26,
				2,
				0x003,
				"READY");
		} else {
			CbkQue_render_printf_framed(pCbkQue,
				PRI__TEXT,
				&render,
				(DISP_X / 2) - 4,
				(DISP_Y / 2) - 26,
				2,
				0x003,
				"GO");
		}
	} while(stGame.nextPosX < X__NEXT);
	//BGM開始
	PlayMusic(MAIN_BGM);
	for(;;) {
		//--- STAT_APPEAR ---
		stGame.stat = STAT_APPEAR;
		next_init();
		do {
			schedule();
			game_draw();
			game_exec();
		} while(stGame.stat != STAT_MOVE);	//操作中になったら抜ける。
		//--- STAT_MOVE ---
//不要		stGame.stat = STAT_MOVE;
		do {
			schedule();
			game_draw();
			game_exec();
		} while(stGame.TBL_Move[0] || stGame.fieldFall);	//両方実体化済み,かつ,フィールド落下中でなければ抜ける。
		//ゲームオーバー判定
		if(check_gameover()) { break; }
	}
	//--- STAT_GAMEOVER ---
	stGame.stat = STAT_GAMEOVER;
	//ゲームオーバーエフェクトを生成する。
	{
		int col, row;
		memset(stGame.TBL_Effect, 0, sizeof stGame.TBL_Effect);	//ゲームオーバーの直前に消滅が発生する可能性も有るが、判り辛いので消滅エフェクトは消す事にした。
		for(row = 0; row < FIELD_ROWS; row++) {
			for(col = 0; col < FIELD_COLS; col++) {
				ST_Cell* cell = &stGame.TBL_Field[row][col];
				if(cell->type) {
					effect_gen(EFFECT_GAMEOVER, cell->type, col, row);
					memset(cell, 0, sizeof(ST_Cell));
				}
			}
		}
		StopMusic();
		sound_stop(0);
		sound_stop(1);
		sound_stop(2);
		sound_play(3, sound_table[SND_SE_GOVER], 1);
	}
	{
		int i;
		for(i = 0; i < SEC(5); i++) {
			schedule();
			game_draw();
			game_exec();
			if(i >= SEC(1)) {
				CbkQue_render_printf_framed(pCbkQue,
					PRI__TEXT,
					&render,
					(DISP_X / 2) - 16,
					(DISP_Y / 2) - 3,
					2,
					0x003,
					"GAMEOVER");
				if(joy & TRG_AB) { break; }	//キャンセル
			}
		}
	}
}
/****************************************************************************
 *	
 ****************************************************************************/
void demo_run() {
	int iStart = 0;
	StopMusic();	//BGMを確実に停止する。ゲームをSELECTボタンで抜けた場合のためです。
	sound_stop(0);
	sound_stop(1);
	sound_stop(2);
	sound_stop(3);
	for(;;) {
		schedule();
		//背景・スコア
		{
			CbkQue_sprite_draw(pCbkQue, 0, 0, 0, SPR_BG, DRW_NOMAL);
			CbkQue_render_rectangle_fill(pCbkQue, 0, &render, 0, 10, DISP_X, DISP_Y - 10, 2);
			score_draw();
		}
		//タイトル
		{
			CbkQue_sprite_draw(pCbkQue,
				0,
				(DISP_X-76)/2,
				(DISP_Y-40)/2-18,
				SPR_TITLE,
				DRW_NOMAL);
			CbkQue_render_printf_framed(pCbkQue,
				0,
				&render,
				(DISP_X-76)/2+64,
				(DISP_Y-40)/2-8,
				2,
				0x330,
				"ﾓﾄｷ");
			CbkQue_render_printf_framed(pCbkQue,
				0,
				&render,
				(DISP_X-76)/2+64+5,
				(DISP_Y-40)/2-8-2,
				2,
				0x330,
				"ﾞ");
		}
		//ブロックの種類数選択
		{
			if(!iStart) {	//A,又は,Bボタンを押す前ならば…
				if((colors_sel >              2) && (joy & TRG_LF)) {
					colors_sel--;
					sound_play(0, sound_table[SND_SE_IDOU], 1);
				}
				if((colors_sel < BLOCK_TYPE_MAX) && (joy & TRG_RI)) {
					colors_sel++;
					sound_play(0, sound_table[SND_SE_IDOU], 1);
				}
			}
			CbkQue_render_printf_framed(pCbkQue,
				0,
				&render,
				(DISP_X-(4*18))/2,
				DISP_Y-46,
				2,
				0x330,
				"%c BLOCK TYPES %2d %c",
				(colors_sel >              2) ? '<' : ' ',
				colors_sel,
				(colors_sel < BLOCK_TYPE_MAX) ? '>' : ' ');
		}
		//プロンプト
		if(!iStart) {	//A,又は,Bボタンを押す前ならば…
			if((now % SEC(1.0)) < SEC(0.5)) {
				CbkQue_render_printf_framed(pCbkQue,
					0,
					&render,
					(DISP_X-(4*14))/2,
					 DISP_Y-23,
					2,
					0x330,
					"PRESS A BUTTON");
			}
			if(joy & TRG_AB) {
				sound_play(0, sound_table[SND_SE_START], 1);
				iStart = 1;
			}
		} else {	//A,又は,Bボタンを押した後ならば…
			if((now % SEC(0.2)) < SEC(0.1)) {
				CbkQue_render_printf_framed(pCbkQue,
					0,
					&render,
					(DISP_X-(4*5))/2,
					 DISP_Y-23,
					2,
					0x330,
					"START");
			}
			if(++iStart >= SEC(1.0)) { break; }
		}
	}
}
/****************************************************************************
 *	app_main
 ****************************************************************************/
int app_main(int argc, char* argv[]) {
//{{隠しコマンド。Bを押しながら起動すると電池表示を消す。デバッグ時に電池表示を消しやすいようにするためです。
	delay(1);
	if(joy & PAD_B) {
		pceWaveSetMasterAtt(0);
		pcePowerSetReport(PWR_RPTOFF);
	}
//}}隠しコマンド。Bを押しながら起動すると電池表示を消す。デバッグ時に電池表示を消しやすいようにするためです。
	colors_sel = 4;
	sound_att(1, 4);	//サウンドチャネル1は落下音専用とした。落下音が大きすぎるので少し小さくした。	0=操作音一般,1=落下音,2=接地音/回転音,3=消滅音/ゲームオーバー音
	for(;;) {
		SEH_use_shared;
		SEH_try_shared {
			demo_run();
		} SEH_catch(MAKEFOURCC('T','R','G','D')) {
			break;
		} SEH_end
		SEH_try_shared {
			game_run();
		} SEH_catch(MAKEFOURCC('T','R','G','D')) {
			/** no job **/
		} SEH_end
		//※TODO:サウンド停止
	}
	return 0;
}
