Code viewer for World: CA1 (Agent not trapped)

// Cloned by Cian on 17 Nov 2021 from World "CA1 (Agent trapped. Final score zero)" by Cian 
// Please leave this clone trail here.
 


// Cloned by Cian on 17 Nov 2021 from World "CA1 (no diagonal)" by Cian 
// Please leave this clone trail here.
 
// Cloned by Cian on 17 Nov 2021 from World "CA1" by Cian 
// Please leave this clone trail here.
// global vars
AB.clockTick = 100; // speed of run in pixels
AB.maxSteps = 1e3; // len of run before final score in pixels
AB.screenshotStep = 50;

// is diagonal move allowed, true results in enemey/agent not moving
const diagonal = false;

TEXTURE_WALL = "/uploads/duadur2/wallB.jpg";
TEXTURE_MAZE = "/uploads/c123ian/hedge.jpg";
TEXTURE_AGENT = "/uploads/c123ian/morty.png";
TEXTURE_ENEMY = "/uploads/c123ian/rick_yellow.jpg";
TEXTURE_Arrow = "/uploads/c123ian/portal.jpg";
// MUSIC_BACK = "/uploads/starter/Defense.Line.mp3";
SOUND_ALARM = "/uploads/starter/air.horn.mp3";

// 50 * 50
gridsize = 50; // num of squares changed to 50 x 50

// change box density to no. of squares / 3

NOBOXES = Math.trunc(gridsize * gridsize / 10); // density of maze, makes it havea lot of walls og val of 10
squaresize = 100; //size of square in pixels
MAXPOS = gridsize * squaresize; // cal to get density of maze
SKYCOLOR = 14548957;

startRadiusConst = .8 * MAXPOS;
maxRadiusConst = 10 * MAXPOS;
ABHandler.MAXCAMERAPOS = maxRadiusConst, ABHandler.GROUNDZERO = !0;

// change background world , need unique name (x,y ect.) added per image /uploads/c123ian/
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"]

// the Mind's movement options
ACTION_LEFT = 0;
ACTION_RIGHT = 1;
ACTION_UP = 2;
ACTION_DOWN = 3;
ACTION_STAYSTILL = 4;

// Grid search
GRID_BLANK = 0;
GRID_WALL = 1;
GRID_MAZE = 2;

var BOXHEIGHT, theagent, theenemy, shape, thecube, thestar, wall_texture, agent_texture, enemy_texture, maze_texture, arrow_texture, ei, ej, ai, aj, badsteps, goodsteps, start, end, GRID = new Array(gridsize), xi = [], xj = [], starArray = [], openSet = [], closedSet = [], path = [];

function loadResources() { // file loads, call initScene when all finished loading
    var i = new THREE.TextureLoader();
    var j = new THREE.TextureLoader();
    var a = new THREE.TextureLoader();
    var r = new THREE.TextureLoader();
    var neighborG = new THREE.TextureLoader();

    i.load(TEXTURE_WALL, function (i) { i.minFilter = THREE.LinearFilter, wall_texture = i, asynchFinished() && initScene() });
    j.load(TEXTURE_AGENT, function (i) { i.minFilter = THREE.LinearFilter, agent_texture = i, asynchFinished() && initScene() });
    a.load(TEXTURE_ENEMY, function (i) { i.minFilter = THREE.LinearFilter, enemy_texture = i, asynchFinished() && initScene() });
    r.load(TEXTURE_MAZE, function (i) { i.minFilter = THREE.LinearFilter, maze_texture = i, asynchFinished() && initScene() });
    neighborG.load(TEXTURE_Arrow, function (i) { i.minFilter = THREE.LinearFilter, arrow_texture = i, asynchFinished() && initScene() });

}
// display assets
function asynchFinished() {
    return !!(wall_texture && agent_texture && enemy_texture && maze_texture && arrow_texture)
}

