Code viewer for World: Halloween A* + Moving Walls

// Cloned by Deborah Djon on 4 Nov 2022 from World "Halloween A*" by Deborah Djon 
// Please leave this clone trail here.
 
// Cloned by Deborah Djon on 31 Oct 2022 from World "Complex World" by Starter user 
// Please leave this clone trail here.
 



// ==== Starter World =================================================================================================
// This code is designed for use on the Ancient Brain site.
// This code may be freely copied and edited by anyone on the Ancient Brain site.
// To include a working run of this program on another site, see the "Embed code" links provided on Ancient Brain.
// ====================================================================================================================



// =============================================================================================
// More complex starter World 
// 3d-effect Maze World (really a 2-D problem)
// Movement is on a semi-visible grid of squares 
//
// This more complex World shows:
// - Skybox
// - Internal maze (randomly drawn each time)
// - Enemy actively chases agent
// - Music/audio
// - 2D world (clone this and set show3d = false)
// - User keyboard control (clone this and comment out Mind actions to see)
// =============================================================================================


// =============================================================================================
// Scoring:
// Bad steps = steps where enemy is within one step of agent.
// Good steps = steps where enemy is further away. 
// Score = good steps as percentage of all steps.
//
// There are situations where agent is trapped and cannot move.
// If this happens, you score zero.
// =============================================================================================


 





// ===================================================================================================================
// === Start of tweaker's box ======================================================================================== 
// ===================================================================================================================

// The easiest things to modify are in this box.
// You should be able to change things in this box without being a JavaScript programmer.
// Go ahead and change some of these. What's the worst that could happen?


AB.clockTick       = 150;    

	// Speed of run: Step every n milliseconds. Default 100.
	
AB.maxSteps        = 1000;    

	// Length of run: Maximum length of run in steps. Default 1000.

AB.screenshotStep  = 50;   
  
	// Take screenshot on this step. (All resources should have finished loading.) Default 50.



//---- global constants: -------------------------------------------------------

	const show3d = true;						// Switch between 3d and 2d view (both using Three.js) 


 const TEXTURE_WALL 	= '/uploads/starter/door.jpg' ;
 const TEXTURE_MAZE 	= '/uploads/starter/latin.jpg' ;
 const TEXTURE_AGENT 	= '/uploads/starter/pacman.jpg' ;
 const TEXTURE_ENEMY 	= '/uploads/starter/ghost.3.png' ;
 const GROUND_GRID = '/uploads/tharealog/grid6.png';
 const GROUND_DISPLACEMENT = '/uploads/tharealog/displacement.png';

// credits:
// http://commons.wikimedia.org/wiki/File:Old_door_handles.jpg
// https://commons.wikimedia.org/wiki/Category:Pac-Man_icons
// https://commons.wikimedia.org/wiki/Category:Skull_and_crossbone_icons
// http://en.wikipedia.org/wiki/File:Inscription_displaying_apices_(from_the_shrine_of_the_Augustales_at_Herculaneum).jpg

 
    //source: Music by <a href="https://pixabay.com/users/geoffharvey-9096471/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=music&amp;utm_content=122118">geoffharvey</a> from <a href="https://pixabay.com/music//?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=music&amp;utm_content=122118">Pixabay</a>
	const MUSIC_BACK  = '/uploads/tharealog/let-the-mystery-unfold-122118.mp3' ;
	
	const SOUND_ALARM = '/uploads/tharealog/evil_laugh.mp3' ;

// credits:
// http://www.dl-sounds.com/royalty-free/defense-line/
// http://soundbible.com/1542-Air-Horn.html 

	
	
const gridsize = 35;						// number of squares along side of world	   

const NOBOXES =  Math.trunc ( (gridsize * gridsize) / 7 );
		// density of maze - number of internal boxes
		// (bug) use trunc or can get a non-integer 

const squaresize = 100;					// size of square in pixels

