1
0
vt100-games/Snake/SNAKE.PAS

381 lines
10 KiB
Plaintext
Raw Normal View History

2020-06-25 11:35:08 +02:00
program snake;
{$I- No I/O checking}
{$R- No range checking}
{$V- No string checking}
(*
Snake game for CP/M-80 V2.2 and VTxx/ANSI terminal
Original version (C) 2018, Karl A. Brokstad (www.z80.no)
Turbo Pascal conversion and other mods with permission
(C) 2018, linker3000 (linker3000-at-gmail-dot-com)
V1.2T: 21-Jul 2019
Corrected Y boundary max in putFood.
V1.1T: 14-Oct-2018
Command line switch -m (monochrome) suppresses all colour codes
Version numbering detached from Karl A. Brokstad's original
V23N: 24-Sep-2018 First public release
Game map used for collision detection and food position
generation - this takes out several long loops.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*)
Type
Str255 = string[255];
Const
progVersion : string[6] = '1.2acn';
borderChar : char = '#';
snakeHeadChar : char = 'O';
snakeBodyChar : char = '#';
xMax : Byte = 80;
yMax : Byte = 25;
var
snakeX : array [1..100] of byte;
snakeY : array [1..100] of byte;
gameGrid : array [2..80, 2..25] of byte; {Tracks snake body and tail}
snakeBodyANSI : String[8];
foodANSI : String[8];
snakeHeadANSI : String[8];
borderANSI : String[8];
resetANSI : String[4];
msgANSI : String[8];
snakeHead, snakeTail, snakeLength : integer;
I, level, score : integer;
inp, dir : char;
X, Y : byte;
crash, escaped : boolean;
time : integer; {Game delay factor}
food : boolean;
foodX, foodY, foodV : byte;
(* * * * * * * * * * writeCtr * * * * * * * * * *)
procedure writeCtr(Line : Byte; S : Str255);
{ Write a centered line of text}
Var
I : Integer;
begin
I := 40 - round(Length(S)/2);
If I < 1 then I := 1;
gotoxy(I,Line);
write(S);
end;
(* * * * * * * * * * clrKbd * * * * * * * * * *)
procedure clrKbd; {Clear keyboard buffer}
begin
repeat until (bdos(6,255)) = 0;
end;
(* * * * * * * * * * readKbd * * * * * * * * * *)
procedure readKbd; {Check for direction keys}
begin
if keypressed then case upcase(chr(bdos(6,255))) of
'4','H','A' : dir := 'L'; (* left *)
'8','K','W' : dir := 'U'; (* up *)
'2','J','S' : dir := 'D'; (* down *)
'6','L','D' : dir := 'R'; (* right *)
'+' : time := (time + 5) and 255;
'-' : begin
time := time - 5;
if time < 0 then time := 0;
end;
'Q' : escaped := true;
end;
end;
(* * * * * * * * * conIn * * * * * * * * * * * *)
function conIn : char;
begin
conIn := chr(bdos(6, 255)); (* SILENT READ CHARACTER *)
end;
(* * * * * * * * sDelay * * * * * * * * * * * * * *)
{A non-blocking-ish delay during which we check the keyboard}
procedure sDelay (D : integer);
var I : integer;
begin
for I := 1 to D do
begin
Delay(1);
readKbd;
end;
end;
(* * * * * * * * * SPLASH SCREEN * * * * * * * * * * * *)
procedure SplashScreen;
begin
clrscr;
write (msgANSI);
writeCtr( 4,'SNAKE '+progVersion+' by Linker3000');
writeCtr( 5,'From the original by Karl A. Brokstad (www.Z80.no)');
writeCtr( 7,'This program comes with ABSOLUTELY NO WARRANTY.');
writeCtr( 9,'This is free software, and you are welcome to redistribute it');
writeCtr(10,'under certain conditions - see GPLV2 license:');
writeCtr(11,'https://www.gnu.org/licenses/gpl-2.0.html');
writeCtr(13,'Requirements:');
writeCtr(15,'80x25 ANSI/VT100 compatible console');
if (msgANSI = '') then
writeCtr(16,'(Monochrome mode was chosen by command line switch)')
else
writeCtr(16,'(Start program with -m switch to disable ANSI colors)');
writeCtr(18,'Movement:');
writeCtr(20,'4 = left, 8 = up, 2 = down, 6 = right OR use HJKL keys');
writeCtr(21,'Q = Quit to here during game');
writeCtr(23,'Press ENTER to START or Q to QUIT');
repeat
repeat until keypressed;
inp := conIn;
if ((inp = 'Q') or (inp = 'q')) then
begin
write (resetANSI);
halt;
end;
until (inp = #13);
end;
(* Put down food *)
Procedure putFood;
begin
(* food value 1-9 *)
foodV := 1 + random(9);
repeat {Find a new food location}
readKbd;
foodX := 3 + random(xMax-3); {Stay within walls}
foodY := 3 + random(yMax-4);
until (gameGrid[foodX,foodY] = 0); {Don't put food down over snake}
food := true;
gotoXY(foodX,foodY); {Draw food}
write(foodANSI, foodV ,resetANSI);
gotoXY(1,1);
readKbd;
end; {food}
(* * * * * * * * * DRAW SCREEN * * * * * * * * * * * *)
procedure DrawScreen;
var I : integer;
begin
clrscr;
write(borderANSI);
for I:=1 to xMax do
begin
gotoXY(I,1);
write(borderChar);
gotoXY(I,yMax);
write(borderChar);
end;
for I:=1 to yMax do
begin
gotoXY(1,I);
write(borderChar);
gotoXY(xMax,I);
write(borderChar);
end;
write(resetANSI);
end;
(* * * * * * * * * * * * * * * * * * * * * * *)
(* * * * * * * * * * MAIN * * * * * * * * * * *)
begin
snakeBodyANSI := #27'[40m';
foodANSI := #27'[33;40m'; {Yellow on black background}
snakeHeadANSI := #27'[40m';
borderANSI := #27'[31;44m';
resetANSI := #27'[0m'; {Reset ANSI attributes}
msgANSI := #27'[33;40m';
if (ParamCount > 0) and ((ParamStr(1) = '-m') or (ParamStr(1) = '-M')) then
begin
snakeBodyANSI := '';
foodANSI := '';
snakeHeadANSI := '';
borderANSI := '';
resetANSI := '';
msgANSI := '';
end;
repeat
randomize;
SplashScreen; (* show splash screen *)
(* * * * * * * * * INIT GAME * * * * * * * * *)
escaped := false;
crash := false;
score := 1;
level := 1;
time := 100; (* delay time *)
(* * * * * * * * * GAME LOOP * * * * * * * * *)
while (not escaped) and (not crash) do
begin
{Clear the object location grid}
for Y := 2 to yMax do for X := 2 to xMax do gameGrid[X,Y] := 0;
DrawScreen;
snakeHead := 1; (* first position *)
snakeLength := 1; (* length and last position *)
snakeTail := 2; (* position to erase snakeTail := snakeHead + snakeLength *)
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,' ');
food := false;
for I := 5 downto 0 do
begin
gotoXY(X,Y);
write(I);
delay(500);
end;
dir := 'R';
(* * * * * * * * * START GAME LEVEL * * * * * * * * *)
repeat {Game level}
readKbd; {Check keyboard for input}
case dir of (* MOVE readKbd *)
'L' : X:=X-1; (* left *)
'R' : X:=X+1; (* right *)
'D' : Y:=Y+1; (* down *)
'U' : Y:=Y-1; (* up *)
end;
(* Save snake position *)
(* PUSH snake positions down the line *)
(* Always use full array size to keep processing speed consistent *)
for I := 100 downto 2 do
begin
readKbd; {Check keyboard for input}
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 *)
write (snakeHeadANSI,snakeHeadChar);
if snakeLength > 1 then
begin
readKbd;
gotoXY(snakeX[snakeHead+1],snakeY[snakeHead+1]); (* Draw body *)
gameGrid[snakeX[snakeHead+1],snakeY[snakeHead+1]] := 1; {Where the body is}
write (snakeBodyANSI,snakeBodyChar);
end;
gotoXY(snakeX[snakeTail],snakeY[snakeTail]); (* erase tail *)
gameGrid[snakeX[snakeTail],snakeY[snakeTail]] := 0; {Remove tail from map}
write (resetANSI,' ');
gotoXY(1,1);
if (food = false) then putFood; {Put down some food}
{Test snake position}
if (X < 2) or (X >= xMax) {Snake crashes into wall or own body }
or (Y < 2) or (Y >= yMax)
or (gameGrid[X,Y] = 1) then crash := true;
if (X = foodX) and (Y = foodY) then {Snake eats food }
begin
readKbd;
snakeLength := snakeLength + foodV;
food := false;
snakeTail := snakeLength +1;
score := score + (foodV * level);
gotoXY(30,1);
write(' LEVEL ',level,' SCORE : ',score,' ');
gotoXY(1,1);
readKbd;
end;
sDelay(time); (* delay *)
until crash or escaped or (snakeLength > 99); {game level}
(* * * * * * * * * * END GAME * * * * * * * *)
write(msgANSI);
if (crash or escaped) then (* game over *)
if crash then writeCtr(12,' YOU CRASHED ')
else writeCtr(12,' YOU QUIT! ');
if snakeLength > 99 then (* advance to next level *)
begin
level := level + 1;
time := time - 10;
if (time < 1) then time := 1;
writeCtr(12,' YOU MADE IT TO THE NEXT LEVEL ');
end;
write (resetANSI);
clrKbd;
writeCtr(14,' PRESS ENTER ');
repeat until conIn = #13;
end {while}
until false;
(* * * * * * * * * * FINISH * * * * * * * *)
end.