Invaders

Generic Invaders Source Code in XT3X

This is the source code to a space shooter inspired by the Space Invaders arcade game of the 1970's. The code is in XT3X.

! Generic Invaders
! By Nils M Holm, 2018
! In the public domain
! https://creativecommons.org/publicdomain/zero/1.0/

str.length(s) return t.memscan(s, 0, 32767);

str.copy(sd, ss) t.memcopy(ss, sd, str.length(ss)+1);

str.append(sd, ss) t.memcopy(ss, @sd::str.length(sd), str.length(ss)+1);

str.equal(s1, s2) return t.memcomp(s1, s2, str.length(s1)+1) = 0;

writes(s) t.write(1, s, str.length(s));

var ntoa_buf::100;
fixed_ntoa(x, len) do var i, k, n;
	i := 99;
	ntoa_buf::i := 0;
	k := x<0-> -x: x;
	n := 0;
	while (k > 0 \/ i = 99) do
		n := n+1;
		i := i-1;
		ntoa_buf::i := '0' + k mod 10;
		k := k/10;
	end
	if (x < 0) n := n+1;
	while (n < len) do
		n := n+1;
		i := i-1;
		ntoa_buf::i := '0';
	end
	if (x < 0) do
		i := i-1;
		ntoa_buf::i := '-';
	end
	return @ntoa_buf::i;
end

ntoa(x) return fixed_ntoa(x, 0);

aton(s) do var v, i;
	i := 0;
	v := 0;
	while ('0' <= s::i /\ s::i <= '9') do
		v := v*10 + s::i - '0';
		i := i+1;
	end
	return v;
end

const	SPEED = 20;
const	SPEEDUP = 3;
const	PAUSE = 50;
const	BMBSPEED = 2;
const	MAXSPEED = 2;
const	LASTSPEED = 1;

const	ROWS = 4;
const	COLS = 6;

var	Fort1, Fort2, Fort3, Inv1, Inv2, Rocket, Boom, Bomb, Exp1, Exp2;
var	Invmap, Kills, Score, Quit, Logo, Gameover, Level;
var	Xfort, Hit, Hits, Shield, Gamespeed, Hiscores::91;
var	Xinv, Yinv, Invdir, Invspeed, Invclk, Invstate;
var	Xrkt, Yrkt, Xbmb, Ybmb, Bmbclk;