const MAXPOS = gridsize * squaresize;		// length of one side in pixels 
	
const SKYCOLOR 	= 0xddffdd;				// a number, not a string 

 
const startRadiusConst	 	= MAXPOS * 0.8 ;		// distance from centre to start the camera at
const maxRadiusConst 		= MAXPOS * 10  ;		// maximum distance from camera we will render things  

/* altered by deborah djon 
Added these constants as described by the task. 
Number of walls mobed is increased to 20. Also, the rate of moving the walls is increased.
This increases the dynamicallity of the wall movements. 
*/
const WALLMOVENO = 20;
const WALLMOVETICK = 2;


//--- change ABWorld defaults: -------------------------------

ABHandler.MAXCAMERAPOS 	= maxRadiusConst ;

ABHandler.GROUNDZERO		= true;						// "ground" exists at altitude zero



//--- skybox: -------------------------------
// skybox is a collection of 6 files 
// x,y,z positive and negative faces have to be in certain order in the array 
// https://threejs.org/docs/#api/en/loaders/CubeTextureLoader 

// mountain skybox, credit:
// http://stemkoski.github.io/Three.js/Skybox.html

/* altered by deborah djon:
I made my own sky box with a vaporwave backgound made from: https://www.artstation.com/artwork/oO0O0J
*/ 
 const SKYBOX_ARRAY = [										 
                "uploads/tharealog/sb11.png",
                "uploads/tharealog/sb31.png",
                "uploads/tharealog/sb_top1.png",
                "uploads/tharealog/sb_top1.png",
                "uploads/tharealog/sb41.png",
                "uploads/tharealog/sb21.png",
                ];

// ===================================================================================================================
// === End of tweaker's box ==========================================================================================
// ===================================================================================================================


// You will need to be some sort of JavaScript programmer to change things below the tweaker's box.









//--- Mind can pick one of these actions -----------------

const ACTION_LEFT 			= 0;		   
const ACTION_RIGHT 			= 1;
const ACTION_UP 			= 2;		 
const ACTION_DOWN 			= 3;
const ACTION_STAYSTILL 		= 4;

// in initial view, (smaller-larger) on i axis is aligned with (left-right)
// in initial view, (smaller-larger) on j axis is aligned with (away from you - towards you)


// contents of a grid square

const GRID_BLANK 	= 0;
const GRID_WALL 	= 1;
const GRID_MAZE 	= 2;
 
 
 


var BOXHEIGHT;		// 3d or 2d box height 

var GRID 	= new Array(gridsize);			// can query GRID about whether squares are occupied, will in fact be initialised as a 2D array   
let mazeBoxes = []; //deborah

var theagent, theenemy;
  
var wall_texture, agent_texture, enemy_texture, maze_texture; 


// enemy and agent position on squares
var ei, ej, ai, aj;

var badsteps;
var goodsteps;


	
function loadResources()		// asynchronous file loads - call initScene() when all finished 
{
	var loader1 = new THREE.TextureLoader();
	var loader2 = new THREE.TextureLoader();
	var loader3 = new THREE.TextureLoader();
	var loader4 = new THREE.TextureLoader();
	
	loader1.load ( TEXTURE_WALL, function ( thetexture )  		
	{
		thetexture.minFilter  = THREE.LinearFilter;
		wall_texture = thetexture;
		if ( asynchFinished() )	initScene();		// if all file loads have returned 
	});
		
	loader2.load ( TEXTURE_AGENT, function ( thetexture )  	 
	{
		thetexture.minFilter  = THREE.LinearFilter;
		agent_texture = thetexture;
		if ( asynchFinished() )	initScene();		 
	});	
	
	loader3.load ( TEXTURE_ENEMY, function ( thetexture )  
	{
		thetexture.minFilter  = THREE.LinearFilter;
		enemy_texture = thetexture;
		if ( asynchFinished() )	initScene();		 
	});
	
	loader4.load ( TEXTURE_MAZE, function ( thetexture )  
	{
		thetexture.minFilter  = THREE.LinearFilter;
		maze_texture = thetexture;
		if ( asynchFinished() )	initScene();		 
	});
	
}


