

--
-- Copyright (C) 2023  <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/>.
--


-- 4tomb.adb ( 4x4x4 )
-- 19jan16: changing corners 7..8 to be simple, 
-- vertical legs : 1x2x1
-- to a table-shaped enclosure for the skull
-- to see if the result is a) solvable, and b) challenging.
--
-- Note:  centroids of 7,8 reference lower rearward cell
--
-- 4mar16:  WAIT!!!
-- ...discovered that I can make 7,8 into L-shaped pieces with
-- rearward projections and still solvable;  BUT starting
-- position must switch 7, 8.
--
-- 6mar16:  WAIT !!!
-- ...let's add two 1x1 blocks along the bottom front to fill in the space.
--
--	13sep23: added solver fbfs4. But, needed to create utils4 to hold
--		move* functions, test4winner, centroid, centroidMid, space, empty
--		& few other objects, because all needed by BOTH solver+tomb4.


with fbfs4;
with utils4;

with snd4ada;

with gl, gl.binding, gl.pointers;
with glu, glu.binding, glu.pointers;
with glext, glext.binding, glext.pointers;

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

----------------------------------------------------------------

with glfw3;		use glfw3;
with zoomwheel; use zoomwheel;

----------------------------------------------------------------

with matutils;
with stex;

with ada.numerics.generic_elementary_functions;

with Ada.Strings.Fixed;
with Ada.Strings.Unbounded;
use Ada.Strings.Unbounded;
with Ada.Strings.Unbounded.Text_IO;
use Ada.Strings.Unbounded.Text_IO;

with ada.characters.latin_1;

with unchecked_deallocation;

with text_io;
with ada.integer_text_io;
with io_exceptions;

with pngloader;
with gametypes;
with matutils;

with pictobj;
with pictobj2;

with shader;  use shader;



with Ada.Command_Line;
with Ada.Directories;
with SysUtils;

with gameutils; use gameutils;

----------------------------------------------------------------




procedure tomb4 is -- 4x4x4 with 2x2x2 skull

use utils4;

	package myint_io is new text_io.integer_io(integer);

	dirname: constant string := ada.directories.current_directory;





use text_io;
use gametypes;
use pngloader;
use matutils;
use gametypes.fmath;

use interfaces.c;
use interfaces.c.strings;
use glext;
use glext.pointers;
use glext.binding;
use gl;
use gl.binding;
use gl.pointers;



	solutionPath: unbounded_string;







------------------------------------------------------------------

eok0 : integer;

function eok return integer is
	k: integer := 0;
begin

	for ix in 1..mx loop
		for iz in 1..mz loop
			for iy in 1..my loop
				if empty(ix,iy,iz) then
					k:=k+1;
				end if;
			end loop;
		end loop;
	end loop;

	return k;

end eok;


procedure initEmptyMid is
begin

	for ix in 1..mx loop
		for iz in 1..mz loop
			for iy in 1..my loop
				empty(ix,iy,iz) := false;
			end loop;
		end loop;
	end loop;


	-- middle of each face is blank:

	-- bottom face:
	for ix in 2..3 loop
	for iz in 2..3 loop
		empty(ix,1,iz):=true;
		empty(ix,1,iz):=true;
	end loop;
	end loop;

	-- top face:
	for ix in 2..3 loop
	for iz in 2..3 loop
		empty(ix,4,iz):=true;
		empty(ix,4,iz):=true;
	end loop;
	end loop;

	-- front face:
	for ix in 2..3 loop
	for iy in 2..3 loop
		empty(ix,iy,mz):=true;
		empty(ix,iy,mz):=true;
	end loop;
	end loop;

	-- rear face:
	for ix in 2..3 loop
	for iy in 2..3 loop
		empty(ix,iy,1):=true;
		empty(ix,iy,1):=true;
	end loop;
	end loop;

	-- left face:
	for iz in 2..3 loop
	for iy in 2..3 loop
		empty(1,iy,iz):=true;
		empty(1,iy,iz):=true;
	end loop;
	end loop;

	-- right face:
	for iz in 2..3 loop
	for iy in 2..3 loop
		empty(mx,iy,iz):=true;
		empty(mx,iy,iz):=true;
	end loop;
	end loop;


