Skip to content

Instantly share code, notes, and snippets.

@tomaes
Last active May 22, 2020 06:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tomaes/4e193f514219ce14a48456003f9f8c3d to your computer and use it in GitHub Desktop.
Save tomaes/4e193f514219ce14a48456003f9f8c3d to your computer and use it in GitHub Desktop.
16-bit DOS game. Download binary from https://tomaes.itch.io/ninja-blitz
{
Ninja Blitz
A spin on city bomber games
for 1980s era 16 bit DOS PCs
designed & written by
Thomas "tomaes" Gruetzmacher
in May 2020
LICENSE: CC BY-NC 4.0
https://creativecommons.org/licenses/by-nc/4.0/legalcode
}
{
2020-05-22: $12.3 audio toggle during attack mode fix
2020-05-20: $12.2 level highscore, optimized code (-> smaller .exe)
2020-05-15: $11.2 public release
}
uses crt;
const REV = '$12.3'; { 2020-05-08/09/10 ..15+}
LASTX = 40;
LASTY = 50;
LASTLV = 9; { final level }
CHR_BO = '*'; CHR_B2 = #254; {big dot}
CHR_PL = '='; CHR_B1 = #219; {full char}
CHR_AT = 'v'; CHR_B3 = '|' ;
CHR_TR = '.'; CHR_SC = '$' ;
CHR_TR2= ' ';
MAP_EMPTY = 0; MAP_CASH = 2;
MAP_BLOCK = 1; MAP_GOLD = 3;
MAP_DETON = 4;
KEY_ENTER = #13; KEY_ESC = #27;
KEY_DOWN = #80; KEY_SPACE = #32;
PL_LV_COLORS : array[1..10] of byte =
( White, LightRed, LightMagenta, LightGray, LightCyan,
Blue, Red, Magenta, DarkGray+Blink, Black );
var px, py : byte; { player pos }
ay, ar : byte; { attack-from y pos, attack range/time }
hp, ohp : byte; { attack HP }
ps, ph : byte; { player shots'n'hits }
cc : byte; { base city color }
lv : byte; { level }
sc, hl : integer; { score, highest level }
at : boolean; { attack mode }
ch : char;
playSfx : boolean;
f : array[1..LASTX, 1..LASTY] of 0..4;
(********************************************************)
function rnd(_r: word): word;
begin
rnd := random(_r);
end;
function onein(_r: word): boolean;
begin
onein := (rnd(_r) = 0);
end;
procedure gxy(_x, _y: byte);
begin
gotoxy(_x, _y);
end;
procedure color(_c: byte);
begin
textcolor(_c);
end;
procedure waitForKey;
var _ch: char;
begin
_ch := readkey;
end;
procedure stopSfx;
begin
nosound;
end;
procedure sfx(_f,_d: word);
begin
if playSfx then sound(_f);
delay(_d);
if playSfx then stopSfx;
end;
procedure toggleAudio;
begin
playSfx := not playSfx;
if not playSfx then stopSfx;
end;
(********************************************************)
procedure delPlayerYline;
var y: byte;
begin
for y :=1 to py-1 do begin
gxy(px, y);
write(' ');
end;
end;
procedure eoLevelAnimation;
var x, y: byte;
i, c: byte;
begin
for i := ord('9') downto ord(' ') do begin
case i-ord(' ') of {explosion fade-out}
0..10: c := Blue;
11..15: c := DarkGray;
16..20: c := LightGray;
21..22: c := Yellow;
23: c := LightMagenta;
24..25: c := White;
end;
color(c);
for y := 1 to LASTY do begin
for x := 1 to LASTX do begin
if (f[x,y] <> 0) then begin
gxy(x,y);
case rnd(3) of
0: write( CHR_B1 );
1: write( CHR_B2 );
2: write( CHR_B3 );
end;
end;
end;
end;
sfx(1000 - i*10, 20);
end;
end;
procedure renderScore;
var s: string;
y: byte;
begin
str(sc, s);
{ ready to detonate? indicate in score display }
if sc >= 1000 then begin
if (sc mod 2 = 0) then insert( CHR_BO, s, 0 )
else insert( ' ', s, 0)
end
else insert( CHR_SC, s, 0 );
for y := 1 to length(s) do
begin
gxy(1, y); write(s[y]);
end;
gxy(1, length(s)+1 );
write(' ');
str(lv, s);
insert('#', s, 0);
for y := 1 to length(s) do
begin
gxy(LASTX, y); write(s[y]);
end;
end;
procedure renderHowTo;
var txt : string;
i,c : byte;
_d : byte;
begin
txt := '#'#15'briefing'#4'##';
txt := txt + 'x hit space to dive down#';
txt := txt + 'x try to grab ('#14'$'#4') from up high#';
txt := txt + 'x avoid going through walls#';
txt := txt + 'x if you have $1000, attack ('#12'*'#4')#';
txt := txt + ' to detonate everything and#';
txt := txt + ' move on to the next stage#';
txt := txt + 'x the game ends when your#';
txt := txt + ' cash score is gone!##'#6;
txt := txt + 'good luck!';
_d := 30;
gxy(1,13);
for i := 1 to length(txt) do begin
if keypressed then begin
ch := readkey;
if ch in ['m','M'] then toggleAudio
else _d := 0;
end;
case txt[i] of
chr(Black)..
chr(White) : color( ord(txt[i]) );
'#' : write( #10#13#10#13' > ');
else write( UpCase(txt[i]) );
end;
if (txt[i] <> ' ') then
begin
if _d > 0 then sfx(200 + i, 1);
delay(_d);
end;
end;
end;
procedure makeLevel;
var x, y, r : byte;
h : byte;
begin
cc := rnd(10) + 1;
fillChar(f, sizeof(f), 0 );
for x:= 6 to LASTX - 5 do
begin
{ height of building }
h := LASTY - rnd(10+lv div 2) + lv div 2 - 1;
for y:= LASTY downto h do
begin
color(cc + y mod 4);
f[x, y] := MAP_BLOCK;
gxy(x,y);
if (y mod 2 = 0) then
write(CHR_B1)
else if not onein(7) then write(CHR_B2)
else write(CHR_B3);
end;
end;
r := 0;
{ render prizes (fewer for higher levels) }
repeat
inc(r);
repeat
x := 6 + rnd(LASTX - 10);
y := LASTY - rnd(LASTY div 4);
until f[x, y] = MAP_BLOCK;
gxy(x, y);
color(Yellow);
write(CHR_SC);
f[x, y] := MAP_CASH;
until (r > 10-lv); { saved score becomes important in later levels }
{ (sometimes) render a big prize at the bottom}
if onein(10) then begin
x := 3 + rnd(LASTX - 4);
y := LASTY;
f[x,y] := MAP_GOLD;
gxy(x,y);
color( LightMagenta );
write(CHR_SC);
end;
{render detonator at the bottom}
x := 3 + rnd(LASTX - 4);
y := LASTY;
f[x,y] := MAP_DETON;
gxy(x,y);
color(LightRed);
write(CHR_BO);
end;
procedure shufflePrizes; { shuffle prizes with adjacent space randomly }
var x,y, rx: word;
begin
for x := 2 to LASTX-1 do begin
for y := 1 to LASTY-1 do
if f[x,y] = MAP_CASH then break;
if (f[x,y] = MAP_CASH) then begin
repeat
rx := x + random(3)-1;
until rx <> x;
if rx < 1 then rx := 1;
if rx > LASTX-1 then rx := LASTX-1;
if (f[rx, y ] = MAP_EMPTY) and
(f[rx, y+1] <> MAP_EMPTY) then
begin
f[x, y] := MAP_EMPTY;
f[rx, y] := MAP_CASH;
gxy(x, y); write(' ');
gxy(rx,y); color(Yellow); write(CHR_SC);
end;
end;
end;
end;
procedure resetPlayer;
begin
ps := 0; ph := 0;
px := 2; py := 1;
ar := 0; ay := py;
hp := 5 + rnd(4);
ohp:= hp;
at := false;
end;
(********************************************************)
var i: integer;
label re;
begin
randomize;
textmode(Font8x8);
playSfx := true;
renderHowTo;
waitForKey;
hl := 1;
re:
lv := 1;
sc := 300;
clrscr;
makeLevel;
resetPlayer;
gxy(16, LASTY div 2 - 4);
color(White);
write('GET READY');
sfx(1000, 20);
delay(1000);
gxy(1, LASTY div 2 - 4); clrEol;
repeat
dec(sc);
color( PL_LV_COLORS[lv] );
renderScore;
gxy(px, py);
if at then begin
if ar > 0 then write(CHR_AT)
end
else write(CHR_PL);
if not at then sfx( sc*2, 2);
if at then begin
if playSfx then sound(80+ar*5);
delay(10);
end
else delay(100);
if (lv > 2) and not at then shufflePrizes; { lv 3+ prizes can be moving targets }
if at then inc(ar); { attack range bonus: long shots prefered }
if at then begin { render attack trail }
for i := ay+1 to ay+ar-1 do begin
gxy(px, i);
if ((i-ar) mod 30 = 0) then write(CHR_TR2)
else write(CHR_TR);
end;
end else begin
gxy(px, py);
write(' ');
end;
case f[px,py] of
MAP_BLOCK: begin
if at then begin { attack block hit }
dec(hp);
dec(sc, (10-hp)*10 );
sfx(500, 50);
end else
sc := 0; { normal block crash: instant death }
end;
MAP_CASH: begin
inc(sc, 300); { cash hit; possible from side }
sfx(200, 50);
sfx(400, 50);
hp := 0;
inc(ph);
inc(sc, ar*2) { range bonus }
end;
MAP_GOLD: begin
inc(sc, 300*2); { big cash/gold hit }
sfx(300, 50);
sfx(500, 50);
sfx(600, 50);
hp := 0;
inc(ph);
inc(sc, ar*2) { range bonus }
end;
MAP_DETON: begin { end-of-level detonator hit}
eoLevelAnimation;
dec(sc, 1000);
inc(lv);
clrscr;
for i := 0 to 400 do sfx( rnd(1000), 1 );
makeLevel;
resetPlayer;
if (sc > 0) and (sc < 500) then sc := 500; { min. score when alive in the next level }
end;
end;
f[px, py] := MAP_EMPTY;
if not at then
begin
if (px < LASTX - 1) then inc(px) else
begin
px := 2;
inc(py);
end;
end else begin
inc(py);
if (py > LASTY) and (ohp - hp = 0) then
begin
dec(sc, 50); { blank shot }
sfx(30, 200); { silent on windows! }
end;
if (py > LASTY) or (hp = 0) then begin
delPlayerYLine; { continue from attack position }
py := ay;
inc(py); { ...but closer to the bottom }
at := false;
hp := 5 + rnd(4);
ohp:= hp;
end;
end;
if keypressed then
begin
ch := readkey;
if not at and ((ch = KEY_SPACE) or (ch = KEY_DOWN)) then
begin
gxy(px, py); write(' ');
at := true;
ay := py;
ar := 0;
inc(ps);
end;
{ pray to the cheat gods }
if ch = '$' then begin
i := rnd(1000)-500;
inc(sc, i);
sfx(580+i, 200);
end;
{ skip level cheat }
if ch = '>' then begin
inc(lv);
clrscr;
makeLevel;
resetPlayer;
end;
{ play sounds or not }
if ch in ['M','m'] then toggleAudio;
end;
{ user exit, player crash or level cap }
until (ch = KEY_ESC) or (sc <= 0) or (lv > LASTLV);
sfx(100, 100);
gxy(15, LASTY div 2 - 4);
color(White);
if (lv > LASTLV) then writeln('.!AMAZiNG!.')
else writeln('NiNjA BLiTZ');
gxy(11, LASTY div 2 - 2);
color(Red);
writeln('HITS:', ph, '/', ps,' ON LEVEL:', lv );
gxy(10, LASTY div 2 + 1);
color(Red + Blink);
writeln('<ENTER> TO TRY AGAIN!');
gxy(14, LASTY div 2 + 5);
if lv > hl then begin
hl := lv;
color(Yellow);
write(' BEST LEVEL!');
end else begin
color(cc);
write(' max level:', hl, ' ');
end;
repeat
ch := readkey;
until ch in [KEY_ESC, KEY_ENTER];
if (ch = KEY_ENTER) then goto re;
textmode(c80);
color(White);
writeln('Written in May 2020 (rev. ', REV,')'#13#10);
color(cc);
writeln('> github.com/tomaes');
writeln('> tomaes.itch.io');
delay(1000);
end.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment