/**
 * Rallx.<br>
 *  迷路脱出ゲームレーダー付き<br><br>
 *
 * Copyright 2004 Kenta Cho. All rights reserved.
 * 
 * @p5 description
 * -車をマウスで操作して、全ての旗を回収、迷路の中央の出口に向かってください。
 * -Control a car with your mouse, get all flags and go to the exit at the center of the maze.
 * -赤い岩にぶつかると車を失います。
 * -Avoid red square shaped rocks. You lose a car when you crash into a rock.
 * -マウスボタンを押しっぱなしでレーダーを見られます。迷路全体を見渡すことができますが、岩は表示されません。
 * -Hold a mouse button to see the radar screen. You can see the entire maze but rocks are not shown on the radar.
 * -画面下にある残り時間がなくなっても車を失います。
 * -You also lose a car when a time(displayed at the bottom of screen) runs out.
 */
public class RallX extends BApplet {
	/**
	 * 迷路データ(0-空、1-壁、2-ゴール、3-旗、10〜-岩)
	 */
	int[][] maze;
	/**
	 * 迷路や出口の大きさ用定数
	 */
	int MAZE_SIZE = 49, EXIT_SIZE = 7;
	/**
	 * 迷路1ブロックのレーダー上の大きさ
	 */
	int mazeRectSize;
	/**
	 * 迷路1ブロックのスクロール画面上の大きさ
	 */
	float mazeWallSize;
	/**
	 * 出口の方向
	 */
	int exitD;

	/**
	 * スクロール画面内に表示するブロック数
	 */
	int wallSightRange;
	/**
	 * 自車関連データ
	 */	
	float cx, cy, cd, cmd, cmx, cmy, csp, cmsp;
	/**
	 * 自車のいるブロック位置
	 */
	int wx, wy;
	/**
	 * レーダー切り替え時カウンタ
	 */
	int rcnt;
	/**
	 * レーダー切り替え時アルファ値
	 */
	int ca;
	/**
	 * ラウンドクリア時カウンタ
	 */
	int ccnt;
	/**
	 * 自車クラッシュ時カウンタ
	 */
	int dcnt;
	/**
	 * 画面中央に表示するメッセージ
	 */
	String msg;
	/**
	 * メッセージ用カウンタ
	 */
	int mcnt;

	/**
	 * パーティクルの最大数
	 */
	int PARTICLE_NUM = 32;
	/**
	 * パーティクルインスタンスプール
	 */
	Particle[] particle;
	/**
	 * 岩の最大数
	 */
	int ROCK_NUM = 50;
	/**
	 * 岩インスタンスプール
	 */
	Rock[] rock;
	/**
	 * 残りの旗の数
	 */
	int flagNum;
	/**
	 * ステージ数
	 */
	int stage;
	/**
	 * スコア
	 */
	float score;
	/**
	 * 残機数
	 */
	int left;
	/**
	 * 残り時間
	 */
	int time;
	/**
	 * ゲーム状態用定数
	 */
	int TITLE = 0, IN_GAME = 1;
	/**
	 * ゲームの状態(タイトル/ゲーム中)
	 */
	int state;
	/**
	 * 各面のスタート時の残り時間(30frame * 60sec)
	 */
	int STAGE_TIME = 1800;

	/**
	 * フォントデータ
	 */
	BFont font;
	/**
	 * BGMデータ
	 */
	BSound bgm;
	/**
	 * SEデータ
	 */
	BSound flagSe, crashSe, clearSe;

	/**
	 * 初期化(フォント、サウンド読み込み、インスタンス生成)
	 */
	void setup() {
		size(300, 300);
		framerate(30);
		smooth();
		cursor();
		ellipseMode(CENTER_DIAMETER);
		font = loadFont("OCR-A.vlw.gz");
		textFont(font);
		bgm = loadSound("bgm.wav");
		flagSe = loadSound("flag.wav");
		crashSe = loadSound("crash.wav");
		clearSe = loadSound("clear.wav");

		mazeRectSize = width / MAZE_SIZE;
		maze = new int[MAZE_SIZE][MAZE_SIZE];
		mazeWallSize = 40;
		wallSightRange = (int) (width / mazeWallSize) / 2 + 2;

		particle = new Particle[PARTICLE_NUM];
		for (int i = 0; i < particle.length; i++)
			particle[i] = new Particle();
		rock = new Rock[ROCK_NUM];
		for (int i = 0; i < rock.length; i++)
			rock[i] = new Rock();

		startTitle();
	}