// occupied func used later for trapping agent in corner logic (agentTrapped)
function occupied(i, j) {
    return ei == i && ej == j || (ai == i && aj == j || !!GRID[i][j].wall)
}

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

// abs needed to make neg values pos (for cal distance 'as the crow flies')
function heuristic(i, j) {
    if (diagonal) return (dist(i.i, i.j, j.i, j.j)); // dist in eucledian dist
    else return (Math.abs(i.i - j.i) + Math.abs(i.j - j.j));
}

// remove elem from array, loop check elem, 
function removeFromArray(i, j) {
    //1) loop through array 'i' backwards (if forward and delete, have to shift all elem)
    //2) check if has elem
    //3) if yes, splice elem (delete and add indx from the arr)
    for (var a = i.length - 1; a >= 0; a--)i[a] == j && i.splice(a, 1)
}

/// every obj will have a f,g,h val to cal at each Spot
///- f 
///- g (known dist so far to get to spot)
///- h (heuristic, educated guess 'as the crow flies')

// constuctor for grid of Spots

function Spot(i, j) {
    // f value, g, h <- needed to cal a cost fir every cell/Spot (init at 0)
    // i abnd j with show() so each spot has func to show itself , so as we create each spot pass in i,j and know where it is
    this.i = i;
    this.j = j;
    this.f = 0;
    this.g = 0;
    this.h = 0;
    // where did I come from?
    this.neighbors = [], this.previous = void 0, 0 === i || i === gridsize - 1 || 0 === j || j === gridsize - 1 ? (this.wall = !0, shape = new THREE.BoxGeometry(squaresize, BOXHEIGHT, squaresize), (thecube = new THREE.Mesh(shape)).material = new THREE.MeshBasicMaterial({ map: wall_texture }), thecube.position.copy(translate(i, j)), ABWorld.scene.add(thecube)) : this.wall = !1;

    for (var a = 0; a <= NOBOXES; a++)i === xi[a] && j === xj[a] && (this.wall = !0);

    this.addNeighbors = function (i) {
        var j = this.i;
        var a = this.j;
        // any given spot, add neighbours depedning on nodes location on grid
        if (j < gridsize - 1) this.neighbors.push(i[j + 1][a]);
        if (j > 0) this.neighbors.push(i[j - 1][a]);
        if (a < gridsize - 1) this.neighbors.push(i[j][a + 1]);
        if (a > 0) this.neighbors.push(i[j][a - 1]);

        if (diagonal)
        // diagonals are also neighbours:
        {
            if (j > 0 && a > 0) this.neighbors.push(i[j - 1][a - 1]);
            if (j < gridsize - 1 && a > 0) this.neighbors.push(i[j + 1][a - 1]);
            if (j > 0 && a < gridsize - 1) this.neighbors.push(i[j - 1][a + 1]);
            if (j < gridsize - 1 && a < gridsize - 1) this.neighbors.push(i[j + 1][a + 1]);
        }

    }
}

function initScene() {
    var i, j; 
    // set up grid as 2-d
    for (i = 0; i < gridsize; i++)
        GRID[i] = new Array(gridsize);

    // setup walls (translate my i,j grid to three.js coordinates)
    for (var a = 0, r = 1; r <= NOBOXES; r++)i = AB.randomIntAtoB(1, gridsize - 2), j = AB.randomIntAtoB(1, gridsize - 2), xi[a] = i, xj[a] = j, shape = new THREE.BoxGeometry(squaresize, BOXHEIGHT, squaresize), (thecube = new THREE.Mesh(shape)).material = new THREE.MeshBasicMaterial({ map: maze_texture }), thecube.position.copy(translate(i, j)), ABWorld.scene.add(thecube), a++;
    for (i = 0; i < gridsize; i++)for (j = 0; j < gridsize; j++)GRID[i][j] = new Spot(i, j);
    for (i = 0; i < gridsize; i++)for (j = 0; j < gridsize; j++)GRID[i][j].addNeighbors(GRID);
    do 
    { 
        i = AB.randomIntAtoB(1, gridsize - 2), j = AB.randomIntAtoB(1, gridsize - 2) 
    } 
    while (occupied(i, j)); 
    ei = i, ej = j, shape = new THREE.BoxGeometry(squaresize, BOXHEIGHT, squaresize), (theenemy = new THREE.Mesh(shape)).material = new THREE.MeshBasicMaterial({ map: enemy_texture }), ABWorld.scene.add(theenemy), drawEnemy();
    do 
    { 
        i = AB.randomIntAtoB(1, gridsize - 2), j = AB.randomIntAtoB(1, gridsize - 2) 
        
    } 
    while (occupied(i, j)); 
    ai = i, aj = j, 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 // start the loop, set to true !0
    }) 
}

