/*
 * MKSoft SnakePit  Copyright (c) 1988  by Michael Sinz
 *
 * Main Game-Playing part...
 */

#include	"Snake.h"

#define	SCREEN_BONUS	250L
#define	FOOD_BONUS	125L
#define	SHRINK_BONUS	5L

#define	SlowSound	(-200)
#define	SoundSpeedUp	7

#define	SOUND_3		35
#define	SOUND_2		25
#define	SOUND_6		15
#define	SOUND_4		5

#define	BIGNUM		20000

#define	ExitSpeed	30000L

/*
 * START_LOOKING is the number of loops without a move before the system
 * starts looking for an out.  After NO_MOVE_FOUND, it kills the snake.
 */
#define	START_LOOKING	9
#define	NO_MOVE_FOUND	18

/* This is the interval of the speed changes		*/
/* MAX Value: 100,000  MIN Value: 50			*/
#define	SPEED_UP	28000L
/* SPEED_UP_LEVEL should be 1/16th of SPEED_UP	*/
#define	SPEED_UP_LEVEL	(SPEED_UP>>4L)

#define	FADE_SPEED	50000L
#define	FLASH_SPEED	1000L

#define	SET_DIR(x,y,z)	(((x+1)<<2)+(y+1)+((z & 0x0F)<<4))

#define	DIR_UP	0x04
#define	DIR_DN	0x06
#define	DIR_LF	0x01
#define	DIR_RT	0x09

#define	DIR_UP_LF	0x14
#define	DIR_DN_LF	0x16
#define	DIR_LF_UP	0x41
#define	DIR_RT_UP	0x49
#define	DIR_UP_RT	0x94
#define	DIR_DN_RT	0x96
#define	DIR_LF_DN	0x61
#define	DIR_RT_DN	0x69

#define	DONE	2
#define	DEAD	3
#define	OK	1
#define	AM_HERE	32

static	short	Play_Pointer[6] = {0x0000,0x0000,0x8000,0x8000,0x0000,0x0000};

static	short		Play_Level;
static	short		Lives;
static	long		Score;

static	short		Food_Count;

static	short		Snake_X[1024];
static	short		Snake_Y[1024];
static	short		Snake_P[1024];

static	short		Snake_Head;
static	short		Snake_Tail;
static	short		Snake_Size;

static	short		Stall_Flag;

static	short		Direction_X;
static	short		Direction_Y;

static	long		MicroSpeed;

static	Pit_Screen	Play_Screen;

static	short		SoundSpeed;

static	VOID	Display_At(x,y,p)	register	short	x;
					register	short	y;
					register	short	p;
{
register	struct	Image	*i=&Empty;

	switch (p)
	{
	case DROP_BRICK:
	case NORMAL_BRICK:	i=&Brick;	break;
	case MOVING_BRICK:	i=&MoveBrick;	break;
	case GROW_FOOD:		i=&GrowFood;	break;
	case SHRINK_FOOD:	i=&ShrinkFood;	break;
	}
	DrawImage(MyWindow->RPort,i,((long)x<<3L),((long)y<<3L));
}

static	VOID	Do_Wait(microSpeed)	register	ULONG	microSpeed;
{
	Time_Req.tr_time.tv_secs=0;
	Time_Req.tr_time.tv_micro=microSpeed;
	DoIO(&Time_Req.tr_node);
}

VOID	Do_Fade()
{
register	short	x;

	for(x=0;x<15;x++)
	{
		Set_Color_Level(x);
		Do_Wait(FADE_SPEED);
	}
	Set_Color_Level(15);
}

VOID	UnDo_Fade()
{
register	short	x;

	for(x=15;x>0;x--)
	{
		Set_Color_Level(x);
		Do_Wait(FADE_SPEED);
	}
	Set_Color_Level(0);
}

static	VOID	SetUp_Screen()
{
register	short	x;
register	short	y;

	Set_Color_Level(15);
	Play_Level--;
	Food_Count=0;
	for(x=0;x<NUM_COL;x++) for(y=0;y<NUM_ROW;y++)
	{
		Display_At(x,y,Play_Screen[x][y]=All_Pit_Screens[Play_Level][x][y]);
		if (Play_Screen[x][y]==GROW_FOOD) Food_Count++;
	}
	Play_Level++;
	UpDate_Status(Play_Level,Score,Lives);
	UnDo_Fade();
}

static	VOID	Drop_Bricks()
{
register	short	x;
register	short	y;

	for(x=0;x<NUM_COL;x++) for(y=0;y<NUM_ROW;y++) if (Play_Screen[x][y]==DROP_BRICK) Display_At(x,y,Play_Screen[x][y]=EMPTY_SQUARE);
}

