/*
	VIC NIBBLER(r2) for the 3K expanded Vic 20
	By Andy H @ hewco.uk - visit hewco.uk for more games
	
	Project settings: Vic 20 + 3k RAM, start address $0400
	Cart settings: Uncomment six lines below, start address $a000
*/
program Nibbler;

@use "unit/cart"

var



@define DEBUG		0	// set to 1 for debugging active (cheat)
//@define BUILDCRT	1	// uncomment to build as a cartridge

// when building and testing with CART, set mem to ALL
// uncomment the following six lines:


@ifndef BUILDCRT
	// these are the 3k RAM expanded PRG settings
	@VicMemoryConfig "3k"
	@projectsettings "ignorebasicsysstart" 0
	@projectsettings "basicsysaddress" $400
	@projectsettings "startaddress" $410

@endif

@ifdef BUILDCRT

	@VicMemoryConfig "all"
	@projectsettings "ignorebasicsysstart" 0
	@projectsettings "basicsysaddress" $1200
	@projectsettings "startaddress" $A1A0
	@exportprg2bin "nibbler.prg" "a000.prg" $a000 $c000

@endif




	// I need to save some bytes, I don't use these
	@ignoremethod "init16x8mul"
	@ignoremethod "init16x8div"
	@ignoremethod "init8x8div"
	@ignoremethod "initeightbitmul"
	@ignoremethod "initjoystick"
	@ignoremethod "initmoveto"

	@define CHAR_ADDR	$1c00		// character address
	@define CHAR_ADDR_CART	$be00	// character address in CART
	@define SCREEN_ADDR	$1e00		// Screen address
	@define COLOR_ADDR	$9600		// Colour address
	@define SCREEN_WIDTH 19			// Custom VIC 20 screen width
	@define SCREEN_HEIGHT 25		// Custom VIC 20 screen height
	@define WAITVBL_LINE 130 		// 130 works for NTSC 150 // 155 appears to be max on PAL
	
	@define KEYB 197				// Read current key pressed

	// sound info
	@define SOUND_HISSDURATION 2
	@define SOUND_HISS 253
	@define SOUND_TJUNCSTART 180
	@define SOUND_TJUNCMIN 130
	@define SOUND_COLLECTSTART 200
	@define SOUND_COLLECTEND 240
	@define SOUND_COLLECTINC 10

	// characters used for Nibbler the snake
	@define HEADUP		2
	@define HEADRIGHT	3
	@define HEADDOWN	4
	@define HEADLEFT	5
	@define BODYRIGHT	6
	@define BODYLEFT	7
	@define BODYUP		8
	@define BODYDOWN	9
	@define BODYBR		10
	@define BODYBL		11
	@define BODYTR		12
	@define BODYTL		13
	@define TAILUP		14
	@define TAILRIGHT	15
	@define TAILDOWN	16
	@define TAILLEFT	17
	@define FOOD		1
	@define GRID		62
	
	@define TAILGROWMAX	4		// how many segments to grow max when eat food odd events -1
	@define STOPTIMEMAX	12		// max time allows to rest/pause at junctions
	@define TIMETICKMAX 25		// for counting a second
	@define TIMETICKOUT 22		// Reset for Timer when out of STOPTIME
	@define ANIMTIMEMAX 2  		// animation timer
	@define MOVESTARTVAL 7  	// slowest wave 1 starting speed
	@define MOVEMINVAL   3		// fastest will move
	@define JOYRESULTTIME 2		// Time to remember last joystick direction

	scrm : array[24] of integer at $00; // screen memory start addresses
	clrm : array[24] of integer at $18; // colour memory start addresses

	t, i, r, s : byte at $30; 			// temp variables, used in for loops or calculations
	
	p1, p2, p3 : pointer;  		// general pointers, often used to reference screenmemory

	head, tail : pointer;  		// head and tail address on screen mem
	headc, tailc : pointer;  	// head and tail address on colour mem

	tailWait : byte at $34;		// how many head moves to wait before tail moves again (to allow growing snake)
	tailGrow : byte at $35;		// how many segments tail grow next time (between 1 and @TAILGROWMAX)
	animTime : byte at $36;		// how fast to animate the body parts
	moveTime : byte at $37;		// how fast Nibbler moves, Counter
	moveTimeReset : byte at $39;	// how fast Nibbler moves, larger means slower
	justMoved : byte at $3a; 		// flag set if player has just pressed key to change direction

	JoyResult2 : byte at $3b;		// save recent joystick move request
	JoyResult2Time: byte at $3c;	// time to remember recent joy move request

	numFood : byte at $3d; 		// number of food on level remaining

	gameState : byte at $3e;		// 0=in progress, 1=hit tail	
	@define GAMESTATE_GAME 0
	@define GAMESTATE_LOOSELIFE 1

	wave : byte at $3f;				// current wave
	levelId : byte at $40; 			// level index number
	time : byte at $41; //=99; 			// current time
	timeTick : byte at $42; //=0; 			// count 50 times a second
	score : integer at $43; //=0;
	lives : byte at $45;//=5;	
	@define WAVEDIFFMAX 3
	wavediff : byte at $46; // = 0;		// counter for how often will get faster

	scrnNo : byte at $47;
	scrnDelay : byte at $48;

	hissDuration : byte at $49; // = 0;
	snd1Freq : byte at $4a; // = 0;
	snd2Freq : byte at $4b; // = 0;

	numberOfRastersToWait : byte at $4c;
	timeColor: byte at $4d;

	stopTime : byte at $4e; // = @STOPTIMEMAX; // how many pauses you are allowed to make before time penalty
	stopped : byte at $4f;

	@define MAXLEVELID  20  	// how many level Id's in the array
	// levels 0 1 2 3 4 5 6 7 8 9
	// food   0 1 2 3 4 0 1 2 3 4
	levelarray : array[] of byte = (
		 0, 1, 2, 11, 4,
		 5,12, 7, 8, 9, 
		10,15, 6, 3,14, 
		13,16,17,18,19
	);
	foodarray : array[] of byte = (
		 0, 1, 2, 6, 4, 
		 0, 7, 2, 3, 4,
		 5, 8, 1, 3, 7, 
		 8, 6, 2, 1, 7
	);
	// 0 - 0000 Black   1 - 0001 White  2 - 0010 Red   3 - 0011 Cyan 
	// 4 - 0100 Purple  5 - 0101 Green  6 - 0110 Blue  7 - 0111 Yellow
	colourarray : array[] of byte = (
		 7, 3, 4, 5, 2, 
		 7, 5, 1, 3, 2,
		 3, 4, 5, 6, 7,
		 1, 2, 3, 4, 5
	);