function asynchFinished()		 // all file loads returned 
{
	if ( wall_texture && agent_texture && enemy_texture && maze_texture )   return true; 
	else return false;
}	
	
	
 

//--- grid system -------------------------------------------------------------------------------
// my numbering is 0 to gridsize-1

	
function occupied ( i, j )		// is this square occupied
{
 if ( ( ei == i ) && ( ej == j ) ) return true;		// variable objects 
 if ( ( ai == i ) && ( aj == j ) ) return true;

 if ( GRID[i][j] == GRID_WALL ) return true;		// fixed objects	 
 if ( GRID[i][j] == GRID_MAZE ) return true;		 
	 
 return false;
}

 
// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates
// logically, coordinates are: y=0, x and z all positive (no negative)    
// logically my dimensions are all positive 0 to MAXPOS
// to centre everything on origin, subtract (MAXPOS/2) from all dimensions 

function translate ( i, j )			
{
	var v = new THREE.Vector3();
	
	v.y = 0;	
	v.x = ( i * squaresize ) - ( MAXPOS/2 );   		 
	v.z = ( j * squaresize ) - ( MAXPOS/2 );   	
	
	return v;
}



	
function initScene()		// all file loads have returned 
{
	 var i,j, shape, thecube;
	 
	// set up GRID as 2D array
	 
	 for ( i = 0; i < gridsize ; i++ ) 
		GRID[i] = new Array(gridsize);		 


	// set up walls
	
	/*
	altered by deborah djon:
	
	changed the walls to be a wireframw and, hence, seethrough. 
	Box height is reduced to half the boxheight. 
	*/
	 
	 for ( i = 0; i < gridsize ; i++ ) 
	  for ( j = 0; j < gridsize ; j++ ) 
		if ( ( i==0 ) || ( i==gridsize-1 ) || ( j==0 ) || ( j==gridsize-1 ) )
		{

			GRID[i][j] = GRID_WALL;		 
			shape    = new THREE.BoxGeometry ( squaresize, BOXHEIGHT/2, squaresize );	
			
		    wireframe = new THREE.WireframeGeometry( shape );
		
		    lineMaterial = new THREE.LineBasicMaterial( { color: 'red' } );
		   
		    thecube = new THREE.LineSegments( wireframe , lineMaterial); 
            thecube.material.transpaent = true;
		    thecube.material.opacity = 0.01;
			
			thecube.position.copy ( translate(i,j) ); 		  	// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates 
			ABWorld.scene.add(thecube);
		}
		else 
   			GRID[i][j] = GRID_BLANK;

		
   // set up maze 
   
   /*
   altered by deborah djon:
   The style of the boxes is changed to be pyramids. 
   The color and material of the pyramids is changed to be a plain red. 
   In additoin references to the created walls are saved in an array, so 
   they can be moved later. 
   */
   
    for ( var c=1 ; c <= NOBOXES ; c++ )
	{
		i = AB.randomIntAtoB(1,gridsize-2);		// inner squares are 1 to gridsize-2
		j = AB.randomIntAtoB(1,gridsize-2);
			
		GRID[i][j] = GRID_MAZE ;
		
		r = squaresize/2+20;
		h = BOXHEIGHT; 
		geo = new THREE.CylinderGeometry(0, r, h, 4, 1);
		material = new THREE.MeshPhongMaterial( { color: 'red'} );
		pyramidMesh = new THREE.Mesh( geo, material );
		// add blak outline to the pyramids
        mat = new THREE.LineBasicMaterial( { color: 'black' } );
        wireframe = new THREE.LineSegments( geo, mat );
        pyramidMesh.add( wireframe );
	    
	    // rotate the pyramids they start out slightly sideways
        pyramidMesh.rotation.y = -Math.PI * 0.25;
		pyramidMesh.position.copy ( translate(i,j) );
		mazeBoxes.push(pyramidMesh);
		ABWorld.scene.add(pyramidMesh);	
	}
	 	 
   
	// set up enemy 
	// start in random location
	
	 do
	 {
	  i = AB.randomIntAtoB(1,gridsize-2);
	  j = AB.randomIntAtoB(1,gridsize-2);
	 }
	 while ( occupied(i,j) );  	  // search for empty square 

	 ei = i;
	 ej = j;
	 
	 //shape    = new THREE.BoxGeometry ( squaresize, BOXHEIGHT, squaresize );
	 shape = new THREE.SphereGeometry( squaresize*0.7, 32, 16 );
	 theenemy = new THREE.Mesh( shape );
 	 theenemy.material =  new THREE.MeshBasicMaterial( { map: enemy_texture } );
	 ABWorld.scene.add(theenemy);
	 drawEnemy();		  

	 
	
	// set up agent 
	// start in random location
	
	 do
	 {
	  i = AB.randomIntAtoB(1,gridsize-2);
	  j = AB.randomIntAtoB(1,gridsize-2);
	 }
	 while ( occupied(i,j) );  	  // search for empty square 

	 ai = i;
	 aj = j;
 
	 //shape    = new THREE.BoxGeometry ( squaresize, BOXHEIGHT, squaresize );	
	 shape = new THREE.SphereGeometry( squaresize/3, 32, 16 );
	 theagent = new THREE.Mesh( shape );
	 theagent.material =  new THREE.MeshBasicMaterial( { color: 'white' } );
	 ABWorld.scene.add(theagent);
	 drawAgent(); 

    /*altered by Deborah Djon
    Added a 3D mountain landscape to the scene
    This is done by creating a plane with a grid pattern, then using a displacement
    texture
    source: https://res.cloudinary.com/dg5nsedzw/image/upload/v1641657200/blog/vaporwave-threejs-textures/displacement.png
    source: https://blog.maximeheckel.com/posts/vaporwave-3d-scene-with-threejs/
    */

    const textureLoader = new THREE.TextureLoader();
    const gridTexture = textureLoader.load(GROUND_GRID);
    const terrainTexture = textureLoader.load(GROUND_DISPLACEMENT);

    floorSize = 30000
    const geometry = new THREE.PlaneGeometry(floorSize, floorSize, 64,64);

    material = new THREE.MeshStandardMaterial({
        map: gridTexture,
        displacementMap: terrainTexture,
        displacementScale: 25000,
    })

    	 
    const plane = new THREE.Mesh(geometry, material);
    plane.position.copy( translate(17,17) );
    
    adjust = 0;
    plane.position.x += adjust
    plane.position.y += adjust
    plane.position.y = - BOXHEIGHT/2;

    plane.rotation.x = -Math.PI * 0.5;

    ABWorld.scene.add(plane)
    
    const pointLight = new THREE.PointLight('red',2);
    pointLight.position.copy( translate(10,10) )
    pointLight.position.y = squaresize*20
    ABWorld.scene.add(pointLight)
    

   
//---------------------------


  // finally skybox 
  // setting up skybox is simple 
  // just pass it array of 6 URLs and it does the asych load 
  
  	 ABWorld.scene.background = new THREE.CubeTextureLoader().load ( SKYBOX_ARRAY, 	function() 
	 { 
		ABWorld.render(); 
	 
		AB.removeLoading();
	
		AB.runReady = true; 		// start the run loop
	 });
	 

}
 
 
 


