--
-- Copyright (C) 2021  <fastrgv@gmail.com>
--
-- 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 3 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 may read the full text of the GNU General Public License
-- at <http://www.gnu.org/licenses/>.
--

--with tput00_h;
with cls_h;

with ada.directories;
with System;
with Interfaces.C;
use  type interfaces.c.unsigned;
with Interfaces.C.Pointers;
with interfaces.c.strings;

with ada.unchecked_conversion;
with Ada.Command_Line;
with Ada.Strings.Unbounded;
with Ada.Strings.Unbounded.Text_IO;
with ada.numerics.generic_elementary_functions;
with ada.numerics.discrete_random;

with text_io;

with ada.calendar;

with ada.strings.fixed;

with sysutils;

with GNATCOLL.Terminal;  use GNATCOLL.Terminal;

with Interfaces.C; use Interfaces.C;
with snd4ada;
with realtime;

with gnat.os_lib;



procedure cpac is

	subtype dirtype is integer range 1..4;
	package random_dir is new ada.numerics.discrete_random(dirtype);
	gen : random_dir.generator;

-- this is how we randomize:
-- random_dir.reset(gen); --time-dependent randomization


	use Ada.Strings.Unbounded;
	use Ada.Strings.Unbounded.Text_IO;
	use text_io;
	use interfaces.c;
	use interfaces.c.strings;

	use ada.calendar;

	mswin: constant boolean := (gnat.os_lib.directory_separator='\');


	package fmath is new
			Ada.Numerics.generic_elementary_functions( float );
	use fmath;

	package myint_io is new text_io.integer_io(integer);


	eks, endgame, collisionpause, 
		userpause, userexit : boolean := false;


	tick : duration := 0.15; -- default
	-- seconds between pacman moves...
	-- critical gameplay setting...
	-- reduce for faster gameplay
	--
	-- 5dec17:  now adjustable using command line
	-- 0=slow .. 5=medium=default .. 9=fast

	levelpause : constant duration := 3.0; -- seconds between levels

	pausehalf : constant duration := 0.5;
	pause1sec : constant duration := 1.0;
	pause3sec : constant duration := 3.0;



	powerfade: constant integer := 55; --22; 
	--steps of invincibility after powerup

	freelife : integer := 1000;
	points   : integer := 0; --score
	lives    : integer := 3;
	howslow  : constant integer := 3;


	--In the interest of making ghosts slightly slower
	--than Pacman, use this adjustable difficulty factor...
	--0=stopped, 2=easy, 5=medium, 9=hard, 
	ghostLimit: integer := 2; -- ghost pause every k-steps
	limitghost: integer := 0;


	-- requires windowsize at least 57x35:
	level_width : constant integer := 28;
	level_height : constant integer := 29;

	nobj : constant integer := 5; -- 4ghosts + pacman


	numlev: constant integer := 9;
	shortname: constant array(1..numlev) of string(1..11) 
	:=(
		"level01.dat",
		"level02.dat",
		"level03.dat",
		"level04.dat",
		"level05.dat",
		"level06.dat",
		"level07.dat",
		"level08.dat",
		"level09.dat");

	levpath: constant string(1..6) := "../../";
	levdir: constant string(1..7) := "Levels/";

	leveldata : array(1..level_height,1..level_width) of integer
		:= (others=>(others=>0));

	loc : array(1..nobj,1..2) of integer --location ghosts/pacman
		:= (others=>(others=>0));
		
	dir : array(1..nobj,1..2) of integer --H,V-direction ghosts/pacman
		:= (others=>(others=>0));

	startingpoints : array(1..nobj,1..2) of integer --location ghosts/pacman
		:= (others=>(others=>0));

	invincible, food, levelnumber, ghostsinarow, tleft
	: integer := 0;


	pending: boolean := false;

	arcade_loop,
	gameover, 
	go, death, eatfruit, eatghost,
	rtime, stun, beep : interfaces.c.int;




procedure myassert( condition : boolean;  flag: integer:=0 ) is
begin
  if condition=false then
  		put("ASSERTION Failed!  ");
		if flag /= 0 then
			put_line( "@ " & integer'image(flag) );
		end if;
		new_line;
  		raise program_error;
  end if;
end myassert;







procedure loadlevel( lev: integer ) is
	fileid : text_io.file_type;
	ini, nlev: integer;
begin

	-- reset defaults
	dir(1,1):= 1;  dir(1,2):= 0;
	dir(2,1):=-1;  dir(2,2):= 0;
	dir(3,1):= 0;  dir(3,2):=-1;
	dir(4,1):= 0;  dir(4,2):= 1;
	dir(5,1):= 0;  dir(5,2):= 0;

	--text_io.open(fileid,text_io.in_file,levdir&shortname(lev));

	if ada.directories.Exists( levdir&shortname(lev) ) then
	text_io.open(fileid,text_io.in_file, levdir&shortname(lev) );
	else
	text_io.open(fileid,text_io.in_file, levpath&levdir&shortname(lev) );
	end if;




	for row in 1..level_height loop
	for col in 1..level_width loop
		myint_io.get(fileid, ini);
		leveldata(row,col):=ini;

		-- 0 => normal
		-- 1 => wall
		-- 2 => pellet
		-- 3 => powerup
		-- 4 => ghostwall
		-- 5 =>
		-- 6 =>
		-- 7 =>
		-- 8 =>
		-- 9 =>

		if ini=2 then food:=food+1; end if;

		if ini>=5 and ini<=9 then
			loc(ini-4,1):=row;
			loc(ini-4,2):=col;
			leveldata(row,col):=0;
		end if;


	end loop;
	end loop;
	myint_io.get(fileid, nlev);
	myassert( nlev = lev );
	text_io.close(fileid);

	for a in 1..nobj loop
		startingpoints(a,1):=loc(a,1);
		startingpoints(a,2):=loc(a,2);
	end loop;
	
end loadlevel;









procedure draw ( nextime: in out Time;  done : boolean := false );


procedure checkcollision is
begin

for a in 1..4 loop -- check each ghost (pacman: a=5)

	if 

		( loc(a,1)=loc(5,1) and loc(a,2)=loc(5,2) )  --g-cell versus current p-cell
		or 
		( loc(a,1)=loc(5,1)+dir(5,1) and loc(a,2)=loc(5,2)+dir(5,2) ) --next p-cell
		or
		( loc(a,1)+dir(a,1)=loc(5,1) and loc(a,2)+dir(a,2)=loc(5,2) )  --next g-cell

	then --ghost a collides w/pacman

		if invincible=1 then

			snd4ada.playSnd(eatghost); -- ghost dies
			points := points + ghostsinarow*20;
			ghostsinarow := ghostsinarow*2;

			--reset ghost pos
			loc(a,1):=startingpoints(a,1);
			loc(a,2):=startingpoints(a,2);

		else -- pacman is vulnerable, dies

			collisionpause:=true;
			pending:=false; --empty move queue
			dir(5,1):= 0;  dir(5,2):= 0; --pacman stops

			--perhaps adjust display from "C" to "X"
			lives:=lives-1;
			eks:=true; --pacman becomes an X

			if lives=-1 then 
				userexit:=true;
				endgame:=true;

				--snd4ada.stopLoops; --background
				snd4ada.playSnd(death); --death

			else --game not over...

				snd4ada.playSnd(stun); --stunned, extra pacman


				--for a in 1..5 loop
				for a in 1..4 loop -- lets leave pacman where he is
					loc(a,1):= startingpoints(a,1);
					loc(a,2):= startingpoints(a,2);
				end loop;

				-- reset ghost defaults
				dir(1,1):= 1;  dir(1,2):= 0;
				dir(2,1):=-1;  dir(2,2):= 0;
				dir(3,1):= 0;  dir(3,2):=-1;
				dir(4,1):= 0;  dir(4,2):= 1;

			end if;
		end if; --invincible
	end if;

end loop; -- for each ghost

end checkcollision;







procedure moveghosts is
	checksides : array(1..6) of integer := (others=>0);
	b,c, slowerghosts, pr,pc, row,col, rdir,cdir, tmp : integer := 0;
	goghost: boolean := true;
begin

	limitghost:=limitghost+1;
	if ( limitghost >= (ghostLimit+1) ) then
		goghost:=false; --pause ghost this tick only
		limitghost:=0;
	else
		goghost:=true;
	end if;


	if invincible=1 then
		slowerghosts:=slowerghosts+1;
		if slowerghosts>howslow then slowerghosts:=0; end if;
	end if;

if 
		( invincible=0 or slowerghosts<howslow ) 
			and 
		goghost
then

	pr:=loc(5,1);  pc:=loc(5,2); --pacman

	for a in 1..4 loop -- thru each ghost

		row:=loc(a,1);  rdir:=dir(a,1);
		col:=loc(a,2);  cdir:=dir(a,2);

		--ghost switches sides per toroidal universe:
		if    row=1 and dir(a,1)=-1 then loc(a,1):=level_height;
		elsif row=level_height and dir(a,1)=1 then loc(a,1):=1;
		elsif col=1 and dir(a,2)=-1 then loc(a,2):=level_width;
		elsif col=level_width and dir(a,2)=1 then loc(a,2):=1;

		else --ghost within normal area

			for b in 1..4 loop checksides(b):=0; end loop;

			if row=level_height then tmp:=1; else tmp:=row+1; end if;
			if leveldata(tmp,col) /= 1 then checksides(1):=1; end if;

			if row=1 then tmp:=level_height; else tmp:=row-1; end if;
			if leveldata(tmp,col) /= 1 then checksides(2):=1; end if;

			if col=level_width then tmp:=1; else tmp:=col+1; end if;
			if leveldata(row,tmp) /= 1 then checksides(3):=1; end if;

			if col=1 then tmp:=level_width; else tmp:=col-1; end if;
			if leveldata(row,tmp) /= 1 then checksides(4):=1; end if;

			-- dont do 180 unless we have to
			c:=0; 
			for b in 1..4 loop 
				if checksides(b)=1 then c:=c+1; end if; 
			end loop;

			if c>1 then
				if    rdir= 1 then checksides(2):=0;
				elsif rdir=-1 then checksides(1):=0;
				elsif cdir= 1 then checksides(4):=0;
				elsif cdir=-1 then checksides(3):=0;
				end if;
			end if; --c>1

			c:=0;
			loop

				b := random_dir.random(gen); -- 1..4


				-- tend to mostly chase pacman if he is vulnerable, or else run away
				if checksides(b)=1 then
					if    b=1 then dir(a,1):= 1; dir(a,2):= 0;
					elsif b=2 then dir(a,1):=-1; dir(a,2):= 0;
					elsif b=3 then dir(a,1):= 0; dir(a,2):= 1;
					elsif b=4 then dir(a,1):= 0; dir(a,2):=-1; end if;
				else
					if invincible=0 then --chase pacman

						if    pr>row and checksides(1)=1 then
							dir(a,1):= 1; dir(a,2):= 0; c:=1;
						elsif pr<row and checksides(2)=1 then
							dir(a,1):=-1; dir(a,2):= 0; c:=1;
						elsif pc>col and checksides(3)=1 then
							dir(a,1):= 0; dir(a,2):= 1; c:=1;
						elsif pc<col and checksides(4)=1 then
							dir(a,1):= 0; dir(a,2):=-1; c:=1;
						end if;

					else -- runaway from pacman

						if    pr>row and checksides(2)=1 then
							dir(a,1):=-1; dir(a,2):= 0; c:=1;
						elsif pr<row and checksides(1)=1 then
							dir(a,1):= 1; dir(a,2):= 0; c:=1;

						elsif pc>col and checksides(4)=1 then
							dir(a,1):= 0; dir(a,2):=-1; c:=1;
						elsif pc<col and checksides(3)=1 then
							dir(a,1):= 0; dir(a,2):= 1; c:=1;
						end if;

					end if;
				end if;

				exit when checksides(b)/=0 or c/=0;
			end loop;

			--move ghost
			loc(a,1):= loc(a,1)+dir(a,1);
			loc(a,2):= loc(a,2)+dir(a,2);

		end if;


	end loop; -- for a

end if; --invincible=0 or...


end moveghosts;






itime : integer := 0;

procedure movepacman is
	prow,pcol, prdir,pcdir, posval : integer;
begin

	prow:=loc(5,1); prdir:=dir(5,1);
	pcol:=loc(5,2); pcdir:=dir(5,2);

--switch sides per toroidal universe
if    prow=1 and prdir=-1 then loc(5,1):=level_height;
elsif prow=level_height and prdir=1 then loc(5,1):=1;
elsif pcol=1 and pcdir=-1 then loc(5,2):=level_width;
elsif pcol=level_width  and pcdir=1 then loc(5,2):=1;

else -- move pacman normally

	-- tentative update:
	prow:=prow+dir(5,1);
	pcol:=pcol+dir(5,2);
	posval:=leveldata(prow,pcol);
		-- 0 => normal
		-- 1 => wall
		-- 2 => pellet
		-- 3 => powerup, fruit
		-- 4 => ghostwall


	-- if hitting wall, move back
	if posval=1 or posval=4 then
		null;
	else
		--proceed with update
		loc(5,1):=prow;
		loc(5,2):=pcol;
	end if;

end if;

	posval:=leveldata( loc(5,1), loc(5,2) );
	if posval=2 then --pellet

		snd4ada.playSnd(beep);

		leveldata( loc(5,1), loc(5,2) ) := 0;
		points := points+1;
		food := food-1;

	elsif posval=3 then --powerup, eat fruit

		snd4ada.playSnd(eatfruit);

		leveldata( loc(5,1), loc(5,2) ) := 0;
		invincible:=1;
		if ghostsinarow=0 then ghostsinarow:=1; end if;
		itime:=powerfade-levelnumber;
	end if;

	
	if invincible=1 then
		itime:=itime-1;
	end if;

	-- has invincibility expired yet?
	if itime<1 then
		invincible:=0;
		ghostsinarow:=0;
	end if;


end movepacman;






procedure draw ( nextime: in out Time;  done : boolean := false ) is

	info: terminal_info;

	chr: character;
	val, pr,pc, row,col: integer;
	Ok: boolean;

	-- According to gnatcoll-terminal.ads
	-- these are all the available colors:
	-- m=magenta, y=yellow, r=red, g=grey, 
	-- b=blue, k=black, n=green, c=cyan
	type enum is (m,y,r,g,b,k,n,c,w,x); -- x => not yet set
	colr : enum := x;


begin

	info.init_for_stdout(auto);

	if mswin then
		-- put cursor @ UL
		--ret:=tput00_h.tput00;
		--tput00_h.cursorHome;
		cls_h.cursorHome;

	else --osx or linux

		--this way is fast but does NOT erase:
		--SysUtils.bShell("tput cup 0 0", Ok); -- put cursor @ UL

		--I like the erase function better:
		SysUtils.bShell("clear", Ok); -- put cursor @ UL

	end if;

   --Info.Set_Color (style=>bright); --this works too

   Info.Set_Style (style=>bright);
	Info.Set_Bg(black);
	colr:=x;

	for rr in 1..level_height loop
		for cc in 1..level_width loop
			-- we cycle thru all screen positions...

			val:=leveldata(rr,cc);
			case val is
				when 0 => 
					chr:=' ';
				when 1 => 
					chr:='#'; --wall
					if colr/=b then
						info.set_fg(blue); colr:=b;
					end if;
				when 2 => 
					chr:='.'; --food
					if colr/=g then
						info.set_fg(grey); colr:=g;
					end if;
				when 3 => 
					chr:='*'; --powerup
					if colr/=g then
						info.set_fg(green); colr:=n;
					end if;
				when 4 => 
					chr:='+'; --ghostwall
					if colr/=c then
						info.set_fg(cyan); colr:=c;
					end if;
				when others => 
					null;
			end case;

			pr:=loc(5,1);
			pc:=loc(5,2);
			if pr=rr and pc=cc then -- pacman @ screenPos

				if colr/=y then
					info.set_fg(yellow); colr:=y;
				end if;

				if eks or endgame then
					chr:='X';
					eks:=false;
				else
					chr:='C';
				end if;
			else

-- remember... bkgd:black, walls:blue,
-- pacman:yellow, food:grey+green, ghostWall:cyan
-- (leaving only magenta, red to use for ghosts)

				--ghosts
				for k in 1..nobj-1 loop
					row:=loc(k,1);
					col:=loc(k,2);
					if row=rr and col=cc then -- ghost @ screenPos

						if invincible=0 then -- ghost is killer
							chr:='&';
							if colr/=r then
								info.set_fg(red); colr:=r;
							end if;
						else -- here, ghost is afraid
							chr:='$'; --'?' is another possible char
							if colr/=n then
								info.set_fg(magenta); colr:=m;
							end if;
						end if;

					end if;
				end loop;

			end if;

			put(" " & chr); --draw object @ screenPos

		end loop; -- col
		new_line;

	end loop; -- row
	new_line;


   Info.Set_Color (Standard_Output, Style => Reset_All);

	put_line(" FreeLife: "&integer'image(freelife));
	put_line(" Lives: "&integer'image(lives));
	put_line(" Level: "&integer'image(levelnumber));
	put_line(" Score: "&integer'image(points));
	if done then
		put_line(" Level Complete...loading next level.");
	elsif endgame then
		put_line(" Sorry, you lose!  Game Over.");
	elsif userpause then
		put_line(" Game Paused:  q=quit;  p= pause-toggle");
	else
		put_line("         q = quit        ");
	end if;

	if collisionpause then
		collisionpause:=false;
		if lives=-1 then
			nextime:=nextime+pause3sec;
		else
			--nextime:=nextime+pause1sec;
			nextime:=nextime+pausehalf;
		end if;
		delay until nextime;
	end if;

end draw;



function isvalid(ch: character) return boolean is
	ok: boolean := false;
begin
	case ch is
		when 'H'|'P'|'M'|'K' => ok:=true; --mswin
		when 'A'|'B'|'C'|'D' => ok:=true; --linux/osx
		when 'w'|'s'|'d'|'a' => ok:=true;
		when 'i'|'k'|'l'|'j' => ok:=true;
		when     'x'|'q'|'p' => ok:=true;
		when others => ok:=false;
	end case;
	return ok;
end;



procedure handle_key_down( ch: character; changed: out boolean ) is
	pr,pc, xr,xc : integer;
begin

	changed:=false;
	pr:=loc(5,1);
	pc:=loc(5,2);


-- note that arrow keys typically produce chars
-- preceded by 1 or 2 non-printable chars.
--
-- on Linux:		<home>='H'	<end>='F'
--   A		
-- D B C
--
-- or on MSWin:	<home>='G'	<end>='O'
--   H
-- K P M




	case ch is


		when 'H' | 'A' | 'w' | 'i'  => --up

			if pr<=1 then xr:=level_height; else xr:=pr-1; end if;
			if leveldata(xr,pc) /= 1 and leveldata(xr,pc) /=4 then
				dir(5,1):=-1; dir(5,2):=0;
				changed:=true;
			end if;


		when 'P' | 'B' | 's' | 'k' =>	--dn

			if pr>=level_height then xr:=1; else xr:=pr+1; end if;
			if leveldata(xr,pc) /= 1 and leveldata(xr,pc) /=4 then
				dir(5,1):=+1; dir(5,2):=0;
				changed:=true;
			end if;

		when 'K' | 'D' | 'a' | 'j' =>	--lf

			if pc<=1 then xc:=level_width; else xc:=pc-1; end if;
			if leveldata(pr,xc) /=1 and leveldata(pr,xc) /=4 then
				dir(5,1):=0; dir(5,2):=-1;
				changed:=true;
			end if;


		when 'M' | 'C' | 'd' | 'l' =>	--rt

			if pc>=level_width then xc:=1; else xc:=pc+1; end if;
			if leveldata(pr,xc) /=1 and leveldata(pr,xc) /=4 then
				dir(5,1):=0; dir(5,2):=+1;
				changed:=true;
			end if;


		when 'x' | 'q' =>	userexit:=true; changed:=true;
		when 'p' => userpause:=not userpause; changed:=true; -- toggle


		-- debug messages shown here are immediately erased
		-- unless we pause for user input...
		when others => null;

	end case;


end handle_key_down;



procedure initsounds( path: string ) is
begin

	snd4ada.initSnds;

	arcade_loop := snd4ada.initLoop(
		Interfaces.C.Strings.New_String(path&"arcade-loop.wav"));
	if arcade_loop<0 then
		put_line("snd4ada.initSnds ERROR arcade-loop");
		raise program_error;
	end if;


	death := snd4ada.initSnd(
		Interfaces.C.Strings.New_String(path&"death.wav"));
	if death<0 then
		put_line("snd4ada.initSnds ERROR death");
		raise program_error;
	end if;

	stun := snd4ada.initSnd(
		Interfaces.C.Strings.New_String(path&"extrapac.wav"));
	if stun<0 then
		put_line("snd4ada.initSnds ERROR stun");
		raise program_error;
	end if;

	eatghost := snd4ada.initSnd(
		Interfaces.C.Strings.New_String(path&"eatghost.wav"));
	if eatghost<0 then
		put_line("snd4ada.initSnds ERROR eatghost");
		raise program_error;
	end if;

	eatfruit := snd4ada.initSnd(
		Interfaces.C.Strings.New_String(path&"eatfruit.wav"));
	if eatfruit<0 then
		put_line("snd4ada.initSnds ERROR eatfruit");
		raise program_error;
	end if;

	go := snd4ada.initSnd(
		Interfaces.C.Strings.New_String(path&"begin.wav"));
	if go<0 then
		put_line("snd4ada.initSnds ERROR go");
		raise program_error;
	end if;

	gameover := snd4ada.initSnd(
		Interfaces.C.Strings.New_String(path&"game-over-02.wav"));
	if gameover<0 then
		put_line("snd4ada.initSnds ERROR gameover");
		raise program_error;
	end if;


	beep := snd4ada.initSnd(
		Interfaces.C.Strings.New_String(path&"munch_b.wav"));
	if beep<0 then
		put_line("snd4ada.initSnds ERROR beep");
		raise program_error;
	end if;



end initsounds;



	ich, qch: character;
	Ok, avail, digested : boolean := false;

	onextime : Time := clock;

	ispd : integer := 5;

	path0 : constant string(1..7)  := "sounds/";
	path1 : constant string(1..13) := "../../sounds/";


begin --pacman

	if mswin then
		rtime:=realtime.hiPriority;
		-- note:  this seems necessary because some, 
		-- but not all, windows commandline terminals 
		-- seem to randomly freeze at normal priority.
	else
		rtime:=1;
	end if;


	-- GameSpeed 0:slow, 9:fast
	ispd := 5; -- default

	-- GhostSpeed 0:stopped, 2:easy, 9:hard
	ghostLimit := 2; -- default

	if ada.command_line.argument_count = 2 then

		declare
			pstr : string := ada.command_line.argument(1);
			gstr : string := ada.command_line.argument(2);
			lst: natural;
		begin
			myint_io.get(pstr, ispd, lst);
			myint_io.get(gstr, ghostLimit, lst);
		end; -- declare

	elsif ada.command_line.argument_count = 1 then

		declare
			pstr : string := ada.command_line.argument(1);
			lst: natural;
		begin
			myint_io.get(pstr, ispd, lst);
		end; -- declare

	end if;

	tick := 0.19 - ispd*0.01; --fast=0.1 ... 0.19=slowestTick






	if ada.directories.Exists(path0) then
		initsounds(path0);
	else
		initsounds(path1);
	end if;





	--randomize
	random_dir.reset(gen); --time-dependent randomization


-- requires windowsize at least 57x35:

	-------------------------- outer levelnumber loop top -----------------
	for levelnumber in 1..numlev loop

		loadlevel(levelnumber);

		if mswin then
			SysUtils.bShell("cls", Ok); -- erase-terminal
		else
			SysUtils.bShell("clear", Ok); -- erase-terminal
		end if;



		draw(onextime);

		if levelnumber = 1 then
			put_line(" ...needs a window with 57 columns X 35 rows...");
		end if;


		snd4ada.playSnd(go); --Intro (5sec)

		-- begin countdown:
		put(" Ready... 5");
		for i in reverse 0..4 loop
			onextime:=onextime+pause1sec;  
			delay until onextime;
			put(integer'image(i));
		end loop;
		onextime:=onextime+pause1sec;  
		delay until onextime;

		snd4ada.playLoop(arcade_loop); --background loop


		-- to try and ameliorate primitive timing...
		-- insert ch into a pending position;
		-- keep trying to use it until either
		-- a) it gets used,
		-- b) or a new "ch" replaces it,
		-- c) or pacman loses a life.

		if mswin then
			SysUtils.bShell("cls", Ok); -- erase-terminal
		else
			SysUtils.bShell("clear", Ok); -- erase-terminal
		end if;



		pending:=false;
------------------- inner event loop top --------------------------
		while not userexit and food>0 loop

			avail:=false;

			if not userpause then
				movepacman;
				moveghosts; 
				checkcollision;
				if points>freelife then
					lives:=lives+1;
					freelife:=freelife*2;
				end if;
			end if;
			draw(onextime);

			-------------- enqueue turns begin -------------------------
			get_immediate(ich, avail);
			if avail and then isvalid(ich) then --enqueue new user command
				qch:=ich;
				pending:=true;
			end if;

			if pending then --deal with most recent user command
				handle_key_down(qch,digested);
				pending := not digested;  --feed again, if need be
			end if;

			-- this regulates normal timing:
			onextime:=onextime+tick;  delay until onextime; --update timer

			get_immediate(ich, avail);
			if avail and then isvalid(ich) then --enqueue new user command
				qch:=ich;
				pending:=true;
			end if;

			if pending then --deal with most recent user command
				handle_key_down(qch,digested);
				pending := not digested;  --feed again, if need be
			end if;
			-------------- enqueue turns end ----------------------------

		end loop;
------------------- inner event loop bottom -----------------------


		if mswin then
			SysUtils.bShell("cls", Ok); -- erase-terminal
		else
			SysUtils.bShell("clear", Ok); -- erase-terminal
		end if;


		exit when userexit or endgame; --quit

		exit when food>0; --Level Failure

		draw(onextime,done=>true); --Completed this Level

		if levelnumber < 9 then
			onextime:=onextime+levelpause;  
			delay until onextime; --update timer
		end if;

		snd4ada.stopLoops; --background

	end loop; -- outer for levelnumber
	------------------------ outer levelnumber loop bottom -----------------

	snd4ada.stopLoops; --background
	snd4ada.playSnd(gameover); --game over



	if mswin then
		SysUtils.bShell("cls", Ok); -- erase-terminal
	else
		SysUtils.bShell("clear", Ok); -- erase-terminal
	end if;



	put_line(" gameSpd="&integer'image(ispd));
	put_line("ghostSpd="&integer'image(ghostLimit));
	if mswin then
		if rtime=0 then
			put_line("RealTime achieved");
		else
			put_line("RealTime NOT achieved");
		end if;
	end if;

	delay 6.0;
	snd4ada.termSnds;


--		if mswin then
--			SysUtils.bShell("cls", Ok); -- erase-terminal
--		else
--			SysUtils.bShell("clear", Ok); -- erase-terminal
--		end if;


end cpac;