	/**
	 * タイトル初期化
	 */
	void startTitle() {
		state = TITLE;
		stop(bgm);
		exitD = 0;
		stage = 0;
		left = 0;
		initStage();
		ca = 150;
		time = -999;
		float d = random(PI * 2);
		cmx = sin(d);
		cmy = cos(d);
		mcnt = 0;
	}

	/**
	 * ゲーム開始
	 */
	void startGame() {
		state = IN_GAME;
		exitD = 0;
		stage = -1;
		score = 0;
		left = 2;
		initStage();
		volume(bgm, 1.0f);
		jump(bgm, 0);
		repeat(bgm);
	}

	/**
	 * ステージ生成
	 */
	void initStage() {
		// Init a car potision.
		switch (exitD) {
			case 0 :
				cx = MAZE_SIZE * mazeWallSize / 2;
				cy = mazeWallSize * 1.5f;
				cd = PI;
				break;
			case 1 :
				cx = MAZE_SIZE * mazeWallSize - mazeWallSize * 1.5f;
				cy = MAZE_SIZE * mazeWallSize / 2;
				cd = PI / 2 * 3;
				break;
			case 2 :
				cx = MAZE_SIZE * mazeWallSize / 2;
				cy = MAZE_SIZE * mazeWallSize - mazeWallSize * 1.5f;
				cd = 0;
				break;
			case 3 :
				cx = mazeWallSize * 1.5f;
				cy = MAZE_SIZE * mazeWallSize / 2;
				cd = PI / 2;
				break;
		}
		cmx = cmy = 0;
		csp = 0;
		rcnt = 0;
		ccnt = -8;
		dcnt = 0;
		time = STAGE_TIME;

		// Set a stage pattern.
		stage++;
		int rockNum = 0;
		switch (stage % 6) {
			case 0 :
				mazeGrid = 8;
				mazePtn = 0;
				rockNum = stage * 3;
				flagNum = 4;
				break;
			case 1 :
				mazeGrid = 4;
				mazePtn = 0;
				rockNum = stage - 1;
				flagNum = 0;
				break;
			case 2 :
				mazePtn = 1;
				rockNum = 10 + stage * 4;
				flagNum = 8;
				break;
			case 3 :
				mazeGrid = 6;
				mazePtn = 0;
				rockNum = stage * 3;
				flagNum = 4;
				break;
			case 4 :
				mazeGrid = 3;
				mazePtn = 0;
				rockNum = stage;
				flagNum = 0;
				break;
			case 5 :
				mazeGrid = 6;
				mazePtn = 2;
				rockNum = 12 + stage * 3;
				flagNum = 3;
				break;
		}
		if (rockNum > rock.length)
			rockNum = rock.length;
		if (stage == 0) {
			msg = "Get flags.";
			mcnt = 60;
		} else if (stage == 2) {
			msg = "Avoid rocks.";
			mcnt = 60;
		} else if (flagNum == 0) {
			msg = "Go to the exit.";
			mcnt = 60;
		} else {
			mcnt = 0;
		}

		// Create a maze and set flags and rocks.
		createMaze();
		wx = (int) (cx / mazeWallSize);
		wy = (int) (cy / mazeWallSize);
		maze[wx][wy] = 1;
		for (int i = 0; i < flagNum; i++) {
			int x, y;
			do {
				x = (int) random(MAZE_SIZE - 2) + 1;
				y = (int) random(MAZE_SIZE - 2) + 1;
			} while (maze[x][y] != 0);
			maze[x][y] = 3;
		}
		for (int i = 0; i < rockNum; i++) {
			int x = (int) random(MAZE_SIZE - 2) + 1;
			int y = (int) random(MAZE_SIZE - 2) + 1;
			if (maze[x][y] == 0) {
				maze[x][y] = i + 10;
				rock[i].ox = random(mazeWallSize / 2) + mazeWallSize / 4;
				rock[i].oy = random(mazeWallSize / 2) + mazeWallSize / 4;
				rock[i].x = x * mazeWallSize + rock[i].ox;
				rock[i].y = y * mazeWallSize + rock[i].oy;
			}
		}
		maze[wx][wy] = 0;
	}

