// Cloned by mick on 10 Nov 2023 from World "Complex World (clone by Pranay Manjunath)" by Pranay Manjunath // Please leave this clone trail here.
AB.clockTick =100;// 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) //AI Practical Start - Changed the images to make a unique world///*const TEXTURE_WALL = '/uploads/pranay85/1636991319.png' ;
const TEXTURE_MAZE = '/uploads/pranay85/1636989691.png' ;
const TEXTURE_AGENT = '/uploads/pranay85/1636822877.png' ;
const TEXTURE_ENEMY = '/uploads/pranay85/1636822913.png' ;
*/const TEXTURE_WALL ='/uploads/pranay85/1638622761.png';const TEXTURE_MAZE ='/uploads/pranay85/1638623030.png';const TEXTURE_AGENT ='/uploads/pranay85/1638623103.png';const TEXTURE_ENEMY ='/uploads/pranay85/1638623071.png';//AI Practical End// 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//AI Practical Start - Changed the backgroud //const MUSIC_BACK ='/uploads/pranay85/BatmanMusic.mp4';const SOUND_ALARM ='/uploads/starter/air.horn.mp3';//AI Practical End// credits:// http://www.dl-sounds.com/royalty-free/defense-line/// http://soundbible.com/1542-Air-Horn.html //AI Practical Startconst gridsize =12;//Changed gridsize to 50 - number of squares along side of world//AI Practical End//AI Practical Start - Changed the number of boxes// density of maze - number of internal boxesconst NOBOXES =0;//Math.trunc ( (gridsize * gridsize) / 3 );//AI Practical Endconst squaresize =100;// size of square in pixelsconst 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 atconst maxRadiusConst = MAXPOS *10;// maximum distance from camera we will render things //--- 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//AI Practical Start - Change background/*
const SKYBOX_ARRAY = [
"/uploads/starter/dawnmountain-xpos.png",
"/uploads/starter/dawnmountain-xneg.png",
"/uploads/starter/dawnmountain-ypos.png",
"/uploads/starter/dawnmountain-yneg.png",
"/uploads/starter/dawnmountain-zpos.png",
"/uploads/starter/dawnmountain-zneg.png"
];
*/// space skybox, credit:// http://en.spaceengine.org/forum/21-514-1// x,y,z labelled differentlyconst SKYBOX_ARRAY =["/uploads/starter/sky_pos_z.jpg","/uploads/starter/sky_neg_z.jpg","/uploads/starter/sky_pos_y.jpg","/uploads/starter/sky_neg_y.jpg","/uploads/starter/sky_pos_x.jpg","/uploads/starter/sky_neg_x.jpg"]//AI Practical End// urban photographic skyboxes, credit:// http://opengameart.org/content/urban-skyboxes/*const SKYBOX_ARRAY = [
"/uploads/starter/posx.jpg",
"/uploads/starter/negx.jpg",
"/uploads/starter/posy.jpg",
"/uploads/starter/negy.jpg",
"/uploads/starter/posz.jpg",
"/uploads/starter/negz.jpg"
];
*/// ===================================================================================================================// === 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 squareconst GRID_BLANK =0;const GRID_WALL =1;const GRID_MAZE =2;var BOXHEIGHT;// 3d or 2d box height var GRID =newArray(gridsize);// can query GRID about whether squares are occupied, will in fact be initialised as a 2D array var theagent, theenemy;var wall_texture, agent_texture, enemy_texture, maze_texture;// enemy and agent position on squaresvar ei, ej, ai, aj;var badsteps;var goodsteps;//AI Practical Start// Open and closed setvar openArray =[];var closedArray =[];// Start and endvar start;var end;var pathcolor ='green';//AI Practical Endfunction 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 )returntrue;elsereturnfalse;}//--- grid system -------------------------------------------------------------------------------// my numbering is 0 to gridsize-1function occupied ( i, j )// is this square occupied{if(( ei == i )&&( ej == j ))returntrue;// variable objects if(( ai == i )&&( aj == j ))returntrue;/*if ( GRID[i][j] == GRID_WALL ) return true; // fixed objects
if ( GRID[i][j] == GRID_MAZE ) return true;*/if(GRID[i][j].wall ==true)returntrue;returnfalse;}// 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 arrayfor( i =0; i < gridsize ; i++)
GRID[i]=newArray(gridsize);// set up wallsfor( i =0; i < gridsize ; i++)for( j =0; j < gridsize ; j++)if(( i==0)||( i==gridsize-1)||( j==0)||( j==gridsize-1)){//AI Practical Start//GRID[i][j] = GRID_WALL;
GRID[i][j]=newLoc(i, j,true);// Builds the wall in i,j position//AI Practical End
shape =new THREE.BoxGeometry( squaresize, BOXHEIGHT, squaresize );
thecube =new THREE.Mesh( shape );
thecube.material =new THREE.MeshBasicMaterial({ map: wall_texture });
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;
GRID[i][j]=newLoc(i, j,false);// Leaves the i,j spot blank// set up maze 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);//AI Practical Start - Checks for the wall//GRID[i][j] = GRID_MAZE ;
GRID[i][j].wall =true;//AI Practical End
shape =new THREE.BoxGeometry( squaresize, BOXHEIGHT, squaresize );
thecube =new THREE.Mesh( shape );
thecube.material =new THREE.MeshBasicMaterial({ map: maze_texture });
thecube.position.copy ( translate(i,j));// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates ABWorld.scene.add(thecube);}// set up enemy // start in random locationdo{
i = AB.randomIntAtoB(1,gridsize-2);
j = AB.randomIntAtoB(1,gridsize-2);}while( occupied(i,j));// search for empty square
ei =2;
ej =2;
shape =new THREE.BoxGeometry( squaresize, BOXHEIGHT, squaresize );
theenemy =new THREE.Mesh( shape );
theenemy.material =new THREE.MeshBasicMaterial({ map: enemy_texture });ABWorld.scene.add(theenemy);
drawEnemy();// set up agent // start in random locationdo{
i = AB.randomIntAtoB(1,gridsize-2);
j = AB.randomIntAtoB(1,gridsize-2);}while( occupied(i,j));// search for empty square
ai =8;
aj =8;
shape =new THREE.BoxGeometry( squaresize, BOXHEIGHT, squaresize );
theagent =new THREE.Mesh( shape );
theagent.material =new THREE.MeshBasicMaterial({ map: agent_texture });ABWorld.scene.add(theagent);
drawAgent();// 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});//AI Practical Start// All the neighboursfor(var i =0; i < gridsize; i++)for(var j =0; j < gridsize; j++)
GRID[i][j].addneighbours();// Start and end
start = GRID[ei][ej];
end = GRID[ai][aj];
start.wall =false;
end.wall =false;// openArray starts with beginning only
openArray.push(start);// console.log("initScene openArray", openArray);//AI Practical End }// --- 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 -----------------------------------//AI Practical Startvar old_path =[]classLoc{
constructor(i, j, wall){this.i = i;this.j = j;this.f =0;this.g =0;this.h =0;this.neighbours =[];this.previous =undefined;this.wall = wall;}
checkWall(){returnthis.wall;}// Used to show the pathcolor while chasing the agent
display(fillColor){this.shape =new THREE.BoxGeometry( squaresize,1, squaresize );this.pathBox =new THREE.Mesh(this.shape );this.pathBox.position.copy(translate(this.i,this.j));this.pathBox.material =new THREE.MeshBasicMaterial();this.pathBox.material.color.setColorName(fillColor);ABWorld.scene.add(this.pathBox);};
removePath(){ABWorld.scene.remove(this.pathBox);}// Figure out the neighbours of the agent
addneighbours(){var i =this.i;var j =this.j;if(i < gridsize -1&&!GRID[i +1][j].wall)this.neighbours.push(GRID[i +1][j]);if(i >0&&!GRID[i -1][j].wall)this.neighbours.push(GRID[i -1][j]);if(j < gridsize -1&&!GRID[i][j +1].wall)this.neighbours.push(GRID[i][j +1]);if(j >0&&!GRID[i][j -1].wall)this.neighbours.push(GRID[i][j -1]);};}function removeFromArray(arr, elt){for(var i = arr.length -1; i >=0; i--)if(arr[i]== elt)
arr.splice(i,1);}function heuristic(a, b){return(Math.abs(a.i - b.i)+Math.abs(a.j - b.j));}function resetFunctions(){for(var i =0; i < gridsize; i++)for(var j =0; j < gridsize; j++)if(!GRID[i][j].wall){
GRID[i][j].f =0;
GRID[i][j].g =0;
GRID[i][j].h =0;
GRID[i][j].previous =undefined;}}//AI Practical Endvar statesSearchedCount =0;function moveLogicalEnemy(){// move towards agent //AI Practical Start - Implementation of A* algorithm/
let start = GRID[ei][ej];
let end = GRID[ai][aj];
resetFunctions();for(var i =0; i < old_path.length; i++){
old_path[i].removePath(pathcolor);//console.log("path[i] remove colour", old_path[i]);}
openArray =[start]
closedArray =[]//console.log("openArray with only start", openArray);var current =null;
let path =[];while(openArray.length >0){//console.log("openArray", openArray);if(openArray.length >0){// Best next optionvar win =0;for(var i =0; i < openArray.length; i++)if(openArray[i].f < openArray[win].f)
win = i;var current = openArray[win];// Did I finish?if(current === end){//console.log("success - found path");break;}// Best option moves from openArray to closedArray
removeFromArray(openArray, current);
closedArray.push(current);// Check all the neighboursvar neighbours = current.neighbours;//--- start of for loop -----------for(var i =0; i < neighbours.length; i++){var neighbor = neighbours[i];// Valid next spot?if(!closedArray.includes(neighbor)&&!neighbor.checkWall()){var tempG = current.g + heuristic(neighbor, current);// Is this a better path than before?var newPath =false;if(openArray.includes(neighbor)){if(tempG < neighbor.g){
neighbor.g = tempG;
newPath =true;}}else{
neighbor.g = tempG;
newPath =true;
openArray.push(neighbor);}// Yes, it's a better pathif(newPath){
neighbor.h = heuristic(neighbor, end);
neighbor.f = neighbor.g + neighbor.h;
neighbor.previous = current;}}}//--- end of for loop -----------}// --- end still searching -----------------------------else{//console.log('fail - no path exists');return;}}
statesSearchedCount=openArray.length+closedArray.length;
console.log(statesSearchedCount);var temp = current;
path.push(temp);while(temp && temp.previous){
path.push(temp.previous);
temp = temp.previous;}
old_path = path
for(var i =0; i < path.length; i++){
path[i].display(pathcolor);// console.log("path[i] colour", path[i]);}//console.log("current location", ei, " ", ej);//console.log("path", path);if(path.length >2){//console.log("path[path.length - 2].i", path[path.length - 2].i);//console.log("path[path.length - 2].j", path[path.length - 2].j);
i = path[path.length -2].i;
j = path[path.length -2].j;}else{
i = ei;
j = ej;}if(! occupied(i,j))// if no obstacle then move, else just miss a turn{//console.log("!occupied");
ei = i;
ej = j;}}// AI Practical Endfunction 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--;elseif( a == ACTION_RIGHT ) i++;elseif( a == ACTION_UP ) j++;elseif( 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 )returntrue;// not ready yet // if not one of our special keys, send it to default key handling:if(! ourKeys ( event ))returntrue;// 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();returnfalse;}// --- score: -----------------------------------function badstep()// is the enemy within one square of the agent{if((Math.abs(ei - ai)<2)&&(Math.abs(ej - aj)<2))returntrue;elsereturnfalse;}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 +" x = ("+ x.toString()+") a = ("+ a +") ");}function updateStatusAfter()// agent and enemy have moved, can calculate score{// new state after both have movedvar y = AB.world.getState();var score =( goodsteps / AB.step )*100;
AB.msg (" y = ("+ y.toString()+") <br>"+" Bad steps: "+ badsteps +" Good steps: "+ goodsteps +" Score: "+ score.toFixed(2)+"% "+" States Searched: "+ statesSearchedCount,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();}
statesSearchedCount =0;};
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.maxStepsvar s =( goodsteps / AB.maxSteps )*100;// float like 93.4372778 var x =Math.round (s *100);// 9344return( 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 =newAudio( SOUND_ALARM );
alarm.play();// play once, no loop }