//Assignment 1 - World 2 - Nuno Correia
//Pratical 1 - World 1 changes are identified by "////"
//Pratical 1 - World 2 changes are identified by "//// ////"
//World 1 link - https://ancientbrain.com/world.php?world=4183445779
// World 2 link - https://ancientbrain.com/world.php?world=1579483516
// ==== 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.
//---- global constants: -------------------------------------------------------
const show3d = false; // Switch between 3d and 2d view (both using Three.js)
const TEXTURE_WALL = '/uploads/nuno55/dd5eb9367bb0c67a4acfbbf8cdde6e03--hand-painted-textures-deserts.jpg' ;
const TEXTURE_MAZE = '/uploads/nuno55/f9581b41075c4e0259656a89cedbff5c.png' ;
const TEXTURE_AGENT = '/uploads/nuno55/Roadrunner3.jpg' ;
const TEXTURE_ENEMY = '/uploads/nuno55/Picture-Of-Wile-E.-Coyote.jpg' ;
const TEXTURE_MOVED_MAZE = '/uploads/starter/rocks.jpg';//// ////
// 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
const MUSIC_BACK = '/uploads/nuno55/blondie-one-way-or-another-audio_qSXCIcqx.mp3' ;
const SOUND_ALARM = '/uploads/nuno55/Meepmeep.mp3' ;
// credits:
// http://www.dl-sounds.com/royalty-free/defense-line/
// http://soundbible.com/1542-Air-Horn.html
const gridsize = 50;////*50 2500 // number of squares along side of world
const NOBOXES = Math.trunc ( (gridsize * gridsize) / 3 ); ////Changed to 3
// density of maze - number of internal boxes
// (bug) use trunc or can get a non-integer
const NOBOXES2 = Math.trunc ( (gridsize * gridsize) / 10 );
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
var moved_maze = []; //// ////
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
//--- 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
const SKYBOX_ARRAY = [
"/uploads/nuno55/eq1lhgkv-900.jpg",
"/uploads/nuno55/eq1lhgkv-900.jpg",
"/uploads/nuno55/eq1lhgkv-900.jpg",
"/uploads/nuno55/eq1lhgkv-900.jpg",
"/uploads/nuno55/eq1lhgkv-900.jpg",
"/uploads/nuno55/eq1lhgkv-900.jpg",
];
// space skybox, credit:
// http://en.spaceengine.org/forum/21-514-1
// x,y,z labelled differently
/*
const 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"
];
*/
// 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 square
const GRID_BLANK = 0;
const GRID_WALL = 1;
const GRID_MAZE = 2;
const GRID_ENEMY = 10;
const GRID_AGENT = 20;
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
var theagent, theenemy;
var cubes = [];//// ////
var wall_texture, agent_texture, enemy_texture, maze_texture,moved_maze_texture;//// ////
// enemy and agent position on squares
var ei, ej, ai, aj;
var badsteps;
var goodsteps;
var diagonal = true;////enable diagonal
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();
var loader5 = 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();
});
loader5.load ( TEXTURE_MOVED_MAZE, function ( thetexture )
{
thetexture.minFilter = THREE.LinearFilter;
moved_maze_texture = thetexture;
if ( asynchFinished() ) initScene();
});
}
function asynchFinished() // all file loads returned
{
if ( wall_texture && agent_texture && enemy_texture && maze_texture && moved_maze_texture ) return true; //// ////
else return false;
}
//--- grid system -------------------------------------------------------------------------------
// my numbering is 0 to gridsize-1
function occupied ( i, j ) // is this square occupied?
{
// in Grid?
if (i <= gridsize && j <= gridsize &&
i >= 0 && j >= 0) {
switch (GRID[i][j]) {
case GRID_WALL:
case GRID_MAZE:
case GRID_ENEMY:
case GRID_AGENT:
return true;
default:return false;
}
}
return true;// off grid
}
function iswall(i, j)
{
switch (GRID[i][j]) {
case GRID_WALL:
case GRID_MAZE:
return true;
default:return false;
}
}
// translate my (i,j) grid newLocation to three.js (x,y,z) newLocation
// logically, newLocation 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;
////var aGrid = [];
// set up GRID as 2D array
for ( i = 0; i < gridsize ; i++ )
GRID[i] = new Array(gridsize);
/*
for (i = 0; i < gridsize; i++) {
aGrid[i] = [];
for (j = 0; j < gridsize; j++)
{
aGrid[i][j] = new spot(i, j);
}
// Neighbors
}
for (i = 0; i < gridsize; i++) {
for (j = 0; j < gridsize; j++) {
aGrid[i][j].addNeighbors();
}
}
*/
// set up walls
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, 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 newLocation to three.js (x,y,z) newLocation
ABWorld.scene.add(thecube);
}
else
GRID[i][j] = GRID_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);
GRID[i][j] = GRID_MAZE ;
shape = new THREE.BoxGeometry ( squaresize, BOXHEIGHT, squaresize );
thecube = new THREE.Mesh( shape );
thecube.material = new THREE.MeshBasicMaterial( { map: maze_texture } );
cubes.push(thecube);
thecube.position.copy ( translate(i,j) ); // translate my (i,j) grid newLocation to three.js (x,y,z) newLocation
ABWorld.scene.add(thecube);
}
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 );
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 );
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
});
}
// --- draw moving objects -----------------------------------
function drawEnemy() // given ei, ej, draw it
{
theenemy.position.copy ( translate(ei,ej) ); // translate my (i,j) grid newLocation to three.js (x,y,z) newLocation
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 newLocation to three.js (x,y,z) newLocation
ABWorld.follow.copy ( theagent.position ); // follow vector = agent position (for camera following agent)
}
function drawCubes() //// ////Function to recreate the maze after the movement is called
{
let counter = 0;
for(var i=0;i<GRID.length;i++)
{
for(var j=0;j<GRID[i].length;j++)
{
if(GRID[i][j] == 2)
{
//Update Tetxure
for(var k=0;k<moved_maze.length;k++)
{
if(moved_maze[k][0] == i && moved_maze[k][1] == j)
{
cubes[counter].material = new THREE.MeshBasicMaterial( { map: moved_maze_texture } );
}
}
//updated maze position showing
cubes[counter].position.copy ( translate(i,j)); // translate my (i,j) grid newLocation to three.js (x,y,z) newLocation
ABWorld.follow.copy ( cubes[counter].position);
counter++;
}
}
}
}
//// A* grid spot function: location, A* calculations and array to the neighbours (including diagonal) - adapted GRID from previous exercise
function spot(i, j) {
// Location
this.i = i;
this.j = j;
// f, g, and h values for A*
this.f = 0;
this.g = 0;
this.h = 0;
// Walls
this.obstacle = 0;
// Neighbors
this.neighbors = [];
// Where did I come from?
this.parent = undefined;
this.closed = false; // are we in closed set
// Figure out who my neighbors are
this.addNeighbors = function () {
var i = this.i;
var j = this.j;
if (i < gridsize - 1) this.neighbors.push(aGrid[i + 1][j]);
if (i > 0) this.neighbors.push(aGrid[i - 1][j]);
if (j < gridsize - 1) this.neighbors.push(aGrid[i][j + 1]);
if (j > 0) this.neighbors.push(aGrid[i][j - 1]);
if (diagonal){
if (i > 0 && j > 0) this.neighbors.push(aGrid[i - 1][j - 1]);
if (i < gridsize - 1 && j > 0) this.neighbors.push(aGrid[i + 1][j - 1]);
if (i > 0 && j < gridsize - 1) this.neighbors.push(aGrid[i - 1][j + 1]);
if (i < gridsize - 1 && j < gridsize - 1) this.neighbors.push(aGrid[i + 1][j + 1]);
}
};
}
//// adapted GRID from previous exercise
function aInitScen() {
var i, j;
//var aGrid = [];// aGrid
for (i = 0; i < gridsize; i++) {
aGrid[i] = [];
for (j = 0; j < gridsize; j++)
{
aGrid[i][j] = new spot(i, j);
}
// Neighbors
}
for (i = 0; i < gridsize; i++) {
for (j = 0; j < gridsize; j++) {
aGrid[i][j].addNeighbors();
}
}
}
var aGrid = [];
//// A* setup returns the next (i,j)
function aStar(grid, start, end) {
var openSet = [];
var parent;
var completed = false;
var startNode = aGrid[start.i][start.j];
var endNode = aGrid[end.i][end.j];
var currentNode;
var approach = new newLocation(start.i, start.j);
openSet.push(startNode);
for (i = 0; i < gridsize; i++) {
for (j = 0; j < gridsize; j++) {
currentNode = aGrid[i][j];
currentNode.f = currentNode.h = currentNode.g = 0;
currentNode.closed = false;
currentNode.parent = null;
}
}
// --- begin search -----------------------------
while ((openSet.length > 0) && !completed) {
if (openSet.length > limit) {
completed = true;
return approach;
}
openSet.sort((a, b) => (a.f > b.f) ? 1 : -1);
currentNode = openSet.shift();
currentNode.closed = true;
if (currentNode == endNode) {
if (PATH){
pathArray.push(endNode);
}
// Best next option
while (currentNode.parent != startNode) {
if (PATH){
pathArray.push(currentNode);
}
currentNode = currentNode.parent;
}
completed = true;
if (PATH){
pathArray.push(startNode);
}
} else {
// Did I finish?
for (n = 0; n < currentNode.neighbors.length; n++) {
parent = currentNode.neighbors[n];
if (parent.closed) {
} else if (iswall(parent.i, parent.j)) {
parent.closed = true;
} else {
if ((parent.i == currentNode.i) || (parent.j == currentNode.j))
parent.g = currentNode.g;
else
parent.g = currentNode.g;
parent.h = aStarHeuristics(parent, endNode);
parent.f = parent.g + parent.h;
parent.parent = currentNode;
if (openSet.indexOf(parent) < 0) {
openSet.push(parent);}
}
}
}
}
approach.i = currentNode.i;
approach.j = currentNode.j;
return approach;
}
// --- end search -----------------------------
///(i,j) newLocation
class newLocation {
constructor(i, j) {
this.i = i;
this.j = j;}
}
var start = new newLocation();
var end = new newLocation();
const limit = gridsize * gridsize;
////=== heuristic ===========================
// this must be always optimistic - real time will be this or longer
function aStarHeuristics(start, end)
{
if (diagonal) return (Math.pow(start.i - end.i, 2) + Math.pow((start.j - end.j), 2));
// 2D distance
else return (Math.abs(start.i - start.i) + Math.abs(start.j - end.j));
// else not diagonal, can only go across and down
// so this is optimistic
// note this is not optimistic if we can do diagonal move
}
//// --- take actions -----------------------------------
function moveLogicalEnemy() {
var i, j;
start.i = ei;
start.j = ej;
end.i = ai;
end.j = aj;
bestPath = aStar(GRID, start, end);//Call A*
if (!occupied(bestPath.i, bestPath.j)) {
GRID[ei][ej] = GRID_BLANK;
ei = bestPath.i;
ej = bestPath.j;
GRID[ei][ej] = GRID_ENEMY;
}
}
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;
}
//// ////The Trap
/* Here I have added a condition that if the agent has no maze around itself then we move other maze towards it
Empty
Empty PACMAN Empty
Empty
Then we try to move near maze toward itself
*/
if(GRID[ai+1][aj] !=2 && GRID[ai-1][aj]!=2 && GRID[ai][aj+1]!=2 && GRID[ai][aj-1] !=2)
{
if(GRID[ai+1][aj] === 0) //// //// checking on right side of it, if there is not any maze then we move other toward the right of the agent
{
if(GRID[ai+2][aj] === 2)
{
GRID[ai+2][aj] = 0;
GRID[ai+1][aj] = 2;
moved_maze.push([ai+1,aj]);
}
else if(GRID[ai+1][aj+1] == 2)
{
GRID[ai+1][aj+1] = 0;
GRID[ai+1][aj] = 2;
moved_maze.push([ai+1,aj]);
}
else if(GRID[ai+1][aj-1] == 2)
{
GRID[ai+1][aj-1] = 0;
GRID[ai+1][aj] = 2;
moved_maze.push([ai+1,aj]);
}
}
else if(GRID[ai-1][aj] === 0) //// //// checking on left side of it, if there is not any maze then we move other toward the left of the agent
{
if(GRID[ai-2][aj] === 2)
{
GRID[ai-2][aj] = 0;
GRID[ai-1][aj] = 2;
moved_maze.push([ai-1,aj]);
}
else if(GRID[ai-1][aj+1] == 2)
{
GRID[ai-1][aj+1] = 0;
GRID[ai-1][aj] = 2;
moved_maze.push([ai-1,aj]);
}
else if(GRID[ai-1][aj-1] == 2)
{
GRID[ai-1][aj-1] = 0;
GRID[ai-1][aj] = 2;
moved_maze.push([ai-1,aj]);
}
}
if(GRID[ai][aj+1] === 0)//// //// checking on the up side of it, if there is not any maze then we move other toward the up of the agent
{
if(GRID[ai][aj+2] === 2)
{
GRID[ai][aj+2] = 0;
GRID[ai][aj+1] = 2;
moved_maze.push([ai,aj+1]);
}
else if(GRID[ai-1][aj+1] == 2)
{
GRID[ai-1][aj+1] = 0;
GRID[ai][aj+1] = 2;
moved_maze.push([ai,aj+1]);
}
else if(GRID[ai+1][aj+1] == 2)
{
GRID[ai+1][aj+1] = 0;
GRID[ai][aj+1] = 2;
moved_maze.push([ai,aj+1]);
}
}
else if(GRID[ai][aj-1] === 0)//// //// checking on the down side of it, if there is not any maze then we move other toward the down of the agent
{
if(GRID[ai][aj-2] === 2)
{
GRID[ai][aj-2] = 0;
GRID[ai][aj-1] = 2;
moved_maze.push([ai,aj-1]);
}
else if(GRID[ai-1][aj-1] == 2)
{
GRID[ai-1][aj-1] = 0;
GRID[ai][aj-1] = 2;
moved_maze.push([ai,aj-1]);
}
else if(GRID[ai+1][aj-1] == 2)
{
GRID[ai+1][aj-1] = 0;
GRID[ai][aj-1] = 2;
moved_maze.push([ai,aj-1]);
}
}
}
drawCubes();
}
////Draw A*
const PATH = true;
var pathArray = [];
function drawPath(path, color)
{
//const material = new THREE.LineBasicMaterial({
//color:'#F10B0B', linewidth:1000 });
//var a;
//var b;
//var line;
//var points = [];
//var lastPathLine = [];
//var formerLine;
var geometry = new THREE.Geometry();
var material = new THREE.LineBasicMaterial( {
color:'#F10B0B', linewidth:1000 });
if(pathArray!== null){
if(pathArray.length >1){
for(let i = 0; i < pathArray.length-1; i++){
geometry.vertices.push(translate(pathArray[i].i,pathArray[i].j));
geometry.vertices.push(translate(pathArray[i+1].i,pathArray[i+1].j));
}
var line = new THREE.LineSegments( geometry, material );
ABWorld.scene.add(line);
setTimeout( () => {
geometry.dispose();
material.dispose();
ABWorld.scene.remove( line );
}, AB.clockTick);
}
}
}
/// --- 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 + " x = (" + x.toString() + ") 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 ( " y = (" + y.toString() + ") <br>" +
" Bad steps: " + badsteps +
" Good steps: " + goodsteps +
" Score: " + score.toFixed(2) + "% ", 2 );
}
AB.world.newRun = function ()
{
AB.loadingScreen();
AB.runReady = false;
badsteps = 0;
goodsteps = 0;
aInitScen();
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();
}
if (PATH){
drawPath();
}
};
AB.world.endRun = function()
{
musicPause();
if ( AB.abortRun ) AB.msg ( " <br> <font color=red> <B> Roadrunner trapped. Final score zero. </B> </font> ", 3 );
else AB.msg ( " <br> <font color=#F10B0B> <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
}