	/**
	 * 迷路の壁と壁の間隔
	 */
	int mazeGrid;
	/**
	 * 迷路パターン(0-通常、1-なにもなし、2-うずまき)
	 */
	int mazePtn;
	/**
	 * 壁を延ばした地点の数(迷路作成終了判定用)
	 */
	int pointNum;
	/**
	 * 壁を延ばしている座標
	 */
	int stx, sty, std;
	/**
	 * 壁を延ばす方向の移動量定数
	 */
	int[] DMX = { 0, 1, 0, -1 }, DMY = { -1, 0, 1, 0 };
	/**
	 * うずまき型迷路作成用カウンタ
	 */
	int turnCnt;

	/**
	 * 壁を延ばして迷路作成
	 */
	void createMaze() {
		for (int x = 0; x < MAZE_SIZE; x++)
			maze[x][0] = maze[x][MAZE_SIZE - 1] = 1;
		for (int y = 1; y < MAZE_SIZE - 1; y++) {
			maze[0][y] = maze[MAZE_SIZE - 1][y] = 1;
			for (int x = 1; x < MAZE_SIZE - 1; x++)
				maze[x][y] = 0;
		}
		int ep = (MAZE_SIZE - EXIT_SIZE) / 2;

		int bn = 0;
		switch (mazePtn) {
			case 0 :
				bn = 100;
				pointNum = MAZE_SIZE / mazeGrid;
				pointNum -= 1;
				pointNum *= pointNum;
				pointNum -= (int) ((EXIT_SIZE + 1) / mazeGrid)
					* (int) ((EXIT_SIZE + 1) / mazeGrid);
				break;
			case 1 :
				bn = 0;
				pointNum = 0;
				break;
			case 2 :
				bn = 1;
				pointNum = 999;
				turnCnt = 0;
				break;
		}
		for (int i = 0; i < bn; i++) {
			if (pointNum <= 0)
				break;
			if (mazePtn == 0) {
				stx =
					(int) random((MAZE_SIZE - 1) / mazeGrid - 1) * mazeGrid
						+ mazeGrid;
				sty =
					(int) random((MAZE_SIZE - 1) / mazeGrid - 1) * mazeGrid
						+ mazeGrid;
				std = (int) random(4);
				if (maze[stx][sty] == 1) {
					if (!moveTillNoWall())
						continue;
				} else {
					if (!moveTillWall())
						continue;
				}
			} else {
				switch (exitD) {
					case 0 :
						stx = ((int) (MAZE_SIZE / mazeGrid) / 2 + 1) * mazeGrid;
						sty = 0;
						std = 2;
						break;
					case 1 :
						stx = MAZE_SIZE - 1;
						sty = ((int) (MAZE_SIZE / mazeGrid) / 2 + 1) * mazeGrid;
						std = 3;
						break;
					case 2 :
						stx = ((int) (MAZE_SIZE / mazeGrid) / 2 - 1) * mazeGrid;
						sty = MAZE_SIZE - 1;
						std = 0;
						break;
					case 3 :
						stx = 0;
						sty = ((int) (MAZE_SIZE / mazeGrid) / 2 - 1) * mazeGrid;
						std = 1;
						break;
				}
			}
			createMazeBranch();
		}

		// Create the exit hole.
		fillMaze(ep - mazeGrid + 1, EXIT_SIZE + (mazeGrid - 1) * 2, 0);
		fillMaze(ep, EXIT_SIZE, 1);
		fillMaze(ep + 1, EXIT_SIZE - 2, 2);
		exitD = (int) random(4);
		int ex, ey;
		switch (exitD) {
			case 0 :
			case 2 :
				ex = ep + 2;
				if (exitD == 0)
					ey = ep;
				else
					ey = ep + EXIT_SIZE - 1;
				for (int x = ex; x < ex + EXIT_SIZE - 4; x++)
					maze[x][ey] = 0;
				break;
			default :
				ey = ep + 2;
				if (exitD == 3)
					ex = ep;
				else
					ex = ep + EXIT_SIZE - 1;
				for (int y = ey; y < ey + EXIT_SIZE - 4; y++)
					maze[ex][y] = 0;
				break;
		}
	}