init() do
	x.init();
	x.wave(1);
	Fort1 := x.bitmap(8, 8, packed
			[ 0x00, 0x00, 0x00, 0x01, 0x07, 0x1f, 0x3f, 0x3f ]);
	Fort2 := x.bitmap(8, 8, packed
			[ 0x00, 0x18, 0x18, 0x18, 0x3c, 0xff, 0xff, 0xff ]);
	Fort3 := x.bitmap(8, 8, packed
			[ 0x00, 0x00, 0x00, 0x80, 0xe0, 0xf8, 0xfc, 0xfc ]);
	Inv1 := x.bitmap(16, 16, packed
			[    0,    0,    0,    0, 0x30, 0x0c, 0x08, 0x10,
                          0x04, 0x20, 0x1f, 0xf8, 0x3f, 0xfc, 0x73, 0xce,
                          0xf3, 0xcf, 0xff, 0xff, 0xbf, 0xfd, 0xa0, 0x05,
                          0x10, 0x08, 0x0c, 0x30, 0,    0,    0,    0    ]);
	Inv2 := x.bitmap(16, 16, packed
			[    0,    0,    0,    0, 0x0c, 0x30, 0x08, 0x10,
                          0x04, 0x20, 0x1f, 0xf8, 0x3f, 0xfc, 0x73, 0xce,
                          0xf3, 0xcf, 0xff, 0xff, 0xbf, 0xfd, 0x88, 0x11,
                          0x08, 0x10, 0x30, 0x0c, 0,    0,    0,    0    ]);
	Rocket := x.bitmap(8, 8, packed
			[ 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x24, 0x00 ]);
	Boom :=  x.bitmap(16, 16, packed
			[ 0x00, 0x00, 0x44, 0x22, 0x24, 0x24, 0x12, 0x48,
                          0xc8, 0x13, 0x20, 0x04, 0x00, 0x00, 0xf8, 0x1f,
                          0x00, 0x00, 0x10, 0x08, 0x64, 0x46, 0x88, 0x11,
                          0x12, 0x48, 0x24, 0x24, 0x24, 0x22, 0x00, 0x00 ]);
	Bomb := x.bitmap(8, 8, packed
			[ 0x00, 0x1c, 0x38, 0x1c, 0x38, 0x1c, 0x38, 0x00 ]);
	Exp1 :=  x.bitmap(16, 16, packed
			[ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                          0x00, 0x7e, 0x03, 0xcf, 0x0f, 0xdf, 0x19, 0xdf,
                          0x33, 0xf9, 0x67, 0xfd, 0x6f, 0xfd, 0x5f, 0xff,
                          0xdd, 0xdf, 0xfd, 0xdf, 0xfd, 0xff, 0xff, 0xff ]);
	Exp2 :=  x.bitmap(8, 16, packed
			[ 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xf0, 0xf8,
                          0x9c, 0x9e, 0xce, 0xee, 0xef, 0xff, 0xff, 0xff ]);
	Logo := [ "...#####.#####.#...#.#####.#####.###.#####....",
		  "...#.....#.....##..#.#.....#...#..#..#........",
		  "...#.###.#####.#.# #.#####.#####..#..#........",
		  "...#...#.#.....#..##.#.....#..#...#..#........",
		  "...#####.#####.#...#.#####.#...#.###.#####....",
		  "..............................................",
	          "###.#...#.#...#.#####.####..#####.#####.#####.",
                  ".#..##..#.#...#.#...#.#...#.#.....#...#.#.....",
                  ".#..#.#.#.#...#.#####.#...#.#####.#####.#####.",
                  ".#..#..##..#.#..#...#.#...#.#.....#..#......#.",
                  "###.#...#...#...#...#.####..#####.#...#.#####.",
                  ".............................................." ];
	Gameover := [" ####   ###  #   # #####",
		     "#    # #   # ## ## #    ",
		     "#      #   # # # # #    ",
		     "# #### ##### #   # #### ",
		     "#    # #   # #   # #    ",
		     "#    # #   # #   # #    ",
		     " ####  #   # #   # #####",
		     "                        ",
		     " ####  #   # ##### #### ",
		     "#    # #   # #     #   #",
		     "#    # #   # #     #   #",
		     "#    # #   # ####  #### ",
		     "#    # #   # #     # #  ",
		     "#    #  # #  #     #  # ",
		     " ####    #   ##### #   #"];
	x.bg(0);
	x.clrscr();
end

initgame() do
	Level := 0;
	Invstate := 1;
	Gamespeed := SPEED;
	Shield := 5;
	Score := 0;
	Quit := 0;
end

initround(spd) do var i, j;
	Xfort  := 14;
	Invmap := [[0, 0, 0, 0, 0, 0],
	           [0, 0, 0, 0, 0, 0],
	           [0, 0, 0, 0, 0, 0],
	           [0, 0, 0, 0, 0, 0]];
	for (j=0, ROWS)
		for (i=0, COLS)
			Invmap[j][i] := 1;
	Xinv := 4;
	Yinv := 1;
	Invdir := 1;
	Invspeed := spd/SPEEDUP;
	if (Invspeed < MAXSPEED) Invspeed := MAXSPEED;
	Invclk := Invspeed;
	Xrkt := 0;
	Xbmb := 0;
	Kills := 0;
	Hits := 0;
	Level := Level + 1;
end

center(y, m) x.print(15-str.length(m)/2, y, m);

print(x, y, m) do var i, j, k;
	k := str.length(m);
	j := 0;
	for (i=0, k)
		ie (m::i = '[') do
			x.bg(14);
			x.fg(0);
		end
		else ie (m::i = ']') do
			x.bg(0);
			x.fg(14);
		end
		else do
			x.blit(x+j, y, m::i);
			j := j+1;
		end
	x.bg(0);
end

post(m) do var i;
	x.fg(4);
	x.bg(14);
	for (i=0,32) x.blit(i, 0, '\s');
	center(0, m);
	x.bg(0);
	x.redraw();
end

ask(q) do var k, b::32;
	str.copy(b, q);
	str.append(b, "? y/n");
	post(b);
	while (1) do
		k := x.getkey();
		if (k = 'y') return %1;
		if (k = 'n') return 0;
	end
end

invader_sound(on) do var i;
	ie (on) do
		for (i=660, 440, %55) do
			x.beep(440, 2);
			x.beep(i, 2);
		end
	end
	else do
		for (i=440, 660, 55) do
			x.beep(440, 2);
			x.beep(i, 2);
		end
	end
end

