
/*---------------------------------------------------------------------*
        Copyright (C) 1998 Nintendo. (Originated by SGI)
        
        $RCSfile: tron.c,v $
        $Revision: 1.36 $
        $Date: 1999/04/16 09:55:13 $
 *---------------------------------------------------------------------*/
#include <ultra64.h>

/* #define	DEBUGGER */

/* Define this to get to an uncaught bowtie triangle after ~5 minutes,
 * if the program is run in demo mode (no controller plugged in).
 * Leave it undefined to get a better-quality application.
 *
 * There is nothing magical about this particular name.  It's just used
 * in this program to change some of the behavior so that it just happens
 * to hit a bad triangle after a while.
 */
/* #define GOTO_BOWTIE */

/*
 * Change the definition of BOWTIE_VAL from the default.  If it is left
 * at the default of zero, and GOTO_BOWTIE is defined above, then
 * this program will hang on an uncaught bowtie triangle after several
 * minutes.
 *
 * General note on BOWTIE_VAL:
 *   BOWTIE_VAL needs to make its way to the RSP in order to do any good.
 *   The method by which it is sent to the RSP is the gSPTexture command.
 *   Don't ask why -- just believe it.
 *   You must use a gSPTexture command (with enable = OFF if desired) in
 *   order for BOWTIE_VAL to take effect.  You must execute this command
 *   every frame before any triangles are rendered that might hang.
 *   Because gSPTexture always sends BOWTIE_VAL to the RSP, you need to
 *   make sure that BOWTIE_VAL is #define'd to the desired value in
 *   ALL of your source files which include a gSPTexture or gsSPTexture
 *   command!!!!
 *   Otherwise the gSPTexture command in another file will replace your
 *   desired value of BOWTIE_VAL with the default one defined in the
 *   Ultra 64 header files (gbi.h to be specific), and all of your efforts
 *   to avoid bowtie hangs will be in vain.
 *
 *   Also:  Valid values for BOWTIE_VAL are 0, 4, 8, 12.  The default value
 *          zero does the least damage to your rendered triangles, but
 *          provides no extra protection against bowtie hangs.  12 provides
 *          maximum hang protection, but results in the most visual damage.
 */
/* #undef BOWTIE_VAL
   #define BOWTIE_VAL 4
*/

/* local includes: */
#include "tron.h"

/*
 * Symbol genererated by "makerom" to indicate the end of the code segment
 * in virtual (and physical) memory
 */
extern char _codeSegmentEnd[];

/*
 * Symbols generated by "makerom" to tell us where the static segment is
 * in ROM.
 */
extern char _staticSegmentRomStart[], _staticSegmentRomEnd[];

/*
 * Stacks for the threads as well as message queues for synchronization
 */
u64	bootStack[STACKSIZE/8];

static void	idle(void *);
static void	mainproc(void *);

static OSThread	idleThread;
u64             idleThreadStack[STACKSIZE/8];

static OSThread	mainThread;
u64             mainThreadStack[STACKSIZE/8];

OSMesgQueue	dmaMessageQ, taskMessageQ, retraceMessageQ,
                controllerMessageQ, serialMessageQ;
OSIoMesg	dmaIoMessageBuf;
OSMesg		dmaMessageBuf, taskMessageBuf, retraceMessageBuf,
                controllerMsgBuf, serialMsgBuf;
OSMesg		dummyMessage;

#define	NUM_PI_MSGS	8
static OSMesg PiMessages[NUM_PI_MSGS];
static OSMesgQueue PiMessageQ;

/*
 * must be in BSS, not on the stack for this to work:
 */
OSTask    tlist[2];       /* globaltask lists */
Gfx	*glistp;	/* global for test case procs */

/*
 * Double-buffered dynamic segments (?)
 */
Dynamic	dynamicBuffer[2];

/*
 * Prototypes
 */

int initControllers(void);

/*
 * Constants
 */

#define RAND_MAX	32767

#define ZBUFFER		1
#define PI		3.1415926
#define	MAXPLAYERS	16
#define N		200

#define HEIGHT		4

#define CRASH(f) ((f) == F_WALL || ((f) >= 0 && p[f].height > HEIGHT/2.0))

#define	F_EMPTY		-2
#define F_WALL		-1

