/*
 ブロック崩し
 
 反射処理に関しては手抜きをして、ぶつかったところで向きだけ変えて停止した状態で
 次のターンに進むようにしている。（本来は 45% 位置で衝突したら、向きを変えて
 55% 進んでから次のターンに向かうべき。ただしそれをするには衝突判定処理を更に
 ループでラップする必要がある。今回は単純さを保つためにそれを採らなかった。）
 
 なお円と四角の衝突、ではなく、線分の交差によって衝突を検出している。つまり
 球の中心が四角や壁を構成する境界線をくぐったら、その交点位置で衝突したと
 見なしている。これではもちろんうまく行かないケースが出るので良くない。
 
 構造体、ポインタなどを使わず、せいぜい二次元配列、大域変数程度の言語要素で
 記述できることを目指した。（一回生秋学期カリキュラムの範囲内）
 
 */


#include <stdio.h>
#include <math.h>
#include <eggx.h>

// ============================ 各種定義

int win;

#define WW (400)
#define HH (600)

#define SPEED 7.0 // ボールのスピード
#define CYCLE 10 // 一サイクルの待ち時間（ミリ秒）

#define COL 6 // 並んでいるブロックの列数
#define RAW 2 // 同、行数
#define BKSIZE 40.0 // ブロックのサイズ
#define BASIZE 5.0 // ボールのサイズ

#define LIVE 100
#define DEAD 0
int bkAlive[COL][RAW]; // ブロックの生死 (100:live, 0:dead) 
int bkColor[COL][RAW]; // ブロックの色(H/HSV)

#define WALLNUM 5 // 壁がいくつの線分から出来ているか
float wallXY[WALLNUM+1][2]={ // 壁データの定義
	{10, 20}, // 左下
	{WW/2, 10}, // 中央下
	{WW-11, 20}, // 右下
	{WW-11, HH-11}, // 右上
	{10, HH-11}, // 左上
	{10, 20}}; // 左下（戻ってきた）

#define NOBLK -1 // 属性 borderOf がどのブロックにも帰属していないことを示す
#define BORDNUM WALLNUM+COL*RAW*4 // 境界線の数
float border[BORDNUM][4]; // 境界線
int borderOf[BORDNUM]; // 境界線の帰属 (NOBLK:壁orどこか。0以上:ブロック番号)



// ========================== 二線分の交差チェック
/*
 colCheck関数のみ、以下を引き写している。
 http://www.hiramine.com/programming/graphics/2d_segmentintersection.html

 線分 AB (ax,ay)-(bx,by) と CD (cx,cy)-(dx,dy) が交差しているかどうかを調べる
 r, s が共に 0-1 の間にあれば衝突と判定
 ただし、
 戻り値は衝突時は r そのもの。範囲は 0.0 <= r <= 1.0 である。
 非衝突時は 9.0 を返す。
 ただし、二線分が並行な場合は 99.0 であり、
 二線分が同一（重なっている）の場合は 999.0 を返す
 この r を用いて交差座標位置を求める
 */

#define NOCROSS  9.0
#define PARALLEL 99.0
#define DOUBLE   999.0

float colCheck(float ax, float ay,
			   float bx, float by,
			   float cx, float cy,
			   float dx, float dy)
{
	float denom; // 途中計算における分母(denominator)
	float acx, acy; // ベクター AC を表現する
	float r, s;
	
	denom = (bx - ax) * (dy - cy) - (by - ay) * (dx - cx);
	if( denom == 0.0 ) return PARALLEL; // 二線は並行である

	// ベクター AC を得る（C - A にあたる） 
	acx = cx - ax; acy = cy - ay;
	
	r=( ( dy - cy ) * acx - ( dx - cx ) * acy ) / denom;
	s=( ( by - ay ) * acx - ( bx - ax ) * acy ) / denom;
	
	if( (r == 0.0)&&(s == 0.0)) return DOUBLE; // 二線は同一である
	
	if( (0.0 <= r) && (r <= 1.0) && (0.0 <= s) && (s <= 1.0)) {
		return r; // 交差した
	} else {
		return NOCROSS; // 交差しない
	}
		
}

// 線分 A, B から角を出す
float theta(float ax, float ay, float bx, float by)
{
	return atan2f(by-ay, bx-ax);
}