	/**
	 * 壁がない地点まで移動(壁延ばし開始地点探索用)
	 * @return 地点が見つかった
	 */
	boolean moveTillNoWall() {
		int tx = stx, ty = sty;
		for (;;) {
			tx += DMX[std] * mazeGrid;
			ty += DMY[std] * mazeGrid;
			if (tx < 0 || tx >= MAZE_SIZE || ty < 0 || ty >= MAZE_SIZE)
				return false;
			if (maze[tx][ty] == 0)
				return true;
			stx = tx;
			sty = ty;
		}
	}
	/**
	 * 壁がある地点まで移動(壁延ばし開始地点探索用)
	 * @return 地点が見つかった
	 */	
	boolean moveTillWall() {
		for (;;) {
			stx -= DMX[std] * mazeGrid;
			sty -= DMY[std] * mazeGrid;
			if (stx < 0 || stx >= MAZE_SIZE || sty < 0 || sty >= MAZE_SIZE)
				return false;
			if (maze[stx][sty] == 1)
				return true;
		}
	}

	/**
	 * 壁を延ばす(一定の確率で曲がり、四方がふさがれたら終了)
	 */
	void createMazeBranch() {
		for (;;) {
			if (maze[stx + DMX[std] * mazeGrid][sty + DMY[std] * mazeGrid]
				== 0) {
				for (int i = 0; i < mazeGrid; i++) {
					stx += DMX[std];
					sty += DMY[std];
					maze[stx][sty] = 1;
				}
				if (mazePtn == 2) {
					if ((turnCnt % 6) == 0) {
						std++;
						std &= 3;
						turnCnt++;
					}
				} else {
					if (random(1) < 0.4f) {
						std += (int) random(2) * 2 - 1;
						std &= 3;
					}
				}
				pointNum--;
				if (pointNum <= 0)
					return;
				continue;
			} else {
				if (mazePtn == 2) {
					std++;
					std &= 3;
					turnCnt++;
				} else {
					std += (int) random(2) * 2 - 1;
					std &= 3;
				}
				if (maze[stx + DMX[std] * mazeGrid][sty + DMY[std] * mazeGrid]
					== 0)
					continue;
				std += 2;
				std &= 3;
				if (maze[stx + DMX[std] * mazeGrid][sty + DMY[std] * mazeGrid]
					== 1)
					return;
			}
		}
	}

	/**
	 * 出口作成用に迷路を正方形に埋める
	 * @param p 左上の座標
	 * @param w 大きさ
	 * @param v 埋める値
	 */
	void fillMaze(int p, int w, int v) {
		for (int y = p; y < p + w; y++)
			for (int x = p; x < p + w; x++)
				maze[x][y] = v;
	}

	/**
	 * 1フレームごとの処理
	 */
	void loop() {
		if (state == IN_GAME)
			loopGame();
		else
			loopTitle();
	}

	/**
	 * マウスリリース時にゲームスタートするためのフラグ
	 */
	boolean startPressed = false;

