// ==============================================================
// Simple starter World for WWM
// 3d-effect World (really a 2-D problem)
// Mark Humphrys, 2016.
//
// Hero = agent = pacman.
// Enemy moves randomly.
//
// This simple World shows:
// - Texture load from files (asynchronous file reads)
// - Write status to <span> in run.php
// - A check: "if (true)" surrounding all code for online runs (only needed in two places)
//
// It also shows functionality that is built-in to every World:
// - Camera control buttons
// - Pause/step run
// =============================================================================================
// =============================================================================================
// Scoring:
// Bad steps = steps where enemy is within one step of agent.
// Good steps = steps where enemy is further away.
// Score = good steps.
//
// Scoring on the server side is done by taking average of n runs.
// =============================================================================================
// World must define these:
const CLOCKTICK = 100; // speed of run - move things every n milliseconds
const MAXSTEPS = 10000; // length of a run before final score
const SCREENSHOT_STEP = 50;
//---- global constants: -------------------------------------------------------
const gridsize = 30; // number of squares along side of world
const squaresize = 100; // size of square in pixels
const MAXPOS = gridsize * squaresize; // length of one side in pixels
const SKYCOLOR = 0xffffcc; // a number, not a string
const BLANKCOLOR = SKYCOLOR ; // make objects this color until texture arrives (from asynchronous file read)
const startRadiusConst = MAXPOS * 0.8 ; // distance from centre to start the camera at
const maxRadiusConst = MAXPOS * 3 ; // maximum distance from camera we will render things
//--- 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)
var GRID = new Array(gridsize); // can query GRID about whether squares are occupied, will in fact be initialised as a 2D array
var blockObjects = new Array(gridsize);
var WALLS = new Array ( 4 * gridsize ); // need to keep handle to each wall block object so can find it later to paint it
var BLOCKS = new Array ((gridsize - 2) * (gridsize - 2));
var theagent, theenemy;
var lives = 3;
var t = 0;
// enemy and agent position on squares
var ei, ej, ai, aj;
// needed vars
var currentMove;
//---- start of World class -------------------------------------------------------
function World() {
//--- my functions -----------------------
// parameter = pacmans location, puts a 7 on the GRID, so drawBlocks() can add blocks at that location
function addBlock ( i, j )
{
if(GRID[i][j] != 0 && GRID[i][j] != 1)
GRID[i][j] = 7;
}
// checks if block is within the grid
function isInTheGrid ( i, j )
{
if( i < 0 || i > gridsize - 1 || j < 0 || j > gridsize - 1 ) return false;
return true
}
var lasti = 0;
var lastj = 0;
//only runs when an arrow button is pressed
//what makes pacman move
function moveObject(e)
{
var i = ai;
var j = aj;
// keeps track of last locations
lasti = i;
lastj = j;
switch(e.keyCode)
{
case 37:
// if pacman is on the surrounding walls the pacman can move in any direction, same for each direction below
if(GRID[i][j] == 0)
{
i--;
currentMove = "left";
}
else // if pacman is not on surrounding wall, it cant go right when its moving left
{
if(currentMove != "right")
{
i--;
currentMove = "left";
}
}
break;
case 38:
if(GRID[i][j] == 0)
{
j--;
currentMove = "up";
}
else // if pacman is not on surrounding wall, it cant go down when its moving up
{
if(currentMove != "down")
{
j--;
currentMove = "up";
}
}
break;
case 39:
if(GRID[i][j] == 0)
{
i++;
currentMove = "right";
}
else // if pacman is not on surrounding wall, it cant go left when its moving right
{
if(currentMove != "left")
{
i++;
currentMove = "right";
}
}
break;
case 40:
if(GRID[i][j] == 0)
{
j++;
currentMove = "down";
}
else // if pacman is not on surrounding wall, it cant go up when its moving down
{
if(currentMove != "up")
{
j++;
currentMove = "down";
}
}
break;
}
if( isInTheGrid ( i, j ))
{
//changes pacmans location
ai = i;
aj = j;
// adds blocks to the board as the pacman moves
addBlock( ai, aj );
}
}
// will move pacman continuously when not on the surrounding wall
function continuousMovement()
{
// pacman location
var i = ai;
var j = aj;
// keeps track of last locations
lasti = i;
lastj = j;
if(GRID[i][j] == 1)
{
if(currentMove == "left")
{
i--;
}
else if(currentMove == "right")
{
i++;
}
else if(currentMove == "up")
{
j--;
}
else if(currentMove == "down")
{
j++;
}
if( isInTheGrid ( i, j ))
{
ai = i;
aj = j;
// makes block at ai, aj equal to 7, so it can be added and painted
addBlock( ai, aj );
}
}
}
function drawBlocks()
{
for (var i = 1; i < gridsize-1 ; i++)
for (var j = 1; j < gridsize-1 ; j++)
{
if ( GRID[i][j] == 7 )
{
var shape = new THREE.BoxGeometry( squaresize, 1, squaresize );
var tempBlocks = new THREE.Mesh( shape );
tempBlocks.material.color.setHex( BLANKCOLOR );
tempBlocks.position.x = translate ( i * squaresize ); // translate my simple (i,j) block-numbering coordinates to three.js (x,y,z) coordinates
tempBlocks.position.z = translate ( j * squaresize );
tempBlocks.position.y = 0;
//positionsOfBlocks[i][j] = 0;
// no point in adding a block when a block already exists
if(blockObjects[i][j] == null)
{
// adding block
threeworld.scene.add(tempBlocks);
blockObjects[i][j] = tempBlocks;
t++;
GRID[i][j] = 1;
}
}
}
// if the enemy touches wall
if(isEnemyBesideWall(ei,ej))
{
getRidOfWall();
lives--;
}
// if the wall has successfully been created.
if(GRID[ai][aj] == 0 && GRID[lasti][lastj] == 1)
{
fill(ei,ej);
for(var i = 1; i < gridsize-1; i++)
for(var j = 1; j < gridsize-1; j++)
{
if(GRID[i][j] == 1 || GRID[i][j] == null)
{
var shape = new THREE.BoxGeometry( squaresize, 1, squaresize );
var tempBlocks = new THREE.Mesh( shape );
tempBlocks.material.color.setHex( BLANKCOLOR );
tempBlocks.position.x = translate ( i * squaresize ); // translate my simple (i,j) block-numbering coordinates to three.js (x,y,z) coordinates
tempBlocks.position.z = translate ( j * squaresize );
tempBlocks.position.y = 0;
GRID[i][j] = 0;
threeworld.scene.add(tempBlocks);
blockObjects[i][j] = tempBlocks;
}
else if(GRID[i][j] == 8)
{
GRID[i][j] = null;
}
}
}
}
function initBlocks()
{
for (var i = 0; i < gridsize ; i++)
{
blockObjects[i] = new Array(gridsize); // each element is an array
for (var j = 0; j < gridsize ; j++)
{
blockObjects[i][j] = null;
}
}
}
function paintBlocks ( material ) // paint blank boxes
{
for(var i = 0; i < gridsize; i++)
for(var j = 0; j < gridsize; j++)
if ( blockObjects[i][j] ) blockObjects[i][j].material = material;
}
function isEnemyBesideWall( i, j )
{
if(GRID[i+1][j+1] == 1 || GRID[i-1][j] == 1 ||
GRID[i][j+1] == 1 || GRID[i][j-1] == 1 ) return true;
else return false;
}
function getRidOfWall()
{
for(var i = 0; i < gridsize; i++)
for(var j = 0; j < gridsize; j++)
{
if(GRID[i][j] == 1)
{
threeworld.scene.remove(blockObjects[i][j]);
GRID[i][j] = null;
blockObjects[i][j] = null;
}
}
ai = 0;
aj = 0;
}
function isWall(i, j)
{
if(GRID[i][j] != null) return true;
else return false;
}
// start at enemy
function fill(i, j)
{
if(!(isWall(i, j)))
{
GRID[i][j] = 8;
fill(i+1, j+1);
fill(i, j+1);
fill(i+1, j);
fill(i-1, j-1);
fill(i-1, j);
fill(i, j-1);
fill(i+1, j-1);
fill(i-1, j+1);
}
}
// function paintBlocks ( material ) // paint blank boxes
// {
// for ( var i = 0; i < BLOCKS.length; i++ )
// {
// if ( BLOCKS[i] ) BLOCKS[i].material = material;
// }
// }
// regular "function" syntax means private functions:
function initGrid()
{
for (var i = 0; i < gridsize ; i++)
{
GRID[i] = new Array(gridsize); // each element is an array
for (var j = 0; j < gridsize ; j++)
{
GRID[i][j] = null;
}
}
}
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] == 0) || (GRID[i][j] == 1) ) return true; // fixed object
return false;
}
// 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 ( x )
{
return ( x - ( MAXPOS/2 ) );
}
// --- asynch load textures from file ----------------------------------------
// credits:
// http://commons.wikimedia.org/wiki/File:Old_door_handles.jpg?uselang=en-gb
// https://commons.wikimedia.org/wiki/Category:Pac-Man_icons
// https://commons.wikimedia.org/wiki/Category:Skull_and_crossbone_icons
// loader return can call private function
function loadTextures()
{
var loader1 = new THREE.TextureLoader();
loader1.load ( '/uploads/christopherdurning/green.jpg', function ( thetexture ) {
thetexture.minFilter = THREE.LinearFilter;
paintWalls ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
} );
var loader2 = new THREE.TextureLoader();
loader2.load ( '/uploads/christopherdurning/batman.jpg', function ( thetexture ) {
thetexture.minFilter = THREE.LinearFilter;
theagent.material = new THREE.MeshBasicMaterial( { map: thetexture } );
} );
var loader3 = new THREE.TextureLoader();
loader3.load ( '/uploads/christopherdurning/joker.png', function ( thetexture ) {
thetexture.minFilter = THREE.LinearFilter;
theenemy.material = new THREE.MeshBasicMaterial( { map: thetexture } );
} );
}
function loadTexturesInPlay()
{
var loader4 = new THREE.TextureLoader();
loader4.load ( '/uploads/christopherdurning/green.jpg', function ( thetexture )
{
thetexture.minFilter = THREE.LinearFilter;
paintBlocks ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
} );
}
function initLogicalWalls() // set up logical walls in data structure, whether doing graphical run or not
{
for (var i = 0; i < gridsize ; i++)
for (var j = 0; j < gridsize ; j++)
if ( ( i==0 ) || ( i==gridsize-1 ) || ( j==0 ) || ( j==gridsize-1 ) )
{
GRID[i][j] = 0; // set up data structure, whether using Three.js or not
}
//
}
function initThreeWalls() // graphical run only, set up blank boxes, painted later
{
var t = 0;
for (var i = 0; i < gridsize ; i++)
for (var j = 0; j < gridsize ; j++)
if ( GRID[i][j] == 0 )
{
var shape = new THREE.BoxGeometry( squaresize, 1, squaresize );
var thecube = new THREE.Mesh( shape );
thecube.material.color.setHex( BLANKCOLOR );
thecube.position.x = translate ( i * squaresize ); // translate my simple (i,j) block-numbering coordinates to three.js (x,y,z) coordinates
thecube.position.z = translate ( j * squaresize );
thecube.position.y = 0;
threeworld.scene.add(thecube);
WALLS[t] = thecube; // save it for later
t++;
}
}
function paintWalls ( material ) // paint blank boxes
{
for ( var i = 0; i < WALLS.length; i++ )
{
if ( WALLS[i] ) WALLS[i].material = material;
}
}
// --- enemy functions -----------------------------------
function drawEnemy() // given ei, ej, draw it
{
var x = translate ( ei * squaresize );
var z = translate ( ej * squaresize );
var y = 0;
theenemy.position.x = x;
theenemy.position.y = y;
theenemy.position.z = z;
threeworld.scene.add(theenemy);
threeworld.lookat.copy ( theenemy.position ); // if camera moving, look back at where the enemy is
}
function initLogicalEnemy()
{
// start at same place every time:
ei = (gridsize / 2) // this square will be free
ej = 1 // (bug) use Math.trunc or else you get a bad square number if gridsize is odd
}
function initThreeEnemy()
{
var shape = new THREE.CubeGeometry( squaresize, 1, squaresize );
theenemy = new THREE.Mesh( shape )
theenemy.material.color.setHex( BLANKCOLOR );
drawEnemy();
}
function topRightToLeft(ci, cj, ppi, ppj)
{
if((GRID[ci][cj-1] != null) && (ppi > ci && ppj > cj) ) return true;
else false;
}
function topLeftToRight(ci, cj, ppi, ppj)
{
if((GRID[ci][cj-1] != null) && (ppi < ci && ppj > cj)) return true;
else false;
}
function leftTopToBottom(ci, cj, ppi, ppj)
{
if((GRID[ci-1][cj] != null) && (ppi > ci && ppj < cj)) return true;
else false;
}
function leftBottomToTop(ci, cj, ppi, ppj)
{
if((GRID[ci-1][cj] != null) && (ppi > ci && ppj > cj)) return true;
else false;
}
function bottomLeftToRight(ci, cj, ppi, ppj)
{
if((GRID[ci][cj+1] != null) && (ppi < ci && ppj < cj)) return true;
else false;
}
function bottomRightToLeft(ci, cj, ppi, ppj)
{
if((GRID[ci][cj+1] != null) && (ppi > ci && ppj < cj)) return true;
else false;
}
function rightBottomToTop(ci, cj, ppi, ppj)
{
if((GRID[ci+1][cj] != null) && (ppi < ci && ppj > cj)) return true;
else false;
}
function rightTopToBottom(ci, cj, ppi, ppj)
{
if((GRID[ci+1][cj] != null) && (ppi < ci && ppj < cj)) return true;
else false;
}
function isTopLeftCornerWall(ci, cj)
{
if((GRID[ci+1][cj-1] != null && GRID[ci-1][cj+1] != null && GRID[ci-1][cj-1] != null)
|| (GRID[ci][cj-1] == null && GRID[ci-1][cj] == null && GRID[ci-1][cj-1] != null)
|| (GRID[ci-1][cj-1] != null && GRID[ci][cj-1] != null && GRID[ci-1][cj] != null))return true;
else false;
}
function isTopRightCornerWall(ci, cj)
{
//TL
if(GRID[ci+1][cj+1] != null && GRID[ci-1][cj-1] != null && GRID[ci+1][cj-1] != null
|| (GRID[ci+1][cj] == null && GRID[ci][cj-1] == null && GRID[ci+1][cj-1] != null)
|| (GRID[ci+1][cj-1] != null && GRID[ci+1][cj] != null && GRID[ci][cj-1] != null))return true;
else false;
}
function isBottomLeftCornerWall(ci, cj)
{
//TL
if(GRID[ci+1][cj+1] != null && GRID[ci-1][cj-1] != null && GRID[ci-1][cj+1] != null
|| (GRID[ci-1][cj] == null && GRID[ci][cj+1] == null && GRID[ci-1][cj+1] != null)
|| (GRID[ci-1][cj+1] != null && GRID[ci-1][cj] != null && GRID[ci][cj+1] != null))return true;
else false;
}
function isBottomRightCornerWall(ci, cj)
{
//TL
if(GRID[ci+1][cj-1] != null && GRID[ci-1][cj+1] != null && GRID[ci+1][cj+1] != null
|| (GRID[ci+1][cj] == null && GRID[ci][cj+1] == null && GRID[ci+1][cj+1] != null)
|| (GRID[ci+1][cj+1] != null && GRID[ci+1][cj] != null && GRID[ci][cj+1] != null))return true;
else false;
}
function isCorner(ci, cj)
{
if( (isTopLeftCornerWall(ci, cj)) || (isTopRightCornerWall(ci, cj))
|| (isBottomLeftCornerWall(ci, cj)) || (isBottomRightCornerWall(ci, cj))) return true;
else false;
}
var pi = (gridsize / 2) + 1;
var pj = 2;
function moveEnemy()
{
var ci = ei;
var cj = ej;
var tmpCi = ci;
var tmpCj = cj;
ci = (ci - pi) + ci;
cj = (cj - pj) + cj;
var ppi = pi;
var ppj = pj;
pi = tmpCi;
pj = tmpCj;
if(occupied(ci, cj))
{
ci = ei;
cj = ej;
var tmpi;
var tmpj;
if(isCorner(ci, cj))
{
if(isTopLeftCornerWall(ci, cj))
{
ci = ci + 1;
cj = cj + 1
}
else if(isTopRightCornerWall(ci, cj))
{
ci = ci - 1;
cj = cj + 1;
}
else if(isBottomLeftCornerWall(ci, cj))
{
ci = ci + 1;
cj = cj - 1;
}
else if(isBottomRightCornerWall(ci, cj))
{
ci = ci - 1;
cj = cj - 1;
}
}
else
{
if(topRightToLeft(ci, cj, ppi, ppj))
{
ci = ci - 1;
cj = cj + 1;
}
else if(topLeftToRight(ci, cj, ppi, ppj))
{
ci = ci + 1;
cj = cj + 1;
}
else if(leftTopToBottom(ci, cj, ppi, ppj))
{
ci = ci + 1;
cj = cj + 1;
}
else if(leftBottomToTop(ci, cj, ppi, ppj))
{
ci = ci + 1;
cj = cj - 1;
}
else if(bottomLeftToRight(ci, cj, ppi, ppj))
{
ci = ci + 1;
cj = cj - 1;
}
else if(bottomRightToLeft(ci, cj, ppi, ppj))
{
ci = ci - 1;
cj = cj - 1;
}
else if(rightBottomToTop(ci, cj, ppi, ppj))
{
ci = ci - 1;
cj = cj - 1;
}
else if(rightTopToBottom(ci, cj, ppi, ppj))
{
ci = ci - 1;
cj = cj + 1;
}
}
}
if(!(occupied(ci, cj)))
{
ei = ci;
ej = cj;
}
}
function getAmount()
{
var count = 0;
for(var i = 1; i < gridsize - 1; i++)
{
for(var j = 1; j < gridsize - 1; j++)
{
if(GRID[i][j] == 0)
{
count = count + 1;
}
}
}
console.log(count);
return count;
}
function getPercentage(x, y)
{
var percent = x/y * 100;
percent = Math.round(percent);
return percent;
}
function moveLogicalEnemy()
{
// small random move
var i = ei+1;
var j = ej+1;
if ( ! occupied(i,j) ) // if no obstacle then move, else just miss a turn
{
ei = i;
ej = j;
}
}
// --- agent functions -----------------------------------
function drawAgent() // given ai, aj, draw it
{
var x = translate ( ai * squaresize );
var z = translate ( aj * squaresize );
var y = 1;
theagent.position.x = x;
theagent.position.y = y;
theagent.position.z = z;
threeworld.scene.add(theagent);
threeworld.follow.copy ( theagent.position ); // follow vector = agent position (for camera following agent)
}
function initLogicalAgent()
{
// start at same place every time:
ai = 0; // this square will be free
aj = 0;
}
function initThreeAgent()
{
var shape = new THREE.BoxGeometry( squaresize, 1, squaresize );
theagent = new THREE.Mesh( shape );
theagent.material.color.setHex( BLANKCOLOR );
drawAgent();
}
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: -----------------------------------
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 updateStatus()
{
var amountCovered = getAmount();
var allBlocks = (gridsize - 2) * (gridsize - 2);
var status = "Lives: (" + lives + ") Progress: " + getPercentage(amountCovered,allBlocks) + "/80% -- arrow keys to move pacman --";
$("#user_span1").html( status );
if(getPercentage(amountCovered,allBlocks) >= 80)
{
status = "";
status = status + "$$$$$$$$$$$$$$$$$$$$Â You Win! $$$$$$$$$$$$$$$$$$$$$ "
$("#user_span1").html( status );
return true;
}
else if(lives == 0)
{
status = "";
status = status + "GAME OVER"
$("#user_span1").html( status );
return true
}
else
return false;
}
//--- public functions / interface / API ----------------------------------------------------------
// must have this public variable:
this.endCondition; // If set to true, run will end.
this.newRun = function()
{
// (subtle bug) must reset variables like these inside newRun (in case do multiple runs)
this.endCondition = false;
badsteps = 0;
goodsteps = 0;
step = 0;
// define logical data structure for the World, even if no graphical representation:
initGrid();
initBlocks();
initLogicalWalls();
initLogicalAgent();
initLogicalEnemy();
// if Three.js graphical representation:
if ( true )
{
threeworld.init2d ( startRadiusConst, maxRadiusConst, SKYCOLOR );
// Set up blank objects first:
initThreeWalls();
initThreeAgent();
initThreeEnemy();
//initBlock();
// Then paint them with textures - asynchronous load of textures from files.
// The texture file loads return at some unknown future time in some unknown order.
// Because of the unknown order, it is probably best to make objects first and later paint them, rather than have the objects made when the file reads return.
// It is safe to paint objects in random order, but might not be safe to create objects in random order.
loadTextures(); // will return sometime later, but can go ahead and render now
}
};
this.getState = function()
{
var x = [ ai, aj, ei, ej ];
return ( x );
};
this.nextStep = function()
{
var a = 4;
step++;
//moveLogicalAgent(a);
drawBlocks();
moveEnemy();
document.addEventListener("keydown", moveObject, false);
continuousMovement();
this.endCondition = updateStatus();
if ( badstep() )
badsteps++;
else
goodsteps++;
if ( true )
{
drawBlocks();
drawAgent();
drawEnemy();
loadTexturesInPlay()
updateStatus();
}
};
this.endRun = function()
{
};
// this.getScore = function()
// {
// return goodsteps;
// };
}
//---- end of World class -------------------------------------------------------