#define WEST		0
#define NORTH		1
#define EAST		2
#define SOUTH		3
#define NUMDIRS		4

#ifndef TRUE
#define FALSE		0
#define TRUE		1
#endif

/* Define number of frames per movement in grid */
/* Note that xzscale/SUBSTEP_CNT should be an integer,
 * or 1 to disable substepping */
#define SUBSTEP_CNT     4

#define V(p, x, y, z, f, s, t, r, g, b, a) {				\
	(p)->v.ob[0] = (x);						\
	(p)->v.ob[1] = (y);						\
	(p)->v.ob[2] = (z);						\
	(p)->v.flag  = (f);						\
	(p)->v.tc[0] = (s);						\
	(p)->v.tc[1] = (t);						\
	(p)->v.cn[0] = (r);						\
	(p)->v.cn[1] = (g);						\
	(p)->v.cn[2] = (b);						\
	(p)->v.cn[3] = (a);						\
}

static void tronInit (void);
static void tronUpdate (void);
void sceneDraw (Dynamic *dynamicp, unsigned short *cfbp, Vtx *v);

struct player {
	int	x, y, dir, numhist, dead;
	float	ang, tipang, wantang, height;
	int	hx[MAXHIST], hy[MAXHIST];
	int	red, green, blue;
};

struct player	p[MAXPLAYERS];
int	dx[4] = { -1, 0, 1, 0 };
int	dy[4] = {  0, 1, 0, -1 };
float	ang[4] = { 0, 90, 180, 270 };

float		time; /* Time */
int		yscale, xzscale;
int		NS, numplayers, numalive, demomode;
int		field[N][N];
int		frame_count=0;
int             output_frame = 149;
static int      cfb_size = G_IM_SIZ_16b;
static int      controller;         /* Lowest active controller # */
static u16      button;             /* Controller button read     */
static u16      lastbutton;         /* Previous controller button read */
static OSContStatus statusdata[MAXCONTROLLERS];
static OSContPad    controllerdata[MAXCONTROLLERS];       

int    substep;

OSPiHandle	*handler;

boot(void *arg)
{
	osInitialize();

	handler = osCartRomInit();

	osCreateThread(&idleThread, 1, idle, (void *)0,
		idleThreadStack+STACKSIZE/8, (OSPri)10);
	osStartThread(&idleThread);
}