// draw animation loop

function drawEnemy() {
    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 
}

// draw line between agent and enemy if atleast one spot away, update with len/distance and use BoxGeometry to display texture
// cite: darwPath function developed by @Radwan Duadu https://ancientbrain.com/mind.php?mind=4652957965
function drawPath() {
    for (var i = 0; i < path.length - 2; i++)if (path.length > 2) { var j = path[i]; shape = new THREE.BoxGeometry(squaresize, .1, squaresize), (thestar = new THREE.Mesh(shape)).material = new THREE.MeshBasicMaterial({ map: arrow_texture }), starArray[i] = thestar.uuid, thestar.position.copy(translate(j.i, j.j)), ABWorld.scene.add(thestar) }
}

function drawAgent() { // given 
    theagent.position.copy(translate(ai, aj));
    ABWorld.follow.copy(theagent.position);
}

// AStar loop ----------------------------------------------------------------------------//
// openSet are nodes that need to be eval
// closedSet stores list of all nodes that finished being eval, dont need to revisit
// finsih when openset empty and no solution as cant rach target OR found target

// openSet starts with 1 node (starting node using start var [ei][ej])
// closedSet starts empty []

function AStar() {
    for (openSet = [], closedSet = [], path = [], start = GRID[ei][ej], end = GRID[ai][aj], openSet.push(start);  // end will be target?
        openSet.length > 0;) { //find lowest is the winner 
        for (var i = 0, j = 0; j < openSet.length; j++)openSet[j].f < openSet[i].f && (i = j);
        var a = openSet[i];
        // if found target a, 
        if (a === end) {
            console.log("success - you caught the target");
            break;
        }
        // need to create remove method (a == current), see above
        removeFromArray(openSet, a), closedSet.push(a);
        // 1) check first node which default is the best thus add (push) elem from openSet to closedSet (flag as checked)
        //2) next we check the winning node's neighbours (as long as they have not already been checked and present on closedSet)
        var r = a.neighbors;


        for (j = 0; j < r.length; j++) {
            var neighborG = r[j];
            // as long as neighbour 'neighborG' NOT included in closedSet dont want to revisit AND neighbour is NOT a wall
            if (!closedSet.includes(neighborG) && !neighborG.wall) {
                var tempG = a.g + heuristic(neighborG, a);// i = !1;
                // does openSet include neighbour (eval before) and is actually more efficent path (is tempG score < current neighbours g score), if so then swap and oush that neighbour into openSet
                // ? <- optional conditioning (chaining)
                // after checks, we guess how long it'll take to reqch target from curr position uisng distance from heuristics() func above
                // f score of this node = neighbour g score + neighbours h score
                openSet.includes(neighborG) ? tempG < neighborG.g && (neighborG.g = tempG, i = !0) : (neighborG.g = tempG, i = !0, openSet.push(neighborG)), i && (neighborG.h = heuristic(neighborG, end), neighborG.f = neighborG.g + neighborG.h, neighborG.previous = a)
            }
        }
        var s = a;
        for ((path = []).push(s); s.previous;)path.push(s.previous), s = s.previous
    }
    for (j = 0; j < gridsize; j++)
        for (var u = 0; u < gridsize; u++)0 !== GRID[j][u].f && (GRID[j][u].f = 0, GRID[j][u].g = 0, GRID[j][u].h = 0, GRID[j][u].previous = void 0)
}
function removePath() {
    console.log("removing");
    for (var i = 0; i < starArray.length; i++) {
        var j = starArray[i]; const a = ABWorld.scene.getObjectByProperty("uuid", j); ABWorld.scene.remove(a)
    }
}