// 反射角を得る（壁CDに対してABが反射する場合）
float bounceRect(float ax, float ay,
				 float bx, float by,
				 float cx, float cy,
				 float dx, float dy)
{
	float th0, th1, th2;
	th1=theta(ax, ay, bx, by);
	th2=theta(cx, cy, dx, dy);
	th0=th1 - th2;
	return M_PI - th0 + th2 + M_PI; // 反射角
}


// ============================================ ゲーム関連

// 指定された位置、色に線を引く
void drawLine(float ax, float ay, float bx, float by, int color)
{
	newpen(win, color);
	drawline(win, ax, ay, bx, by);
}

// 壁を描く
void drawWall()
{
	int i;
	for(i=0; i<WALLNUM; i++) {
		drawLine(wallXY[i][0], wallXY[i][1], 
				 wallXY[i+1][0], wallXY[i+1][1], 
				 2); // 色番号はここにハードコードする
	}
}

// 指定された位置にボールを描く
void drawBall(float x, float y)
{
	newpen(win, 1); // 色番号はここにハードコードする
	fillcirc(win, x, y, BASIZE, BASIZE);
}

// ブロックを描く
void drawBlocks(void) 
{
	int col, raw;
	float x, y;
	for(col=0; col<COL; col++) {
		for(raw=0; raw<RAW; raw++) {
			if(bkAlive[col][raw]!=DEAD) { 
				x=(WW-BKSIZE*COL)/2.0   +col*BKSIZE;
				y=(HH-BKSIZE*(RAW+2)) +raw*BKSIZE;
				newhsvcolor(win, bkColor[col][raw], 255, 255*((float)(bkAlive[col][raw])/LIVE));
				fillrect(win, x, y, BKSIZE, BKSIZE);
				newpen(win, 0);
				drawrect(win, x, y, BKSIZE, BKSIZE);
			}
		}
	}
}

// ========================== 補助サブルーチン群

// 境界線データをセットアップする
void setupBorder(void)
{
	int i, col, raw, b=0;
	float x1, y1, x2, y2; // ブロックの対角座標位置（左下と右上）
	// 壁データを境界線データに展開
	for(i=0; i<WALLNUM; i++) {
		border[b][0]=wallXY[i  ][0];
		border[b][1]=wallXY[i  ][1];
		border[b][2]=wallXY[i+1][0];
		border[b][3]=wallXY[i+1][1];
		borderOf[b]=NOBLK; 
		b++;
	}
	// ブロックの境界線データを合成
	for(col=0; col<COL; col++) {
		for(raw=0; raw<RAW; raw++) {
			x1=(WW-BKSIZE*COL)/2.0   +col*BKSIZE;
			y1=(HH-BKSIZE*(RAW+2)) +raw*BKSIZE;
			x2=x1+BKSIZE;
			y2=y1+BKSIZE;
			border[b][0]=x1; border[b][1]=y1; border[b][2]=x1; border[b][3]=y2; // 左
			borderOf[b++]=raw*COL+col; // ブロック番号を計算して、一つ進める
			border[b][0]=x1; border[b][1]=y1; border[b][2]=x2; border[b][3]=y1; // 下
			borderOf[b++]=raw*COL+col; // ブロック番号を計算して、一つ進める
			border[b][0]=x1; border[b][1]=y2; border[b][2]=x2; border[b][3]=y2; // 上
			borderOf[b++]=raw*COL+col; // ブロック番号を計算して、一つ進める
			border[b][0]=x2; border[b][1]=y1; border[b][2]=x2; border[b][3]=y2; // 右
			borderOf[b++]=raw*COL+col; // ブロック番号を計算して、一つ進める
		}
	}
}

// ブロック情報をセットアップする
void setupBlocks(void) 
{
	int col, raw;
	for(col=0; col<COL; col++) {
		for(raw=0; raw<RAW; raw++) {
			bkAlive[col][raw]=LIVE;
			bkColor[col][raw]=(360.0/(COL*RAW)) * (col+(raw*COL));
		}
	}
}

// 生きているブロックを数える
int countLiveBlocks(void)
{
	int col, raw, cnt=0;
	for(col=0; col<COL; col++) {
		for(raw=0; raw<RAW; raw++) {
			switch (bkAlive[col][raw]) {
				case LIVE:
					cnt++; // LIVE ひとつ加算
					break;
				case DEAD:
					// 消えたブロックではすることなし
					break;
				default: // それ以外、は、消えていく途中のブロックを意味する
					cnt++; // これも LIVE のうちに数える
					bkAlive[col][raw]--; // 一つカウントダウン
					break;
			}
		}
	}
	return cnt;
}

