program snake;

var
   snakex : array [1..100] of byte;
   snakey : array [1..100] of byte;

   snakehead, snaketail, snakelength : integer;

   i : integer;
   inp, dir : char;
   x,y : byte;
   score : integer;
   crash : boolean;
   level : integer;
   time : integer;    
   seed, r : integer;     (* pseudo-random *)

   food : boolean;
   foodx, foody, foodv : byte;

(* * * * * * * * * @BDOS * * * * * * * * * * * * * * *)

external
   function @BDOS (Func : integer; Para : word) : integer; 

(* * * * * * * * DELAY  * * * * * * * * * * * * * *)

procedure delay (d : integer);
var i,j : integer;
begin
     for i := 1 to d do 
        begin
           j := i*i ;
        end;
end;

(* * * * * * * * * KEYPRESSED * * * * * * * * * * * *)

function keypressed : boolean;
begin
  keypressed := (@BDOS(11, wrd(0)) <> 0)
end;

(* * * * * * * * * CONIN * * * * * * * * * * * *)

function conin : char;
begin
   conin := chr(@BDOS(1, wrd($FF))); (* READ CHARACTER *)
end;

(* * * * * * * * * CONOUT * * * * * * * * * * * *)

procedure conout ( c : char);
var r : integer;
begin
   r := @BDOS(2, wrd(c));
end;

(* * * * * * * * * CLRSCR * * * * * * * * * * * *)

