/*
 *	app.c
 *
 *	パチスロP/ECE ART
 *
 *	* Fri Jul 04 00:33:00 JST 2014 Naoyuki Sawa
 *	- 1st リリース。
 */
#include "app.h"

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

#ifdef USE_MUSIC
#include "instdef2.c"
#endif /*USE_MUSIC*/

#ifdef USE_SOUND
const void* sound_table[SOUND_COUNT];
#endif /*USE_SOUND*/

#ifdef USE_TEXTURE
TEXTURE texture_table[TEXTURE_COUNT];
#endif /*USE_TEXTURE*/

#ifdef USE_SPRITE
#define SPRITE_ SPRITE_INIT2
const SPRITE sprite_table[SPRITE_COUNT] = {
#include "sprite.h"
};
#undef SPRITE_
#endif /*USE_SPRITE*/

#define extern
#include "work.h"
#undef extern

/****************************************************************************
 *	main
 ****************************************************************************/

int
main()
{
	/* 初期化。 */
	app_init();

	/* メインループ。 */
	app_main();

	return 0;
}

/****************************************************************************
 *	app_init
 ****************************************************************************/

#ifdef  USE_SOUND_MANAGER
static void sound_play_stub(int iCh, int iData, int iLoop) {
	if((unsigned)iData >= SOUND_COUNT) { DIE(); }
	sound_play(iCh, sound_table[iData], iLoop);
}
#endif/*USE_SOUND_MANAGER*/

#ifdef  USE_SOUND_MIXER
static void SndMxr_Update() {
	int i;
	unsigned char AttSum[32];
	SndMxr_GetSndChAttAll(pSndMxr,-1,AttSum); //全ｻｳﾝﾄﾞﾁｬﾈﾙの減衰量を取得する｡
	for(i=0;i<SOUND_MAXCH;i++) {sound_att(i,AttSum[i]);} //全ｻｳﾝﾄﾞﾁｬﾈﾙの減衰量を設定する｡
}
static void SndMxr_TimerProc(void* context) { //Sound Mixer用ﾀｲﾏ割込処理
	SndMxr_Exec(pSndMxr); //Sound Mixerを実行する｡
	SndMxr_Update(); //全ｻｳﾝﾄﾞﾁｬﾈﾙの減衰量を設定する｡
}
#endif/*USE_SOUND_MIXER*/

#ifdef  USE_SOUND_SEQUENCER
static int SndSeq_fnLen(int iPhr) {
	return ppd_playback_time(sound_table[iPhr]);
}
static int SndSeq_fnNext(int iPhr) {
	switch(iPhr) {
	case SND_BGM_INTR: return SND_BGM_LOOP;
	case SND_BGM_LOOP: return SND_BGM_LOOP;
	}
	return -1;
}
static int SndSeq_fnPrev(int iPhr) {
	switch(iPhr) {
	case SND_BGM_INTR: return SND_BGM_INTR;
	case SND_BGM_LOOP: return SND_BGM_INTR;
	}
	return -1;
}
static void SndSeq_fnPan(int iTrk, int iPan) {
	//P/ECEにはステレオ再生は無いので、本関数は使用しない。
}
static void SndSeq_fnVol(int iTrk, int iVol) {
	SndMxr_SetMxrChAtt(pSndMxr, iTrk, 64-(iVol*64/SndVol_Max), 0, 0);
	SndMxr_Update();
}
static void SndSeq_fnStop(int iTrk) {
	sound_stop(iTrk);
}
static void SndSeq_fnPlay(int iTrk, int iPhr, int iVol, int iPan) {
	sound_play(iTrk, sound_table[iPhr], -1);
	SndMxr_SetMxrChAtt(pSndMxr, iTrk, 64-(iVol*64/SndVol_Max), 0, 0);
	SndMxr_Update();
}
static int SndSeq_fnTime() {
	return pceTimerGetCount(); //現在時刻を[ms]単位で返せ。
}
#endif/*USE_SOUND_SEQUENCER*/

#ifdef  USE_EVENT_TIMER
static void EvtTmr_onEvent(int iEvtTmrCh, int iEvent) { //ｲﾍﾞﾝﾄﾀｲﾏ用ｲﾍﾞﾝﾄ処理関数
	switch(iEvtTmrCh) {
	case EvtTmrCh_Mute:	/* 無操作減音 */
		SndMxr_SetMxrChAtt(pSndMxr, SOUND_MAXCH, iEvent/*減衰量*/, 64, 30);
		break;
	}
}
#endif/*USE_EVENT_TIMER*/