// --- draw moving objects -----------------------------------


function drawEnemy()		// given ei, ej, draw it 
{
	theenemy.position.copy ( translate(ei,ej) ); 		  	// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates 

	ABWorld.lookat.copy ( theenemy.position );	 		// if camera moving, look back at where the enemy is  
}


function drawAgent()		// given ai, aj, draw it 
{
	theagent.position.copy ( translate(ai,aj) ); 		  	// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates 

	ABWorld.follow.copy ( theagent.position );			// follow vector = agent position (for camera following agent)
}




// --- take actions -----------------------------------

function moveLogicalEnemy()
{ 
// move towards agent 
// put some randomness in so it won't get stuck with barriers 
 calculateAStarPath();
 console.log("calculated A* path. Take step: "+aStarPath[0][0]+','+aStarPath[0][1]);
 drawAStarPath(aStarPath);
 console.log("calculated A* path. Take step: "+aStarPath[0][0]+','+aStarPath[0][1]);
 x=aStarPath.at(-1)[0];
 y=aStarPath.at(-1)[1];
 
 if ( ! occupied(x,y) ){  	// if no obstacle then move, else just miss a turn
 console.log("calculated A* path. No obsitcales.");
  ei = x;
  ej = y;
 }
}


function moveLogicalAgent( a )			// this is called by the infrastructure that gets action a from the Mind 
{ 
 var i = ai;
 var j = aj;		 

      if ( a == ACTION_LEFT ) 	i--;
 else if ( a == ACTION_RIGHT ) 	i++;
 else if ( a == ACTION_UP ) 		j++;
 else if ( a == ACTION_DOWN ) 	j--;

 if ( ! occupied(i,j) ) 
 {
  ai = i;
  aj = j;
 }
}