// moving enemy logic
function moveLogicalEnemy() {
    AStar(), drawPath();
    var i = path.length, j = path[i - 2];
    occupied(j.i, j.j) || (ei = j.i, ej = j.j)
}
function sleep(i) {
    return new Promise(j => setTimeout(j, i))
}


function moveLogicalAgent(i) {
    var j = ai, a = aj; i == ACTION_LEFT ? j-- : i == ACTION_RIGHT ? j++ : i == ACTION_UP ? a++ : i == ACTION_DOWN && a--, occupied(j, a) || (ai = j, aj = a)
}
var OURKEYS = [37, 38, 39, 40]; function ourKeys(i) {
    return OURKEYS.includes(i.keyCode)
}

function keyHandler(i) {
    return !AB.runReady || (!ourKeys(i) || (37 == i.keyCode && moveLogicalAgent(ACTION_LEFT), 38 == i.keyCode && moveLogicalAgent(ACTION_DOWN), 39 == i.keyCode && moveLogicalAgent(ACTION_RIGHT), 40 == i.keyCode && moveLogicalAgent(ACTION_UP), i.stopPropagation(), i.preventDefault(), !1))
}

function badstep() {
    return Math.abs(ei - ai) < 2 && Math.abs(ej - aj) < 2
}

// trap agent 'a' in corner spot 'i', 'j'
function agentTrapped() {
    return occupied(ai - 1, aj) && occupied(ai + 1, aj) && occupied(ai, aj + 1) && occupied(ai, aj - 1)
}

function updateStatusBefore(i) {
    AB.world.getState(); AB.msg(" Step: " + AB.step + " &nbsp; a = (" + i + ") ")
}
// display status of trapping agent
function updateStatusAfter() {
    AB.world.getState(); var i = goodsteps / AB.step * 100; AB.msg(" Colder: " + badsteps + " &nbsp; Warmer: " + goodsteps + " &nbsp; Score: " + i.toFixed(2) + "% ", 2)
}
AB.world.newRun = function () {
    AB.loadingScreen(), AB.runReady = !1, badsteps = 0, goodsteps = 0, BOXHEIGHT = squaresize, ABWorld.init3d(startRadiusConst, maxRadiusConst, 14548957), loadResources(), document.onkeydown = keyHandler
},
    AB.world.getState = function () {
        return [ai, aj, ei, ej, GRID]
    },

    AB.world.takeAction = function (i) {
        updateStatusBefore(i), moveLogicalAgent(i), AB.step % 2 == 0 && moveLogicalEnemy(), badstep() ? badsteps++ : goodsteps++, drawAgent(), drawEnemy(), updateStatusAfter(), agentTrapped() && (AB.abortRun = !0, goodsteps = 0, musicPause(), soundAlarm()), sleep(100).then(() => { removePath() })
    },
    AB.world.endRun = function () {
        musicPause(), AB.abortRun ? AB.msg(" <br> <font color=red> <B> Agent trapped! Final score zero. </B> </font>   ", 3) : AB.msg(" <br> <font color=green> <B> Run over. </B> </font>   ", 3)
    },
    AB.world.getScore = function () {
        var i = goodsteps / AB.maxSteps * 100; return Math.round(100 * i) / 100
    };

var backmusic = AB.backgroundMusic(MUSIC_BACK);
function musicPlay() {
    backmusic.play()
}
function musicPause() {
    backmusic.pause()
}
function soundAlarm() {
    new Audio(SOUND_ALARM).play()
}