void
app_init()
{
	/* 一般的な初期化。 */
	pceAppSetProcPeriod(1000 / FPS);
#ifdef V_SCREEN
	pceLCDSetBuffer(_def_vbuff);
#else /*V_SCREEN*/
	pceLCDSetBuffer(vbuff);
#endif /*V_SCREEN*/
	pceLCDDispStart();
#ifdef USE_REPEAT
	pad_set_repeat(REPEAT_PAD, REPEAT_DELAY, REPEAT_INTERVAL);
#endif /*USE_REPEAT*/

	/* 描画環境初期化。 */
	surface.w = DISP_X;
	surface.h = DISP_Y;
	surface.vbuff = vbuff;
#ifdef USE_3D
	surface.zbuff = zbuff;
	render_init_3d(&render, &surface, 0, 0, 0, 0, FRONT, BACK, FOVY);
#else /*USE_3D*/
	render_init_2d(&render, &surface, 0, 0, 0, 0);
#endif /*USE_3D*/
#ifdef USE_RENDF
	surface.zbuff = zbuff;
	rendf_init(&rendf, &surface, 0, 0, 0, 0, FRONT, BACK, FOVY);
#endif /*USE_RENDF*/

#if defined(TRACE_ON) && (TRACE_ON)
	/* 標準エラー出力を開きます。 */
	stderr_open(9/*標準エラー出力モニタ接続待ち時間(調整可)*/);
#endif /*TRACE_ON*/

#ifdef USE_UFE
	/* USBファイルシステムエミュレーションを開始します。 */
	ufe_setup(9/*PC側ファイルサーバ接続待ち時間(調整可)*/);
#endif /*USE_UFE*/

#ifdef USE_RESOURCE
	/* pexファイル添付リソースを取得します。 */
	resource = resource_address(PRESCAN_STRINGIFY(PROJECT) ".pex");
#endif /*USE_RESOURCE*/

#ifdef TURBO
	/* クロック・メモリウェイトを切替えます。 */
	turbo(TURBO);
#endif /*TURBO*/

#ifdef USE_DBG
	/* DBGユニットを設定します。 */
	dbg_init();
#endif /*USE_DBG*/

#ifdef GC_LEVEL
	/* ガーベージコレクターを初期化します。 */
	gc_init(GC_LEVEL);
#endif /*GC_LEVEL*/

#ifdef LDIRECT_INTERVAL
	/* 高速切替・16階調表示を初期化します。 */
	ldirect_init(LDIRECT_INTERVAL);
#endif /*LDIRECT_INTERVAL*/

#ifdef USE_MUSIC
	/* 音楽ライブラリを初期化します。 */
	music_loudness(1);
	loadInst(); /* 「Randomism Zone(http://www8.plala.or.jp/randomism/)」さんの『ドラム音色分離キットver.2.10』を使用 */
	InitMusic();
#ifdef USE_2BIT_ADPCM
	/* - 2-bit ADPCM Decoderと、音楽ライブラリを併用する場合は、音楽ライブラリ側の使用チャネルをずらすことにしました。
	 *   この対策を行わないと、両方がチャネル0を使用しようとして、ハングアップしてしまいます。
	 * - 2-bit ADPCM Decoderに限らず、stream_play()を利用する機能と、音楽ライブラリと併用する場合は、同様の対策が必要です。
	 * - music_wchを変更する処理は、InitMusic()よりも後で行う必要があります。
	 *   InitMusic()が、「music_wch = 0;」を行っているからです。InitMusic()よりも先に変更すると、0に戻ってしまいます。
	 * - 尚、music_loudness()の機能もmusic_wchを参照していますが、
	 *   随時、値を取得しているので、music_loudness()を呼び出した後にmusic_wchを変更しても、問題はありません。
	 */
	music_wch = 3; /* チャネル0=2-bit ADPCM Decoder、チャネル1-2=アプリケーション使用、チャネル3=音楽ライブラリとする */
#endif /*USE_2BIT_ADPCM*/
#endif /*USE_MUSIC*/

	/* クリーンアップ関数を登録。 */
	atexit(app_exit);

#ifdef USE_2BIT_ADPCM
	/* 2-bit ADPCM Decoderを起動します。 */
	adpcm_init(SOUND_MAXCH);
#endif /*USE_2BIT_ADPCM*/

	/* サウンドを初期化します。 */
#ifdef USE_SOUND
	{
#define SOUND_(name) { extern const unsigned char name[]; sound_table[SND_##name] = name; }
#define SOUND_RESOURCE_(name,fname) { sound_table[SND_##name] = fpk_address(resource, fname, NULL); }
#include "sound.h"
#undef SOUND_
#undef SOUND_RESOURCE_
	}
#endif /*USE_SOUND*/

	/* Sound Managerを初期化します。 */
#ifdef  USE_SOUND_MANAGER
	{
		static const ST_SndMgrInfo stSndMgrInfo = { sound_play_stub, sound_stop, sound_stat, sound_att };
		pSndMgrBgm = SndMgr_Init(&stSndMgrInfo, SOUND_MANAGER_BGM_MINCH, SOUND_MANAGER_BGM_MAXCH); /* BGM Sound Manager */
		pSndMgrSe1 = SndMgr_Init(&stSndMgrInfo, SOUND_MANAGER_SE1_MINCH, SOUND_MANAGER_SE1_MAXCH); /* SE1 Sound Manager */
		pSndMgrSe2 = SndMgr_Init(&stSndMgrInfo, SOUND_MANAGER_SE2_MINCH, SOUND_MANAGER_SE2_MAXCH); /* SE2 Sound Manager */
	}
#endif/*USE_SOUND_MANAGER*/

	/* Sound Mixerを初期化します。 */
#ifdef  USE_SOUND_MIXER
	{
		static const int TBL_SndMxr_SndChMask[SOUND_MAXCH+1] = { //ｻｳﾝﾄﾞﾁｬﾈﾙﾏｽｸ配列
			(1<<0),	//ﾄﾗｯｸ0
			(1<<1),	//ﾄﾗｯｸ1
			(1<<2),	//ﾄﾗｯｸ2
			(1<<3),	//ﾄﾗｯｸ3
			-1};	//全ﾄﾗｯｸ
		pSndMxr = SndMxr_Init(ARRAY_SIZE(TBL_SndMxr_SndChMask), TBL_SndMxr_SndChMask); //Sound Mixerを作成する｡
		timer_start(100, SndMxr_TimerProc, NULL); //Sound Mixer用ﾀｲﾏ割込処理を開始する｡
	}
#endif/*USE_SOUND_MIXER*/

	/* サウンドシーケンサを初期化します。 */
#ifdef  USE_SOUND_SEQUENCER
	{
		static const ST_SndSeqInfo stSndSeqInfo = { &SndSeq_fnTime, &SndSeq_fnPlay, &SndSeq_fnStop, &SndSeq_fnVol, &SndSeq_fnPan, &SndSeq_fnLen, &SndSeq_fnNext, &SndSeq_fnPrev };
		SndSeq_Init(&stSndSeqInfo);
		timer_start(10, SndSeq_Exec, NULL); //ｻｳﾝﾄﾞｼｰｹﾝｻ用ﾀｲﾏ割込処理を開始する｡
	}
#endif/*USE_SOUND_SEQUENCER*/

	/* イベントタイマを初期化します。 */
#ifdef  USE_EVENT_TIMER
	{
		static const uint16_t TBL_EvtTmr_Mute[]={//無操作減音タイマ
			SEC(30),16,//30秒待って、少し減音する。
			SEC(30),64,//30秒待って、完全消音する。
			0,0};//終端
		static const uint16_t* const TBL_EvtTmr_Data[]={//各ｲﾍﾞﾝﾄﾀｲﾏﾁｬﾈﾙのﾃﾞｰﾀ
			TBL_EvtTmr_Mute};//ｲﾍﾞﾝﾄﾀｲﾏﾁｬﾈﾙ#0のﾃﾞｰﾀ
		pEvtTmr=EvtTmr_Init(ARRAY_SIZE(TBL_EvtTmr_Data),TBL_EvtTmr_Data,EvtTmr_onEvent);//ｲﾍﾞﾝﾄﾀｲﾏを初期化する｡
	}
#endif/*USE_EVENT_TIMER*/

	/* テクスチャを初期化します。 */
#ifdef USE_TEXTURE
	{
#define TEXTURE_(name) extern const unsigned char name[];
#define TEXTURE_RESOURCE_(name,fname) const void* const name = fpk_address(resource, fname, NULL);
#include "texture.h"
#undef TEXTURE_
#undef TEXTURE_RESOURCE_
		texture_table_init(texture_table, TEXTURE_COUNT,
#define TEXTURE_(name) name,
#define TEXTURE_RESOURCE_(name,fname) name,
#include "texture.h"
#undef TEXTURE_
#undef TEXTURE_RESOURCE_
		NULL/*最後の','を吸収するためのダミー。texture_table_init()に対しては無意味です。*/);
	}
#endif /*USE_TEXTURE*/

#ifdef USE_SPRITE
	/* 簡易スプライトドライバを初期化します。 */
	sprite_init(&render, sprite_table, SPRITE_COUNT, texture_table, TEXTURE_COUNT);
#endif /*USE_SPRITE*/

#ifdef USE_TCPIP
	/* TCP/IPプロトコルスイートを開始します。 */
	tcpip_start(0/*Ethernetドライバ送信バッファサイズ(調整可)*/);
#endif /*USE_TCPIP*/

#ifdef USE_VIDREC
	/* 画面出力を初期化します。 */
	yield(); /* runで実行した場合の実行確認に応答するため、USB機能の切り替えを僅かに遅らせる */
#ifdef LDIRECT_INTERVAL
	cdcacm_start(0, sizeof vbuff / 2, NULL, NULL);
#else /*LDIRECT_INTERVAL*/
	cdcacm_start(0, sizeof vbuff / 4, NULL, NULL);
#endif /*LDIRECT_INTERVAL*/
#endif /*USE_VIDREC*/

/*{{アプリケーション特有の初期化*/
	/* 乱数生成器を初期化する。 */
	WELL_init(&stApp.Rng, pceTimerGetCount());
	/* 設定を初期化する。 */
	stApp.Rank = WELL_next(&stApp.Rng) % 6;
	/* タスクスケジューラを初期化する。 */
	task_init();
	/* フレームイベントを作成する。 */
	stApp.FrmEvt = event_create(1);		//手動リセットイベント
	/* メインタスクを初期化する。 */
	Main_Init();
	/* サブタスクを初期化する。 */
	Sub_Init();
/*}}アプリケーション特有の初期化*/
}