// --- key handling --------------------------------------------------------------------------------------
// This is hard to see while the Mind is also moving the agent:
// AB.mind.getAction() and AB.world.takeAction() are constantly running in a loop at the same time 
// have to turn off Mind actions to really see user key control 

// we will handle these keys: 

var OURKEYS = [ 37, 38, 39, 40 ];

function ourKeys ( event ) { return ( OURKEYS.includes ( event.keyCode ) ); }
	

function keyHandler ( event )		
{
	if ( ! AB.runReady ) return true; 		// not ready yet 

   // if not one of our special keys, send it to default key handling:
	
	if ( ! ourKeys ( event ) ) return true;
	
	// else handle key and prevent default handling:
	
	if ( event.keyCode == 37 )   moveLogicalAgent ( ACTION_LEFT 	);   
    if ( event.keyCode == 38 )   moveLogicalAgent ( ACTION_DOWN  	); 	 
    if ( event.keyCode == 39 )   moveLogicalAgent ( ACTION_RIGHT 	); 	 
    if ( event.keyCode == 40 )   moveLogicalAgent ( ACTION_UP		);   
	
	// when the World is embedded in an iframe in a page, we want arrow key events handled by World and not passed up to parent 

	event.stopPropagation(); event.preventDefault(); return false;
}





// --- score: -----------------------------------


function badstep()			// is the enemy within one square of the agent
{
 if ( ( Math.abs(ei - ai) < 2 ) && ( Math.abs(ej - aj) < 2 ) ) return true;
 else return false;
}


function agentBlocked()			// agent is blocked on all sides, run over
{
 return ( 	occupied (ai-1,aj) 		&& 
		occupied (ai+1,aj)		&&
		occupied (  ai,aj+1)		&&
		occupied (  ai,aj-1) 	);		
} 


function updateStatusBefore(a)
// this is called before anyone has moved on this step, agent has just proposed an action
// update status to show old state and proposed move 
{
 var x 		= AB.world.getState();
 AB.msg ( " Step: " + AB.step + " &nbsp; x = (" + x.toString() + ") &nbsp; a = (" + a + ") " ); 
}


function   updateStatusAfter()		// agent and enemy have moved, can calculate score
{
 // new state after both have moved
 
 var y 		= AB.world.getState();
 var score = ( goodsteps / AB.step ) * 100; 

 AB.msg ( " &nbsp; y = (" + y.toString() + ") <br>" +
		" Bad steps: " + badsteps + 
		" &nbsp; Good steps: " + goodsteps + 
		" &nbsp; Score: " + score.toFixed(2) + "% ", 2 ); 
}