procedure clrscr;
begin
   {  ESC, [2J }
   write(chr(27),'[2J');
end;

(* * * * * * * * * gotoXY * * * * * * * * * * * *)

procedure gotoXY (x, y : byte);
begin
   { Go to coordinate x,y }
   {  ESC, y ; x, H  }
   { write(chr(27), chr(48+y), ';', chr(48+x), 'H') }
   write(chr(27),'[',y,';',x,'H')  
end;

(* * * * * * * * * RANDOM * * * * * * * * * * * * *)


Function Random (s: integer) : byte;
var a,b,c,d: integer;
Begin
    d := d + 1;
    a := (s * d) div 16;
    b := (s * d) mod 16;
    c := a + b + d;
    c := a * b * c;
    Random := c div 7;
End;


(* * * * * * * * * SPLASH SCREEN * * * * * * * * * * * *)

procedure splashscreen;
begin
   clrscr;

   gotoXY(20,5);
   writeln('SNAKE v0.20');
   gotoXY(20,6);
   writeln('(c) 2018, Karl A. Brokstad');
   gotoXY(20,7);
   writeln('www.Z80.no');
   gotoXY(20,10);
   writeln('requirements:'); 
   gotoXY(20,11);
   writeln('ANSI/VT100 compatible console');
   gotoXY(20,12);
   writeln('screen size 80x25');
   gotoXY(20,15);
   writeln('movement:');
   gotoXY(20,16);
   writeln('Z = left, X = up');
   gotoXY(20,17);
   writeln('N = down, M = right');
   gotoXY(20,18);
   writeln('ESC = quit');
   gotoXY(20,20);
   writeln('press RETURN to START');
  
   repeat
      inp := conin;       {chr(@BDOS(1, wrd($FF)));} (* READ CHARACTER *)
   until inp = chr(13);
end;

(* * * * * * * * * DRAW SCREEN * * * * * * * * * * * *)

procedure drawscreen;
var i : integer;
begin
   clrscr;
   write(chr(27),'[34m'); (* blue text *)
   write(chr(27),'[42m'); (* green background *)

   for i:=1 to 80 do
      begin
         gotoXY(i,1);
         conout('#');   { write('#'); }
         gotoXY(i,25);
         conout('#');   { write('#'); }
      end;
      
   for i:=1 to 25 do
      begin
         gotoXY(1,i);
         conout('#');   { write('#'); }
         gotoXY(80,i);
         conout('#');   { write('#'); }
      end;

   write(chr(27),'[0m');
end;

(* * * * * * * * * * * * * * * * * * * * * * *)
(* * * * * * * * * * MAIN * * * * * * * * * * *)

begin
   splashscreen;  (* show splash screen *)

(* * * * * * * * * INIT GAME * * * * * * * * *)
   
   r := 1;
   seed := 1;
   score := 0;
   level := 1;
(******************************************************)
   time := 1000; (* delay time *)
(******************************************************)

REPEAT        (* * * * * * * * * GAME LOOP * * * * * * * * *)

   drawscreen;

   snakehead := 1;               (* first position *)
   snakelength := 1;             (* length and last position *)
   snaketail := 2;               (* position to erase snaketail := snakehead + snakelength *)

   for i :=1 to 100 do           (* clear arrays, not nessecary *)
      begin
         snakex[i] := 0;
         snakey[i] := 0;
      end;

   x := 39;                       (* position in middle of screen *)
   y := 12;

   snakex[snakelength] := x;
   snakey[snakelength] := y;
 
   score := score + snakelength;    (* write level and score *)
   gotoXY(30,1);
   write(' LEVEL ',level,'  SCORE : ',score,' ');

   crash := false;
   food := false;

    (* wait until key pressed *)
(*
   repeat
      if keypressed then
         inp := chr(@BDOS(1, wrd($FF)));     
   until inp <> '';
*)

   gotoXY(x,y);
   conout('3');   { write('3'); }
   delay(5000);
   gotoXY(x,y);
   conout('2');   { write('2'); }
   delay(5000);
   gotoXY(x,y);
   conout('1');   { write('1'); }
   delay(5000);
   gotoXY(x,y);
   conout('0');   { write('0'); }

   dir := 'R';
 

(* * * * * * * * * START GAME LEVEL  * * * * * * * * *)

   repeat


      seed := seed + 1;
      
      if keypressed then                                    (* READ KEYBOARD/INPUT *)
         begin
            inp := conin;      { chr(@BDOS(1, wrd($FF))); }

            case inp of 
              'z','Z' : dir := 'L';    (* left *)
              'x','X' : dir := 'U';    (* right *)
              'n','N' : dir := 'D';    (* down *)
              'm','M' : dir := 'R';    (* up *)
              '-'     : time := time + 100;
              '+'     : time := time - 100; 
            end;
 
           if time < 0 then time:=0;

           while keypressed do
             inp := conin;      { chr(@BDOS(1, wrd($FF))); }      (* CLEAR INPUT BUFFER *)

        end;

      case dir of                                          (* MOVE DIRECTION *)
           'L' : x:=x-1;    (* left *)
           'R' : x:=x+1;    (* right *)
           'D' : y:=y+1;    (* down *)
           'U' : y:=y-1;    (* up *)
         end;

(* save snake position *)
(* reduce overhead by using i := snakelenth *)
(* PUSH snake positions down the line *)

         for i := 100 downto 2 do
             begin
                snakex[i] := snakex[i-1];
                snakey[i] := snakey[i-1];
             end;
       
      snakex[snakehead] := x;
      snakey[snakehead] := y;

      gotoXY(snakex[snakehead],snakey[snakehead]);   (* Draw new head *)
      conout('@'); {write('@');} 

      if snakelength > 1 then
      begin
        gotoXY(snakex[snakehead+1],snakey[snakehead+1]);   (* Draw body *)
        conout('O'); {write('O');} 
      end;
      
      gotoXY(snakex[snaketail],snakey[snaketail]);   (* erase tail *)
      conout(' '); {write(' ');}
      gotoXY(1,1);   

                                    (* make food *)

      if (food = false) then 
	begin
           repeat
              food := true;
  
              repeat                                    (* food value 1-9 *)
                 foodv := random(seed) DIV 26;
                 seed := seed + 1;
              until (foodv > 0) and (foodv < 10);
              repeat                                    (* food location x *)           
                 foodx := random(seed) DIV 3;                  (* location x = 3-77, no walls *)   
                 seed := seed + 1;
              until (foodx > 2) and (foodx < 78);
              repeat                                    (* food location y *)               
                foody := random(seed) DIV 10;                 (* location y = 3-22, no walls *)   
                seed := seed + 1;
              until (foody > 2) and (foody < 23);

              for i:=1 to snakelength do                (* check food location *)    
                 begin                                  (* that it does not intercept with snake *)    
                    if (snakex[i] = foodx) and (snakey[i] = foody) 
                       then food := false;
                 end;

           until (food = true);

           gotoXY(foodx,foody);                             (* draw food *)
           write(chr(27),'[31m', foodv , chr(27),'[37m');
           gotoXY(1,1);
        end;
     
                         (* test snake position *)

      if (x < 2) or (x > 79) then                     (* snake crash wall *)
        crash := true; 
      if (y < 2) or (y > 24) then
        crash := true;

     for i := 2 to snakelength do                     (* snake eat snake ? *)    
         begin                             
            if (snakex[i] = x) and (snakey[i] = y) 
               then crash := true;
         end;

      if (x = foodx) and (y = foody) then             (* snake eat food *)
        begin
           snakelength := snakelength + foodv;
           food := false;
           snaketail := snakelength +1;
           score := score + (foodv * level);
           gotoXY(30,1);
           write(' LEVEL ',level,'  SCORE : ',score,' ');
           gotoXY(1,1);
        end;

      delay(time);                                    (* delay *)

   until (inp = chr(27)) or (crash = true) or (snakelength > 99);

(* * * * * * * * * * END GAME * * * * * * * *)

   if crash=true then                        (* game over *)
     begin
         write(chr(27),'[31m');
         gotoXY(35,12);
         writeln(' CRASHED ');
         gotoXY(35,14);
         writeln(' GAME OVER ');
         write(chr(27),'[0m');
     end;

   if snakelength > 99 then                 (* advance to next level *)
     begin
        level := level + 1;
        time := time div level;
        write(chr(27),'[34m');
        gotoXY(35,12);
        writeln(' YOU MADE IT TO THE NEXT LEVEL ');
        gotoXY(35,13);
        writeln(' HIT A KEY to CONTINUE ');
        write(chr(27),'[0m');
        inp := chr(@BDOS(6, wrd($FF))); 
     end;
  
UNTIL (inp = chr(27)) or (crash = true)


(* * * * * * * * * * FINISH  * * * * * * * *)

end.
