Code viewer for World: SonicWorld1
// ==== 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       = 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.

var show3d = false; // Switch between 3d and 2d view (both using Three.js)
var TEXTURE_WALL = "/uploads/vyoma136/hills.jpg";
var TEXTURE_MAZE = "/uploads/vyoma136/snow.jpg";
var TEXTURE_AGENT = "/uploads/vyoma136/sonic.jpg";
var TEXTURE_ENEMY = "/uploads/vyoma136/dr.eggman.jpg";
var TEXTURE_PATH = "/uploads/vyoma136/line.jpg";
var MUSIC_BACK = "/uploads/vyoma136/sonicfinalzone.mp3";
var SOUND_ALARM = "/uploads/vyoma136/sonicgameover.mp3";
var gridsize = 50; // number of squares along side of world	
var NOBOXES = Math.trunc(gridsize * gridsize / 3);   // density of maze - number of internal boxes
           // (bug) use trunc or can get a non-integer 
var squaresize = 100; // size of square in pixels
var MAXPOS = gridsize * squaresize;// length of one side in pixels 
var SKYCOLOR = 0xddffdd;  
var startRadiusConst = .8 * MAXPOS; // distance from centre to start the camera at
var maxRadiusConst = 10 * MAXPOS; // maximum distance from camera we will render things 
//--- change ABWorld defaults: -------------------------------
ABHandler.MAXCAMERAPOS = maxRadiusConst, ABHandler.GROUNDZERO = true;

var SKYBOX_ARRAY = ["/uploads/vyoma136/snowy.jpg", "/uploads/vyoma136/snowy1.jpg", "/uploads/vyoma136/snowy2.jpg", 
                    "/uploads/vyoma136/snowy3.jpg", 
                    "/uploads/vyoma136/snowy.jpg", 
                    "/uploads/vyoma136/snowy.jpg"];