static	VOID	Move_Tail()
{
register	struct	Image	*i;
register		short	x;
register		short	y;
register		short	p;

	Snake_Size--;
	if (Snake_P[Snake_Tail])
	{
		p=(Play_Screen[x=Snake_X[Snake_Tail]][y=Snake_Y[Snake_Tail]])&31;
		Play_Screen[x][y]=p;
		Display_At(x,y,p);
	}
	Snake_Tail++;
	Snake_Tail&=1023;
	if (Snake_P[Snake_Tail])
	{
		switch (15 & Snake_P[Snake_Tail])
		{
		case DIR_RT:	i=&Tail[0];	break;
		case DIR_UP:	i=&Tail[1];	break;
		case DIR_LF:	i=&Tail[2];	break;
		case DIR_DN:	i=&Tail[3];	break;
		}
		DrawImage(MyWindow->RPort,i,((long)Snake_X[Snake_Tail]<<3L),((long)Snake_Y[Snake_Tail]<<3L));
	}
}

static	VOID	Display_Snake_Head()
{
register	struct	Image	*i;

	switch (15 & Snake_P[Snake_Head])
	{
	case DIR_RT:	i=&Head[0];	break;
	case DIR_UP:	i=&Head[1];	break;
	case DIR_LF:	i=&Head[2];	break;
	case DIR_DN:	i=&Head[3];	break;
	}
	DrawImage(MyWindow->RPort,i,((long)Snake_X[Snake_Head]<<3L),((long)Snake_Y[Snake_Head]<<3L));
}

static	VOID	Move_Head(New_X,New_Y)	register	short	New_X;
					register	short	New_Y;
{
register		short	New_Head;
register	struct	Image	*i;

	Stall_Flag=0;
	Score++;
	Snake_Size++;
	Direction_X=New_X;
	Direction_Y=New_Y;
	New_Head=(Snake_Head+1)&1023;
	Play_Screen[Snake_X[New_Head]=Snake_X[Snake_Head]+New_X][Snake_Y[New_Head]=Snake_Y[Snake_Head]+New_Y]|=AM_HERE;
	Snake_P[New_Head]=SET_DIR(New_X,New_Y,0);
	switch (Snake_P[Snake_Head]=SET_DIR(New_X,New_Y,Snake_P[Snake_Head]))
	{
	case DIR_UP_LF:
	case DIR_RT_DN:	i=&Turn[0];	break;
	case DIR_UP_RT:
	case DIR_LF_DN:	i=&Turn[1];	break;
	case DIR_DN_RT:
	case DIR_LF_UP:	i=&Turn[2];	break;
	case DIR_DN_LF:
	case DIR_RT_UP:	i=&Turn[3];	break;
	default:	i=&Body[(New_X ? (1-New_X) : (New_Y+2))];	break;
	}
	DrawImage(MyWindow->RPort,i,((long)Snake_X[Snake_Head]<<3L),((long)Snake_Y[Snake_Head]<<3L));
	Snake_Head=New_Head;
	Display_Snake_Head();
}

static	VOID	New_Snake()
{
register	short	x;
register	short	y;
register	short	found_x=1;
register	short	found_y=0;

	Direction_X=0;
	Direction_Y=1;
	Snake_Size=1;
	Snake_Head=2;
	Snake_Tail=0;
	Snake_P[0]=Snake_P[1]=0;
	for(x=0;x<NUM_COL;x+=NUM_COL-1) for(y=1;y<(NUM_ROW-1);y++) if (Play_Screen[x][y]==POPUP_BRICK)
	{
		found_x=x;
		found_y=y;
		Direction_Y=0;
		Direction_X=(x ? (-1):(1) );
	}
	for(x=1;x<(NUM_COL-1);x++) for(y=0;y<NUM_ROW;y+=NUM_ROW-1) if (Play_Screen[x][y]==POPUP_BRICK)
	{
		found_x=x;
		found_y=y;
		Direction_X=0;
		Direction_Y=(y ? (-1):(1) );
	}
	Play_Screen[found_x][found_y]=NORMAL_BRICK | AM_HERE;
	Snake_X[Snake_Head]=found_x;
	Snake_Y[Snake_Head]=found_y;
	Snake_P[Snake_Head]=SET_DIR(Direction_X,Direction_Y,0);
	Display_Snake_Head();
}

