/*
 *	game.c
 *
 *	TESTER PHASE ONE(移植版)
 *
 *	* Fri Mar 20 15:53:18 JST 2009 Naoyuki Sawa
 *	- 1st リリース。
 */
#include "app.h"

/****************************************************************************
 *	
 ****************************************************************************/

#define PRIMARY_W		640
#define PRIMARY_H		480

#define REPLAY_NAME		"tester.dat"
#define REPLAY_BLOCK		4			/*調整可*/
#define TITLE_WAIT		(60 * 15)
#define DEMO_CNT		(60 * 30)

#define LEVEL_MAX		999
#define REFLECT_MAX		180
#define STAR_MAX		96

#define GROUP_SYSTEM		0
#define GROUP_SCREFF		1
#define GROUP_SSHOT		2
#define GROUP_ENEMY		3
#define GROUP_ESHOT		4
#define GROUP_SHIP		5
#define GROUP_SYSINF		6
#define GROUP_MAX		7

#define ID_COLL			(1 << 0)
#define ID_REFSHOT		(1 << 1)
#define ID_LOCKED		(1 << 2)

#define X			0
#define Y			1
#define XY			2

typedef struct _OBJECT {
	LIST_ENTRY list_entry;
	short id;
	short sw;
	short cnt;
	short wait;
	void (*func)(struct _OBJECT*);
	float pos[XY];
	float vel[XY];
	float acc[XY];
	float trg[XY];
	short col[XY];
	short energy;
	short anm_pat;
	short anm_wait;
} OBJECT;

typedef struct _REPLAY {
	unsigned char digest[16];				/* + 0,16 */
	int score;						/* +16, 4 */
	int start_level;					/* +20, 4 */
	int end_level;						/* +24, 4 */
	int seed;						/* +28, 4 */
	unsigned char data[BLOCKSIZE * REPLAY_BLOCK - 32];	/* +32, ? */
} REPLAY;

#define SHIP_SX1	(6.0f)
#define SHIP_SY1	(6.0f)
#define SHIP_SX2	(SHIP_SX1 * M_SQRT_2)
#define SHIP_SY2	(SHIP_SY1 * M_SQRT_2)
const float ship_vel[][XY] = {
	{         0,         0 },	/* 0x00 NONE */
	{ +SHIP_SX1,         0 },	/* 0x01 PAD_RI */
	{ -SHIP_SX1,         0 },	/* 0x02 PAD_LF */
	{         0,         0 },	/* 0x03 NONE */
	{         0, +SHIP_SY1 },	/* 0x04 PAD_DN */
	{ +SHIP_SX2, +SHIP_SY2 },	/* 0x05 PAD_DN | PAD_RI */
	{ -SHIP_SX2, +SHIP_SY2 },	/* 0x06 PAD_DN | PAD_LF */
	{         0,         0 },	/* 0x07 NONE */
	{         0, -SHIP_SY1 },	/* 0x08 PAD_UP */
	{ +SHIP_SX2, -SHIP_SY2 },	/* 0x09 PAD_UP + PAD_RI */
	{ -SHIP_SX2, -SHIP_SY2 },	/* 0x0A PAD_UP + PAD_LF */
	{         0,         0 },	/* 0x0B NONE */
	{         0,         0 },	/* 0x0C NONE */
	{         0,         0 },	/* 0x0D NONE */
	{         0,         0 },	/* 0x0E NONE */
	{         0,         0 },	/* 0x0F NONE */
};

/*--------------------------------------------------------------------------*/

int start_level = 1;

int menu_num;

int gctrl_mode;
int score;
int level;
int enemy_cnt;
int stage_wait;

int shot_wait;
int reflect_mode;
int reflect_now;

int gauge_y;
int gauge_ty;

LIST_ENTRY object[GROUP_MAX];
OBJECT* ship_obj;
OBJECT* collision_obj;

int replay_f;
REPLAY replay;
REPLAY record;
TinyLZ tlz;
TinyLZc lzc;

/*--------------------------------------------------------------------------*/

void replay_load(REPLAY* r);
void replay_save(REPLAY* r);

void game_init();
void game_exit();
void game_exec();
void game_collision();
void game_collision_sub(OBJECT* p, int group);

void OBJ_init();
OBJECT* OBJ_set(int group, void (*func)(struct _OBJECT*));
void OBJ_delete(OBJECT* p);
void OBJ_deleteAll(OBJECT* p);
void OBJ_deleteGroup(OBJECT* p, int group);
void OBJ_exec();
void OBJ_execDirect(OBJECT* p);

void obj_gctrl(OBJECT* p);
void obj_title(OBJECT* p);
void obj_stage(OBJECT* p);
void obj_ship(OBJECT* p);
void obj_ring(OBJECT* p);
void obj_sshot(OBJECT* p);
void obj_sshot2(OBJECT* p);
void obj_barrier(OBJECT* p);
void obj_zako01(OBJECT* p);
void obj_zako02(OBJECT* p);
void obj_zako03(OBJECT* p);
void obj_eshot(OBJECT* p);
void obj_sysinf(OBJECT* p);
void obj_gameover(OBJECT* p);
void obj_explode(OBJECT* p);
void obj_star(OBJECT* p);

void stage_init();
void stage_levelup();
void stage_ctrl();
void stage_next(void (*func)(struct _OBJECT*));