inv_expl_sound() do var i;
	for (i=0, 20)
		x.beep(x.rnd(300)+200-i*10, 10);
end

fort_expl_sound(i) do
	x.beep(x.rnd(300)+200-i, 10);
end

hit_sound() do var i;
	for (i=0, 20) do
		x.beep(400-i*10, 5);
		x.beep(400+i*10, 5);
	end
end

launch_sound() do var i;
	for (i=400, 550, 4)
		x.beep(i, 2);
end

victory_sound(bon) do var i, j;
	for (i=0, 160+bon*40, 40)
		for (j=400, 660, 55)
			x.beep(i+j, 50);
end

draw_fort(x) do
	if (Shield < 0) return;
	x.fg(Hit-> 11: 14);
	if (Hit) Hit := Hit-1;
	x.blit(x-1, 21, Fort1);
	x.blit(x,   21, Fort2);
	x.blit(x+1, 21, Fort3);
end

draw_invaders() do var i, j;
	x.fg(13);
	for (j=0, ROWS) do
		for (i=0, COLS) do
			if (Invmap[j][i])
				x.blit(i*4+Xinv, j*3+Yinv,
					Invstate-> Inv1: Inv2);
		end
	end
end

drop_bomb() do var i, j;
	if (Xbmb) return;
	Xbmb := x.rnd(COLS);
	for (i=0, COLS) do
		for (j=ROWS-1, %1, %1)
			if (Invmap[j][Xbmb] \= 0) do
				Ybmb := j;
				leave;
			end
		if (j > %1) leave;
		Xbmb := Xbmb + 1;
		if (Xbmb > COLS-1) Xbmb := 0;
	end
	Xbmb := Xbmb*4+Xinv + x.rnd(2);
	Ybmb := 2+Ybmb*3+Yinv;
	Bmbclk := Bmbspeed;
end

move_invaders() do var i, j, x0, x1;
	x0 := %1;
	x1 := 0;
	for (i=0, COLS) do
		for (j=0, ROWS) do
			if (Invmap[j][i]) do
				if (x0 = %1) x0 := i;
				x1 := i;
			end
		end
	end
	if (Invclk = 0) do
		Xinv := Xinv + Invdir;
		if (Xinv > 29-x1*4) do
			Invdir := %1;
			Xinv := 29-x1*4;
			Yinv := Yinv + 1;
		end
		if (Xinv < 1-x0*4) do
			Invdir := 1;
			Xinv := 1-x0*4;
			Yinv := Yinv + 1;
		end
		Invclk := Invspeed;
		invader_sound(Invstate);
		drop_bomb();
		Invstate := \Invstate;
	end
end

explode_invader(x, y) do var xi, yi, i;
	xi := (x - Xinv) / 4;
	yi := (y - Yinv) / 3;
	Invmap[yi][xi] := 0;
	x.fg(15);
	x.blit(xi*4+Xinv, yi*3+Yinv, Boom);
	inv_expl_sound();
	Xrkt := 0;
	Kills := Kills + 1;
	Score := Score + 100;
	if (Kills mod 6 = 0) do
		Invspeed := Invspeed - 1;
		if (Invspeed < MAXSPEED /\ Kills < 23)
			Invspeed := MAXSPEED;
	end
	if (Kills = 23)
		Invspeed := LASTSPEED;
end

move_rocket() do
	Yrkt := Yrkt-1;
	if (x.screen(Xrkt, Yrkt)) explode_invader(Xrkt, Yrkt);
	if (Yrkt < 3) Xrkt := 0;
	x.fg(15);
	if (Xrkt \= 0)
		x.blit(Xrkt, Yrkt, Rocket);
end

explode_fort() do var i;
	Shield := %1;
	for (i=0, 200) do
		x.fg(9+x.rnd(7));
		x.blit(Xfort-1, 20, Exp1);
		x.blit(Xfort+1, 20, Exp2);
		fort_expl_sound(i);
		x.redraw();
	end
	x.delay(100);
end

hit_fortress() do
	Hit := 10;
	Hits := Hits + 1;
	Shield := Shield - 1;
	if (Shield < 0) explode_fort();
end

move_bomb() do
	Bmbclk := Bmbclk-1;
	if (Bmbclk = 0) do
		Bmbclk := Bmbspeed;
		Ybmb := Ybmb+1;
		if (Ybmb > 21) do
			if (Xbmb >= Xfort-1 /\ Xbmb <= Xfort+1)
				hit_fortress();
			Xbmb := 0;
		end
	end
	x.fg(11);
	if (Xbmb \= 0)
		x.blit(Xbmb, Ybmb, Bomb);
