use t3x: t;
use console: con;
use rng;

const	COLS	= 80,
	LINES	= 23;

const	SPEEDUP	= 100;

const	SMOOTHNESS	= 5;

const	NMISSILES = 3;

var	Sx, Sy;

var	Types::COLS;
var	Objects::COLS, Top::COLS, Bottom::COLS;
var	Topseed, Botseed;

var	Speed, Tick;

var	Shields, Missiles, Credits;
var	Flash;
var	Fired::NMISSILES, Mx::NMISSILES, My::NMISSILES;

drawship() do
	con.move(Sx, Sy);   con.writes("|\\=- ");
	con.move(Sx, Sy+1); con.writes(">=o==");
	con.move(Sx, Sy+2); con.writes("|/=- ");
end

drawflash() do
	con.move(Sx, Sy);   con.writes("&&&& ");
	con.move(Sx, Sy+1); con.writes("&&&&&");
	con.move(Sx, Sy+2); con.writes("&&&& ");
end

var	Ntoa_buf::10;

ntoa(n, k) do var i, j;
	i := 9;
	Ntoa_buf::9 := 0;
	while (n \/ i = 9) do
		i := i - 1;
		Ntoa_buf::i := n mod 10 + '0';
		n := n ./ 10;
	end
	for (j=9-i, k) do
		i := i - 1;
		Ntoa_buf::i := '0';
	end
	return @Ntoa_buf::i;
end

status() do var i, j;
	con.move(0, LINES);
	con.clreol();
	con.writes("##---...____...---''''---=====[[[  SCORE ");
	con.writes(ntoa(Credits, 5));
	con.writes("  ]]]=====---...____...---''''---");
	con.move(0, 1);
	con.standout();
	con.writes(ntoa(Missiles, 2));
	con.normal();
	for (i=0, Shields) do
		for (j=1, 4) do
			con.move(0, LINES-i*4-j);
			con.writes("&&");
		end
		con.move(0, LINES-i*4-4);
		con.writes("==");
	end
	for (i=Shields, 5) do
		for (j=1, 4) do
			con.move(0, LINES-i*4-j);
			con.writes("..");
		end
		con.move(0, LINES-i*4-4);
		con.writes("==");
	end
end

scrollset(coords, obj) do var i;
	for (i = 2, COLS) do
		if (coords::i \= 255 /\ (obj \/ Types::i)) do
			ie (i > 2) do
				con.move(i-1, coords::i);
				con.wrch(obj-> obj: Types::i);
				con.wrch('\s');
				coords::(i-1) := coords::i;
				if (\obj) Types::(i-1) := Types::i;
				coords::i := 255;
			end
			else do
				con.move(2, coords::i);
				con.wrch('\s');
			end
		end
	end
end

scroll() do var objs;
	scrollset(Objects, 0);
	Tick := Tick + 1;
	if (Tick mod Speed = 0) do
		Objects::(COLS-1) :=
			rng.rand(Botseed - Topseed - 2) + Topseed + 1;
		Types::(COLS-1) := '@';
		if (rng.rand(100) > 90) do
			objs := packed [ '%', '&', '*' ];
			Types::(COLS-1) := objs::rng.rand(3);
		end
		con.move(COLS-1, Objects::(COLS-1));
		con.wrch(Types::(COLS-1));
	end
	if (Tick mod 200 = 0) do
		Credits := Credits + 100;
		status();
	end
	scrollset(Top, '#');
	Top::(COLS-1) := Topseed;
	con.move(COLS-1, Top::(COLS-1));
	con.wrch('#');
	scrollset(Bottom, '#');
	Bottom::(COLS-1) := Botseed;
	con.move(COLS-1, Bottom::(COLS-1));
	con.wrch('#');
	if (Tick mod SMOOTHNESS = 0) do
		Topseed := Topseed + rng.rand(3)-1;
		if (Topseed < 0) Topseed := 0;
		if (Topseed > 5) Topseed := 5;
		Botseed := Botseed + rng.rand(3)-1;
		if (Botseed > LINES-1) Botseed := LINES-1;
		if (Botseed < LINES-5) Botseed := LINES-5;
	end
end

collision_in(coords) do var i, j;
	for (i=Sx, Sx+4)
		for (j=Sy, Sy+3)
			if (coords::i = j) return i;
	if (Coords::(Sx+4) = Sy+1) return Sx+4;
end

collision() do var x, obj;
	x := collision_in(Objects);
	if (x) do
		obj := Types::x;
		Types::x := 0;
		drawship();
		return obj;
	end
	x := collision_in(Top);     if (x) return %1;
	x := collision_in(Bottom);  if (x) return %1;
end