AB.world.newRun = function() 
{
	AB.loadingScreen();

	AB.runReady = false;  

	badsteps = 0;	
	goodsteps = 0;

	
	if ( show3d )
	{
	 BOXHEIGHT = squaresize;
	 ABWorld.init3d ( startRadiusConst, maxRadiusConst, SKYCOLOR  ); 	
	}	     
	else
	{
	 BOXHEIGHT = 1;
	 ABWorld.init2d ( startRadiusConst, maxRadiusConst, SKYCOLOR  ); 		     
	}
	
	
	loadResources();		// aynch file loads		
							// calls initScene() when it returns 

	document.onkeydown = keyHandler;	
		 
};



AB.world.getState = function()
{
 var x = [ ai, aj, ei, ej ];
  return ( x );  
};



AB.world.takeAction = function ( a )
{
  updateStatusBefore(a);			// show status line before moves 

  moveLogicalAgent(a);

  if ( ( AB.step % 2 ) == 0 )		// slow the enemy down to every nth step
    moveLogicalEnemy();


  if ( badstep() )  badsteps++;
  else   			goodsteps++;

   drawAgent();
   drawEnemy();
   updateStatusAfter();			// show status line after moves  


  if ( agentBlocked() )			// if agent blocked in, run over 
  {
	AB.abortRun = true;
	goodsteps = 0;			// you score zero as far as database is concerned 			 
	musicPause();
	soundAlarm();
  }
  /* altered by Deborah Djon:
  added this for moving the walls at the predefined rate. 
  */
  
  if ( ( AB.step % WALLMOVETICK ) == 0 ){
      moveWalls();
  }

};



AB.world.endRun = function()
{
  musicPause(); 
  if ( AB.abortRun ) AB.msg ( " <br> <font color=red> <B> Agent trapped. Final score zero. </B> </font>   ", 3  );
  else    				AB.msg ( " <br> <font color=green> <B> Run over. </B> </font>   ", 3  );
};

 
AB.world.getScore = function()
{
    // only called at end - do not use AB.step because it may have just incremented past AB.maxSteps
    
    var s = ( goodsteps / AB.maxSteps ) * 100;   // float like 93.4372778 
    var x = Math.round (s * 100);                // 9344
    return ( x / 100 );                          // 93.44
};


 




// --- music and sound effects ----------------------------------------

var backmusic = AB.backgroundMusic ( MUSIC_BACK );

function musicPlay()   { backmusic.play();  }
function musicPause()  { backmusic.pause(); }

											 
function soundAlarm()
{
	var alarm = new Audio ( SOUND_ALARM );
	alarm.play();							// play once, no loop 
}


// --- altered by Deborah Djon ------------------------------------------------------------

let aStarPath = [];
let aStarPathTiles = [];
let openList = [];
let openListTiles = [];
let closedList = [];
let closedListTiles = [];

let deadEnd = [];
let currentNode = null;

// Prevent user from using a too high value for "WALLMOVENO"
if (WALLMOVENO>NOBOXES) {
    throw new Error(message || " Number or walls to move is higher than the number of boxes in the field.");
}


class treeNode {
    constructor(position, goalHeuristic, parentNode){
        this.position = position;
        // heuristic distance to Goal
        this.goalHeuristic = goalHeuristic; //h 
        this.parentNode=parentNode;
        if(parentNode==null){
            this.pathLength = 0; //g
        }else{
            this.pathLength = parentNode.pathLength+1;
        }
        this.cost=this.goalHeuristic+this.pathLength; //f
    }
}

// euclidian distance
function heuristicDistance([x,y]){
    return Math.sqrt((ai-x)*(ai-x)+(aj-y)*(aj-y));
}