@vbmexport	"bin/chars.flf" "bin/nibbler.bin" 0 64 1 0


@ifndef BUILDCRT
	charset : incbin("bin\nibbler.bin", @CHAR_ADDR);  // custom character set
@endif

@ifdef BUILDCRT
	charset : incbin("bin\nibbler.bin", @CHAR_ADDR_CART);  // custom character set
@endif

	@include "setup.ras"
	@include "sound.ras"
	@include "levels.ras"
	@include "gameplay.ras"
	@include "gamescrn.ras"



// -------------------------------------------------------------------------------
// The magic bytes needed at address $A000

@ifdef BUILDCRT
@startblock $A000 "CARTAUTOSTART"
@donotremove cartAutostart
procedure cartAutostart();
begin

	cart::Autostart();
	
end;
@endblock
@endif

// -------------------------------------------------------------------------------



// ---- Game Loop ----
procedure GameLoop();
begin

	r:=0;
	while (r=0) do
	begin

		GetJoyKeyInput();
		
		// slow Nibbler down, only move when moveTime = 0
		if moveTime = 0 then
		begin

			NibblerControl();
			// if the player changed direction, don't do an automatic move
			if (justMoved = 0) then MoveNibblerHead();
			MoveNibblerTail();
			
			// reset movetime - check head - horiz = move slower
			if (head[0] = @HEADRIGHT or head[0] = @HEADLEFT) then
				moveTime := moveTimeReset + 1
			else
				moveTime := moveTimeReset;

			// leave Game loop if gameState changes
			if gameState = @GAMESTATE_LOOSELIFE then return(); // lose life
			if numFood = 0 then return();

		end;
		
		// adjust movetime, to control Nibbler speed
		dec(moveTime);
				
		// animate the body scrolling characters
		inc(animTime);
		if animTime = @ANIMTIMEMAX then 
		begin

			if stopped = false then AnimateNibblerBody();
			animTime := 0;
			// hit T junc sound
			PlaySND1();
			// collect food
			PlaySND2();
			// hiss sound on turning
			PlaySFX();
		end;			

		// count down clock
		inc(timeTick);
		if timeTick = @TIMETICKMAX then
		begin
			timeTick := 0;
			if stopTime = 0 then timeTick := @TIMETICKOUT; // run out of stop time, count down faster
			dec(time);
			DrawUITime();
			if time = 12 then begin timeColor := RED; ColourTime(); end;
			if time = 0 then
			begin
				gameState := @GAMESTATE_LOOSELIFE;
				return();
			end;
		end;

		//PlaySFX();
	
		/* debug - to enable debug mode, uncomment the DEBUG define at the top of this file */
	@if DEBUG=1
		//screenmemory := $1fb5; //addresstable(scrm,0,23);
		//printdecimal(^@KEYB,1);
		
		// skip to next level with N key
		if ^@KEYB = 28 then
		begin
			numFood := 0;
		end;
	@endif
	
		// this is a hack if it slows down
		//if peek(^$9004,0) > 20 then 