/****************************************************************************
 *	app_exit
 ****************************************************************************/

void
app_exit()
{
/*{{アプリケーション特有のクリーンアップ*/
	/** no job **/
/*}}アプリケーション特有のクリーンアップ*/

#ifdef USE_VIDREC
	/* 画面出力を終了します。 */
	cdcacm_stop();
#endif /*USE_VIDREC*/

#ifdef USE_TCPIP
	/* TCP/IPプロトコルスイートを終了します。 */
	tcpip_stop();
#endif /*USE_TCPIP*/

#ifdef USE_MUSIC
	/* 確実に音楽演奏を停止します。
	 * サウンドの停止はライブラリ内のpceAppExit()で行っているので、
	 * ここでは必要ありません。
	 */
	StopMusic();
	music_loudness(0);
#endif /*USE_MUSIC*/

#ifdef LDIRECT_INTERVAL
	/* 高速切替・16階調表示を解除します。 */
	ldirect_free();
#endif /*LDIRECT_INTERVAL*/

#ifdef GC_LEVEL
	/* ガーベージコレクターを解除します。 */
	gc_free();
#endif /*GC_LEVEL*/

#ifdef USE_DBG
	/* DBGユニットの設定を解除します。 */
	dbg_free();
#endif /*USE_DBG*/

#ifdef USE_UFE
	/* USBファイルシステムエミュレーションを終了します。 */
	ufe_stop();
#endif /*USE_UFE*/
}

/****************************************************************************
 *	update
 ****************************************************************************/

/* * Mon Mar 10 13:29:56 JST 2008 Naoyuki Sawa
 * - update()から、画面更新処理を分離し、高速化しました。
 */