static void
mainproc(void *arg)
{
	int		current = 0;
	OSMesg		dummyMesg;
	OSTask		*tlistp;
	Dynamic		*dynamicp;
    	char		*staticSegment;
	unsigned short 	*cfbp;

	yscale = 20;
#ifdef GOTO_BOWTIE
	xzscale = 5;
#else
	xzscale = 20;
#endif

	NS = N * yscale;

	/*
	 * Setup the message queues
	 */
	osCreateMesgQueue(&dmaMessageQ, &dmaMessageBuf, 1);

	osCreateMesgQueue(&taskMessageQ, &taskMessageBuf, 1);
	osSetEventMesg(OS_EVENT_DP, &taskMessageQ, dummyMessage);

	osCreateMesgQueue(&retraceMessageQ, &retraceMessageBuf, 1);
	osViSetEvent(&retraceMessageQ, dummyMessage, 1);

	/*
	 * Stick the static segment right after the code/data segment
	 */
	staticSegment = _codeSegmentEnd;

	dmaIoMessageBuf.hdr.pri      = OS_MESG_PRI_NORMAL;
	dmaIoMessageBuf.hdr.retQueue = &dmaMessageQ;
	dmaIoMessageBuf.dramAddr     = staticSegment;
	dmaIoMessageBuf.devAddr      = (u32)_staticSegmentRomStart;
	dmaIoMessageBuf.size         = _staticSegmentRomEnd-_staticSegmentRomStart;

	osEPiStartDma(handler, &dmaIoMessageBuf, OS_READ);

	/*
	 * Wait for DMA to finish
	 */
	(void)osRecvMesg(&dmaMessageQ, &dummyMesg, OS_MESG_BLOCK);

	time = 0;

	/*
         * Init controller.  # returned is lowest # active controller,
         * or -1 if none.
         */
	demomode   = TRUE;
	controller = initControllers();
	if (controller != -1) demomode = FALSE;

	tronInit ();

	cfbp = cfb_16_b;

	for (;;) {

	        /*
                 * Get ready to read controller later
                 */
	        if (controller != -1) {
		  osContStartReadData(&controllerMessageQ);
		}
		
		/*
		 * pointers to build the display list.
		 */
		tlistp = &tlist[current];
		dynamicp = &dynamicBuffer[current];
		glistp = dynamicp->glist;

		/*
		 * Tell RCP where each segment is
		 */
		gSPSegment(glistp++, PHYSICAL_SEGMENT, 0);
		gSPSegment(glistp++, STATIC_SEGMENT,
			   osVirtualToPhysical(staticSegment));
		gSPSegment(glistp++, DYNAMIC_SEGMENT,
			   osVirtualToPhysical(dynamicp));
	
		/*
		 * Graphics pipeline state initialization
		 */
		gSPDisplayList(glistp++, setup_rspstate);
		gSPDisplayList(glistp++, setup_rdpstate);

		/*
                 * Extraneous gSPTexture call.  The sole purpose is to
                 * send BOWTIE_VAL to the RSP, which it does as a side-effect.
                 */
		gSPTexture(glistp++, 0x0, 0x0, 0x0, 0x0, G_OFF);
	
		/*
		 * Clear framebuffer
		 */
		gDPSetColorImage(glistp++, G_IM_FMT_RGBA, G_IM_SIZ_16b,
		 SCREEN_WD, OS_K0_TO_PHYSICAL(cfbp));
		gSPDisplayList(glistp++, clear_fb);
		gDPSetColorImage(glistp++, G_IM_FMT_RGBA, G_IM_SIZ_16b,
		 SCREEN_WD, OS_K0_TO_PHYSICAL(cfbp));
	
		/* end of standard display list part. */

	        sceneDraw(dynamicp, cfbp, dynamicp->v);

		/*
		 * Force top-level display list to have an END.
		 */
		gDPFullSync(glistp++);
		gSPEndDisplayList(glistp++);
	
		/* build graphics task */
		tlistp->t.type = M_GFXTASK;
		tlistp->t.flags = OS_TASK_DP_WAIT;
		tlistp->t.ucode_boot = ( u64 * )rspbootTextStart;
		tlistp->t.ucode_boot_size = (u32)rspbootTextEnd - (u32)rspbootTextStart;
		tlistp->t.ucode = ( u64 * )gspF3DEX2_xbusTextStart;
		tlistp->t.ucode_data = ( u64 * )gspF3DEX2_xbusDataStart;
		tlistp->t.ucode_size = SP_UCODE_SIZE;
		tlistp->t.ucode_data_size = SP_UCODE_DATA_SIZE;

		tlistp->t.dram_stack = ( u64 * ) &dram_stack[0];
		tlistp->t.output_buff = ( u64 * ) NULL;
		tlistp->t.output_buff_size = ( u64 * ) NULL;

		/* initial display list: */
		tlistp->t.data_ptr = ( u64 * )dynamicBuffer[current].glist;
		tlistp->t.data_size =
		  ((int)(glistp - dynamicBuffer[current].glist) * sizeof(Gfx));
		tlistp->t.yield_data_ptr = ( u64 *)&(dram_yield[0]);
		tlistp->t.yield_data_size = 0xDA0;

		/*
		 * Can just flush 16KB and forget about each individual pieces
		 * of data to flush.
		 */
		osWritebackDCacheAll();
	
        	osSpTaskLoad(tlistp);
        	osSpTaskStartGo(tlistp);
	
		(void)osRecvMesg(&taskMessageQ, &dummyMesg, OS_MESG_BLOCK);
	
		osViSwapBuffer(cfbp);

		/*
		 * If graphics overruns, you will have redundant vertical
		 * retrace that need to be flushed.
		 */

		if (MQ_IS_FULL(&retraceMessageQ))
			(void)osRecvMesg(&retraceMessageQ,
					 &dummyMessage, OS_MESG_BLOCK);
	
		(void)osRecvMesg(&retraceMessageQ,
				 &dummyMessage, OS_MESG_BLOCK);

		current = 1 - current;
	
		if (current)    cfbp = cfb_16_a;
		else            cfbp = cfb_16_b;

		tronUpdate ();

		frame_count++;
		time++;
	}
}

