program tutorial_14_horizontal_starfield;



// number of stars
@define noStars 25
@define useKernal 0
// Which character to use?
@define charPos 64
// where to put char ROM data?
@define charsetPos $2000


/*
	This effect assumes 1 star per horizontal line, in total 25
	you could theoretically add more stars and goto x,y location, but this will slow down the process considerably. I would rather recommend having 2 stars per line if you need more

*/


var  
   	i,j,x,dx, time: byte = 0; 
	// a star can have 8 values in a single char
	charsetVals: array[8] of byte =(1,2,4,8,16,32,64,128);	
		// Star position in memory (0-40)
	posx: array[@noStars] of byte;
	// Star position in char pos (0-64) / 8 with range 0-7
	posdx: array[@noStars] of byte;
	// speed of star, subtracted to posdx, range 0-64 divided by 8
	posddx: array[@noStars] of byte;

	// Star color
	color: array[@noStars] of byte;
	// random table
	rands: array[256] of byte;
	zp,cp:pointer;
	starColors:array[] of byte=(grey,white,red,light_red,blue,light_blue,yellow,light_blue,white);
	
	text: string = "EAT LOTS OF OATS";

// Creates 8 characters with 1 dot in each char from @charPos
procedure GenerateCharset();
begin
	zp:=@charsetPos + @charPos*8;
	for i:=0 to 8 do begin
		for j:=0 to 8 do begin
			zp[j]:=0;
		end;
		zp[4]:=charsetVals[i];
		zp:=zp+8;
	end;

end;

// initializes starfield
procedure InitializeStarfield();
begin
	j:=42;
	for i:=0 to @noStars do begin
		posx[i]:=rands[i]/2;  // 0-40
		posdx[i]:=rands[j]&63; // 0-64 (will be divided by 8)
		color[i]:=starColors[j&7];  // 0-7
		posddx[i]:=rands[i]&7+1;  // 1-8
		j:=j+11; // just some random shift
	end;
end;

// Initialize random table
procedure InitializeRand();
begin
	for i:=0 to 0 do begin
		rands[i]:=random()/2;
		if (rands[i]>80) then rands[i]:=rands[i]-80;
	end;

end;

// Renders field & colors
// this procedure can be seriously optimized, for example you don't need to write color memory (can be done while initializing), divide by 8 can be in a table, 
// the charpos should be inverted and lose the +7, write in assembler etc
procedure RenderStarfield();
begin
	moveto(0,0,hi(screen_char_loc));
	cp:=#screen_col_loc;
	for i:=0 to @noStars do begin
		if i<>12 then begin // ignore stars at line 12
			x:=posx[i];
			dx:=posdx[i]/8;
			screenmemory[x]:=@charPos+7-dx;
			if x<>screen_width-1 then
				screenmemory[x+1]:=key_space
			else
				screenmemory[0]:=key_space;

			cp[x]:=color[i];

		end;
		screenmemory:=screenmemory+screen_width;
		cp:=cp+screen_width;
	end;
end;

// Move the stars

procedure UpdateStarfield();
begin
	for i:=0 to @noStars do begin
		dx:=posdx[i]-posddx[i];
		if dx>127 then begin
			posx[i]:=posx[i]-1;
			if posx[i]>127 then begin	
				posx[i]:=screen_width-1;
				posddx[i]:=rands[time]&7+1;
			end;
			posdx[i]:=63;
		end
		else 	
		posdx[i]:=dx;
	end;

end;

procedure InitializeText();
begin
	moveto(12,12,hi(screen_char_loc));
	printstring(text,0,20);
end;

interrupt Raster1();
begin
	StartIRQ(@useKernal);
	inc(time);
	RenderStarfield();	
	UpdateStarfield();
	closeIRQ();
end;

// Main routine
begin
	InitializeRand();
	InitializeStarfield();
	screen_bg_col:=black;
	screen_fg_col:=black;
	clearscreen($20,screen_char_loc);
	clearscreen($64,screen_col_loc);
	SetCharsetLocation(@charsetPos);
	// Copy built-in charset from ROM to charset area (only the first 64 characters)
	BlockMemCpy(^$D000,^@charsetPos,2);
	GenerateCharset();
	InitializeText();
	asm(" sei"); // disable all interrupts
	SetMemoryConfig(1); // turning off ROM, so the non-kernal IRQ can run.
	StartRasterChain(Raster1(),$00,@useKernal); // it also enables interrupt, which is already redirected to Raster1().
	Loop(); 
end.