end initEmptyMid;









-- normal starting configuration:
procedure initEmptyHi is -- skull is on TOP
begin

	empty := (others=>(others=>(others=>false)));


	-- edges of top 2 layers is blank:
	for ix in 1..mx loop
	for iz in 1..mz loop
	if 
		(ix/=2 and ix/=3) or (iz/=2 and iz/=3)
	then
		empty(ix,4,iz):=true;
		empty(ix,3,iz):=true;
	end if;
	end loop;
	end loop;


end initEmptyHi;










procedure setNextSkin is
	xinpath: string := "../../data/"; -- windows or linux
	nopath: string := "./data/"; --below current dir
	path: unbounded_string;
begin

	if ada.directories.exists(nopath) then
		append(path,nopath);
	else
		append(path,xinpath);
	end if;



	skin := (1+skin) mod mxskin; --0..3
	myassert( (skin < mxskin) and (skin>=0) );

	if skin=0 then --Original colors

		nun_texid    := loadPng(mirror,to_string(path)&"none.png");

		num_texid(0) := loadPng(mirror,to_string(path)&"zero.png");
		num_texid(1) := loadPng(mirror,to_string(path)&"one.png");
		num_texid(2) := loadPng(mirror,to_string(path)&"two.png");
		num_texid(3) := loadPng(mirror,to_string(path)&"three.png");
		num_texid(4) := loadPng(mirror,to_string(path)&"four.png");
		num_texid(5) := loadPng(mirror,to_string(path)&"five.png");
		num_texid(6) := loadPng(mirror,to_string(path)&"six.png");
		num_texid(7) := loadPng(mirror,to_string(path)&"seven.png");
		num_texid(8) := loadPng(mirror,to_string(path)&"eight.png");
		num_texid(9) := loadPng(mirror,to_string(path)&"nine.png");

	elsif skin=1 then --wornRock

		nun_texid    := loadPng(mirror,to_string(path)&"worn.png");

		num_texid(0) := loadPng(mirror,to_string(path)&"worn0.png");
		num_texid(1) := loadPng(mirror,to_string(path)&"worn1.png");
		num_texid(2) := loadPng(mirror,to_string(path)&"worn2.png");
		num_texid(3) := loadPng(mirror,to_string(path)&"worn3.png");
		num_texid(4) := loadPng(mirror,to_string(path)&"worn4.png");

		num_texid(5) := loadPng(mirror,to_string(path)&"worn5.png");
		num_texid(6) := loadPng(mirror,to_string(path)&"worn6.png");
		num_texid(7) := loadPng(mirror,to_string(path)&"worn7.png");
		num_texid(8) := loadPng(mirror,to_string(path)&"worn8.png");
		num_texid(9) := loadPng(mirror,to_string(path)&"worn9.png");

	elsif skin=2 then --wood

		nun_texid    := loadPng(mirror,to_string(path)&"wood.png");

		num_texid(0) := loadPng(mirror,to_string(path)&"w0.png");
		num_texid(1) := loadPng(mirror,to_string(path)&"w1.png");
		num_texid(2) := loadPng(mirror,to_string(path)&"w2.png");
		num_texid(3) := loadPng(mirror,to_string(path)&"w3.png");
		num_texid(4) := loadPng(mirror,to_string(path)&"w4.png");

		num_texid(5) := loadPng(mirror,to_string(path)&"w5.png");
		num_texid(6) := loadPng(mirror,to_string(path)&"w6.png");
		num_texid(7) := loadPng(mirror,to_string(path)&"w7.png");
		num_texid(8) := loadPng(mirror,to_string(path)&"w8.png");
		num_texid(9) := loadPng(mirror,to_string(path)&"w9.png");

	elsif skin=3 then --granite

		nun_texid    := loadPng(mirror,to_string(path)&"gran.png");

		num_texid(0) := loadPng(mirror,to_string(path)&"g0.png");
		num_texid(1) := loadPng(mirror,to_string(path)&"g1.png");
		num_texid(2) := loadPng(mirror,to_string(path)&"g2.png");
		num_texid(3) := loadPng(mirror,to_string(path)&"g3.png");
		num_texid(4) := loadPng(mirror,to_string(path)&"g4.png");

		num_texid(5) := loadPng(mirror,to_string(path)&"g5.png");
		num_texid(6) := loadPng(mirror,to_string(path)&"g6.png");
		num_texid(7) := loadPng(mirror,to_string(path)&"g7.png");
		num_texid(8) := loadPng(mirror,to_string(path)&"g8.png");
		num_texid(9) := loadPng(mirror,to_string(path)&"g9.png");

	end if;