static void
idle(void *arg)
{
	/*
	 * Initialize video
	 */
	osCreateViManager(OS_PRIORITY_VIMGR);
	osViSetMode(&osViModeTable[OS_VI_NTSC_LAN1]);

	/*
	 * Start PI Mgr for access to cartridge
	 */
	osCreatePiManager((OSPri)OS_PRIORITY_PIMGR, &PiMessageQ, PiMessages,
			  NUM_PI_MSGS);

	/*
	 * Start main thread
	 */
	osCreateThread(&mainThread, 3, mainproc, arg, mainThreadStack+STACKSIZE/8,
		(OSPri)10);
#ifndef DEBUGGER
	osStartThread(&mainThread);
#endif

	osSetThreadPri( 0, 0 );
	for(;;);
}

static void tronInit (void)
{
	int	i, j;

	for (i = 0; i < N; i++) {
		for (j = 0; j < N; j++) {
			field[i][j] = F_EMPTY;
		}
	}

	for (i = 0; i < N; i++) {
		field[0][i] = F_WALL;
		field[i][0] = F_WALL;
		field[N-1][i] = F_WALL;
		field[i][N-1] = F_WALL;
	}

	p[0].x       = N/2;
	p[0].y       = N/10;
#ifdef GOTO_BOWTIE
	p[0].dir     = SOUTH;
#else
	p[0].dir     = NORTH;
#endif
	p[0].dead    = FALSE;
	p[0].numhist = 2;
	p[0].height  = HEIGHT;
	p[0].ang     = ang[p[0].dir];
	p[0].red     = 0;
	p[0].green   = 54;
	p[0].blue    = 234;
	p[0].hx[0]   = p[0].x;
	p[0].hy[0]   = p[0].y;

	numplayers   = 1;
	numalive     = numplayers;
	substep      = 0;
}

static unsigned int rand (void)
{
	/* Straight from the C library */

	static unsigned long seed = 1;

	seed = seed * 1103515245 + 12345;

	return (seed >> 16) & RAND_MAX;
}

/*
 * Wait for Start button on controller to be pressed
 */
static void waitForReset(void) {
  while(1) {
    osContStartReadData(&controllerMessageQ);
    osRecvMesg(&controllerMessageQ, NULL, OS_MESG_BLOCK);
    osContGetReadData(controllerdata);
    if (controllerdata[controller].errno != 0) continue;
    button = controllerdata[controller].button;
    if (button & CONT_START) {
      lastbutton = 0;
      return;
    }
  }
}

static void killplayer (int i)
{
	p[i].dead = TRUE;
	numalive--;
	if (numalive == 0) {
	  if (!demomode) waitForReset();
	  tronInit ();
	}
}

static void changedir (int i)
{
	int     f, cnt, turn, newdir;

	if (p[i].dead) {
		return;
	}

	cnt = 0;
	turn = (rand () % 2) * 2 - 1;
	newdir = (p[i].dir + turn + NUMDIRS) % NUMDIRS;

	f = field[p[i].x + dx[newdir]][p[i].y + dy[newdir]];
	if (CRASH (f)) {
		turn = -turn;
		newdir = (p[i].dir + turn + NUMDIRS) % NUMDIRS;
		f = field[p[i].x + dx[newdir]][p[i].y + dy[newdir]];
		if (CRASH (f)) {
			turn = 0;
			newdir = p[i].dir;
			f = field[p[i].x + dx[newdir]][p[i].y + dy[newdir]];
			if (CRASH (f)) {
				killplayer (i);
				return;
			}
		}
	}
	switch (turn) {
		case -1:
			p[i].dir = newdir;
			p[i].numhist++;
			p[i].wantang -= 90;
			p[i].tipang = 45;
			break;
		case 0:
			break;
		case 1:
			p[i].dir = newdir;
			p[i].numhist++;
			p[i].wantang += 90;
			p[i].tipang = -45;
			break;
	}
	if (p[i].numhist == MAXHIST) {
		tronInit ();
	}
}

static avoidwall (int i)
{
	int	f;

	f = field[p[i].x + dx[p[i].dir]][p[i].y + dy[p[i].dir]];
	if (CRASH (f)) {
		changedir (i);
	}
}