//--- Mind can pick one of these actions -----------------                    
var ACTION_LEFT = 0;
var ACTION_RIGHT = 1;
var ACTION_UP = 2;
var ACTION_DOWN = 3;
var 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
var GRID_BLANK = 0;
var GRID_WALL = 1;
var GRID_MAZE = 2;
var BOXHEIGHT; // 3d or 2d box height 
var theagent;
var theenemy;
var wall_texture;
var agent_texture;
var enemy_texture;
var maze_texture;
var ei;
var ej;
var ai;
var aj;
var badsteps;
var goodsteps;
var start;
var end;
var isblocked;
var shape;
var thecube;
var thepath;
var path_texture;
var GRID = new Array(gridsize); // can query GRID about whether squares are occupied, will in fact be initialised as a 2D array   
var openSet = [];
var closedSet = [];
var pathArray = [];
var children = [];
function myheuristic(a, b) { //Heuristic function Manhattan Distance
 return Math.abs(a.i - b.i) + Math.abs(a.j - b.j);
}
function removePath() {
 console.log("Finding new path at every move");
 var i = 0;
 for (; i < pathArray.length; i++) {
   var key = pathArray[i];
   var a = ABWorld.scene.getObjectByProperty("id", key);
   ABWorld.scene.remove(a);
 }
}
function Spot(i, j, width) { 
 this.i = i;
 this.j = j;
 this.iswallormaze = true === width;
 this.f = 0;
 this.g = 0;
 this.h = 0;
 this.neighbors = [];
 this.parent = void 0;
 this.addNeighbors = function(arr) {
   var i = this.i;
   var j = this.j;
   if (i < gridsize - 1 && false === arr[i + 1][j].iswallormaze) {
     this.neighbors.push(arr[i + 1][j]);
   }
   if (i > 0 && false === arr[i - 1][j].iswallormaze) {
     this.neighbors.push(arr[i - 1][j]);
   }
   if (j < gridsize - 1 && false === arr[i][j + 1].iswallormaze) {
     this.neighbors.push(arr[i][j + 1]);
   }
   if (j > 0 && false === arr[i][j - 1].iswallormaze) {
     this.neighbors.push(arr[i][j - 1]);
   }
 };
}
function AStarAlgo(begin, end) { //A Star algorithm
 resetGrid();
 removePath();
 var current = void 0;
 var events = [begin];
 var result = [];
 for (; 0 !== events.length;) {
   var j = 0;
   var i = 0;
   for (; i < events.length; i++) {
     if (events[i].f < events[j].f) {
       j = i;
     }
   }
   if ((current = events[j]) === end) {
     console.log("Dr. Eggman has reached to the Sonic!");
     break;
   }
   removeFromArray(events, current);
   result.push(current);
   var neighbors = current.neighbors;
   var n = 0;
   for (; n < neighbors.length; n++) {
     var neighbor = neighbors[n];
     if (!result.includes(neighbor) && !neighbor.iswallormaze) {
       var ng = current.g + myheuristic(neighbor, current);
       var l = false;
       if (events.includes(neighbor)) {
         if (ng < neighbor.g) {
           neighbor.g = ng;
           l = true;
         }
       } else {
         neighbor.g = ng;
         l = true;
         events.push(neighbor);
       }
       if (l) {
         neighbor.h = myheuristic(neighbor, end);
         neighbor.f = neighbor.g + neighbor.h;
         neighbor.parent = current;
       }
     }
   }
 }
 path = [];
 var p = current;
 path.push(p);
 for (; p.parent;) {
   path.push(p.parent);
   p = p.parent;
 }
 return path;
}
function removeFromArray(array, item) {
 var i = array.length - 1;
 for (; i >= 0; i--) {
   if (array[i] == item) {
     array.splice(i, 1);
   }
 }
}
function loadResources() { // asynchronous file loads - call initScene() when all finished 
 var mockObjectLoader = new THREE.TextureLoader();
 var outbound_chart = new THREE.TextureLoader();
 var inbound_chart = new THREE.TextureLoader();
 var snippetFrame = new THREE.TextureLoader();
 var CHANNEL_STORE = new THREE.TextureLoader();
 mockObjectLoader.load(TEXTURE_WALL, function(texture) {
   texture.minFilter = THREE.LinearFilter;
   wall_texture = texture;
   if (asynchFinished()) {
     initScene();
   }
 });
 outbound_chart.load(TEXTURE_AGENT, function(texture) {
   texture.minFilter = THREE.LinearFilter;
   agent_texture = texture;
   if (asynchFinished()) {
     initScene();
   }
 });
 inbound_chart.load(TEXTURE_ENEMY, function(texture) {
   texture.minFilter = THREE.LinearFilter;
   enemy_texture = texture;
   if (asynchFinished()) {
     initScene();
   }
 });
 snippetFrame.load(TEXTURE_MAZE, function(texture) {
   texture.minFilter = THREE.LinearFilter;
   maze_texture = texture;
   if (asynchFinished()) {
     initScene();
   }
 });
 CHANNEL_STORE.load(TEXTURE_PATH, function(texture) {
   texture.minFilter = THREE.LinearFilter;
   path_texture = texture;
   if (asynchFinished()) {
     initScene();
   }
 });
}
function asynchFinished() {
 return !!(wall_texture && agent_texture && enemy_texture && maze_texture && path_texture);  // all file loads returned 
}
function occupied(x, y) { // is this square occupied
 return ei == x && ej == y || ai == x && aj == y || true === GRID[x][y].iswallormaze;
}
 // translate my (e,_) 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(e, _) {
 var globalBonePosition = new THREE.Vector3();
 return globalBonePosition.y = 0, globalBonePosition.x = e * squaresize - MAXPOS / 2, globalBonePosition.z = _ * squaresize - MAXPOS / 2, globalBonePosition;
}
function initScene() {    //all files have returned
 var value;
 var name;
 // set up GRID as 2D array
 value = 0;
 for (; value < gridsize; value++) {
   GRID[value] = new Array(gridsize);
 }
 value = 0;
 for (; value < gridsize; value++) {
   name = 0;
   for (; name < gridsize; name++) {
     if (0 === value || value == gridsize - 1 || 0 === name || name == gridsize - 1) {
       isblocked = true;
       GRID[value][name] = new Spot(value, name, isblocked);
       shape = new THREE.BoxGeometry(squaresize, BOXHEIGHT, squaresize);
       (thecube = new THREE.Mesh(shape)).material = new THREE.MeshBasicMaterial({
         map : wall_texture
       });
       thecube.position.copy(translate(value, name));
       ABWorld.scene.add(thecube);
     } else {
       GRID[value][name] = new Spot(value, name);
     }
   }
 }
 var a = 1;
 for (; a <= NOBOXES; a++) {
   isblocked = true;
   value = AB.randomIntAtoB(1, gridsize - 2);
   name = AB.randomIntAtoB(1, gridsize - 2);
   GRID[value][name] = new Spot(value, name, isblocked);
   shape = new THREE.BoxGeometry(squaresize, BOXHEIGHT, squaresize);
   (thecube = new THREE.Mesh(shape)).material = new THREE.MeshBasicMaterial({
     map : maze_texture
   });
   thecube.position.copy(translate(value, name));
   ABWorld.scene.add(thecube);
 }
 value = 0;
 for (; value < gridsize; value++) {
   name = 0;
   for (; name < gridsize; name++) {
     if (0 !== value && value != gridsize - 1 && 0 !== name && name != gridsize - 1) {
       GRID[value][name].addNeighbors(GRID);
     }
   }
 }
   // set up enemy 
       // start in random location
 do {
   value = AB.randomIntAtoB(1, gridsize - 2);
   name = AB.randomIntAtoB(1, gridsize - 2);
 } while (occupied(value, name));
 ei = value;
 ej = name;
 shape = new THREE.BoxGeometry(squaresize, BOXHEIGHT, squaresize);
 (theenemy = new THREE.Mesh(shape)).material = new THREE.MeshBasicMaterial({
   map : enemy_texture
 });
 ABWorld.scene.add(theenemy);
 drawEnemy();
 // set up agent 
       // start in random location
 do {
   value = AB.randomIntAtoB(1, gridsize - 2);
   name = AB.randomIntAtoB(1, gridsize - 2);
 } while (occupied(value, name));
 ai = value;
 aj = name;
 shape = new THREE.BoxGeometry(squaresize, BOXHEIGHT, squaresize);
 (theagent = new THREE.Mesh(shape)).material = new THREE.MeshBasicMaterial({
   map : agent_texture
 });
 ABWorld.scene.add(theagent);
 drawAgent();
 ABWorld.scene.background = (new THREE.CubeTextureLoader()).load(SKYBOX_ARRAY, function() {
   ABWorld.render();
   AB.removeLoading();
   AB.runReady = true;
 });
}
function drawEnemy() {
 theenemy.position.copy(translate(ei, ej));
 ABWorld.lookat.copy(theenemy.position);
}
function drawAgent() {
 theagent.position.copy(translate(ai, aj));
 ABWorld.follow.copy(theagent.position);
}
function moveLogicalEnemy() {
 var c = AStarAlgo(GRID[ei][ej], GRID[ai][aj]);
 i = c[c.length - 2].i;
 j = c[c.length - 2].j;
 var k = 0;
 for (; k < c.length - 2; k++) {
   c[k];
   shape = new THREE.BoxGeometry(30, 30, 30);
   thepath = new THREE.Mesh(shape);
   pathArray[k] = thepath.id;
   thepath.material = new THREE.MeshBasicMaterial({
     map : path_texture
   });
   thepath.position.copy(translate(c[k].i, c[k].j));
   ABWorld.scene.add(thepath);
 }
 if (!occupied(i, j)) {
   ei = i;
   ej = j;
 }
}
function moveLogicalAgent(opt_parentWin) {
 var i = ai;
 var charsetBitSize = aj;
 if (opt_parentWin == ACTION_LEFT) {
   i--;
 } else {
   if (opt_parentWin == ACTION_RIGHT) {
     i++;
   } else {
     if (opt_parentWin == ACTION_UP) {
       charsetBitSize++;
     } else {
       if (opt_parentWin == ACTION_DOWN) {
         charsetBitSize--;
       }
     }
   }
 }
 if (!occupied(i, charsetBitSize)) {
   ai = i;
   aj = charsetBitSize;
 }
}
 
   // --- 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 COUNTER = [37, 38, 39, 40];