void obj_disp(OBJECT* p, int sprno);
void obj_anmset(OBJECT* p, const short* pat, int wait);
OBJECT* get_near_enemy(OBJECT* p);
void add_score(int add);

/****************************************************************************
 *	
 ****************************************************************************/

void
app_main()
{
	game_init();
	for(;;) {
		schedule();
		game_exec();
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

void
replay_load(REPLAY* r)
{
	FILE* fp;
	unsigned char digest[16];

	memset(r, 0, sizeof(REPLAY));
	fp = fopen(REPLAY_NAME, "rb");
	if(fp) {
		fread(r, 1, sizeof(REPLAY), fp);
		fclose(fp);
		md5_digest(digest, r->digest + 16, sizeof(REPLAY) - 16);
		if(memcmp(digest, r->digest, 16)) {
			memset(r, 0, sizeof(REPLAY));
		}
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

void
replay_save(REPLAY* r)
{
	int i;
	FILEACC fa;

	replay_load(&record);
	if(r->score > record.score) {
		if(!pceFileCreate(REPLAY_NAME, sizeof(REPLAY))) {
			if(!pceFileOpen(&fa, REPLAY_NAME, FOMD_WR)) {
				md5_digest(r->digest, r->digest + 16, sizeof(REPLAY) - 16);
				for(i = 0; i < REPLAY_BLOCK; i++) {
					pceFileWriteSct(&fa, (unsigned char*)r + BLOCKSIZE * i, i, BLOCKSIZE);
				}
				pceFileClose(&fa);
			}
		}
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

void
game_init()
{
	OBJ_init();
	OBJ_set(GROUP_SYSTEM, obj_gctrl);
	replay_load(&replay);
}

/****************************************************************************
 *	
 ****************************************************************************/

void
game_exit()
{
	turbo(0); /* 必要! */
	replay_save(&replay);
	exit(0);
}

/****************************************************************************
 *	
 ****************************************************************************/

void
game_exec()
{
	surface_clear(&surface, 3);
	OBJ_exec();
	game_collision();
}

/****************************************************************************
 *	
 ****************************************************************************/

void
game_collision()
{
	LIST_ENTRY* list_head;
	LIST_ENTRY* list_entry;

	list_head = &object[GROUP_ENEMY];
	list_entry = list_head->Flink;
	while(list_entry != list_head) {
		OBJECT* p = CONTAINING_RECORD(list_entry, OBJECT, list_entry);
		list_entry = list_entry->Flink;
		if(p->id & ID_COLL) game_collision_sub(p, GROUP_SSHOT);
	}
	list_head = &object[GROUP_SHIP];
	list_entry = list_head->Flink;
	while(list_entry != list_head) {
		OBJECT* p = CONTAINING_RECORD(list_entry, OBJECT, list_entry);
		list_entry = list_entry->Flink;
		if(p->id & ID_COLL) game_collision_sub(p, GROUP_ESHOT);
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

void
game_collision_sub(OBJECT* p, int group)
{
	LIST_ENTRY* const list_head = &object[group];
	LIST_ENTRY* list_entry = list_head->Flink;
	int sw;

	while(list_entry != list_head) {
		OBJECT* ep = CONTAINING_RECORD(list_entry, OBJECT, list_entry);
		list_entry = list_entry->Flink;
		if(ep->id & ID_COLL) {
			if(abs(p->pos[X] - ep->pos[X]) < (p->col[X] + ep->col[X]) &&
			   abs(p->pos[Y] - ep->pos[Y]) < (p->col[Y] + ep->col[Y])) {
				sw = p->sw;
				collision_obj = ep;
				p->sw = 100;
				OBJ_execDirect(p);
				if(p->energy > 0) p->sw = sw;
				sw = ep->sw;
				collision_obj = p;
				ep->sw = 100;
				OBJ_execDirect(ep);
				if(ep->energy > 0) ep->sw = sw;
				return;
			}
		}
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

void
OBJ_init()
{
	int i;

	for(i = 0; i < GROUP_MAX; i++) {
		InitializeListHead(&object[i]);
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

OBJECT*
OBJ_set(int group, void (*func)(struct _OBJECT*))
{
	static POOL pool = { sizeof(OBJECT) };
	OBJECT* p = pool_alloc(&pool);

	p->func = func;
	InsertTailList(&object[group], &p->list_entry);
	return p;
}

/****************************************************************************
 *	
 ****************************************************************************/

void
OBJ_delete(OBJECT* p)
{
	RemoveEntryList(&p->list_entry);
	pool_free(p);
}

/****************************************************************************
 *	
 ****************************************************************************/

void
OBJ_deleteAll(OBJECT* p)
{
	int i;

	for(i = 0; i < GROUP_MAX; i++) {
		OBJ_deleteGroup(p, i);
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

void
OBJ_deleteGroup(OBJECT* p, int group)
{
	LIST_ENTRY* const list_head = &object[group];
	LIST_ENTRY* list_entry = list_head->Flink;
	while(list_entry != list_head) {
		OBJECT* ep = CONTAINING_RECORD(list_entry, OBJECT, list_entry);
		list_entry = list_entry->Flink;
		if(ep != p) {
			OBJ_delete(ep);
		}
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

void
OBJ_exec()
{
	int i;

	for(i = 0; i < GROUP_MAX; i++) {
		LIST_ENTRY* const list_head = &object[i];
		LIST_ENTRY* list_entry = list_head->Flink;
		while(list_entry != list_head) {
			OBJECT* p = CONTAINING_RECORD(list_entry, OBJECT, list_entry);
			list_entry = list_entry->Flink;
			OBJ_execDirect(p);
		}
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

void
OBJ_execDirect(OBJECT* p)
{
	if(p->func) p->func(p);
}

/****************************************************************************
 *	
 ****************************************************************************/

void
obj_gctrl(OBJECT* p)
{
	switch(p->sw) {
	/* title */
	case 0:
		gctrl_mode = 0;
		OBJ_set(GROUP_SYSINF, obj_title);
		p->sw++;
		break;
	case 1:
		if(joy & TRG_SELECT) game_exit();
		if(!gctrl_mode) break;
		p->sw = 10; /* game */
		break;
	/* game */
	case 10:
		gctrl_mode = 0;
		if(!replay_f) {
			memset(&record, 0, sizeof(REPLAY));
			record.start_level = level = start_level;
			record.seed = seed;
			TinyLZc_init(&lzc, record.data, sizeof record.data);
		} else {
			level = replay.start_level;
			seed = replay.seed;
			TinyLZ_init(&tlz, replay.data);
		}
		score = 0;
		enemy_cnt = 0;
		stage_wait = 60;
		gauge_y = gauge_ty = DISP_Y - 10;
		OBJ_set(GROUP_SYSTEM, obj_stage);
		ship_obj = OBJ_set(GROUP_SHIP, obj_ship);
		OBJ_set(GROUP_SHIP, obj_ring);
		OBJ_set(GROUP_SYSINF, obj_sysinf);
		OBJ_set(GROUP_SCREFF, obj_star);
		play_music(SND_TEST_BGM);
		p->cnt = 0;
		p->sw++;
		break;
	case 11:
		p->cnt++;
		if(!replay_f) {
			if(joy & TRG_SELECT) {
				stop_music();
				gctrl_mode = 1;
				p->sw = 100; /* exit */
				break;
			}
		} else {
			if((joy & (TRG_SELECT | TRG_AB)) ||
			   ((replay_f == 2) && (p->cnt == DEMO_CNT))) {
				stop_music();
				gctrl_mode = 1;
				p->sw = 100; /* exit */
				break;
			}
		}
		if(!gctrl_mode) break;
		stop_music();
		OBJ_set(GROUP_SYSINF, obj_gameover);
		p->wait = 60 * 3;
		p->sw++;
		break;
	case 12:
		p->wait--;
		if(p->wait < 60 * 2) {
			if(joy & (TRG_SELECT | TRG_AB)) p->wait = 0;
		}
		if(p->wait) break;
		if(!replay_f) {
			if(score > replay.score) {
				record.score = score;
				record.end_level = level;
				replay = record;
			}
			start_level = level;
		}
		p->sw = 100; /* exit */
		break;
	/* exit */
	case 100:
		OBJ_deleteAll(p);
		p->sw = 0; /* title */
		break;
	default:
		DIE();
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

void
obj_title(OBJECT* p)
{
	int px;
	int py;

	switch(p->sw) {
	/* init */
	case 0:
		p->sw++; /* title */
		break;
	/* title */
	case 1:
		p->cnt++;
		if(p->cnt == TITLE_WAIT) {
			if(replay.score) {
				replay_f = 2;
				p->cnt = 0;
				p->sw = 100; /* exit */
				break;
			}
		}
		if(joy & TRG_A) {
			play_se(SND_EXPLODE1);
			menu_num = 0;
			p->sw++; /* menu */
			break;
		}
		break;
	/* menu */
	case 2:
		if(joy & TRG_UP) {
			play_se(SND_REFLECT);
			if(--menu_num < 0) menu_num = 2;
		}
		if(joy & TRG_DN) {
			play_se(SND_REFLECT);
			if(++menu_num > 2) menu_num = 0;
		}
		if(menu_num == 0) {
			if(joy & TRG_LF) {
				start_level--;
				if(start_level < 1) start_level = LEVEL_MAX;
			}
			if(joy & TRG_RI) {
				start_level++;
				if(start_level > LEVEL_MAX) start_level = 1;
			}
		}
		if(joy & TRG_A) {
			if(menu_num == 0) {
				play_se(SND_EXPLODE1);
				replay_f = 0;
				p->cnt = 0;
				p->sw = 100;
				break;
			}
			if(menu_num == 1) {
				if(replay.score) {
					play_se(SND_EXPLODE1);
					replay_f = 1;
					p->cnt = 0;
					p->sw = 100;
					break;
				} else {
					play_se(SND_BARRIER);
				}
			}
			if(menu_num == 2) {
				game_exit();
			}
		}
		if(joy & TRG_B) {
			play_se(SND_CHARGE);
			p->cnt = 0;
			p->sw--; /* title */
			break;
		}
		break;
	/* exit */
	case 100:
		p->cnt++;
		if(p->cnt < 30) break;
		gctrl_mode = 1;
		OBJ_delete(p);
		return;
	default:
		DIE();
	}

	px = 4;
	py = 32;
	render_string(&render, px, py - 6, "====================", 2, 0);
	render_string(&render, px, py    , "= TESTER PHASE ONE =", 2, 0);
	render_string(&render, px, py + 6, "====================", 2, 0);
	px = 3;
	py = 114;
	sprite_draw(px, py, SPR_COPYRIGHT, DRW_NOMAL);
	switch(p->sw) {
	/* title */
	case 1:
		if(!(p->cnt & 0x20)) {
			px = 10;
			py = 66;
			render_string(&render, px, py, "PRESS SHOT BUTTON", 2, 0);
		}
		break;
	/* menu */
	case 2:
		px = 34;
		py = 60;
		render_string(&render, px, py + 6 * 0, "START" , 2, 0);
		render_string(&render, px, py + 6 * 1, "REPLAY", 2, 0);
		render_string(&render, px, py + 6 * 2, "EXIT"  , 2, 0);
		render_string(&render, px - 8, py + 6 * menu_num, ">", 2, 0);
		if(menu_num == 0) {
			px = 18;
			py = 102;
			render_printf(&render, px, py, 2, 0, "< LEVEL %3d >", start_level);
		}
		if(menu_num == 1) {
			px = 6;
			py = 102;
			if(replay.score) {
				render_printf(&render, px, py, 2, 0, "REP. LEVEL %3d", replay.start_level);
				px += 58;
				render_font(&render, px, py, '-', 2, 0);
				px += 6;
				render_printf(&render, px, py, 2, 0, "%3d", replay.end_level);
			} else {
				render_string(&render, px, py, "REP. DATA NOT EXIST", 2, 0);
			}
		}
		if(menu_num != 2) {
			px = 6;
			py = 96;
			render_printf(&render, px, py, 2, 0, "HIGH SCORE %08d", replay.score);
		}
		break;
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

void
obj_stage(OBJECT* p)
{
	if(gctrl_mode) return;
	switch(p->sw) {
	case 0:
		stage_init();
		p->sw = 10;
		break;
	case 10:
		p->wait = stage_wait;
		p->sw++;
		break;
	case 11:
		p->wait--;
		if(p->wait) break;
		stage_ctrl();
		p->sw = 10;
		break;
	default:
		DIE();
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

void
obj_ship(OBJECT* p)
{
	OBJECT* ep;
	int ship_dir;
	int ship_pad;
	float vx;
	float vy;
	int sprno = SPR_SHIP;

	if(!replay_f) {
		ship_pad = joy & (PAD_LRUD | PAD_AB);
		TinyLZc_put(&lzc, ship_pad);
	} else {
		ship_pad = TinyLZ_get(&tlz);
	}
	ship_dir = ship_pad & PAD_LRUD;

	switch(p->sw) {
	case 0:
		p->id |= ID_COLL;
		p->pos[X] = PRIMARY_W / 2;
		p->pos[Y] = PRIMARY_H - 64;
		p->col[X] = 2;
		p->col[Y] = 2;
		shot_wait = 0;
		reflect_mode = 0;
		reflect_now = REFLECT_MAX;
		p->sw++;
		return;
	case 1:
		/* ship move */
		vx = ship_vel[ship_dir][X];
		vy = ship_vel[ship_dir][Y];
		if(vx || vy) add_score(10);
		p->pos[X] += vx;
		p->pos[Y] += vy;
		if(p->pos[X] <             128) p->pos[X] =             128;
		if(p->pos[X] > PRIMARY_W - 128) p->pos[X] = PRIMARY_W - 128;
		if(p->pos[Y] <         0) p->pos[Y] =         0;
		if(p->pos[Y] > PRIMARY_H) p->pos[Y] = PRIMARY_H;
		/* normal shot */
		if(ship_pad & PAD_A) {
			if(!shot_wait) {
				play_se(SND_SHOT);
				p->cnt = 5;
				ep = OBJ_set(GROUP_SSHOT, obj_sshot);
				ep->pos[X] = p->pos[X];
				ep->pos[Y] = p->pos[Y] - 8;
				OBJ_execDirect(ep);
				ep = OBJ_set(GROUP_SSHOT, obj_sshot);
				ep->pos[X] = p->pos[X] - 14;
				ep->pos[Y] = p->pos[Y];
				OBJ_execDirect(ep);
				ep = OBJ_set(GROUP_SSHOT, obj_sshot);
				ep->pos[X] = p->pos[X] + 14;
				ep->pos[Y] = p->pos[Y];
				OBJ_execDirect(ep);
				shot_wait = 3;
			} else {
				shot_wait--;
			}
		} else {
			shot_wait = 0;
		}
		/* reflect */
		if(!reflect_mode) {
			if(ship_pad & PAD_B) {
				play_se(SND_BARRIER);
				p->id &= ~ID_COLL;
				reflect_mode = 1;
				ep = OBJ_set(GROUP_SHIP, obj_barrier);
				OBJ_execDirect(ep);
			}
		} else if(reflect_mode == 1) {
			reflect_now -= REFLECT_MAX / 90;
			if(reflect_now <= 0) {
				p->id |= ID_COLL;
				reflect_mode = 2;
				reflect_now = 0;
			}
		} else /*if(reflect_mode == 2)*/ {
			reflect_now++;
			if(vx || vy) reflect_now++;
			if(reflect_now >= REFLECT_MAX) {
				play_se(SND_CHARGE);
				reflect_mode = 0;
				reflect_now = REFLECT_MAX;
			}
		}
		/* ship animation */
		if(p->cnt) {
			sprno += --p->cnt;
		}
		break;
	/* collision */
	case 100:
		gctrl_mode = 1;
		OBJ_deleteGroup(NULL, GROUP_SSHOT);
		p->id &= ~ID_COLL;
		p->wait = 32;
		p->cnt = -1;
		p->sw++;
		return;
	case 101:
		p->cnt++;
		p->wait--;
		if(p->wait) break;
		p->sw = -1;
		return;
	default:
		ship_obj = NULL;
		OBJ_delete(p);
		return;
	}

	if(p->sw != 101) {
		if((reflect_mode != 1) || (now & 1)) {
			obj_disp(p, sprno);
		}
	} else {
		obj_explode(p);
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

void
obj_ring(OBJECT* p)
{
	static const short pat[] = { 1, 2, 3, 4, 3, 2, -1 };
	static vec2f* pv;
	vec2f v;
	int i;
	int sprno;

	switch(p->sw) {
	case 0:
		p->sw++;
		return;
	case 1:
		obj_anmset(p, pat, 2);
		if(!ship_obj || ship_obj->sw >= 100) {
			p->sw = -1;
			return;
		}
		break;
	default:
		OBJ_delete(p);
		return;
	}

	if(!reflect_mode) {
		if(!pv) {
			pv = malloc(sizeof(vec2f) * 90);
			for(i = 0; i < 90; i++) {
				pv[i].x = cosf(M_PI2 * i / 90) * 34;
				pv[i].y = sinf(M_PI2 * i / 90) * 34;
			}
		}
		p->pos[X] = ship_obj->pos[X];
		p->pos[Y] = ship_obj->pos[Y];
		obj_disp(p, SPR_RING);
		sprno = SPR_RING + pat[p->anm_pat];
		v = pv[now % 90];
		p->pos[X] = ship_obj->pos[X] + v.x;
		p->pos[Y] = ship_obj->pos[Y] + v.y;
		obj_disp(p, sprno);
		p->pos[X] = ship_obj->pos[X] - v.x;
		p->pos[Y] = ship_obj->pos[Y] - v.y;
		obj_disp(p, sprno);
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

void
obj_sshot(OBJECT* p)
{
	switch(p->sw) {
	case 0:
		p->id |= ID_COLL;
		p->col[X] = 2;
		p->col[Y] = 8;
		p->energy = 1;
		p->sw++;
		return;
	case 1:
		p->pos[Y] -= 14;
		if(p->pos[Y] > -8) break;
		p->sw = -1;
		return;
	/* collision */
	case 100:
		p->id &= ~ID_COLL;
		p->sw = -1;
		return;
	default:
		OBJ_delete(p);
		return;
	}

	obj_disp(p, SPR_SSHOT);
}

/****************************************************************************
 *	
 ****************************************************************************/

void
obj_sshot2(OBJECT* p)
{
	OBJECT* ep;
	float tmp;

	switch(p->sw) {
	case 0:
		p->id |= ID_COLL | ID_REFSHOT;
		p->col[X] = 2;
		p->col[Y] = 4;
		ep = get_near_enemy(p);
		if(ep) {
			p->vel[X] = ep->pos[X] - p->pos[X];
			p->vel[Y] = ep->pos[Y] - p->pos[Y];
			tmp = sqrtf(p->vel[X] * p->vel[X] + p->vel[Y] * p->vel[Y]) / 16;
			if(tmp) {
				p->vel[X] /= tmp;
				p->vel[Y] /= tmp;
			}
		} else {
			p->vel[Y] = -16;
		}
		p->energy = 4;
		p->sw++;
		return;
	case 1:
		p->pos[X] += p->vel[X];
		p->pos[Y] += p->vel[Y];
		if(p->pos[X] < 120 || p->pos[X] > 520 ||
		   p->pos[Y] <  -8 || p->pos[Y] > 448) {
			p->sw = -1;
			return;
		}
		break;
	/* collision */
	case 100:
		p->id &= ~ID_COLL;
		p->sw = -1;
		return;
	default:
		OBJ_delete(p);
		return;
	}

	obj_disp(p, SPR_SSHOT2);
}

/****************************************************************************
 *	
 ****************************************************************************/

void
obj_barrier(OBJECT* p)
{
	OBJECT* ep;

	switch(p->sw) {
	case 0:
		p->id |= ID_COLL;
		p->pos[X] = ship_obj->pos[X];
		p->pos[Y] = ship_obj->pos[Y];
		p->cnt = -1;
		p->sw++;
		return;
	case 1:
		if(!ship_obj || ship_obj->sw >= 100 || reflect_mode != 1) {
			p->id &= ~ID_COLL;
			p->sw = -1;
			return;
		}
		p->pos[X] = ship_obj->pos[X];
		p->pos[Y] = ship_obj->pos[Y];
		if(p->cnt < 13) {
			if(!p->wait--) {
				p->cnt++;
				p->wait = 1;
				p->col[X] = p->col[Y] = (p->cnt + 1) * (4 / 2);
			}
		}
		break;
	/* collision */
	case 100:
		add_score(100);
		play_se(SND_REFLECT);
		ep = OBJ_set(GROUP_SSHOT, obj_sshot2);
		ep->pos[X] = collision_obj->pos[X];
		ep->pos[Y] = collision_obj->pos[Y];
		OBJ_execDirect(ep);
		p->sw = 1;
		return;
	default:
		OBJ_delete(p);
		return;
	}

	if(now & 1) {
		obj_disp(p, SPR_BARRIER + p->cnt);
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

void
obj_zako01(OBJECT* p)
{
	static const short pat[] = { 0, 1, 0, 2, -1 };
	OBJECT* ep;

	switch(p->sw) {
	case 0:
		p->id |= ID_COLL;
		p->pos[X] = PRIMARY_W / 2 + RNDF_RANGE(seed, -176, 176);
		p->pos[Y] =                 RNDF_RANGE(seed,  -48, -16);
		if(p->pos[X] < PRIMARY_W / 2) {
			p->trg[X] = p->pos[X] + RNDF_RANGE(seed, 0, 64);
		} else {
			p->trg[X] = p->pos[X] - RNDF_RANGE(seed, 0, 64);
		}
		p->trg[Y] = p->pos[Y] + 224;
		p->col[X] = 16;
		p->col[Y] = 16;
		p->energy = 4;
		p->wait = 60;
		p->sw++;
		return;
	case 1:
		p->vel[X] = (p->trg[X] - p->pos[X]) / 15;
		p->vel[Y] = (p->trg[Y] - p->pos[Y]) / 15;
		p->pos[X] += p->vel[X];
		p->pos[Y] += p->vel[Y];
		p->wait--;
		if(p->wait) break;
		p->acc[X] = p->vel[X] / 10;
		p->acc[Y] = p->vel[Y] / 10;
		p->wait = 4;
		p->sw++;
		/* FALLTHRU */
	case 2:
		obj_anmset(p, pat, 4);
		if(p->wait) {
			p->wait--;
		} else {
			ep = OBJ_set(GROUP_ESHOT, obj_eshot);
			ep->pos[X] = p->pos[X];
			ep->pos[Y] = p->pos[Y];
			OBJ_execDirect(ep);
			p->wait = 4;
		}
		p->pos[X] += p->vel[X];
		p->pos[Y] += p->vel[Y];
		p->vel[X] += p->acc[X];
		p->vel[Y] += p->acc[Y];
		if(p->pos[Y] < PRIMARY_H + 16) break;
		p->sw = -1;
		return;
	/* collision */
	case 100:
		add_score(collision_obj->energy * 10);
		p->energy -= collision_obj->energy;
		if(p->energy > 0) return;
		if(collision_obj->id & ID_REFSHOT) {
			p->id |= ID_REFSHOT;
			add_score(200);
		} else {
			add_score(100);
		}
		p->id &= ~ID_COLL;
		p->wait = 32;
		p->cnt = -1;
		p->sw++;
		return;
	case 101:
		p->cnt++;
		p->wait--;
		if(p->wait) break;
		p->sw = -1;
		return;
	default:
		OBJ_delete(p);
		return;
	}

	if(p->sw != 101) {
		obj_disp(p, SPR_ZAKO01 + pat[p->anm_pat]);
	} else {
		obj_explode(p);
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

void
obj_zako02(OBJECT* p)
{
	static const short pat[] = { 0, 1, -1 };
	OBJECT* ep;

	switch(p->sw) {
	case 0:
		p->id |= ID_COLL;
		if(ship_obj && RND32_RANGE(seed, 0, 100) < 50) {
			p->pos[X] = ship_obj->pos[X];
		} else {
			p->pos[X] = PRIMARY_W / 2 + RNDF_RANGE(seed, -176, 176);
		}
		p->pos[Y] = -16;
		p->col[X] = 16;
		p->col[Y] = 16;
		p->energy = 4;
		p->sw++;
		return;
	case 1:
		obj_anmset(p, pat, 4);
		if(p->wait) {
			p->wait--;
		} else {
			ep = OBJ_set(GROUP_ESHOT, obj_eshot);
			ep->pos[X] = p->pos[X];
			ep->pos[Y] = p->pos[Y];
			OBJ_execDirect(ep);
			p->wait = 4;
		}
		p->pos[Y] += 5;
		if(p->pos[Y] < PRIMARY_H + 16) break;
		p->sw = -1;
		return;
	/* collision */
	case 100:
		add_score(collision_obj->energy * 10);
		p->energy -= collision_obj->energy;
		if(p->energy > 0) return;
		if(collision_obj->id & ID_REFSHOT) {
			p->id |= ID_REFSHOT;
			add_score(200);
		} else {
			add_score(100);
		}
		p->id &= ~ID_COLL;
		p->wait = 32;
		p->cnt = -1;
		p->sw++;
		return;
	case 101:
		p->cnt++;
		p->wait--;
		if(p->wait) break;
		p->sw = -1;
		return;
	default:
		OBJ_delete(p);
		return;
	}

	if(p->sw != 101) {
		obj_disp(p, SPR_ZAKO02 + pat[p->anm_pat]);
	} else {
		obj_explode(p);
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

void
obj_zako03(OBJECT* p)
{
	static const short pat[] = { 0, 1, 1, -1 };
	OBJECT* ep;

	switch(p->sw) {
	case 0:
		p->id |= ID_COLL;
		if(RND32_RANGE(seed, 0, 100) < 50) {
			p->pos[X] =             128 - 16;
			p->vel[X] =  4;
		} else {
			p->pos[X] = PRIMARY_W - 128 + 16;
			p->vel[X] = -4;
		}
		p->pos[Y] = RNDF_RANGE(seed, 0, 144);
		p->col[X] = 16;
		p->col[Y] = 16;
		p->energy = 3;
		p->sw++;
		return;
	case 1:
		obj_anmset(p, pat, 4);
		if(p->wait) {
			p->wait--;
		} else {
			ep = OBJ_set(GROUP_ESHOT, obj_eshot);
			ep->pos[X] = p->pos[X] - 14;
			ep->pos[Y] = p->pos[Y];
			OBJ_execDirect(ep);
			ep = OBJ_set(GROUP_ESHOT, obj_eshot);
			ep->pos[X] = p->pos[X] + 14;
			ep->pos[Y] = p->pos[Y];
			OBJ_execDirect(ep);
			p->wait = 8;
		}
		p->pos[X] += p->vel[X];
		if(p->pos[X] > PRIMARY_W - 128 + 16) {
			p->sw = -1;
			return;
		}
		if(p->pos[X] <             128 - 16) {
			p->sw = -1;
			return;
		}
		break;
	/* collision */
	case 100:
		add_score(collision_obj->energy * 10);
		p->energy -= collision_obj->energy;
		if(p->energy > 0) return;
		if(collision_obj->id & ID_REFSHOT) {
			p->id |= ID_REFSHOT;
			add_score(200);
		} else {
			add_score(100);
		}
		p->id &= ~ID_COLL;
		p->wait = 32;
		p->cnt = -1;
		p->sw++;
		return;
	case 101:
		p->cnt++;
		p->wait--;
		if(p->wait) break;
		p->sw = -1;
		return;
	default:
		OBJ_delete(p);
		return;
	}

	if(p->sw != 101) {
		obj_disp(p, SPR_ZAKO03 + pat[p->anm_pat]);
	} else {
		obj_explode(p);
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

void
obj_eshot(OBJECT* p)
{
	switch(p->sw) {
	case 0:
		p->id |= ID_COLL;
		p->col[X] = 2;
		p->col[Y] = 4;
		p->sw++;
		break;
	case 1:
		p->pos[Y] += 8;
		if(p->pos[Y] < PRIMARY_H + 16) break;
		p->sw = -1;
		return;
	/* collision */
	case 100:
		p->id &= ~ID_COLL;
		p->sw = -1;
		return;
	default:
		OBJ_delete(p);
		return;
	}

	obj_disp(p, SPR_ESHOT);
}

/****************************************************************************
 *	
 ****************************************************************************/

void
obj_sysinf(OBJECT* p)
{
	int px;
	int py;
	int ex;

	switch(p->sw) {
	case 0:
		p->sw++;
		break;
	case 1:
		if(ship_obj && (ship_obj->pos[Y] >= 448)) {
			gauge_ty = DISP_Y;
		} else {
			gauge_ty = DISP_Y - 10;
		}
		if(gauge_y < gauge_ty) gauge_y++;
		if(gauge_y > gauge_ty) gauge_y--;
		p->cnt++;
		break;
	default:
		DIE();
	}

	if(replay_f != 2) {
		px = 2;
		py = 2;
		render_printf(&render, px, py, 2, 1, "%08d", score);
		py += 6;
		render_printf(&render, px, py, 2, 1, "LEVEL%3d", level);
		if(replay_f == 1) {
			px = DISP_X - 33;
			py = 2;
			render_printf(&render, px, py, 2, 1, "%08d", replay.score);
			py += 6;
			render_printf(&render, px, py, 2, 1, "%3d", replay.start_level);
			px += 18;
			render_font(&render, px, py, '-', 2, 1);
			px += 2;
			render_printf(&render, px, py, 2, 1, "%3d", replay.end_level);
		}
		px = 4;
		py = gauge_y;
		sprite_draw(px, py, SPR_GAUGE, DRW_NOMAL);
		if((reflect_now == REFLECT_MAX) || (now & 1)) {
			ex = 78 * reflect_now / REFLECT_MAX;
			render_rectangle_fill(&render, px + 1, py + 6, ex, 2, 2);
		}
	} else {
		if(!(p->cnt & 0x20)) {
			px = 10;
			py = 66;
			render_string(&render, px, py, "PRESS SHOT BUTTON", 2, 0);
		}
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

void
obj_gameover(OBJECT* p)
{
	switch(p->sw) {
	case 0:
		p->sw++;
		break;
	case 1:
		break;
	default:
		DIE();
	}

	sprite_draw(DISP_X / 2, 42, SPR_GAMEOVER, DRW_NOMAL);
	if(!replay_f) {
		if(score > replay.score) {
			if(now & 2) {
				sprite_draw(DISP_X / 2, 68, SPR_HIGHSCORE + 0, DRW_NOMAL);
			} else {
				sprite_draw(DISP_X / 2, 68, SPR_HIGHSCORE + 1, DRW_NOMAL);
			}
		}
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

void
obj_explode(OBJECT* p)
{
	int i;

	if(!p->cnt) {
		if(p->id & ID_REFSHOT) {
			play_se(SND_EXPLODE1);
		} else {
			play_se(SND_EXPLODE2);
		}
	}

	if((i = p->cnt / 2) < 12) {
		obj_disp(p, SPR_EXPLODE + i);
	}
}

/****************************************************************************
 *	
 ****************************************************************************/


void
obj_star(OBJECT* p)
{
	static unsigned short y_tbl[STAR_MAX];
	static unsigned short v_add;
	int s = 100;
	int i;
	int x;
	int y;
	int v;
	int c;

	switch(p->sw){
	case 0:
		for(i = 0; i < STAR_MAX; i++) {
			y_tbl[i] = RND32_RANGE(s, 0, DISP_Y << 9);
		}
		p->sw++;
		return;
	case 1:
		break;
	default:
		DIE();
	}

	if(reflect_mode != 1) {
		v_add = v_add * 7 / 8;
		for(i = 0; i < STAR_MAX; i++) {
			x = RND32_RANGE(s, 0, DISP_X);
			v = RND32_RANGE(s, (1 << 9) * DISP_Y / PRIMARY_H, (3 << 9) * DISP_Y / PRIMARY_H);
			y_tbl[i] += v + v_add;
			y = y_tbl[i] >> 9;
			c = RND32_RANGE(s, 1, 3);
			render_point(&render, x, y, c);
		}
	} else {
		v_add = 8 << 9;
		y_tbl[0] += v_add;
		y = y_tbl[0] >> 9;
		for(i = 0; i < DISP_Y / 16; i++) {
			for(x = 0; x < DISP_X; x++) {
				c = RND32_RANGE(s, 1, 20);
				if(c <= 2) {
					render_line(&render, x, y, x, y + 16, c);
					if(y > DISP_Y - 16) {
						render_line(&render, x, y - DISP_Y, x, y - DISP_Y + 16, c);
					}
				}
			}
			y += 16;
			if(y >= DISP_Y) {
				y -= DISP_Y;
			}
		}
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

void
stage_init()
{
	int i = level;

	level = 0;
	while(level < i) {
		stage_levelup();
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

void
stage_levelup()
{
	if(level < LEVEL_MAX) {
		level++;
		if(!(level % 5)) {
			stage_wait -= 1;
			if(stage_wait < 5) stage_wait = 5;
		}
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

void
stage_ctrl()
{
	       if(level <  5) {	stage_next(obj_zako01);
	} else if(level < 10) {	stage_next(obj_zako02);
	} else if(level < 15) {	stage_next(obj_zako03);
	} else if(level < 20) {	stage_next(obj_zako01);
				stage_next(obj_zako02);
	} else if(level < 30) {	stage_next(obj_zako01);
				stage_next(obj_zako03);
	} else if(level < 40) {	stage_next(obj_zako02);
				stage_next(obj_zako03);
	} else if(level < 50) {	stage_next(obj_zako01);
				stage_next(obj_zako01);
				stage_next(obj_zako02);
				stage_next(obj_zako03);
	} else if(level < 60) {	stage_next(obj_zako01);
				stage_next(obj_zako02);
				stage_next(obj_zako02);
				stage_next(obj_zako03);
	} else if(level < 70) {	stage_next(obj_zako01);
				stage_next(obj_zako02);
				stage_next(obj_zako03);
				stage_next(obj_zako03);
	} else if(level < 80) {	stage_next(obj_zako01);
				stage_next(obj_zako01);
				stage_next(obj_zako02);
				stage_next(obj_zako02);
				stage_next(obj_zako03);
	} else if(level < 90) {	stage_next(obj_zako01);
				stage_next(obj_zako02);
				stage_next(obj_zako02);
				stage_next(obj_zako03);
				stage_next(obj_zako03);
	} else                {	stage_next(obj_zako01);
				stage_next(obj_zako01);
				stage_next(obj_zako02);
				stage_next(obj_zako02);
				stage_next(obj_zako03);
				stage_next(obj_zako03);
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

void
stage_next(void (*func)(struct _OBJECT*))
{
	int n;

	OBJ_execDirect(OBJ_set(GROUP_ENEMY, func));
	     if(level <  15) n =  3;
	else if(level <  30) n =  5;
	else if(level <  50) n = 10;
	else if(level <  70) n = 15;
	else if(level <  90) n = 20;
	else if(level < 110) n = 30;
	else if(level < 130) n = 40;
	else                 n = 50;
	if(!(++enemy_cnt % n)) stage_levelup();
}

/****************************************************************************
 *	
 ****************************************************************************/

void
obj_disp(OBJECT* p, int sprno)
{
	int x = ((int)p->pos[X] - 128) * DISP_X / (PRIMARY_W - 256);
	int y = ((int)p->pos[Y]      ) * DISP_Y / (PRIMARY_H      );
	sprite_draw(x, y, sprno, DRW_NOMAL);
}

/****************************************************************************
 *	
 ****************************************************************************/

void obj_anmset(OBJECT* p, const short* pat, int wait)
{
	if(p->anm_wait) {
		p->anm_wait--;
		if(!p->anm_wait) {
			p->anm_pat++;
			if(pat[p->anm_pat] < 0) {
				p->anm_pat = 0;
			}
		}
	} else {
		p->anm_wait = wait;
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

OBJECT*
get_near_enemy(OBJECT* p)
{
	LIST_ENTRY* const list_head = &object[GROUP_ENEMY];
	LIST_ENTRY* list_entry = list_head->Flink;
	OBJECT* rp = NULL;
	float x = p->pos[X];
	float y = p->pos[Y];
	float len = FLT_MAX;

	while(list_entry != list_head) {
		OBJECT* ep = CONTAINING_RECORD(list_entry, OBJECT, list_entry);
		list_entry = list_entry->Flink;
		if((ep->id & ID_COLL) && !(ep->id & ID_LOCKED)) {
			float lx = ep->pos[X] - x;
			float ly = ep->pos[Y] - y;
			float tmp = lx * lx + ly * ly;
			if(tmp < len) {
				len = tmp;
				rp = ep;
			}
		}
	}
	if(rp) {
		rp->id |= ID_LOCKED;
	}
	return rp;
}

/****************************************************************************
 *	
 ****************************************************************************/

void
add_score(int add)
{
	if(gctrl_mode) return;
	score += add;
}

