Code viewer for World:
World
const BLOCKPUNISH = 50
const [ACTION_LEFT, ACTION_RIGHT, ACTION_UP, ACTION_DOWN, ACTION_STAYSTILL] = [0, 1, 2, 3, 4]
AB.clockTick = 5
AB.maxSteps = 2000
AB.screenshotStep = 50
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 SOUND_ALARM = '/uploads/starter/air.horn.mp3'
const gridsize = 20
const BOXDENSITY = 0.1
const NOBOXES = Math.trunc(gridsize * gridsize * BOXDENSITY)
const squaresize = 100
const MAXPOS = gridsize * squaresize
const SKYCOLOR = 0xddffdd
const startRadiusConst = MAXPOS * 0.8
const skyboxConst = MAXPOS * 2
const maxRadiusConst = MAXPOS * 10
ABHandler.MAXCAMERAPOS = skyboxConst * 0.6
ABHandler.GROUNDZERO = true
const downmountain = [ 'xpos', 'xneg', 'ypos', 'yneg', 'zpos', 'zneg' ].map(name => 'dawnmountain-' + name + '.png')
const stsky = [ 'posx', 'negx', 'posy', 'negy', 'posz', 'negz' ].map(name => 'st.' + name + '.jpg')
const SKYBOX_ARRAY = downmountain.map(name => '/uploads/starter/' + name)
const [GRID_BLANK, GRID_WALL, GRID_MAZE] = [0, 1, 2]
const soundAlarm = () => new Audio(SOUND_ALARM).play()
function World() {
var BOXHEIGHT = squaresize;
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 wall_texture, agent_texture, enemy_texture, maze_texture
// enemy and agent position on squares
var ei, ej, ai, aj;
var badsteps;
var goodsteps;
var stuckfor = 0; // how long the enemy has been stuck - if stuck for a while start making random moves
var self = this; // needed for private fn to call public fn
const getTreeTex = url => new Promise(whenDone => {
new THREE.TextureLoader().load(url, texture => whenDone(texture))
})
function loadResources() {
const res = [TEXTURE_WALL, TEXTURE_AGENT, TEXTURE_ENEMY, TEXTURE_MAZE]
Promise.all(res.concat(SKYBOX_ARRAY).map(url => getTreeTex(url)))
.then(textures => {
textures.forEach(texture => texture.minFilter = THREE.LinearFilter);
[wall_texture, agent_texture, enemy_texture, maze_texture] = textures.slice(0, 4)
textures.slice(4).forEach((texture, i) => SKYBOX_ARRAY[i] = texture)
initScene()
})
}
//--- 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() {
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
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 coordinates to three.js (x,y,z) coordinates
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 } )
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 location
resetEnemyPos();
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()
var skyGeometry = new THREE.CubeGeometry ( skyboxConst, skyboxConst, skyboxConst )
var skyMaterial = new THREE.MeshFaceMaterial ( Array(6).fill(0).map((_, i) => new THREE.MeshBasicMaterial({map: SKYBOX_ARRAY[i], side: THREE.BackSide})))
ABWorld.scene.add(new THREE.Mesh(skyGeometry, skyMaterial))
ABWorld.render()
AB.removeLoading()
ABRun.runReady = true
}
function resetEnemyPos()
{
var i,j;
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;
}
// --- 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 getEnemyAction()
{
// move towards agent
// after n failed moves, start making random moves
if ( stuckfor > 2 ) return ( AB.randomIntAtoB (0,3) );
if ( AB.randomBoolean() )
{
if ( ej < aj ) return ( ACTION_UP );
if ( ej > aj ) return ( ACTION_DOWN );
}
else
{
if ( ei < ai ) return ( ACTION_RIGHT );
if ( ei > ai ) return ( ACTION_LEFT );
}
return ( AB.randomIntAtoB (0,3) );
}
function moveLogicalEnemy( a )
{
var i = ei;
var j = ej;
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) )
stuckfor++;
else
{
stuckfor = 0;
ei = i;
ej = j;
}
}
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;
}
}
// --- score: -----------------------------------
const badstep = () => Math.abs(ei - ai) < 2 && Math.abs(ej - aj) < 2
const agentBlocked = () => occupied(ai - 1, aj) && occupied(ai + 1, aj) && occupied(ai, aj + 1) && occupied(ai, aj - 1)
const updateStatusBefore = a => $('#user_span1').html(`Step: ${ABRun.step} x = (${this.getState()}) a = (${a}) `)
const updateStatusAfter = () => $("#user_span2").html(` y = (${this.getState()}) <BR> Bad steps: ${badsteps} Good steps: ${goodsteps} Score: ${(( goodsteps / ABRun.step ) * 100).toFixed(2)}% `)
//--- public functions / interface / API ----------------------------------------------------------
this.newRun = () => {
AB.loadingScreen()
ABRun.runReady = false
badsteps = 0
goodsteps = 0
ABWorld.init3d ( startRadiusConst, maxRadiusConst, SKYCOLOR )
loadResources()
}
this.getState = () => [ ai, aj, ei, ej ]
this.takeAction = a => {
updateStatusBefore(a);
moveLogicalAgent(a);
ABRun.step % 2 === 0 && moveLogicalEnemy(getEnemyAction());
badstep() ? badsteps++ : goodsteps++;
drawAgent();
drawEnemy();
updateStatusAfter()
if (agentBlocked()) {
badsteps = badsteps + BLOCKPUNISH
ABRun.step = ABRun.step + BLOCKPUNISH
$('#user_span3').html(' <br> <font color=red> <B> Agent blocked. ' + BLOCKPUNISH + ' extra bad steps and reset enemy. </B> </font> ')
soundAlarm()
resetEnemyPos()
}
}
this.endRun = () => $('#user_span3').html(' <br> <font color=green> <B> Run over. </B> </font> ')
this.getScore = () => (( goodsteps / AB.maxSteps ) * 100).toFixed(2)
}