end setNextSkin;






procedure checkfile is
	ifil: file_type;
	i: integer;
	ch,junk: character;
	use myint_io;
begin

	set_unbounded_string(prerestoreBuf, "");

	if ada.directories.Exists("t4restore.txt") then

		text_io.open(ifil, in_file, "t4restore.txt");

		nmoves:=0;
		loop
			get(ifil, i);
			exit when i<0 or i>10;
			get(ifil, junk);
			get(ifil, ch);
			nmoves:=nmoves+1; --currently in 1..9999 (utils4.ads)
			exit when nmoves>9_999;
			sel(nmoves) := i;
			dir(nmoves) := ch;
			append(prerestoreBuf, integer'image(i) & " " & ch &" ");
		end loop;

		text_io.close(ifil);

		--put(integer'image(nmoves)); new_line;
		--if nmoves<10 then
		--for i in 1..nmoves loop
		--	put( integer'image( sel(i) ) );
		--	put(" "&dir(i));
		--	new_line;
		--end loop;
		--end if;

		playscript:=true;
	end if;


end checkfile;







procedure first_prep is -- main program setup
	xinpath: string := "../../data/"; -- windows or linux
	nopath: string := "./data/"; --below current dir
	path: unbounded_string;
	fontcol: constant vec4 := (0.0,0.0,0.0,1.0); --black

	fontfile: constant string := "data/NotoSans-Regular.ttf";
	annex: constant string := "../../";
	nerr: integer;
begin


	if ada.directories.exists(nopath) then
		append(path,nopath);
	else
		append(path,xinpath);
	end if;

	snd4ada.initSnds;

	brick := snd4ada.initSnd(
		Interfaces.C.Strings.New_String(to_string(path)&"concrete_third.wav"));
	
	whoosh := snd4ada.initSnd(
		Interfaces.C.Strings.New_String(to_string(path)&"whoosh_4th.wav"));
	
	fanfare := snd4ada.initSnd(
		Interfaces.C.Strings.New_String(to_string(path)&"fanfare.wav"));
	
	shriek := snd4ada.initSnd(
		Interfaces.C.Strings.New_String(to_string(path)&"medusa.wav"));
	
	clap := snd4ada.initSnd(
		Interfaces.C.Strings.New_String(to_string(path)&"applause.wav"));
	
	if brick<0 or whoosh<0 or fanfare<0 or shriek<0 or clap<0 then
		put_line("snd4ada.initSnds ERROR");
		raise program_error;
	end if;


------- begin GLFW prep ---------------------------------------------------

	gameutils.InitGlfw( wwid,whit,fwid,fhit,"Reliquary4 - CrystalSkull");
	zoomwheel.enable(mainWin);

	-- prepare font -------------
	if ada.directories.exists(fontfile) then
		stex.InitFont ( fontfile );
	else
		stex.InitFont ( annex&fontfile );
	end if;

	stex.setColor( fontcol );
	stex.reSize(wwid,whit);


	glViewport(0,0,Fwid,Fhit);


	glgenvertexarrays(1, vertexarrayid'address );
	glbindvertexarray(vertexarrayid);

	glactivetexture(gl_texture0); -- moved here 5nov14 (outside main loop)

	glgenbuffers(1, vertbuff'address);
	glgenbuffers(1, rgbbuff'address);
	glgenbuffers(1, uvbuff'address);
	glgenbuffers(1, elembuff'address);


	glenable(gl_depth_test);
	gldepthfunc( gl_lequal );
	glenable( gl_cull_face );


	-- theoretically reduces aliasing (can't tell for sure):
	glEnable(GL_MULTISAMPLE);
	glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
	glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);

	glClearColor(0.4,0.4,0.4,1.0);

	nerr:=dumpGLerrorQueue("main 1"); 
	--prevents misleading messages in pngloader or loadshaders



	box_texid := loadPng(mirror,to_string(path)&"fence.png");
	setNextSkin;
	num_texid(10) := loadPng(mirror,to_string(path)&"tskull.png");

	wall_texid := loadPng(mirror,to_string(path)&"granite.png");


	pgmTexShadID := loadshaders(to_string(path)&"texobj.vs",to_string(path)&"texobj.fs");
	matrixid := glgetuniformlocation(pgmtexshadid, pmvp);
	uniftex  := glgetuniformlocation(pgmtexshadid, pmyts);

	pgmTexID := loadshaders(to_string(path)&"otexobj.vs",to_string(path)&"otexobj.fs");
	matid := glgetuniformlocation(pgmtexid, pmvp);
	unitex  := glgetuniformlocation(pgmtexid, pmyts);


	-- note:  deep copies of structures are automatic in Ada
	--        (unless we use pointer assignments)
	if normalMode then
		centroid:=centroidHi;
		initEmptyHi;
	else
		centroid:=centroidMid;
		initEmptyMid;
	end if;

	eok0 := eok; --20jun23 sanity check
	--put(" #spaces=" & integer'image(eok0));
	--new_line;

end first_prep;















-- the enclosing chainlink box:
procedure drawBigCube( po: pictobj2.pictangle ) is

	xr, zr : constant float := 2.02;
	    yr : constant float := 2.02;

	xc : constant float := xr - xxcc;
	yc : constant float := yr - yycc;
	zc : constant float := zr - zzcc;

	r,g,b : constant float := 0.5;
begin
	pictobj2.setrect( po, xc,yc,zc, xr,yr,zr, r,g,b);
	pictobj2.draw( po, vertbuff, uvbuff, elembuff );
end drawBigCube;












-- this is the 10th puzzle piece...a skull double size: 2x2x2
procedure drawSkull( po: pictobj.pictangle; nowTime: float ) is
	tt, r,g,b, xc,yc,zc, xo,yo,zo, xt,yt,zt : float;
	ix,iy,iz: integer;
begin

	ix:=centroid(10)(1);
	iy:=centroid(10)(2);
	iz:=centroid(10)(3);

	xc:=float(ix)-xxcc;
	yc:=float(iy)-yycc;
	zc:=float(iz)-zzcc;

	xo:=float(oxc)-xxcc;
	yo:=float(oyc)-yycc;
	zo:=float(ozc)-zzcc;

	r:=0.6; g:=0.6; b:=0.6;

	if grinding(10) then
		tt := (nowTime-movebegin)/movedelay;
		if tt>1.0 then
			pictobj.setrect( po, xc,yc,zc, 0.95,0.95,0.95, r,g,b);
			grinding(10):=false;
		else
			xt := xo + tt*(xc-xo);
			yt := yo + tt*(yc-yo);
			zt := zo + tt*(zc-zo);
			pictobj.setrect( po, xt,yt,zt, 0.95,0.95,0.95, r,g,b);
		end if;
	else
		pictobj.setrect( po, xc,yc,zc, 0.95,0.95,0.95, r,g,b);
	end if;


	pictobj.draw( po, vertbuff, uvbuff, elembuff );

end drawSkull;










-- these are individual 1x1 parts of each of corner pieces,
-- where the 6/8 corners have 4 cubelets each, corners 7,8 have 3 cubelets each.
procedure drawCube( 
	po: pictobj.pictangle; ii, 
	cx,cy,cz, i,j,k, ixc,iyc,izc : integer; nowTime: float ) is

	-- (xxcc,yycc,zzcc)==(2,3,2) : centroid of whole puzzle...

	-- (ixc,iyc,izc) = centroid of the current puzzle piece = PP,
	-- (i,j,k) = offset from PP-centroid to current cubelet
	-- (cx,cy,cz) = offset from PP-centroid to corner cubelet


	xc : float := float(ixc+i) -0.5 - xxcc;
	yc : float := float(iyc+j) -0.5 - yycc;
	zc : float := float(izc+k) -0.5 - zzcc;

	xo : float := float(oxc+i) -0.5 - xxcc;
	yo : float := float(oyc+j) -0.5 - yycc;
	zo : float := float(ozc+k) -0.5 - zzcc;

----------- begin addendum -------------------------

	-- cubelet centroid before offset to puzzle center:
	xcc : float := float(ixc+i) -0.5;
	ycc : float := float(iyc+j) -0.5;
	zcc : float := float(izc+k) -0.5;

	fr : float := 0.06; -- move cubelet-center toward corner cubelet
	dx : float := fr*(float(ixc+cx) - xcc);
	dy : float := fr*(float(iyc+cy) - ycc);
	dz : float := fr*(float(izc+cz) - zcc);

	-- this is to slightly separate the puzzle pieces
	-- but not their constituent cubelets.

----------- end addendum -------------------------


	sc: float := 0.47; --reduce radius from 0.5


	r,g,b, tt, xt,yt,zt : float;

begin

	if    ii=0 then
		r:=0.2; g:=1.0; b:=0.2; --green

	elsif ii=1 then
		r:=1.0; g:=1.0; b:=0.2; -- yellow

	elsif ii=2 then
		r:=0.2; g:=1.0; b:=1.0; --cyan

	elsif ii=3 then
		r:=1.0; g:=1.0; b:=1.0; --white

	elsif ii=4 then
		r:=0.2; g:=0.2; b:=0.2; --gray

	elsif ii=5 then
		r:=1.0; g:=0.2; b:=0.3; --red

	elsif ii=6 then --front lower left
		r:=0.2; g:=0.2; b:=1.0; -- blue

	elsif ii=7 then -- 1x1
		r:=0.2; g:=0.2; b:=1.0; --blue

	elsif ii=8 then -- 1x1
		r:=1.0; g:=0.2; b:=1.0; --magenta

	elsif ii=9 then -- front lower right
		r:=1.0; g:=0.2; b:=1.0; --magenta


	elsif ii=10 then -- skull
		r:=0.6; g:=0.6; b:=0.6; -- light gray
	end if;


	if grinding(ii) then
		tt := (nowTime-movebegin)/movedelay;
		if tt>1.0 then
			pictobj.setrect( po, xc+dx,yc+dy,zc+dz, sc,sc,sc, r,g,b);
			grinding(ii):=false;
		else
			xt := xo + tt*(xc-xo);
			yt := yo + tt*(yc-yo);
			zt := zo + tt*(zc-zo);
			pictobj.setrect( po, xt+dx,yt+dy,zt+dz, sc,sc,sc, r,g,b);
		end if;
	else
		pictobj.setrect( po, xc+dx,yc+dy,zc+dz, sc,sc,sc, r,g,b);
	end if;


	pictobj.draw( po, vertbuff, rgbbuff, uvbuff, elembuff );

end drawCube;






--------------------------------------------------------------------------





	playedonce, userexit : boolean := false;
	wintime, currentTime : float;

	v4, vcc : vec4;



	otitle : constant interfaces.c.char_array 
		:= value( new_string("Ada7") );
	vtitle : constant interfaces.c.char_array 
		:= value( new_string("Vadasz") );



	xize: float := 0.25;

	ix,iy,iz, sx,sy,sz, cx,cy,cz : integer := 1;


	ixsel: integer:=1;
	pik: boolean := false;

	hc: boolean := true; --HighContrast

	rfile: file_type;
	char: character;


	procedure shutdown is
	begin

		glext.binding.glDeleteProgram(pgmtexshadid);

		glext.binding.glDeleteBuffers(1, vertbuff'address);
		glext.binding.glDeleteBuffers(1, rgbbuff'address);
		glext.binding.glDeleteBuffers(1, elembuff'address);
		glext.binding.glDeleteBuffers(1, uvbuff'address);

		glext.binding.glDeleteVertexArrays(1, vertexarrayid'address);

		stex.CloseFont;

		snd4ada.termSnds;

		glfwdestroywindow(mainWin);
		glfwTerminate;

	end shutdown;


	procedure replay is
		res: boolean;
	begin

		for i in 1..nmoves loop
			ixsel:= sel(i);
			char := dir(i);

			currentTime := float(glfwGetTime);

			case char is
				when 'n' => res:=movenear(ixsel,centroid,empty);
				when 'a' => res:=moveaway(ixsel,centroid,empty);
				when 'u' => res:=moveup(ixsel,centroid,empty);
				when 'd' => res:=movedown(ixsel,centroid,empty);
				when 'l' => res:=moveleft(ixsel,centroid,empty);
				when 'r' => res:=moveright(ixsel,centroid,empty);
				when others => null;
			end case;
		end loop;

	end replay;


----------------- main program begin ==========================
	res: boolean;
	nsteps: integer;

	enableResume: constant boolean := true;

begin --tomb

	set_unbounded_string(restoreBuf, "" );
	--note: this is not needed as long as checkfile is called

	checkfile; --see if restore file exists; sets preRestoreBuf


normalMode:=false; --shows goal cfg
	first_prep;  -- main program setup

	show_axes:=true;


	-- rotate into preferred initial orientation:
	degRotate( mm, 20.0, 1.0, 0.0, 0.0 );



	-- note:  only mm changes:
	updateMVP( float(wwid), float(whit) );


	ixsel:=0;


	-- main event loop begin: ------------------------------------------
   while not userexit loop

------- begin response to keys ------------------------------------------

		currentTime := float(glfwGetTime);
		--deltaT := currentTime - oldTimeKb;


		glfwPollEvents;



		--check for <esc>,<q>:
		if glfwgetkey( mainWin, glfw_key_escape ) = Glfw_Press then
				userexit:=true;

		elsif glfwgetkey( mainWin, glfw_key_q ) = Glfw_Press then
				userexit:=true;

		end if;

		exit when glfwWindowShouldClose(mainWin) /= 0; --19oct21

		if not normalMode then
			if glfwgetkey( mainWin, glfw_key_z ) = Glfw_Press then

				normalMode:=true;
				centroid:=centroidHi;
				initEmptyHi;

				-- FTTB I have disabled this complex resume capability
				if playscript and enableResume then
					replay;
				end if;
		
				set_unbounded_string(restoreBuf, to_string(prerestoreBuf) );

			end if;
		end if;



		anyGrinding:=false;
		for i in rngp loop
			anyGrinding := anyGrinding or grinding(i);
		end loop;

		if not anyGrinding then

			if normalMode then
				gameutils.getKeyInputs(
					mainWin,
					solutionPath,
					rngp'first, rngp'last,
					ixsel,pik);
			end if;


			if trysolve and not solving then
				trysolve:=false;

				saveBuf:=restoreBuf;
				fbfs4.bfs(centroid,empty,solutionPath);
				restoreBuf:=saveBuf;
				--because the solver writes junk into restoreBuf...

				haveSolution := length(solutionPath);
				solving:=( haveSolution>0 );

			elsif goUp then
				goUp:=false;
				res:=moveUp(ixsel,centroid,empty);
				if res and not mute then snd4ada.playSnd(brick); end if;
				if res then movebegin:=currentTime; end if;
			elsif goDown then
				goDown:=false;
				res:=moveDown(ixsel,centroid,empty);
				if res and not mute then snd4ada.playSnd(brick); end if;
				if res then movebegin:=currentTime; end if;
			elsif goLeft then
				goLeft:=false;
				res:=moveLeft(ixsel,centroid,empty);
				if res and not mute then snd4ada.playSnd(brick); end if;
				if res then movebegin:=currentTime; end if;
			elsif goRight then
				goRight:=false;
				res:=moveRight(ixsel,centroid,empty);
				if res and not mute then snd4ada.playSnd(brick); end if;
				if res then movebegin:=currentTime; end if;
			elsif goAway then
				goAway:=false;
				res:=moveAway(ixsel,centroid,empty);
				if res and not mute then snd4ada.playSnd(brick); end if;
				if res then movebegin:=currentTime; end if;
			elsif goNear then
				goNear:=false;
				res:=moveNear(ixsel,centroid,empty);
				if res and not mute then snd4ada.playSnd(brick); end if;
				if res then movebegin:=currentTime; end if;

			elsif doRestart then
				set_unbounded_string(restoreBuf, "");
				doRestart:=false;
				centroid:=centroidHi;
				initEmptyHi;
			elsif tryNextSkin then
				tryNextSkin:=false;
				setNextSkin;
			end if;

			--sanity check added 20jun23
			if eok /= eok0 then
				put(" Sanity Fails !");
				new_line;
				raise program_error;
			end if;

			gameutils.handle_mouse(
				pik, 
				rngp'first, rngp'last, 
				ixsel,
				centroid,
				xxcc,yycc,zzcc
				);
		end if;




----///////////////////// end response to key/mouse


		updateMVP( float(wwid), float(whit) );


-------- here we should handle resized window ----------------------


		glfwGetWindowSize( mainWin, Nwid'access, Nhit'access );
		if( (Nwid /= wwid) or (Nhit /= whit) ) then
			wwid:=Nwid;  whit:=Nhit;

			glfwGetFramebufferSize(mainwin, fwid'access, fhit'access);
			glViewport(0,0,Fwid,Fhit);

		end if;



--------- begin drawing ===============================================

		glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);


	if not help then --normal draw 23jun23

------ draw here ----------------------

		-- now, the 10 puzzle pieces:
		glUseProgram( pgmTexShadID );
		gluniformmatrix4fv( matrixid, 1, gl_false, mvp(1,1)'address );
		gluniform1i(uniftex,0);

		for ii in rngp loop -- 8 corners + 2 edge + 1 egg


			if numerals then
				glBindTexture(gl_texture_2d, num_texid(ii) );
			else
				glBindTexture(gl_texture_2d, nun_texid );
			end if;


			ix:=centroid(ii)(1);
			iy:=centroid(ii)(2);
			iz:=centroid(ii)(3);


			if ii = rngp'last then

				--drawSkull(ppo, currentTime);
				null;

			elsif ii>=0 and ii<=5 then

				sx:=space(ii)(1);
				sy:=space(ii)(2);
				sz:=space(ii)(3);

				cx:=1-sx;
				cy:=1-sy;
				cz:=1-sz; --corner cubelet indexes


				for i in 0..1 loop
				for j in 0..1 loop
				for k in 0..1 loop
					-- this is the 4-cube-corner:
					if i/=sx and j/=sy and k/=sz then
						drawCube(ppo, ii, cx,cy,cz, i,j,k, ix, iy, iz, currentTime );
					elsif i=sx and j/=sy and k/=sz then
						drawCube(ppo, ii, cx,cy,cz, i,j,k, ix, iy, iz, currentTime );
					elsif i/=sx and j=sy and k/=sz then
						drawCube(ppo, ii, cx,cy,cz, i,j,k, ix, iy, iz, currentTime );
					elsif i/=sx and j/=sy and k=sz then
						drawCube(ppo, ii, cx,cy,cz, i,j,k, ix, iy, iz, currentTime );
					else -- empty space
						null;
					end if;
				end loop;
				end loop;
				end loop;

			elsif ii=6 or ii=9 then -- ?? is (0,0,1) the corner ??
				drawCube(ppo, ii, 0,0,1, 0,0,0, ix,iy,iz, currentTime); --bottom rearward
				drawCube(ppo, ii, 0,0,1, 0,0,1, ix,iy,iz, currentTime);
				drawCube(ppo, ii, 0,0,1, 0,1,1, ix,iy,iz, currentTime);

			else -- 7,8 (1x1)
				drawCube(ppo, ii, 0,0,0, 0,0,0, ix,iy,iz, currentTime); --bottom rearward
			end if;


		end loop; --ii



		glUseProgram( pgmTexID );
		gluniformmatrix4fv( matid, 1, gl_false, mvp(1,1)'address );
		gluniform1i(unitex,0);

		--if solving or not winner then
		if not winner or haveSolution>0 then
			glBindTexture(gl_texture_2d, box_texid ); 
			drawBigCube(ppo2); --bounding chainlink box
		end if;

		glBindTexture(gl_texture_2d, num_texid(rngp'last) );
		drawSkull(ppo, currentTime);





		if show_axes then

			v4 := (+2.0, -2.0, -2.0, 1.0);
			matXvec(mvp, v4, vcc);
			stex.print3d("+X", vcc(1), vcc(2), vcc(3), vcc(4), 1.0, 1.0);

			v4 := (-2.2, +2.0, -2.2, 1.0);
			matXvec(mvp, v4, vcc);
			stex.print3d("+Y", vcc(1), vcc(2), vcc(3), vcc(4), 1.0, 1.0);

			v4 := (-2.0, -2.0, +2.0, 1.0);
			matXvec(mvp, v4, vcc);
			stex.print3d("+Z", vcc(1), vcc(2), vcc(3), vcc(4), 1.0, 1.0);

			v4 := (-2.0, -2.0, -2.0, 1.0);
			matXvec(mvp, v4, vcc);
			stex.print3d("O", vcc(1), vcc(2), vcc(3), vcc(4), 1.0, 1.0);

		end if;



		xize:=0.25;

		-- fixed 2d location text:

		stex.print2d("(=)=>Solve", 0.02, 0.05, xize );
		stex.print2d("(spc)=>Restart", 0.2, 0.05, xize );
		stex.print2d("(esc)=>exit",    0.4, 0.05, xize );
		stex.print2d("(i,o,mousewheel)=>Zoom", 0.6, 0.05, xize);

		stex.print2d("0-9=>pick",      0.02, 0.02, xize );
		stex.print2d("s=>skull",        0.20, 0.02, xize );
		stex.print2d("n=>nums",        0.40, 0.02, xize ); --8oct21
		stex.print2d("c=>nextColor",        0.58, 0.02, xize );
		stex.print2d("h=>help",        0.85, 0.02, xize );

	end if; --not help (normal draw) 23jun23



		if help then
			xize:=0.4; hc:=false;
			stex.print2d("Reckless raiders have plundered a reliquary",0.02,0.90,xize,hc);
			stex.print2d("...and displaced an ancient Crystal Skull !",0.02,0.85,xize,hc);

			stex.print2d("Return the Skull to its proper position in the", 0.02, 0.80, xize,hc);
			stex.print2d("center of its [numerically ordered] Cubical Frame.", 0.02, 0.75, xize,hc);

			stex.print2d("With X rightward, Y upward, and Z outward,", 0.02, 0.70, xize,hc);
			stex.print2d("use the keys:  u/d  l/r  f/b", 0.02, 0.65, xize,hc);
			stex.print2d("(Up/Down Left/Right Forward/Backward)", 0.02, 0.60, xize,hc);

			stex.print2d("Select a block by number or mouse-click...", 0.02, 0.50, xize,hc);
			stex.print2d("Laptop users can place cursor on block", 0.02, 0.45, xize,hc);
			stex.print2d("then hit <enter>-key to select", 0.02, 0.40, xize,hc);

			stex.print2d("Zoom keys:   i/o = In/Out", 0.02, 0.35, xize,hc);

			stex.print2d("V-key to mute moves", 0.02, 0.30, xize,hc);

			xize:=0.25;
		else
			if normalMode then
			stex.print2d("Hard:   Move the skull into the center of its cubical tomb.",0.10,0.95,xize);
			else
			stex.print2d("This is the goal configuration...Press  z  to begin",0.10,0.95,xize);
			end if;
		end if;

		if haveSolution>0 and solving then
			nsteps := haveSolution/5;
			stex.print2d(integer'image(nsteps)&" steps remain...press = to solve",0.10,0.90,xize);
		end if;

		if winner  and haveSolution=0 then
			stex.print2d("Correct!", 0.19, 0.83, 1.0 );

			if not playedonce then
		 		snd4ada.playSnd(clap); --clap
				playedonce:=true;
				wintime:=currentTime;
			end if;

			if( currentTime-wintime > 5.0 ) then 
				winner:=false;
			end if;

		else
			playedonce:=false;
		end if;


--------- end drawing =================================================



		glflush;
		glfwSwapBuffers( mainWin );


   end loop; ---------------- main event loop end -------------------

-- FTTB I have disabled this complex resume capability...
	if enableResume then
		text_io.create(rfile,out_file,"t4restore.txt");
		put( rfile, restoreBuf );
		put( rfile, " -1 ");
		new_line(rfile);
		text_io.close(rfile);
		put_line(" Saved config to t4restore.txt");
	end if;

	shutdown;


--exception
--	when others=>
--		shutdown;

end tomb4;