shipup() do
	if (Sy = 0) return;
	con.move(Sx,Sy+2);
	con.writes("\s\s\s\s\s");
	Sy := Sy - 1;
	drawship();
end

shipdown() do
	if (Sy >= LINES-3) return;
	con.move(Sx,Sy);
	con.writes("\s\s\s\s\s");
	Sy := Sy + 1;
	drawship();
end

disp(x, y, c) do
	if (x >= 0 /\ x < COLS /\ y >= 0 /\ y < LINES) do
		con.move(x, y);
		con.wrch(c);
	end
end

star(x, y, c) do var i;
	for (i=0, 10) do
		disp(x+i, y+i, c);
		disp(x-i, y+i, c);
		disp(x+i, y-i, c);
		disp(x-i, y-i, c);
		disp(x,   y+i, c);
		disp(x,   y-i, c);
		disp(x+i, y,   c);
		disp(x-i, y,   c);
		con.move(COLS-1, LINES);
		con.wait(100);
	end
end

boom() do
	star(Sx+2, Sy+1, '@');
	star(Sx+2, Sy+1, '\s');
end

collect(n) do
	Flash := 2;
	Credits := Credits + n;
	status();
end

shieldsup() do
	Flash := 2;
	if (Shields = 5) return collect(200);
	Shields := Shields + 1;
	status();
end

firepower() do
	Flash := 2;
	if (Missiles > 99) return collect(150);
	Missiles := Missiles + 5;
	status();
end

fire() do var i;
	if (Missiles = 0) return;
	for (i=0, NMISSILES)
		if (\Fired::i) leave;
	if (i = NMISSILES) return;
	Mx::i := Sx + 4;
	My::i := Sy + 1;
	Fired::i := %1;
	Missiles := Missiles - 1;
	status();
end

movemsl(n) do var mx0, mx1, my0;
	mx0 := Mx::n+1;
	mx1 := mx0 + 1;
	my0 := My::n;
	if (	Top::mx0 = my0 \/ Bottom::mx0 = my0 \/
		Top::mx1 = my0 \/ Bottom::mx1 = my0 \/
		mx0 >= COLS-1)
	do
		con.move(mx0, my0);
		con.wrch('\s');
		Fired::n := 0;
		return;
	end
	if (Objects::mx0 = my0) do
		Objects::mx0 := 255;
		Fired::n := 0;
		con.move(mx0, my0);
		con.writes("\s\s");
		return;
	end
	if (Objects::mx1 = my0) do
		Objects::mx1 := 255;
		Fired::n := 0;
		con.move(mx0, my0);
		con.writes("\s\s");
		return;
	end
	con.move(mx0, my0);
	con.writes(" -");
	Mx::n := Mx::n + 1;
end

movemsls() do var i;
	for (i=0, NMISSILES)
		if (Fired::i)
			movemsl(i);
end

setup() do
	t.memfill(Objects, 255, COLS);
	t.memfill(Top, 255, COLS);
	t.memfill(Bottom, 255, COLS);
	t.memfill(Fired, 0, NMISSILES);
	Sx := 15;
	Sy := 10;
	Speed := 10;
	Tick := 0;
	Topseed := 0;
	Botseed := LINES-1;
	Shields := 1;
	Missiles := 5;
	Credits := 0;
	Flash := 0;
end

play() do var k, obj;
	setup();
	drawship();
	status();
	while (%1) do
		scroll();
		if (Flash) do
			ie (Flash & 1)
				drawship();
			else
				drawflash();
			Flash := Flash - 1;
		end
		movemsls();
		con.move(COLS-1, LINES);
		con.wait(100);
		k := con.pollkey();
		     ie (k = 'q') leave;
		else ie (k = 'k') shipup();
		else ie (k = 'j') shipdown();
		else ie (k = 'a') fire();
		else ;
		rng.reseed(k);
		obj := collision();
		     ie (obj = '%') collect(100);
		else ie (obj = '&') shieldsup();
		else ie (obj = '*') firepower();
		else if (obj) do
			if (\Shields) do
				boom();
				leave;
			end
			Flash := 6;
			Shields := Shields - 1;
			status();
		end
		if (Tick mod SPEEDUP = 0 /\ Speed > 2)
			Speed := Speed - 1;
	end
end

init() do
	con.setup([24,
		   "\e[H\e[J",
		   "\e[K",
		   "\e[%yd1;%xd1H",
		   "\eM",
		   "\e[?25l",
		   "\e[?25h",
		   "\e[7m",
		   "\e[m"]);
	con.csroff();
	con.clrscr();
end

fini() do
	con.move(0, LINES);
	con.csron();
	con.shutdown();
end

do
	init();
	play();
	fini();
end