static	short	Move_Snake(New_X,New_Y)	register	short	New_X;
					register	short	New_Y;
{
register	short	x;
register	short	y;
		short	x1;
		short	y1;
		short	flag=FALSE;

	if ((New_X!=Direction_X)||(New_Y!=Direction_Y))
	{
		if ((-New_X!=Direction_X)&&(-New_Y!=Direction_Y))
		{
			x=Snake_X[Snake_Head]+New_X;
			y=Snake_Y[Snake_Head]+New_Y;
			if ((x>=0)&&(y>=0)&&(x<NUM_COL)&&(y<NUM_ROW))
			{
				switch (Play_Screen[x][y])
				{
				case POPUP_BRICK:
				case EMPTY_SQUARE:
				case GROW_FOOD:
				case SHRINK_FOOD:
				case MOVING_BRICK:	break;
				default:		New_X=Direction_X;
							New_Y=Direction_Y;
							break;
				}
			}
		}
		else
		{
			New_X=Direction_X;
			New_Y=Direction_Y;
		}
	}
	x=Snake_X[Snake_Head]+New_X;
	y=Snake_Y[Snake_Head]+New_Y;
	if ((x<0)||(y<0)||(x>=NUM_COL)||(y>=NUM_ROW)) return(DONE);
	if (Play_Screen[x][y]>=AM_HERE) return(DEAD);
	if (Play_Screen[x][y]==MOVING_BRICK)
	{
		x1=x+New_X;
		y1=y+New_Y;
		if ((x1>=0)&&(y1>=0)&&(x1<NUM_COL)&&(y1<NUM_ROW))
		{
			if (Play_Screen[x1][y1]==EMPTY_SQUARE)
			{
				Display_At(x1,y1,Play_Screen[x1][y1]=Play_Screen[x][y]);
				Play_Screen[x][y]=EMPTY_SQUARE;
			}
		}
	}
	switch (Play_Screen[x][y])
	{
	case SHRINK_FOOD:	Play_Screen[x][y]=EMPTY_SQUARE;
				Score+=SHRINK_BONUS;
				Move_Tail();
				Sound_Eat(x);
				if (Snake_Size)
				{
					Move_Head(New_X,New_Y);
					Move_Tail();
				}
				else return(DEAD);
				break;
	case GROW_FOOD:		Play_Screen[x][y]=EMPTY_SQUARE;
				Score+=FOOD_BONUS;
				Food_Count--;
				Move_Head(New_X,New_Y);
				Sound_Eat(x);
				switch (Food_Count)
				{
				case SOUND_2:	Sound_Play(2,BIGNUM,SoundSpeed-SOUND_2);	break;
				case SOUND_3:	Sound_Play(3,BIGNUM,SoundSpeed-SOUND_3);	break;
				case SOUND_4:	Sound_Play(4,BIGNUM,SoundSpeed-SOUND_4);	break;
				case SOUND_6:	Sound_Play(6,BIGNUM,SoundSpeed-SOUND_6);	break;
				}
				if (!Food_Count) Drop_Bricks();
				break;
	case POPUP_BRICK:	Play_Screen[x][y]=NORMAL_BRICK;
	case EMPTY_SQUARE:	Move_Head(New_X,New_Y);
				Sound_Move(x);
				Move_Tail();
				break;
	}
	return(OK);
}

static	VOID	Do_Kill_Snake()
{
	Sound_Dead();
	while (Snake_Size) Move_Tail();
	Move_Tail();
	Snake_P[(Snake_Tail+1)&1023]=0;
	Move_Tail();
	Do_Fade();
}