	/**
	 * タイトル時の1フレーム処理
	 */
	void loopTitle() {
		background(255);
		cx += cmx;
		cy += cmy;
		wx = (int) (cx / mazeWallSize);
		wy = (int) (cy / mazeWallSize);
		if (cx < 0 || cx > MAZE_SIZE * mazeWallSize)
			cmx *= -1;
		if (cy < 0 || cy > MAZE_SIZE * mazeWallSize)
			cmy *= -1;
		drawMaze();
		boolean onStart;
		if (mouseX > width / 2 - 50
			&& mouseX < width / 2 + 50
			&& mouseY > height - 100
			&& mouseY < height - 75)
			onStart = true;
		else
			onStart = false;
		push();
		translate(0, 0, 30);
		textSize(42);
		textMode(ALIGN_CENTER);
		fill(0, 0, 0);
		text("RallX", width / 2, height / 3);
		stroke(250, 100, 50);
		if (onStart)
			fill(250, 180, 100);
		else
			fill(200, 150, 50);
		strokeWeight(2);
		rect(width / 2 - 50, height - 100, 100, 25);
		strokeWeight(1);
		fill(0, 0, 0);
		textSize(25);
		text("START", width / 2, height - 80);
		pop();
		drawStatus();
		if (onStart && mousePressed)
			startPressed = true;
		if (!mousePressed && startPressed) {
			startPressed = false;
			if (onStart)
				startGame();
		}
	}

	/**
	 * ゲーム中の1フレーム処理
	 */
	void loopGame() {
		background(255);
		float pcx = cx, pcy = cy;
		cx += cmx;
		cy += cmy;
		cmx += (sin(cd) * csp - cmx) * 0.04f;
		cmy += (cos(cd) * csp - cmy) * 0.04f;
		int mox = mouseX - width / 2, moy = mouseY - height / 2;
		float mouseDst = sqrt(mox * mox + moy * moy);
		float brk = 0.85f + (mouseDst / (width / 2)) * 0.15f;
		if (brk > 1)
			brk = 1;
		cmx *= brk;
		cmy *= brk;
		cmd = atan2(cmx, cmy);
		cmsp = sqrt(cmx * cmx + cmy * cmy);
		score += cmsp * 0.05f;
		float md = adjustDeg(atan2(mox, moy) - cd);
		cd += md * 0.2f;
		cd = adjustDeg(cd);
		wx = (int) (cx / mazeWallSize);
		wy = (int) (cy / mazeWallSize);
		int pwx = (int) (pcx / mazeWallSize);
		int pwy = (int) (pcy / mazeWallSize);
		if (maze[wx][wy] == 1) {
			if (maze[pwx][wy] == 0) {
				cx = pcx;
				cmx *= -0.8f;
			} else if (maze[wx][pwy] == 0) {
				cy = pcy;
				cmy *= -0.8f;
			} else {
				cx = pcx;
				cmx *= -0.8f;
				cy = pcy;
				cmy *= -0.8f;
			}
		} else if (maze[wx][wy] == 2 && ccnt == 0 && flagNum <= 0) {
			ccnt = 1;
			stop(clearSe);
			play(clearSe);
		} else if (maze[wx][wy] == 3) {
			maze[wx][wy] = 0;
			score += 500;
			flagNum--;
			stop(flagSe);
			play(flagSe);
			if (flagNum <= 0) {
				msg = "Go to the exit.";
				mcnt = 60;
			}
		} else if (maze[wx][wy] >= 10) {
			int ri = maze[wx][wy] - 10;
			if (abs(rock[ri].x - cx) + abs(rock[ri].y - cy) < 20)
				miss();
		}
		float od = abs(adjustDeg(cd - cmd));
		if (od > 0.5f && cmsp > 6)
			addParticle(
				width / 2,
				height / 2,
				cmd + PI + random(-0.3f, 0.3f),
				cmsp);
		if (mousePressed) {
			csp += (5 - csp) * 0.3f;
			if (rcnt < 8)
				rcnt++;
		} else {
			csp += (20 - csp) * 0.3f;
			if (rcnt > 0)
				rcnt--;
		}
		if (ccnt > 0) {
			ccnt++;
			rcnt = 0;
			cmx *= 0.8;
			cmy *= 0.8;
			csp *= 0.6;
			if (time > 0) {
				time -= 30;
				score += 100;
				if (time > 30) {
					time -= 30;
					score += 100;
				}
			}
			if (ccnt > 30) {
				initStage();
				return;
			}
		} else if (ccnt < 0) {
			ccnt++;
		}
		if (dcnt > 0) {
			dcnt--;
			rcnt = 0;
			cmx *= 0.8;
			cmy *= 0.8;
			csp *= 0.6;
			if (dcnt > 30) {
				float v = 1 - (float) (250 - dcnt) / 60;
				if (v < 0)
					v = 0;
				volume(bgm, v);
				csp = 0;
				push();
				translate(0, 0, 30);
				textSize(32);
				textMode(ALIGN_CENTER);
				fill(0, 0, 0);
				text("Game Over", width / 2, height / 3);
				pop();
				if (dcnt < 100 || (dcnt < 230 && mousePressed)) {
					startTitle();
					return;
				}
			}
			if (dcnt == 1)
				time = STAGE_TIME;
		}
		if (dcnt == 0 && ccnt == 0) {
			time--;
			if (time <= 0)
				miss();
		}
		if (rcnt < 8) {
			push();
			if (ccnt > 0) {
				translate(0, 0, ccnt * 5);
				ca = 255 - ccnt * 12;
				if (ca < 0)
					ca = 0;
			} else {
				translate(0, 0, -rcnt * 30);
				ca = 255 - rcnt * 31;
				if (ccnt < 0)
					ca = 255 + ccnt * 31;
			}
			if (dcnt <= 0)
				drawCar(width / 2, height / 2, cd, cmd);
			drawMaze();
			noStroke();
			for (int i = 0; i < particle.length; i++)
				if (particle[i].cnt >= 0)
					particle[i].loop();
			pop();
		}
		if (rcnt > 0)
			drawMazeOnRadar();
		drawStatus();
	}