function resetStarPath() {
	// remove tiles and path
	aStarPath = [];
    openList = [];
    closedList = [];
    	
	visited = [];
	aStarPathTiles.forEach(tile=>{
	    ABWorld.scene.remove(tile)
	})
	
	openListTiles.forEach(tile=>{
	    ABWorld.scene.remove(tile)
	})
	
	closedListTiles.forEach(tile=>{
	    ABWorld.scene.remove(tile)
	})
	
	aStarPathTiles = [];
	openListTiles = [];
    closedListTiles = [];

}

// function coded from scratch without reference for the maximum learning challange haha
function calculateAStarPath(){
    resetStarPath();
    // open list: nodes I am evaluating 
    // closed list: nodes I have already evaluated
    
    const goalHeuristic = heuristicDistance([ei, ej]);
    const enemyPosition = [ei, ej];
    const agentPosition = [ai, aj];
    const pathLength = 0;
    const parentNode = null;
    
    let agentFound = false;
    let treeRoot = new treeNode(enemyPosition, goalHeuristic, parentNode);
    openList.push(treeRoot);

    // prevent loop from going forever
    loopCount = 0; 
    maxLoopCount = 1000;
    while (!agentFound){
        /*
        sort the open list by f value and then h value
        source sorting: https://levelup.gitconnected.com/sort-array-of-objects-by-two-properties-in-javascript-69234fa6f474
        */
        openList.sort((a, b)=> {
          if (a.cost === b.cost){
            return a.goalHeuristic < b.goalHeuristic ? -1 : 1
          } else {
            return a.cost < b.cost ? -1 : 1
          }
        });
        currentNode = openList.splice(0,1)[0];
        closedList.push(currentNode); //current node
        ; 

        if(currentNode.position.toString() === agentPosition.toString()){
            console.log("agent found");
            agentFound = true;
            break
        }
    	const nextPositions = getNextSteps(currentNode.position);
	    nextPositions.forEach(position=>{
    	    nodesAtPosition = openList.filter(node => node.position.toString()===position.toString());
    	    if (nodesAtPosition.length==0){
    	        //no node with this position is in the open list
    	        newNode = new treeNode(position, heuristicDistance(position), currentNode);
    	        openList.push(newNode)
    	        console.log("added new node to open list. Node at position: "+newNode.position);
    	    }else{
    	        //node is in the open list
    	        //if the node leads to a new shorter path, update the node
    	        nodeAtPositoin = nodesAtPosition[0]
    	        if(nodeAtPositoin.pathLength<currentNode.pathLength){
    	            nodeAtPositoin.parentNode = currentNode;
    	            nodeAtPositoin.pathLength = nodeAtPositoin.parentNode.pathLength+1;
    	            nodeAtPositoin.cost = nodeAtPositoin.goalHeuristic+nodeAtPositoin.pathLength;
    	        }
    	    }
        });

        loopCount++;
        if(loopCount==maxLoopCount){
            agentFound=true;
        }
    }
    
    // Recreate the A* Path
    loopCount = 0;
    while(currentNode.position.toString()!=enemyPosition.toString()){
        aStarPath.push(currentNode.position);
        currentNode = currentNode.parentNode;
        loopCount++;
        if(loopCount==maxLoopCount){
            break
        }
    }
}


function getNextSteps ([x,y]){
            console.log([x,y])
            console.log(currentNode);

    let result = [];
    let positions = [
	                [x+1,y], 
	                [x-1,y], 
	                [x,y+1], 
	                [x,y-1]
	                ];
    positions.forEach(pos => {
        if ((0 < pos[0] < gridsize) && // position within grid
            (0 < pos[1] < gridsize) && 
            (
                (pos.toString() == [ai, aj].toString()) || // the next step is the agent
                (! occupied (pos[0], pos[1])) // no wall or enemy there
            ) &&
            (!closedList.some(node => node.position.toString()==pos.toString())) && //not in closed
            (pos.toString() != currentNode.position.toString())) // I did not come from there
            {
            result.push(pos);
        }
    });
    return result;
}