function counter(params) {
 return COUNTER.includes(params.keyCode);
}
function keyHandler(evt) {
 return !AB.runReady || !counter(evt) || (37 == evt.keyCode && moveLogicalAgent(ACTION_LEFT), 38 == evt.keyCode && moveLogicalAgent(ACTION_DOWN), 39 == evt.keyCode && moveLogicalAgent(ACTION_RIGHT), 40 == evt.keyCode && moveLogicalAgent(ACTION_UP), evt.stopPropagation(), evt.preventDefault(), false);
}
function badstep() {
 return Math.abs(ei - ai) < 2 && Math.abs(ej - aj) < 2;
}
function agentBlocked() {
 return occupied(ai - 1, aj) && occupied(ai + 1, aj) && occupied(ai, aj + 1) && occupied(ai, aj - 1);
}
function updateStatusBefore(opt_parentWin) {
 var default_favicon = AB.world.getState();
 AB.msg(" Step: " + AB.step + " &nbsp; x = (" + default_favicon.toString() + ") &nbsp; a = (" + opt_parentWin + ") ");
}
function updateStatusAfter() {
 var default_favicon = AB.world.getState();
 var e_total = goodsteps / AB.step * 100;
 AB.msg(" &nbsp; y = (" + default_favicon.toString() + ") <br> Bad steps: " + badsteps + " &nbsp; Good steps: " + goodsteps + " &nbsp; Score: " + e_total.toFixed(2) + "% ", 2);
}
AB.world.newRun = function() {
 AB.loadingScreen();
 AB.runReady = false;
 badsteps = 0;
 goodsteps = 0;
 BOXHEIGHT = 1;
 ABWorld.init2d(startRadiusConst, maxRadiusConst, 16768477);
 loadResources();
 document.onkeydown = keyHandler;
}, AB.world.getState = function() {
 return [ai, aj, ei, ej];
}, AB.world.takeAction = function(optionString) {
 updateStatusBefore(optionString);
 moveLogicalAgent(optionString);
 if (AB.step % 2 === 0) {
   moveLogicalEnemy();
 }
 if (badstep()) {
   badsteps++;
   console.log("No place for Agent to move! Please re-load the game.");
 } else {
   goodsteps++;
 }
 drawAgent();
 drawEnemy();
 updateStatusAfter();
 if (agentBlocked()) {
   console.log("Agent is trapped on all sides.Please re-load the game.");
   AB.abortRun = true;
   goodsteps = 0;
   musicPause();
   soundAlarm();
 }
}, AB.world.endRun = function() {
 musicPause();
 if (AB.abortRun) {
   AB.msg(" <br> <font color=red> <B> Sonic(agent) got trapped. Final score zero. </B> </font>   ", 3);
 } else {
   AB.msg(" <br> <font color=green> <B> Run over. </B> </font>   ", 3);
 }
}, AB.world.getScore = function() {
 var ipw = goodsteps / AB.maxSteps * 100;
 return Math.round(100 * ipw) / 100;
};
var backmusic = AB.backgroundMusic(MUSIC_BACK);
function musicPlay() {
 backmusic.play();
}
function musicPause() {
 backmusic.pause();
}
function soundAlarm() {
 (new Audio(SOUND_ALARM)).play();
}
function resetGrid() {
 i = 1;
 for (; i < gridsize - 1; i++) {
   j = 1;
   for (; j < gridsize - 1; j++) {
     if (false === GRID[i][j].iswallormaze) {
       GRID[i][j].parent = null;
       GRID[i][j].g = 0;
       GRID[i][j].h = 0;
       GRID[i][j].f = 0;
     }
   }
 }
}
;