	/**
	 * 自車が岩に突っ込んだ/時間切れ
	 */
	void miss() {
		stop(crashSe);
		play(crashSe);
		dcnt = 30;
		for (int i = 0; i < 24; i++)
			addParticle(width / 2, height / 2, random(PI * 2), random(20));
		maze[wx][wy] = 0;
		left--;
		if (left < 0)
			dcnt = 250;
	}

	/**
	 * 方向を -PI〜PI に収めることで曲がる方向の計算を楽に
	 * @param d 補正前の角度
	 * @return 補正後の角度
	 */
	float adjustDeg(float d) {
		if (d > PI)
			return d - PI * 2;
		else if (d < -PI)
			return d + PI * 2;
		else
			return d;
	}

	/**
	 * 車を書く
	 * @param x x座標
	 * @param y y座標(残機表示兼用のため座標が指定できる)
	 * @param d1 前輪の方向(マウスの方向を向く)
	 * @param d2 後輪の方向(移動方向を向く後輪)
	 */
	void drawCar(int x, int y, float d1, float d2) {
		fill(100, 150, 250, ca);
		stroke(150, 200, 255, ca);
		push();
		translate(x, y);
		rotateZ(-d1);
		translate(0, 6);
		rect(-3, -6, 6, 12);
		pop();
		push();
		translate(x, y);
		rotateZ(-d2);
		translate(-6, -6);
		rect(-3, -6, 6, 12);
		translate(12, 0);
		rect(-3, -6, 6, 12);
		pop();
	}

	/**
	 * スクロール画面上の迷路表示
	 */
	void drawMaze() {
		float wsx, wsy;
		float swsx = -cx + (wx - wallSightRange) * mazeWallSize + width / 2;
		float swsy = -cy + (wy - wallSightRange) * mazeWallSize + height / 2;
		wsy = swsy;
		beginShape(QUADS);
		for (int y = wy - wallSightRange;
			y <= wy + wallSightRange;
			y++, wsy += mazeWallSize) {
			wsx = swsx;
			for (int x = wx - wallSightRange;
				x <= wx + wallSightRange;
				x++, wsx += mazeWallSize) {
				if (x >= 0 && x < MAZE_SIZE && y >= 0 && y < MAZE_SIZE) {
					if (maze[x][y] == 1)
						drawWall(wsx, wsy);
					else if (maze[x][y] == 2)
						drawGoal(wsx, wsy);
					else if (maze[x][y] == 3)
						drawFlag(wsx, wsy);
					else if (maze[x][y] >= 10) {
						int ri = maze[x][y] - 10;
						drawRock(wsx, wsy, rock[ri].ox, rock[ri].oy);
					}
				}
			}
		}
		endShape();
	}

	/**
	 * 壁のz軸方向高さ
	 */
	float WALL_height = 20;