#ifdef V_SCREEN
/* vbuff(縦画面)→_def_vbuff(横画面)変換 */
//static void rotate_screen() {
//	unsigned char *src = &vbuff[88 - 1];	/* 転送先の横画面は左上から走査開始します */
//	unsigned char *dst = &_def_vbuff[0];	/* 転送元の縦画面は右上から走査開始します */
//	int x = 88;
//	do {
//		int y = 128;
//		do {
//			*dst++ = *src;		/* 横画面を右方向に走査します */
//			src += 88;		/* 縦画面を下方向に走査します */
//		} while(--y);
//						/* 横画面は既に次の行へ移動しています */
//		src -= 88 * 128 + 1;		/* 縦画面を左の列の一番上へ移動します */
//	} while(--x);
//}
//↓高速化 (C版 = 9.2ms/1call → ASM版 = 3.2ms/1call)
/*static*/ void rotate_screen();
asm("
	.code
	.align	1
rotate_screen:
	xld.w	%r12, 0x0e00			; %r12 := FRAM4_START
	xld.w	%r13, rotate_screen_DOX		; %r13 := DOY
	xld.w	%r14, rotate_screen_END		; %r14 := END
	xcall.d	memcpy				; %r10 := FRAM4_START = memcpy(FRAM4_START, DOX, END - DOX)
	sub	%r14, %r13			; %r14 := END - DOY			*delay*
	xld.w	%r4, vbuff+87			; %r4  := src = &vbuff[88 - 1]
	xld.w	%r5, _def_vbuff			; %r5  := dst = &_def_vbuff[0]
	xld.w	%r6, 88				; %r6  := 88
	xld.w	%r7, 128			; %r7  := 128
	xld.w	%r11, 11265			; %r11 := 88 * 128 + 1
	ld.w	%r12, %r6			; %r12 := x = 88
	jp	%r10				; goto FRAM4_START			(jp.dは不可!!)
;//{{ここからFRAM4領域へ転送
rotate_screen_DOX:				; do {
	ld.w	%r13, %r7			;   %r13 := y = 128
rotate_screen_DOY:				;   do {
	ld.ub	%r9, [%r4]			;     %r9  := c = *src
	sub	%r13, 1				;     %r13 := --y			*anti-interlock*
	ld.b	[%r5]+, %r9			;     %r5  := *dst++ = c
	jrne.d	rotate_screen_DOY		;   } while(--y)
	add	%r4, %r6			;     %r4  := src += 88			*delay*
	sub	%r12, 1				;   %r12 := --x
	jrne.d	rotate_screen_DOX		; } while(--x)
	sub	%r4, %r11			;   %r4  := src -= 88 * 128 + 1		*delay*
	ret
rotate_screen_END:
;//}}ここまでFRAM4領域へ転送
");
#endif /*V_SCREEN*/

/* * Mon Nov 06 17:37:17 JST 2006 Naoyuki Sawa
 * - schedule()から、画面更新処理だけを分離しました。
 *   フレーム待ち無し画面更新だけを行う場合は、この関数を呼び出してください。
 */
void
update()
{
#ifdef V_SCREEN
	rotate_screen();
#endif /*V_SCREEN*/

	/* 画面転送 */
	pceLCDTrans();
}

/****************************************************************************
 *	vidrec
 ****************************************************************************/

#ifdef USE_VIDREC
static void
vidrec()
{
#ifdef LDIRECT_INTERVAL
	static const unsigned char hdr[4] = { DISP_X,DISP_Y,4/*[bpp]*/,FPS };
#else /*LDIRECT_INTERVAL*/
	static const unsigned char hdr[4] = { DISP_X,DISP_Y,2/*[bpp]*/,FPS };
#endif /*LDIRECT_INTERVAL*/
	static int active;
	//
	unsigned char buf[128/*調整可*/]; /* (sizeof vbuff)/Nのサイズとすること */
	const unsigned char* src;
	unsigned char* dst;

	if(cdcacm_recv(buf, 1) > 0) {
		switch(buf[0]) {
		case 0:
			active = 0; /* 画面出力停止 */
			break;
		case 1:
			active = 1; /* 画面出力開始 */
			src = hdr;
			do {
				if(pcePadGetDirect() & PAD_SELECT) {
					exit(0); /* 送信待ちに割り込んで強制終了 */
				}
				src += cdcacm_send(src, &hdr[sizeof hdr] - src);
			} while(src < &hdr[sizeof hdr]);
			break;
		}
	}
	if(active) {
		src = vbuff;
		do {
			dst = buf;
			do {
#ifdef LDIRECT_INTERVAL
				*dst++ = src[0]|(src[1]<<4); 
				src += 2;
#else /*LDIRECT_INTERVAL*/
				*dst++ = src[0]|(src[1]<<2)|(src[2]<<4)|(src[3]<<6);
				src += 4;
#endif /*LDIRECT_INTERVAL*/
			} while(dst < &buf[sizeof buf]);
			dst = buf;
			do {
				if(pcePadGetDirect() & PAD_SELECT) {
					exit(0); /* 送信待ちに割り込んで強制終了 */
				}
				dst += cdcacm_send(dst, &buf[sizeof buf] - dst);
			} while(dst < &buf[sizeof buf]);
		} while(src < &vbuff[sizeof vbuff]);
	}
}
#endif /*USE_VIDREC*/

/****************************************************************************
 *	schedule
 ****************************************************************************/

/* * Fri Dec 15 00:49:15 JST 2006 Naoyuki Sawa
 * - schedule()関数に、サウンドポーズ対応コードを追加しました。
 *   少し試した限りでは上手く動作しているようですが、スリープからの復帰時に問題が起きないか未確認です。
 * - もし、ポーズ状態のまま、すなわち、サウンドポーズ状態のままでアプリケーションを終了してしまった場合にも、
 *   CLiPライブラリ内のpceAppExit()でpceWaveStop(1)を呼び出しており、サウンド状態がリセットされるので、大丈夫です。
 */

void
schedule()
{
#ifdef USE_PAUSE
	{
		/* PAUSEメッセージ領域のイメージ退避用バッファは、324バイト必要です。
		 * スタック上に取るのは危険ですし、staticに取るのもメモリの無駄です。
		 * そこで、PAUSE時にのみヒープから確保することにします。
		 */
		//unsigned char save[PAUSE_H][PAUSE_W];
		unsigned char (*save)[PAUSE_W] = NULL/*警告抑制*/;
		int pause = 0;
		/*{{2006/12/15:サウンドポーズ対応*/
		int sound_pause = 0/*警告抑制*/;
		/*}}2006/12/15:サウンドポーズ対応*/
		int x, y;
		for(;;) {
#endif /*USE_PAUSE*/

			/* 画面更新。 */
			update();

#ifdef USE_VIDREC
			/* 画面出力。 */
			vidrec();
#endif /*USE_VIDREC*/

#ifdef  USE_SOUND_MANAGER
			/* Sound Manager処理。 */
			SndMgr_Exec(pSndMgrBgm); /* BGM Sound Manager */
			SndMgr_Exec(pSndMgrSe1); /* SE1 Sound Manager */
			SndMgr_Exec(pSndMgrSe2); /* SE2 Sound Manager */
#endif/*USE_SOUND_MANAGER*/

#ifdef  USE_EVENT_TIMER
			/* イベントタイマ処理。 */
			EvtTmr_Exec(pEvtTmr);
#endif/*USE_EVENT_TIMER*/

#ifdef USE_IDLE
			/* アイドル処理+システム処理。 */
			idle_yield();
#else /*USE_IDLE*/
			/* システム処理。 */
			yield();
#endif /*USE_IDLE*/

			/* パッド状態更新。 */
			joy = pad_get();

#ifdef V_SCREEN
			joy = (joy & ~(PAD_LF | PAD_RI | PAD_UP | PAD_DN | /* 縦画面用入力変換 */
				       TRG_LF | TRG_RI | TRG_UP | TRG_DN))
				| (joy & PAD_LF ? PAD_UP : 0)
				| (joy & PAD_RI ? PAD_DN : 0)
				| (joy & PAD_UP ? PAD_RI : 0)
				| (joy & PAD_DN ? PAD_LF : 0)
				| (joy & TRG_LF ? TRG_UP : 0)
				| (joy & TRG_RI ? TRG_DN : 0)
				| (joy & TRG_UP ? TRG_RI : 0)
				| (joy & TRG_DN ? TRG_LF : 0);
#endif /*V_SCREEN*/

//			/* SELECTボタンが押されたら終了する。 */ /* アプリケーションによって不要ならば、この2行を削除してください。 */
//			if(joy & TRG_SELECT) exit(EXIT_SUCCESS); /* アプリケーションによって不要ならば、この2行を削除してください。 */

#ifdef USE_PAUSE
			/* PAUSE処理。 */
			if(joy & TRG_START) {
				pause = !pause;	/* PAUSE状態切り替え */
				if(pause) {	/* PAUSE IN */
					/*{{2006/12/15:サウンドポーズ対応*/
					ENTER_CS;
					sound_pause = fINT_EDMA.EHDM1;
					if(sound_pause) {
						fINT_EDMA.EHDM1 = 0;
					}
					LEAVE_CS;
					/*}}2006/12/15:サウンドポーズ対応*/
					/* PAUSEメッセージ表示部分のイメージを退避。 */
					save = (unsigned char (*)[PAUSE_W])malloc(PAUSE_W * PAUSE_H); /* イメージ退避用バッファ確保 */
					if(save == NULL) DIE();
					for(y = 0; y < PAUSE_H; y++) {
						for(x = 0; x < PAUSE_W; x++) {
							save[y][x] = vbuff[DISP_X * (PAUSE_Y + y) + (PAUSE_X + x)];
						}
					}
				} else {	/* PAUSE OUT */
					/*{{2006/12/15:サウンドポーズ対応*/
					ENTER_CS;
					if(sound_pause) {
						fINT_EDMA.EHDM1 = 1;
					}
					LEAVE_CS;
					/*}}2006/12/15:サウンドポーズ対応*/
					/* PAUSEメッセージ表示部分のイメージを復帰。 */
					for(y = 0; y < PAUSE_H; y++) {
						for(x = 0; x < PAUSE_W; x++) {
							vbuff[DISP_X * (PAUSE_Y + y) + (PAUSE_X + x)] = save[y][x];
						}
					}
					free(save); /* イメージ退避用バッファ開放 */
				}
			}
			if(!pause) break;
			/* PAUSEメッセージ点滅表示。 */
			if(pceTimerGetCount() % 1000 < 750) {
				render_string_framed(&render, PAUSE_X, PAUSE_Y, PAUSE_STR, PAUSE_FONT, PAUSE_COLOR);
			} else {
				for(y = 0; y < PAUSE_H; y++) {
					for(x = 0; x < PAUSE_W; x++) {
						vbuff[DISP_X * (PAUSE_Y + y) + (PAUSE_X + x)] = save[y][x];
					}
				}
			}
		}
	}
#endif /*USE_PAUSE*/

	now++;	/* グローバルタイマ更新 */

/*{{アプリケーション特有の周期処理*/
	/* 乱数を空取得する。 */
	WELL_next(&stApp.Rng);
/*}}アプリケーション特有の周期処理*/
}

/****************************************************************************
 *	app_main
 ****************************************************************************/

static void DoMenu();
static void DoInput();
static void DrawCab();

void app_main() {
	int FastSkip = 0;
//{{デバッグ処理
#if defined(TRACE_ON) && (TRACE_ON)
	pcePowerSetReport(PWR_RPTOFF);
	pceWaveSetMasterAtt(0);
#endif
//}}デバッグ処理
	for(;;) {
		/* データ表示 */
		DoMenu();
		/* 入力更新 */
		DoInput();
		/* 背景を描く。 */
		sprite_draw(0, 0, SPR_CAB, 0);
		/* 受付可能なワンキー操作をクリアする。 */
		stApp.OkReady = 0;	//必要!!メインタスクはセットするだけで、クリアしていないから。
		/* メインタスクとサブタスクを起床する。 */
		event_pulse(stApp.FrmEvt);	//手動リセットイベントへのパルスであるから、イベントを待っている全てのタスクを起床した後、イベントをリセットする動作となる。
		/* 高速オートプレイならば、アプリケーションタスクの描画と周期処理を間引く。 */
		if((stApp.ApExec == Ap_Fast) && (FastSkip++ & 7/*調整可*/)) { continue; }
		/* 筐体を描く。 */
		DrawCab();
		/* 周期処理 */
		schedule();
	}
}

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

/* 条件装置を抽選する。
 * [in]
 *	iRT		RT状態		0〜2
 * [out]
 *	戻り値		条件装置番号	0〜(HitCd_Zk-1)
 */
int LotHitCd(int iRT) {
	int iRndVal = (uint16_t)WELL_next(&stApp.Rng);
	int iHitCd, iWeight;
	for(iHitCd = 0; iHitCd < HitCd_Zk; iHitCd++) {
		iWeight = TBL_HitCd[iHitCd][iRT][stApp.Rank];
		iWeight = (unsigned short)(iWeight + 1);
		iRndVal -= iWeight;
		if(iRndVal < 0) { return iHitCd; }
	}
	DIE();
}

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

/* 条件装置iHitCdが、絵柄組合せiHitFlgを含むか調べる。
 * [in]
 *	iHitCd		条件装置番号	0〜(HitCd_Size-1)
 *	iHitFlg		絵柄組合せ番号	0〜(HitFlg_Size-1)
 * [out]
 *	戻り値		含むならば、0以外の値を返す。
 */
int GetHitCd_HitFlg(int iHitCd, int iHitFlg) {
	if((unsigned)iHitCd >= HitCd_Size) { DIE(); }
	if((unsigned)iHitFlg >= HitFlg_Size) { DIE(); }
	return (TBL_HitCd_HitFlg[iHitCd][iHitFlg/8] >> (iHitFlg%8)) & 1;
}

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

/* 絵柄組合せiHitFlgの、払出枚数を取得する。
 * [in]
 *	iHitFlg		絵柄組合せ番号	0〜(HitFlg_Size-1)
 *[out]
 * 	戻り値		払出枚数	0〜15
 */
int GetHitFlg_Pay(int iHitFlg) {
	if((unsigned)iHitFlg >= HitFlg_Size) { DIE(); }
	return (TBL_HitFlg[iHitFlg] >> 12) & 15;
}

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

void SendCmd(int iCmd) {
	int iPos;
	/* バッファが一杯でないことを確認する。 */
	if(stApp.CmdCnt >= ARRAY_SIZE(stApp.CmdBuf)) { DIE(); }
	/* 格納位置を求める。 */
	iPos  = stApp.CmdPos;
	iPos += stApp.CmdCnt;
	if(iPos >= ARRAY_SIZE(stApp.CmdBuf)) { iPos -= ARRAY_SIZE(stApp.CmdBuf); }
	/* コマンドを格納する。 */
	stApp.CmdBuf[iPos] = iCmd;
	/* 格納数を増やす。 */
	stApp.CmdCnt++;
}

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

int RecvCmd() {
	int iCmd;
	/* バッファが空ならば、(-1)を返す。 */
	if(!stApp.CmdCnt) { return -1; }
	/* 格納数を減らす。 */
	stApp.CmdCnt--;
	/* コマンドを読み出す。 */
	iCmd = stApp.CmdBuf[stApp.CmdPos];
	/* 読み出し位置を進める。 */
	stApp.CmdPos++;
	if(stApp.CmdPos >= ARRAY_SIZE(stApp.CmdBuf)) { stApp.CmdPos = 0; }
	/* コマンドを返す。 */
	return iCmd;
}

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

static void DoMenu() {
	int x, y, w, h, i, j, n, s, t, c, x1, y1, x2, y2;
	if(!(joy & PAD_SELECT)) { return; }
	for(;;) {
		schedule();
		surface_clear(&surface, 3);
		/* オートプレイを選択する。 */
		if(joy & TRG_UP) { if(stApp.ApExec > 0      ) { stApp.ApExec--; stApp.ApWait = 0; } }
		if(joy & TRG_DN) { if(stApp.ApExec < Ap_Fast) { stApp.ApExec++; stApp.ApWait = 0; } }
		if(joy & TRG_A ) { while(joy) { schedule(); } return; }
		if(joy & TRG_B ) { break; }
		/*--- 描画 ---*/
		/* タイトル */
		y = 1;
		x = 1 + (DISP_X - 4 * 4) / 2;
		render_rectangle_fill(&render, 0, 0, DISP_X, 7, 2);
		render_printf(&render, x, y, 2, 0, "ﾃﾞｰﾀ");
		/* 集計 */
		x = 1;
		y = 1 + 8;
		n = 0; for(i = HitCd_Be2L_1; i <= HitCd_Be2R_9; i++) { n += stApp.HitCnt[i]; }	/* 押順ベル合算出現回数 */
		render_printf(&render, x, y, 2, 0, "ﾄｳｼｷﾝｶﾞｸ  %4d[k]", stApp.Invest / 1000); y += 6;
		render_printf(&render, x, y, 2, 0, "ｿｳｶｲﾃﾝｽｳ  %4d", stApp.TotalG); y += 6;
		render_printf(&render, x, y, 2, 0, "ﾎﾞｰﾅｽ     %4d (1/%.2f)", stApp.HitCnt[HitCd_Bo ], stApp.HitCnt[HitCd_Bo ] ? ((double)stApp.TotalG / stApp.HitCnt[HitCd_Bo ]) : (double)0); y += 6;
		render_printf(&render, x, y, 2, 0, "ﾘﾌﾟﾚｲ     %4d (1/%.2f)", stApp.HitCnt[HitCd_Rp1], stApp.HitCnt[HitCd_Rp1] ? ((double)stApp.TotalG / stApp.HitCnt[HitCd_Rp1]) : (double)0); y += 6;
		render_printf(&render, x, y, 2, 0, "ﾄｸｼｭﾘﾌﾟﾚｲ %4d (1/%.2f)", stApp.HitCnt[HitCd_Rp2], stApp.HitCnt[HitCd_Rp2] ? ((double)stApp.TotalG / stApp.HitCnt[HitCd_Rp2]) : (double)0); y += 6;
		render_printf(&render, x, y, 2, 0, "ﾁｪﾘｰ      %4d (1/%.2f)", stApp.HitCnt[HitCd_Ch ], stApp.HitCnt[HitCd_Ch ] ? ((double)stApp.TotalG / stApp.HitCnt[HitCd_Ch ]) : (double)0); y += 6;
		render_printf(&render, x, y, 2, 0, "ｽｲｶ       %4d (1/%.2f)", stApp.HitCnt[HitCd_Sk ], stApp.HitCnt[HitCd_Sk ] ? ((double)stApp.TotalG / stApp.HitCnt[HitCd_Sk ]) : (double)0); y += 6;
		render_printf(&render, x, y, 2, 0, "ﾍﾞﾙ       %4d (1/%.2f)", stApp.HitCnt[HitCd_Be1], stApp.HitCnt[HitCd_Be1] ? ((double)stApp.TotalG / stApp.HitCnt[HitCd_Be1]) : (double)0); y += 6;
		render_printf(&render, x, y, 2, 0, "ｵｼｼﾞｭﾝﾍﾞﾙ %4d (1/%.2f)", n                      , n                       ? ((double)stApp.TotalG / n                      ) : (double)0); y += 7;
		render_line(&render, 1, y, DISP_X - 1, y, 2); y += 1;
		/* オートプレイ */
		x = DISP_X - 4 * 6;
		y = 1 + 8;
		render_printf(&render, x, y, 2, 0, "ｵｰﾄﾌﾚｲ"); render_point(&render, x + 4 * 3 + 2, y - 1, 0); y += 6;
		render_printf(&render, x, y, 2, 0, "%c ﾅｼ  ", (stApp.ApExec ==       0) ? '>' : ' '); y += 6;
		render_printf(&render, x, y, 2, 0, "%c ﾃｲｿｸ", (stApp.ApExec == Ap_Slow) ? '>' : ' '); y += 6;
		render_printf(&render, x, y, 2, 0, "%c ｺｳｿｸ", (stApp.ApExec == Ap_Fast) ? '>' : ' '); y += 6;
		/* プロンプト */
		x = DISP_X - 4 * 6;
		y = DISP_Y - 12;
		render_printf(&render, x, y, 2, 0, "[A]ﾓﾄﾙ");
		render_point(&render, x + 4 * 4 + 2, y - 1, 0);
		render_point(&render, x + 4 * 4 + 3, y - 1, 0); y += 6;
		render_printf(&render, x, y, 2, 0, "[B]ｵﾜﾙ");   y += 6;
		/* 当選履歴グラフ */
		x = 1;
		y = DISP_Y - 2;
		for(i = 0; i < ARRAY_SIZE(stApp.History); i++) {
			if(stApp.History[i].StartG) {
				if((n = (stApp.History[i].StartG + 99) / 100) > 10) { n = 10; }
			} else {
				n = 0;
			}
			for(j = 0; j <= 10; j++) {
				if(j == 0) {
					c = 1;			/* 土台の部分 */
				} else {
					c = (j <= n) ? 0 : 2;	/* ゲーム数の粒の部分 */
				}
				render_rectangle_fill(&render, x, y - j * 2, 3, 1, c);
			}
			x += 4;
		}
		/* スランプグラフ */
		x = 42;
		y = DISP_Y - 21;
		w = 61;
		h = 19;
		render_rectangle(&render, x-1, y-1, w+2, h+2, 2);
		render_line(&render, x, y+h/2, x+w, y+h/2, 1);
		s = 100;	//100枚単位の最大差枚数を探し、グラフの縦スケールとする。
		for(i = 0; i < stApp.SamHisG; i++) {
			if(abs(stApp.SamHis[i]) > s) { s = abs(stApp.SamHis[i]); }
		}
		s = (s + (100-1)) / 100 * 100;
		t = 100;	//差枚数履歴G数を100G単位に切上げ、グラフの横スケールとする。
		while(stApp.SamHisG > t) { t += 100; }
		render.top    = y;
		render.bottom = y + h;
		x1 = x;
		y1 = y + (h/2);
		for(i = 0; i < w; i++) {
			j = i * t / w;
			if(j >= stApp.SamHisG) { break; }	//※要検討:これは無駄処理か?
			n = (h/2) - (stApp.SamHis[j] * (h/2) / s);
			x2 = x + i;
			y2 = y + n;
			render_line(&render, x1, y1, x2, y2, 0);
			x1 = x2;
			y1 = y2;
		}
		render.top    = 0;
		render.bottom = DISP_Y;
	}
	/* 設定を表示して、終了する。 */
	SndMxr_SetMxrChAtt(pSndMxr, SOUND_MAXCH, 128, 128, 50);	/* 減音 */
	surface_clear(&surface, 3);
	x = (DISP_X - 10 * 3) / 2;
	y = 20;
	render_string(&render, x, y, "設定は、", 0, 0);
	x = (DISP_X - 10) / 2;
	y = (DISP_Y - 16) / 2 - 1;
	render_printf_framed(&render, x, y, 1, 0x220, "%d", stApp.Rank + 1);
	x = (DISP_X - 10 * 3) / 2;
	y = DISP_Y - 20 - 10;
	render_string(&render, x, y, "でした。", 0, 0);
	for(i = 0; i < SEC(5); i++) { schedule(); if(joy & TRG_MASK) { break; } }	/* 5秒経過するか,又は,何かボタンが押されたら終了する。 */
	exit(EXIT_SUCCESS);
}

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

/* ワンキー操作シーケンス */
static const char TBL_Ok[][8]={{0},
{Sw_Max,-1},				//Ok_Max
{Sw_Max,0,0,0,0,0,Sw_Med,-1},		//Ok_Max_Med
{Sw_Max,0,0,Sw_Med,0,0,Sw_Med,-1},	//Ok_Max_Med_Med
{Sw_Med,0,0,Sw_Med,0,0,Sw_Med,-1},	//Ok_Med_Med_Med
{Sw_Med,0,0,Sw_Med,-1},			//Ok_Med_Med
{Sw_Med,-1},	//Ok_Med
{Sw_Lev,-1},	//Ok_Lev
{Sw_1st,-1},	//Ok_1st
{Sw_2nd,-1},	//Ok_2nd
{Sw_3rd,-1}};	//Ok_3rd

static void DoInput() {
	/* オートプレイ実行中ならば… */
	if(stApp.ApExec) {
		stApp.Sw = 0;
		/* オートプレイウェイトが無ければ… */
		if(!stApp.ApWait) {
			/* 受付可能なワンキー操作が有れば… */
			switch(stApp.OkReady) {
			case Ok_Max:		/* MAXBETボタン押下 */
			case Ok_Max_Med:	/* MAXBETボタン押下⇒メダル1枚投入 */
			case Ok_Max_Med_Med:	/* MAXBETボタン押下⇒メダル2枚投入 */
				stApp.Sw = Sw_Max;
				stApp.ApWait = (stApp.ApExec == Ap_Fast) ? 0 : 2;
				break;
			case Ok_Med_Med_Med:	/* メダル3枚投入 */
			case Ok_Med_Med:	/* メダル2枚投入 */
			case Ok_Med:		/* メダル1枚投入 */
				stApp.Sw = Sw_Med;
				stApp.ApWait = (stApp.ApExec == Ap_Fast) ? 0 : 2;
				break;
			case Ok_Lev:		/* スタートレバー操作 */
				stApp.Sw = Sw_Lev;
				stApp.ApWait = (stApp.ApExec == Ap_Fast) ? 0 : SEC(0.5);
				break;
			case Ok_1st:		/* 左停止ボタン押下 */
				stApp.Sw = Sw_1st;
				stApp.ApWait = (stApp.ApExec == Ap_Fast) ? 0 : SEC(0.5);
				break;
			case Ok_2nd:		/* 中停止ボタン押下 */
				stApp.Sw = Sw_2nd;
				stApp.ApWait = (stApp.ApExec == Ap_Fast) ? 0 : SEC(0.5);
				break;
			case Ok_3rd:		/* 右停止ボタン押下 */
				stApp.Sw = Sw_3rd;
				stApp.ApWait = (stApp.ApExec == Ap_Fast) ? 0 : SEC(1.0);
				break;
			}
		}
		/* オートプレイウェイトが有れば、オートプレイウェイトを減らす。 */
		if(stApp.ApWait) { stApp.ApWait--; }
	/* オートプレイ実行中でなければ… */
	} else {
		/* 受付可能なワンキー操作が有れば… */
		if(stApp.OkReady) {
			/* 既にワンキー操作実行中でなく、Aボタンが押されたら、ワンキー操作の実行を開始する。 */
			if(!stApp.OkExec && (joy & TRG_A)) { stApp.OkExec = stApp.OkReady; }
		}
		/* ワンキー操作実行中ならば… */
		if(stApp.OkExec) {
			/* 押下キーを取得する。 */
			int c = TBL_Ok[stApp.OkExec][stApp.OkStep++];
			/* 前回の状態のうち、ワンキー操作シーケンスの最初のキーをクリアする。(メダルは常にクリア)
			 * Aボタンが押し続けられていなければ、ワンキー操作シーケンスの最初のキーもクリアする。 */
			stApp.Sw &= (joy & PAD_A) ? (TBL_Ok[stApp.OkExec][0] & ~Sw_Med) : 0;
			/* ワンキー操作シーケンス終端でなければ、今回のキーをセットし、処理を終了する。 */
			if(c != -1) { stApp.Sw |= c; return; }
			/* ワンキー操作シーケンス終端ならば、ワンキー操作シーケンスの最初のキーが離されるまで待つ。(メダルは除く)
			 * ワンキー操作でも、長押しでねじり操作をできるようにするための判断です。 */
			if(stApp.Sw & TBL_Ok[stApp.OkExec][0]) { stApp.OkStep--; return; }
			/* ワンキー操作を終了し、以下の処理を継続する。 */
			stApp.OkExec = stApp.OkStep = 0;
		}
		/* 前回のスイッチ状態のMAXBET以外をクリアする。前回MAXBET押下中ならば… */
		if(stApp.Sw &= Sw_Max) {
			/* 今回もMAXBET押下中ならば、そのまま。 */
			if(joy & PAD_B) {
				/** no job **/
			/* 今回MAXBETが離されたら、クリアする。 */
			} else {
				stApp.Sw = 0;
			}
		/* 前回MAXBET押下中でなければ… */
		} else {
			/* MAXBET押下可能でなく、メダル投入可能ならば… */
			if((stApp.Led & (Led_Ins|Led_Max)) == Led_Ins) {
				if(joy & TRG_B) { stApp.Sw |= Sw_Med; }	/* メダル1枚投入 */
			/* それ以外ならば…(MAXBET押下可能でないときも含む) */
			} else {
				if(joy & PAD_B) { stApp.Sw |= Sw_Max; }	/* MAXBETボタン */
			}
		}
		if(joy & PAD_LF) { stApp.Sw |= Sw_1st; }	/* 左停止ボタン */
		if(joy & PAD_DN) { stApp.Sw |= Sw_2nd; }	/* 中停止ボタン */
		if(joy & PAD_RI) { stApp.Sw |= Sw_3rd; }	/* 右停止ボタン */
		if(joy & PAD_UP) { stApp.Sw |= Sw_Lev; }	/* スタートレバー */
	}
}

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

static void DrawCab() {
	int iSpr;
	/* 左停止ボタン */
	iSpr = (stApp.Led & Led_1st) ? SPR_BTN_LI_UP : SPR_BTN_DK_UP;
	if(stApp.Sw & Sw_1st) { iSpr++; }
	sprite_draw(Cab_1st_X, Cab_1st_Y, iSpr, 0);
	/* 中停止ボタン */
	iSpr = (stApp.Led & Led_2nd) ? SPR_BTN_LI_UP : SPR_BTN_DK_UP;
	if(stApp.Sw & Sw_2nd) { iSpr++; }
	sprite_draw(Cab_2nd_X, Cab_2nd_Y, iSpr, 0);
	/* 右停止ボタン */
	iSpr = (stApp.Led & Led_3rd) ? SPR_BTN_LI_UP : SPR_BTN_DK_UP;
	if(stApp.Sw & Sw_3rd) { iSpr++; }
	sprite_draw(Cab_3rd_X, Cab_3rd_Y, iSpr, 0);
	/* MAXBETボタン */
	if(stApp.Sw & Sw_Max) {
		if(stApp.MaxPos < 2) { stApp.MaxPos++; }
	} else {
		if(stApp.MaxPos) { stApp.MaxPos--; }
	}
	iSpr = (stApp.Led & Led_Max) ? SPR_MAX_LI_UP : SPR_MAX_DK_UP;
	iSpr += stApp.MaxPos;
	sprite_draw(Cab_Max_X, Cab_Max_Y, iSpr, 0);
	/* スタートレバー */
	if(stApp.Sw & Sw_Lev) {
		if(stApp.LevPos < 2) { stApp.LevPos++; }
	} else {
		if(stApp.LevPos) { stApp.LevPos--; }
	}
	iSpr = SPR_LEV_UP;
	iSpr += stApp.LevPos;
	sprite_draw(Cab_Lev_X, Cab_Lev_Y, iSpr, 0);
	/* メダル */
	iSpr = SPR_MED;
	iSpr += stApp.MedPos;
	sprite_draw(Cab_Med_X, Cab_Med_Y, iSpr, 0);
	if(stApp.MedPos && (++stApp.MedPos > 7)) { stApp.MedPos = 0; }
	/* BONUS回数,ART回数 */
	if((stApp.DspSel % SEC(5)) < SEC(4.8)) { /* 表示切替が判り易いように */
		if((stApp.DspSel % SEC(10)) < SEC(5)) {
			render_printf(&render, Dtc_Bns_X, Dtc_Bns_Y, 2, 0, "B%4d", stApp.BnsNum);
		} else {
			render_printf(&render, Dtc_Bns_X, Dtc_Bns_Y, 2, 0, "A%4d", stApp.ArtNum);
		}
	}
	stApp.DspSel++;
	/* スタート回転数 */
	render_printf(&render, Dtc_StG_X, Dtc_StG_Y, 2, 0, "S%4d", stApp.StartG);
	/* 持ちメダル枚数 */
	render_printf(&render, Dtc_Med_X, Dtc_Med_Y, 2, 0, "M%4d", stApp.Medal);
}