function drawAStarPath(){
    console.log("draw A* path: "+aStarPath);
    // takes in an array of positions and places squarrs on these positions
    
    color = new THREE.Color("white");
    // draw A* path
    aStarPath.forEach(pos => {
        shape    = new THREE.BoxGeometry ( squaresize, 1, squaresize );			 
        material = new THREE.MeshBasicMaterial( { color: color } );
        material.transparent = true; 
        material.opacity = 0.7;
        tile  = new THREE.Mesh( shape , material);
        position = translate(pos[0],pos[1]);
        position.y = position.y - BOXHEIGHT/2; //for the path
        tile.position.copy(position);
        ABWorld.scene.add(tile);
        aStarPathTiles.push(tile);
    });
    
    // draw closed tiles
    closedList.forEach(node => {
        shape    = new THREE.BoxGeometry ( squaresize, 1, squaresize );			 
        material = new THREE.MeshBasicMaterial( { color: color } );
        material.transparent = true; 
        material.opacity = 0.5;
        tile  = new THREE.Mesh( shape , material);
        position = translate(node.position[0],node.position[1]);
        position.y = position.y - BOXHEIGHT/2; //for the path
        tile.position.copy(position);
        ABWorld.scene.add(tile);
        aStarPathTiles.push(tile);
    });
    
    
    // draw open tiles
    openList.forEach(node => {
        shape    = new THREE.BoxGeometry ( squaresize, 1, squaresize );			 
        material = new THREE.MeshBasicMaterial( { color: color } );
        material.transparent = true; 
        material.opacity = 0.2;
        tile  = new THREE.Mesh( shape , material);
        position = translate(node.position[0],node.position[1]);
        position.y = position.y - BOXHEIGHT/2; //for the path
        tile.position.copy(position);
        ABWorld.scene.add(tile);
        aStarPathTiles.push(tile);
    });
    
}

//source: https://stackoverflow.com/questions/4550505/getting-a-random-value-from-a-javascript-array
Array.prototype.sample = function(){
  return this[Math.floor(Math.random()*this.length)];
}


function moveWalls(){
  console.log("Moving some walls")
  movedWalls = [];
  while(movedWalls.length < WALLMOVENO){
      // sample a random maze box  
      wallToMove = mazeBoxes.sample();
      console.log("sampled wall position: "+untranslate(wallToMove.position).x+","+untranslate(wallToMove.position).y)
      // try to move to some random adjacent location
      position = untranslate(wallToMove.position);
      x = [position.x-1, position.x, position.x+1].sample();
      y = [position.y-1, position.y, position.y+1].sample();
      newPosition = translate(x,y);
      console.log("check if wall can be moved: ")
      console.log("occupied: "+!occupied(x,y));
      console.log("already moved: "+(!movedWalls.includes(newPosition.toString())));
      console.log("recorded walls that were moved:"+ movedWalls)
      if(
          (!occupied(x,y)) && 
          (!movedWalls.includes([newPosition.x, newPosition.y].toString()))
      ){
        // for making sure the wall is only moved once in one moving cycle
        wallToMove.position.copy(newPosition);
        movedWalls.push([newPosition.x, newPosition.y].toString());
        
        //Update the grid
        GRID[position.x][position.y] = GRID_BLANK;
   		GRID[x][y] = GRID_MAZE;
        
        console.log("Wall moved from: "+position.x+","+position.y+"to: "+x+","+y)
        console.log("We have moved " + movedWalls.length+ " walls.")
      }
  }
  
}

function untranslate(position){
    var result = new THREE.Vector2();
    result.x = (position.x +  ( MAXPOS/2 ))/squaresize;
    result.y = (position.z +  ( MAXPOS/2 ))/squaresize;
    return result;
}