	/**
	 * 壁を書く
	 * @param sx スクロール画面上のx座標
	 * @param sy スクロール画面上のy座標
	 */
	void drawWall(float sx, float sy) {
		fill(200, 250, 50, ca);
		stroke(100, 150, 100, ca);
		vertex(sx, sy, WALL_height);
		vertex(sx + mazeWallSize, sy, WALL_height);
		vertex(sx + mazeWallSize, sy + mazeWallSize, WALL_height);
		vertex(sx, sy + mazeWallSize, WALL_height);
		fill(220, 250, 150, ca);
		stroke(150, 200, 100, ca);
		vertex(sx, sy, WALL_height);
		vertex(sx + mazeWallSize, sy, WALL_height);
		vertex(sx + mazeWallSize, sy, 0);
		vertex(sx, sy, 0);
		vertex(sx + mazeWallSize, sy, WALL_height);
		vertex(sx + mazeWallSize, sy + mazeWallSize, WALL_height);
		vertex(sx + mazeWallSize, sy + mazeWallSize, 0);
		vertex(sx + mazeWallSize, sy, 0);
		vertex(sx + mazeWallSize, sy + mazeWallSize, WALL_height);
		vertex(sx, sy + mazeWallSize, WALL_height);
		vertex(sx, sy + mazeWallSize, 0);
		vertex(sx + mazeWallSize, sy + mazeWallSize, 0);
		vertex(sx, sy + mazeWallSize, WALL_height);
		vertex(sx, sy, WALL_height);
		vertex(sx, sy, 0);
		vertex(sx, sy + mazeWallSize, 0);
	}

	/**
	 * 旗を回収し終わっていたらゴールを書く
	 * @param sx スクロール画面上のx座標
	 * @param sy スクロール画面上のy座標
	 */
	void drawGoal(float sx, float sy) {
		if (flagNum > 0)
			return;
		fill(250, 200, 150, ca);
		stroke(250, 150, 200, ca);
		vertex(sx, sy, 0);
		vertex(sx + mazeWallSize, sy, 0);
		vertex(sx + mazeWallSize, sy + mazeWallSize, 0);
		vertex(sx, sy + mazeWallSize, 0);
	}

	/**
	 * 岩を書く
	 * @param x スクロール画面上のブロックのx座標
	 * @param y スクロール画面上のブロックのx座標
	 * @param ox ブロック座標からの補正xオフセット
	 * @param oy ブロック座標からの補正yオフセット
	 */
	void drawRock(float x, float y, float ox, float oy) {
		float sx = x + ox, sy = y + oy;
		sy -= 3;
		stroke(250, 50, 100, ca);
		fill(200, 100, 50, ca);
		vertex(sx - 8, sy - 8, 0.2f);
		vertex(sx + 8, sy - 8, 0.2f);
		vertex(sx + 8, sy + 8, 0.2f);
		vertex(sx - 8, sy + 8, 0.2f);
		fill(240, 200, 100, ca);
		sx += 5;
		sy += 8;
		vertex(sx - 8, sy - 8, 0.1f);
		vertex(sx + 8, sy - 8, 0.1f);
		vertex(sx + 8, sy + 8, 0.1f);
		vertex(sx - 8, sy + 8, 0.1f);
		fill(200, 220, 120, ca);
		sx -= 9;
		sy -= 4;
		vertex(sx - 8, sy - 8, 0);
		vertex(sx + 8, sy - 8, 0);
		vertex(sx + 8, sy + 8, 0);
		vertex(sx - 8, sy + 8, 0);
	}

	/**
	 * 旗を書く
	 * @param x スクロール画面上のx座標
	 * @param y スクロール画面上のy座標
	 */
	void drawFlag(float x, float y) {
		float sx = x + mazeWallSize / 3, sy = y + mazeWallSize / 3;
		fill(250, 250, 100, ca);
		stroke(250, 100, 50, ca);
		vertex(sx, sy, WALL_height);
		vertex(sx + 3, sy, WALL_height);
		vertex(sx + 3, sy + mazeWallSize / 3 * 2, 0);
		vertex(sx, sy + mazeWallSize / 3 * 2, 0);
		vertex(sx + 3, sy, WALL_height);
		vertex(sx + 13, sy, WALL_height);
		vertex(sx + 13, sy + 10, WALL_height / 3 * 2);
		vertex(sx + 3, sy + 10, WALL_height / 3 * 2);
	}