end

draw_status() do var i;
	x.fg(13);
	x.line(0, 182, 255, 182);
	x.fg(11);
	x.print(0, 23, "Level:");
	x.print(6, 23, fixed_ntoa(Level, 2));
	x.print(9, 23, "Shld:");
	for (i=0, 5) x.blit(i+14, 23, 14);
	for (i=0, Shield) x.blit(i+14, 23, 16);
	x.print(20, 23, "Score:");
	x.print(26, 23, fixed_ntoa(Score, 6));
end

really_quit() do
	Quit := ask("really quit");
	return Quit;
end

get_ready() do var i;
	for (i=0, 5) do
		x.fg(0);
		x.bg(14);
		x.print(10, 16, "get ready");
		x.redraw();
		x.delay(250);
		x.fg(14);
		x.bg(0);
		x.print(10, 16, "get ready");
		x.redraw();
		x.delay(250);
	end
	return %1;
end

round(spd) do var i, ready;;
	ready := 0;
	initround(spd);
	x.clrscr();
	while (1) do
		draw_fort(Xfort);
		move_invaders();
		draw_invaders();
		draw_status();
		if (Xrkt \= 0) move_rocket();
		if (Xbmb \= 0) move_bomb();
		if (Quit /\ really_quit()) return;
		if (Shield < 0) return;
		if (\ready) ready := get_ready();
		if (Kills > 23) return;
		x.redraw();
		for (i=0, 32, 2)
			if (x.screen(i, 21) = Inv1 \/ x.screen(i, 21) = Inv2)
			do
				explode_fort();
				return;
			end
		x.delay(PAUSE);
		if (Hit = 10 /\ Shield >= 0) hit_sound();
		ie (x.keydown(2) /\ Xfort < 30) do
			Xfort := Xfort+1;
		end
		else ie (x.keydown(4) /\ Xfort > 1) do
			Xfort := Xfort-1;
		end
		else ie (x.keydown('a') /\ Xrkt = 0) do
			launch_sound();
			Xrkt := Xfort;
			Yrkt := 21;
		end
		else ie (x.keydown('p') \/ x.keydown('\s')) do
			post("paused");
			x.getkey();
		end
		else if (x.keydown(27)) do
			Quit := %1;
		end
		Invclk := Invclk-1;
		x.clrscr();
	end
end

list_scores(w) do var i, j, b::10, s;
	for (j=0, 22)
		for (i=0, 32)
			x.blit(i, j, '\s');
	x.fg(14);
	x.print(10, 5,  "HIGH SCORES");
	for (i=0, 10) do
		s := fixed_ntoa(i+1, 2);
		if (s::0 = '0') s::0 := '\s';
		x.print(8, 7+i, s);
		x.print(10, 7+i, ".");
		t.memcopy(@Hiscores::(9*i), b, 3);
		b::3 := 0;
		x.print(12, 7+i, b);
		t.memcopy(@Hiscores::(3+9*i), b, 6);
		b::6 := 0;
		x.print(17, 7+i, b);
	end
	if (w) do
		x.redraw();
		x.getkey();
	end
end

loadhi() do var f, i;
	f := t.open("invaders.hi", 0);
	if (f < 0) do
		for (i=0, 10)
			t.memcopy("...000000", @Hiscores::(9*i), 9);
		Hiscores::90 := '\n';
		return;
	end
	t.read(f, Hiscores, 91);
	t.close(f);
end

savehi() do var f;
	f := t.create("invaders.hi");
	if (f < 0) return;
	t.write(f, Hiscores, 91);
	t.close(f);
end

printhi() do var i, j, n::4, p;
	n::3 := 0;
	p := 0;
	for (i=0, 2) do
		for (j=0, 5) do
			x.print(i*6+20, j+12, ntoa((p+1) mod 10));
			t.memcopy(@Hiscores::(9*p), n, 3);
			x.print(i*6+22, j+12, n);
			p := p+1;
		end
	end
end