static void tronUpdate (void)
{
        int f;


#ifndef GOTO_BOWTIE
	/*
	 * Because the grid has a limited resolution of NxN, but we want
         * to make the front of the trail "move" every frame, we move
         * the trail on the screen a little every frame, but only mark it
	 * off in the array every fourth frame.  Without this the trail
         * would move across the grid too quickly.
         * An annoying side effect of the way this implementation works
         * is that we don't check the controllers as often as we should.
         */
	if (substep < SUBSTEP_CNT-1) {
	  substep++;
	  return;
	} else {
	  substep = 0;
	}
#endif

	field[p[0].x][p[0].y] = 0;

	if (demomode) {
		if (rand () < 500) {
			changedir (0);
		}
		avoidwall (0);
	} else {
	        /* Get value from controller and adjust dir. if necessary */
	        osRecvMesg(&controllerMessageQ, NULL, OS_MESG_BLOCK);
	        osContGetReadData(controllerdata);
		if (controllerdata[controller].errno != 0) {
		  button = 0;
		} else {
		  button = controllerdata[controller].button;
		}
		if ((button & CONT_L) && !(lastbutton & CONT_L)) {
			p[0].dir = (p[0].dir + NUMDIRS - 1) % NUMDIRS;
			p[0].wantang -= 90;
			p[0].tipang = 45;
			p[0].numhist++;
		} else if ((button & CONT_R) && !(lastbutton & CONT_R)) {
			p[0].dir = (p[0].dir + 1) % NUMDIRS;
			p[0].wantang += 90;
			p[0].tipang = -45;
			p[0].numhist++;
		}
		lastbutton = button;

		/* If hit wall, die */
		f = field[p[0].x + dx[p[0].dir]][p[0].y + dy[p[0].dir]];
		if (CRASH (f)) {
		  killplayer (0);
		}
	}

	p[0].x += dx[p[0].dir];
	p[0].y += dy[p[0].dir];
	
	p[0].hx[p[0].numhist-1] = p[0].x;
	p[0].hy[p[0].numhist-1] = p[0].y;
}

void sceneDraw (Dynamic *dynamicp, unsigned short *cfbp, Vtx *v)
{
	/*
	 * Use either Frustum or Perspective.  The following two
	 * calls are equivalent.
	 */

	int	i;
    	u16 	perspNorm;

	guPerspective(&dynamicp->projection,&perspNorm, 33, 1, 10, 9000, 1);

/*
	guLookAt (&dynamicp->viewing,
		(p[0].y + cosf (time/2000) * 20)*20,
		20*20,
		(p[0].x + sinf (time/2000) * 20)*20,
		p[0].y * 20, 3 * 20, p[0].x * 20,
		0, 1, 0);
*/

#ifdef GOTO_BOWTIE
	guLookAt (&dynamicp->viewing,
		(p[0].y + cosf (time/2000) * 160)*xzscale,
		160*xzscale,
		(p[0].x + sinf (time/2000) * 160)*xzscale,
		p[0].y * xzscale, 3 * xzscale, p[0].x * xzscale,
		0, 1, 0);
#else
	guLookAt (&dynamicp->viewing,
		(p[0].y + cosf (time/300) * 40)*xzscale +
		   substep*dy[p[0].dir]*(xzscale/SUBSTEP_CNT),
		40*xzscale,
		(p[0].x + sinf (time/300) * 40)*xzscale +
		  substep*dx[p[0].dir]*(xzscale/SUBSTEP_CNT),
		p[0].y * xzscale + substep*dy[p[0].dir]*(xzscale/SUBSTEP_CNT),
		3 * xzscale,
		p[0].x * xzscale + substep*dx[p[0].dir]*(xzscale/SUBSTEP_CNT),
		0, 1, 0);
#endif

	gSPPerspNormalize(glistp++, perspNorm);

	gSPMatrix(glistp++, osVirtualToPhysical(&(dynamicp->projection)),
		G_MTX_PROJECTION|G_MTX_LOAD|G_MTX_NOPUSH);
	gSPMatrix(glistp++, osVirtualToPhysical(&(dynamicp->viewing)),
		G_MTX_MODELVIEW|G_MTX_LOAD|G_MTX_NOPUSH);

	/*
	 * render grid
	 */
	gDPPipeSync(glistp++);
	gDPSetCycleType(glistp++, G_CYC_1CYCLE);
	gSPSetGeometryMode(glistp++, G_SHADE | G_ZBUFFER | G_SHADING_SMOOTH);
	gDPSetCombineMode(glistp++, G_CC_SHADE, G_CC_SHADE);
	gDPSetRenderMode(glistp++, G_RM_AA_OPA_SURF, G_RM_AA_OPA_SURF2);

	gSPDisplayList (glistp++, grid);

#ifdef NOTDEF
	/*
	 * render walls
	 */
	gDPPipeSync(glistp++);
	gDPSetRenderMode(glistp++, G_RM_AA_ZB_OPA_SURF, G_RM_AA_ZB_OPA_SURF2);
	gSPDisplayList (glistp++, walls);
#endif

	/*
	 * render cycle trail
	 */
	gDPPipeSync(glistp++);
	gDPSetRenderMode(glistp++, G_RM_AA_ZB_XLU_SURF, G_RM_AA_ZB_XLU_SURF2);
	for (i = 0; i < p[0].numhist; i++) {
#ifndef GOTO_BOWTIE
	  if (i<p[0].numhist-1) {
#endif
		V (v + 3*i,
			p[0].hy[i]*xzscale, 0, p[0].hx[i]*xzscale,
			0, 0, 0, p[0].red, p[0].green, p[0].blue, 0xa0);
		V (v + 3*i + 1,
			p[0].hy[i]*xzscale, p[0].height*yscale,
                        p[0].hx[i]*xzscale,
			0, 0, 0, p[0].red, p[0].green, p[0].blue, 0xa0);
		V (v + 3*i + 2,
			p[0].hy[i]*xzscale,
		        p[0].height*yscale+(yscale*HEIGHT)/4,
		        p[0].hx[i]*xzscale,
			0, 0, 0, 0, 255, 128, 0xff);
#ifndef GOTO_BOWTIE
	   } else {
		V (v + 3*i,
			p[0].hy[i]*xzscale+
                              substep*dy[p[0].dir]*(xzscale/SUBSTEP_CNT),
		        0,
		        p[0].hx[i]*xzscale+
                              substep*dx[p[0].dir]*(xzscale/SUBSTEP_CNT),
			0, 0, 0, p[0].red, p[0].green, p[0].blue, 0xa0);
		V (v + 3*i + 1,
			p[0].hy[i]*xzscale+
		              substep*dy[p[0].dir]*(xzscale/SUBSTEP_CNT),
                        p[0].height*yscale,
                        p[0].hx[i]*xzscale+
                              substep*dx[p[0].dir]*(xzscale/SUBSTEP_CNT),
			0, 0, 0, p[0].red, p[0].green, p[0].blue, 0xa0);
		V (v + 3*i + 2,
			p[0].hy[i]*xzscale+
                              substep*dy[p[0].dir]*(xzscale/SUBSTEP_CNT),
		        p[0].height*yscale+(yscale*HEIGHT)/4,
		        p[0].hx[i]*xzscale+
		              substep*dx[p[0].dir]*(xzscale/SUBSTEP_CNT),
			0, 0, 0, 0, 255, 128, 0xff);
	   }
#endif
	}

	for (i = 0; i < p[0].numhist - 1; i++) {
		gSPVertex (glistp++, osVirtualToPhysical(v + i*3), 6, 0);
		gSP1Triangle (glistp++, 0, 1, 4, 0);
		gSP1Triangle (glistp++, 0, 4, 3, 0);
		gSP1Triangle (glistp++, 1, 2, 5, 0);
		gSP1Triangle (glistp++, 1, 5, 4, 0);
	}
}

/*
 *
 * Return the lowest number controller connected to system
 */
int initControllers()
{
    int             i;
    u8              pattern;

    osCreateMesgQueue(&serialMessageQ, &serialMsgBuf, 1);
    osSetEventMesg(OS_EVENT_SI, &serialMessageQ, (OSMesg)1);

    osContInit(&serialMessageQ, &pattern, &statusdata[0]);

    osCreateMesgQueue(&controllerMessageQ, &controllerMsgBuf, 1);
    osSetEventMesg(OS_EVENT_SI, &controllerMessageQ, (OSMesg)0);

    for (i = 0; i < MAXCONTROLLERS; i++) {
        if ((pattern & (1<<i)) &&
                !(statusdata[i].errno & CONT_NO_RESPONSE_ERROR))
            return i;
    }
    return -1;
}