static	VOID	Do_Play()
{
register	struct	IntuiMessage	*msg;
register	struct	MsgPort		*MyPort;
register		short		New_X;
register		short		New_Y;
register		UBYTE		tmp;
			short		flag;
			short		move_flag;
			short		t_move;

	MyPort=MyWindow->UserPort;
	while(Lives)
	{
		SoundSpeed+=SoundSpeedUp;
		if ((SoundPeriod-SoundSpeed)<80) SoundSpeed=SoundPeriod-80;
		Sound_Play(2,BIGNUM,SlowSound);
		SetUp_Screen();
		New_Snake();
		while (msg=(struct IntuiMessage *)GetMsg(MyPort)) ReplyMsg(msg);

		flag=FALSE;
		while(!flag)
		{
			WaitPort(MyPort);
			msg=(VOID *)(MyPort->mp_MsgList.lh_Head);
			if (msg->ExecMessage.mn_Node.ln_Succ)
			{
				if ((msg->Class==RAWKEY)||(msg->Class==MENUPICK)) flag=TRUE;
				else ReplyMsg(GetMsg(MyPort));
			}
		}

/*
 * This is the start of the playing of a single screen/man.  After
 * some I/O is received, we get to here and start the snake...
 */
 		Sound_Play(1,BIGNUM,SoundSpeed-Food_Count);
		New_X=Direction_X;
		New_Y=Direction_Y;
		Stall_Flag=0;
		while(flag)
		{
			UpDate_Status(Play_Level,Score,Lives);
			Do_Wait(MicroSpeed);
			if (Score) Score--;
			Cycle_Colors();
			Stall_Flag++;
			if (Stall_Flag>START_LOOKING)
			{
				New_X=New_Y=0;
				switch ((Stall_Flag+(Score>>3))&3)
				{
				case 0:	New_X=-1;	break;
				case 1:	New_X= 1;	break;
				case 2:	New_Y= 1;	break;
				case 3:	New_Y=-1;	break;
				}
			}
			while(msg=(struct IntuiMessage *)GetMsg(MyPort))
			{
				if (msg->Class==RAWKEY)
				{
					tmp=(UBYTE)msg->Code;
					if (tmp==KeyList[0].raw)
					{
						New_X=0;
						New_Y=-1;
					}
					else if (tmp==KeyList[1].raw)
					{
						New_X=0;
						New_Y=1;
					}
					else if (tmp==KeyList[2].raw)
					{
						New_X=-1;
						New_Y=0;
					}
					else if (tmp==KeyList[3].raw)
					{
						New_X=1;
						New_Y=0;
					}
				}
				if ((msg->Class==MENUPICK)||(tmp==RAW_ESC))
				{
					ClearPointer(MyWindow);
					tmp=Request_Pause();
					SetPointer(MyWindow,Play_Pointer,1L,1L,-1L,0L);
					if (tmp>1)
					{
						Lives--;
						flag=FALSE;
					}
					if (tmp>2)
					{
						Sound_Dead();
						Lives=0;
					}
				}
				ReplyMsg(msg);
			}
			if (Stall_Flag>NO_MOVE_FOUND)
			{
				Do_Kill_Snake();
				Lives--;
				flag=FALSE;
			}
			if (flag)
			{
				move_flag=Move_Snake(New_X,New_Y);
				if (move_flag==DONE)
				{
					DrawImage(MyWindow->RPort,&Body[(Direction_X ? (1-Direction_X) : (Direction_Y+2))],((long)Snake_X[Snake_Head]<<3L),((long)Snake_Y[Snake_Head]<<3L));
					Sound_Finish();
					while (Snake_Size)
					{
						Move_Tail();
						Cycle_Colors();
						Score+=SCREEN_BONUS;
						UpDate_Status(Play_Level,Score,Lives);
						Do_Wait(ExitSpeed);
					}
					Move_Tail();
					Snake_P[(Snake_Tail+1)&1023]=0;
					Do_Wait(MicroSpeed);	Move_Tail();
					Do_Fade();
					Score+=SCREEN_BONUS;
					flag=FALSE;
					Lives++;
					Play_Level++;
					UpDate_Status(Play_Level,Score,Lives);
					if (MicroSpeed>SPEED_UP_LEVEL) MicroSpeed-=SPEED_UP_LEVEL;
					if (Play_Level>MAX_LEVEL) Play_Level=1;
				}
				else if (move_flag==DEAD)
				{
					Do_Kill_Snake();
					Lives--;
					flag=FALSE;
				}
			}
		}
	}
}

VOID	Play_Game(UserSpeed)	short	UserSpeed;
{
register		ULONG		tmpIDCMP;

	OffMenu(MyWindow,SHIFTMENU(0)|SHIFTITEM(NOITEM)|SHIFTSUB(NOSUB) );
	OffMenu(MyWindow,SHIFTMENU(1)|SHIFTITEM(NOITEM)|SHIFTSUB(NOSUB) );

	tmpIDCMP=MyWindow->IDCMPFlags;
	ModifyIDCMP(MyWindow,RAWKEY|MENUPICK);

	ClearPointer(MyWindow);
	Play_Level=Request_SelectLevel(0);
	Sound_Play(2,BIGNUM,SlowSound);
	Lives=5;
	Score=NULL;
	SetRast(MyWindow->RPort,0L);
	UpDate_Status(Play_Level,Score,Lives);
	MicroSpeed=(10L-((long)UserSpeed))*SPEED_UP;
	SoundSpeed=0;
	SetPointer(MyWindow,Play_Pointer,1L,1L,-1L,0L);
	Do_Play();
	Sound_Play(2,BIGNUM,0);
	ClearPointer(MyWindow);
	Set_Color_Level(0);
	Request_HighScores(Score);
	ShowWait();

	ModifyIDCMP(MyWindow,tmpIDCMP);

	OnMenu (MyWindow,SHIFTMENU(1)|SHIFTITEM(NOITEM)|SHIFTSUB(NOSUB) );
	OnMenu (MyWindow,SHIFTMENU(0)|SHIFTITEM(NOITEM)|SHIFTSUB(NOSUB) );
}