enter_hiscore() do var i, b::10, j, k, p;
	for (p=0, 10) do
		if (Score > aton(@Hiscores::(3+9*p)))
			leave;
	end
	if (p = 10) return;
	t.memcopy(@Hiscores::(9*p), @Hiscores::(9+9*p), 81-9*p);
	t.memcopy(fixed_ntoa(Score, 6), @Hiscores::(9*p+3), 6);
	t.memcopy("...", @Hiscores::(9*p), 3);
	list_scores(0);
	str.copy(b, "...");
	j := 0;
	while (1) do
		x.fg(0);
		x.bg(14);
		x.blit(12+j, 7+p, b::j);
		x.fg(14);
		x.bg(0);
		x.redraw();
		k := x.getkey();
		ie ('a' <= k /\ k <= 'z') do
			b::j := k;
			x.blit(12+j, 7+p, k);
			j := j+1;
			if (j > 2) j := 0;
		end
		else ie (k = 8) do
			x.blit(12+j, 7+p, b::j);
			j := j-1;
			if (j < 0) j := 2;
		end
		else if (k = 13) do
			leave;
		end
	end
	t.memcopy(b, @Hiscores::(9*p), 3);
	savehi();
end

game_over() do var i, j, k, n, x;
	k := str.length(Gameover[0]);
	x := 16-k/2;
	for (n=0, 6) do
		x.fg(0);
		for (j=0, 15) do
			for (i=0, k)
				x.blit(i+x, 3+j, 0);
		end
		x.redraw();
		x.delay(167);
		x.fg(14);
		for (j=0, 15) do
			for (i=0, k)
				x.blit(i+x, 3+j, Gameover[j]::i='#'-> 16: 12);
		end
		x.redraw();
		x.delay(167);
	end
	x.delay(1000);
	enter_hiscore();
end

play_again() return ask("play again");

next_round() do var i, j, y, b::32, bon;
	y := 7;
	x.fg(14);
	bon := 0;
	ie (Hits = 0) do
		center(y, "perfect defense: +500");
		Score := Score + 500;
		bon := bon + 1;
	end
	else if (Hits < 4) do
		str.copy(b, " defense bonus: +");
		str.append(b, ntoa(400-Hits*100));
		center(y, b);
		Score := Score + 400-Hits*100;
		bon := bon + 1;
	end
	y := y+2;
	ie (Shield = 5) do
		center(y, "full shields: +500");
		Score := Score + 1000;
		y := y + 2;
		bon := bon + 1;
	end
	else do
		Shield := Shield + 1;
	end
	x.fg(0);
	x.bg(14);
	str.copy(b, "score: ");
	str.append(b, ntoa(Score));
	center(y, b);
	x.fg(14);
	x.bg(0);
	y := y+2;
	Gamespeed := Gamespeed - 1;
	if (Gamespeed < 1) Gamespeed := 1;
	print(4, y, "press [enter] to continue");
	x.redraw();
	victory_sound(bon);
	x.delay(2000);
	while (x.getkey() \= 13)
		;
end

play() do
	initgame();
	while (1) do
		round(Gamespeed);
		if (Quit) leave;
		ie (Shield < 0) do
			game_over();
			if (\play_again()) leave;
			initgame();
		end
		else do
			next_round();
		end
	end
end

intro() do var i, j, k, n, v, m, b::30;
	loadhi();
	m := [ 00,23,24,19,22,18,30,25,21,29,20,26,17,28,27,16 ];
	n := str.length(Logo[0]);
	while (1) do
		x.bg(0);
		for (k=0, 50) do
			x.clrscr();
			x.fg(k=49-> 14: x.rnd(7)+9);
			for (j=0, 12, 2) do
				for (i=0, n, 2) do
					v := (Logo[j]::i       = '#'-> 8: 0) +
			     		(Logo[j]::(i+1)   = '#'-> 4: 0) +
			     		(Logo[j+1]::(i)   = '#'-> 2: 0) +
			     		(Logo[j+1]::(i+1) = '#'-> 1: 0);
					x.blit(5+i/2, 3+j/2, m[v]);
				end
			end
			x.fg(14);
			str.copy(b, "high score: [");
			t.memcopy(@Hiscores::3, @b::13, 6);
			t.memcopy("]", b::19, 2);
			print(7, 10, b);
			print(1, 12, "[<-] [->] move turret");
			print(1, 14, "[a] fire    [p] pause");
			print(1, 16, "  [esc] quit game");
			print(6, 20, "press [enter] to start");
			printhi();
			x.delay(20);
			x.redraw();
		end
		k := x.getkey();
		if (k = 13) play();
		if (k = 27) leave;
		if (k = 's') list_scores(1);
	end
end

do
	init();
	intro();
	x.fini();
end

contact