// 初期設定
void setup(void)
{
	win=gopen(WW, HH);
	winname(win, "Collision test");
	layer(win, 0, 1);  /* 表示は 0 番、描画は 1 番レイヤーで */
	drawWall();
	copylayer(win, 1, 0); /* レイヤー 1 番の内容を 0 にコピー */

	setupBorder(); // 境界線データを整形する
	setupBlocks(); // ブロック情報を整形する
}

// 終了処理
void closeall(void)
{
	newpen(win, 1);
	drawstr(win, WW/2.0-16.0, HH/2.0, 16, 0.0, "FIN.");
	copylayer(win, 1, 0); /* レイヤー 1 番の内容を 0 にコピー */
	ggetch(); // quit 関数があるので外す
	gclose(win);
}


// ============================================ メインルーチン


int main (int argc, const char * argv[]) {
	float ballx0, bally0; // ボールの現在の位置
	float ballx1, bally1; // ボールの現在の進行方向
	float ballang, ballspd; // 進行方向と速度
	int i, minI; // 壁など衝突した際のオブジェクトの番号（その最小ケースについても保存）
	float r, minR; // 衝突した際の距離比率（最小値についても保存）
	float isx, isy; // 交差位置（＝衝突位置）（intersection x,y)
	int col, raw; // 一時変数
	
	setup();

	ballx0=WW/2.0; bally0=30.0;
	ballang=M_PI_4-0.2; // ちょっと右
	ballspd=SPEED; // 速度はここにハードコード
	
	while(1) {
		gclr(win);
		drawWall();
		// ボールの位置移動（仮）
		ballx1=ballx0+cos(ballang)*ballspd;
		bally1=bally0+sin(ballang)*ballspd;

		// 壁との衝突をチェック
		minR=NOCROSS;
		minI=-1; // warning 避け
		for(i=0; i<BORDNUM; i++) { // 全ての壁について個別にチェック
			if(borderOf[i]!=NOBLK) { // live ではないブロックはチェックしない
				col=borderOf[i]%COL;
				raw=borderOf[i]/COL;
				if(bkAlive[col][raw]!=LIVE) continue; // スキップ
			}
			r=colCheck(ballx0, bally0, ballx1, bally1,
					   border[i][0],border[i][1],border[i][2],border[i][3]);
			if(minR>r) { // より出発点に近いところで交差が見つかった
				minR=r; // より短い距離情報に更新
				minI=i; // そのときの壁番号も保持
			}
		}
		if(minR<NOCROSS) { // 最低一つは交差した
			//交点をボールの軌道から求める（現在地から r ぶんだけ進んだ位置, 0.99 は安全マージン
			isx = ballx0 + minR * ( ballx1 - ballx0 ) * 0.99;
			isy = bally0 + minR * ( bally1 - bally0 ) * 0.99;
			// 反射角を得る（壁wallXY[minI]に対してballが反射する方向）
			ballang=bounceRect(ballx0, bally0, ballx1, bally1,
							   border[minI][0],border[minI][1],border[minI][2],border[minI][3]);
			// 交点 (isx, isy) から角 th 方向に微少に進んだ位置を次の位置とする）
			ballx1 = isx; // isx + 2.0 * cosf(ballang);
			bally1 = isy; // isy + 2.0 * sinf(ballang);
			// 衝突したのがブロックであれば、それは消しておく
			if(borderOf[minI]!=NOBLK) {
				col=borderOf[minI]%COL;	
				raw=borderOf[minI]/COL;
				bkAlive[col][raw]=LIVE*0.5; // カウントダウン開始
			}
		}
		
		drawBall(ballx1, bally1); // 新しい位置でボールを描画
		drawBlocks(); // ブロックの描画
		copylayer(win, 1, 0); /* レイヤー 1 番の内容を 0 にコピー */

		if(countLiveBlocks()==0) { // 表示すべきブロックがまだあるか？
			ballspd -= SPEED/100.0; // ボール速度を徐々に減速
			if(ballspd<=0.1) break; // 速度がほぼゼロ（以下）になれば終了処理に出す
		}
		ballx0=ballx1; bally0=bally1; // 位置情報を一世代進める
		msleep(CYCLE); 
	}
	
	closeall();
    return 0;
}