	/**
	 * レーダー画面上の迷路と旗と自車を表示
	 */
	void drawMazeOnRadar() {
		int a = rcnt * 32 - 1;
		push();
		translate(0, 0, -WALL_height * 2 + (8 - rcnt) * 20);
		noStroke();
		fill(255, 220, 180, a);
		for (int y = 0; y < MAZE_SIZE; y++)
			for (int x = 0; x < MAZE_SIZE; x++)
				if (maze[x][y] == 1)
					rect(
						x * mazeRectSize,
						y * mazeRectSize,
						mazeRectSize,
						mazeRectSize);
				else if (maze[x][y] == 3) {
					fill(200, 200, 50, a);
					rect(
						x * mazeRectSize,
						y * mazeRectSize,
						mazeRectSize,
						mazeRectSize);
					fill(255, 220, 180, a);
				}
		fill(100, 200, 255, a);
		rect(wx * mazeRectSize, wy * mazeRectSize, mazeRectSize, mazeRectSize);
		pop();
	}

	/**
	 * スコアと残機と残りタイムとメッセージを表示
	 */
	void drawStatus() {
		push();
		translate(0, 0, 30);
		textSize(26);
		textMode(ALIGN_RIGHT);
		fill(0, 0, 0);
		text((int) score, width - 18, 36);
		if (time > -999) {
			textMode(ALIGN_CENTER);
			textSize(33);
			text((time + 29) / 30, width / 2, height - 20);
		}
		for (int i = 0; i < left; i++)
			drawCar(i * 20 + 30, 30, PI, PI);
		if (mcnt > 0) {
			mcnt--;
			textMode(ALIGN_CENTER);
			textSize(24);
			fill(0, 0, 0);
			text(msg, width / 2, height / 3);
		}
		pop();
	}

	/**
	 * スモーク状のパーティクル
	 */
	class Particle {
		/**
		 * 表示サイズ
		 */
		float SIZE = 20;
		/**
		 * 位置
		 */
		float x, y;
		/**
		 * 広がる方向および速度
		 */
		float deg, speed;
		/**
		 * アルファ値
		 */
		int alpha;
		/**
		 * 消えるまでのカウンタ
		 */
		int cnt;

		/**
		 * カウンタを-1に設定して非表示に
		 */
		Particle() {
			cnt = -1;
		}

		/**
		 * cntを設定して有効にする
		 * @param x 初期x座標
		 * @param y 初期y座標
		 * @param d 方向
		 * @param s スピード
		 */
		void set(float x, float y, float d, float s) {
			this.x = x;
			this.y = y;
			deg = d;
			speed = s;
			alpha = 200 + (int) random(50);
			cnt = 15 + (int) random(15);
		}

		/**
		 * 1フレーム毎の処理
		 */
		void loop() {
			x += sin(deg) * speed;
			y += cos(deg) * speed;
			speed *= 0.95f;
			alpha *= 0.9f;
			cnt--;
			fill(200, 190, 120, alpha);
			ellipse(x, y, SIZE, SIZE);
		}
	}

	/**
	 * パーティクルプールから次のインスタンスを指し示すためのインデックス
	 */
	int particleIdx = 0;

	/**
	 * パーティクル追加
	 * @param x 初期x座標
	 * @param y 初期y座標
	 * @param d 方向
	 * @param s スピード
	 */
	void addParticle(float x, float y, float d, float s) {
		particleIdx--;
		if (particleIdx < 0)
			particleIdx += particle.length;
		particle[particleIdx].set(x, y, d, s);
	}

	/**
	 * 岩(当たると痛い)
	 */
	class Rock {
		/**
		 * 迷路ブロック内のオフセット
		 */
		float ox, oy;
		/**
		 * どの迷路ブロックに配置されているか
		 */
		float x, y;
	}
}