//		screen_bg_color := SCREEN_BG_BLACK;
		waitforraster(@WAITVBL_LINE);
//		screen_bg_color := screen_bg_color+1;
	end;	

end;

// reset for replaying wave
procedure ResetVars();
var
	hacker: string = ("HELLO HAXOR.URL33T.ENJOY");
begin

	time := 99;
	timeTick :=0;
	stopTime := @STOPTIMEMAX;
	moveTime := moveTimeReset;

	animTime := 1;

	tailGrow := 1;
	tailWait := 0;
	
	JoyResult2 := 0;
	JoyResult2Time := 0;
	
	hissDuration := 0;
	numberOfRastersToWait := 1;

end;



// ---- Main Program ----
begin

	@ifdef BUILDCRT

		cart::Init();
	
	@endif

	ScreenSetup();
	ResetVars();

	score := 0;

	while (true) do
	begin

		TitleSequence();

		moveTime := 0;
		justMoved := 0;
		gameState := @GAMESTATE_GAME;

		// new game starting values
		wave := 0;
		wavediff := 0;
		levelId := 0;
		lives := 5;
		score := 0;
		numFood := 0;
		moveTimeReset := @MOVESTARTVAL - 1; // game start = move 1 faster
											// losing life will then allow 1 slow down

		while lives <> 0 do
		begin
			ScrollScreenDown();
			timeColor := WHITE;
			ColourTime();
			
			// if numFood is 0 either start of first level, or just completed a level
			if numFood = 0 then
			begin
			
				ClearTheScreen(); 
				DrawUITextLabels();
				DrawUIScore();
				DrawUILives();
				DrawLevelFrame();
				DrawLevelMaze();
				DrawLevelFood();	
			
				wave := wave + 1;
				wavediff := wavediff + 1;
				levelId :=  levelId + 1;
				if levelId = @MAXLEVELID then levelId := 0;
				DrawUIWave();
				
				// bonus lives / extra speed up
				if levelId = 4 then lives := lives + 1;
				if levelId = 14 then begin lives := lives + 1; wavediff := wavediff + 1; end;
				if levelId > 19 then wavediff := wavediff + 1; // EVIL!

				// speed up every few waves (3)
				if wavediff >= @WAVEDIFFMAX then 
				begin 
					moveTimeReset := moveTimeReset - 1;
					// bit faster when > wave 20
					if levelId > 19 then moveTimeReset := moveTimeReset - 1; 
					wavediff := 0;
				end;
				if moveTimeReset < @MOVEMINVAL then moveTimeReset := @MOVEMINVAL;

				
			end;

			DrawUITime();
			DrawUILives();
			ScrollScreenUp();
			GameStarting();
			GameLoop();
			
			// game loop ends but numFood <> 0 means lost life, slow Nibbler down
			if (numFood <> 0 and moveTimeReset < @MOVESTARTVAL) then moveTimeReset := moveTimeReset + 1;

			// stop sounds
			snd1Freq := 0;
			SOUND1_REGISTER := 0;
			SOUND2_REGISTER := 0;
			SOUNDFX_REGISTER := 0;	

			if numFood = 0 then CountBonus();	
			if gameState = @GAMESTATE_LOOSELIFE then LoseLife();
			
			ResetVars();
			gameState := @GAMESTATE_GAME;
		end;	
	
	end;
	
end.
// ---- Main Program ----


