Code viewer for World: Adding Multiplayer functio...

// Cloned by John Lasis on 5 Dec 2022 from World "Adding Multiplayer functionality to Football World (In Collaboration with Mustafa Kamal)" by Kline 
// Please leave this clone trail here.
 
// 
 
const   CLOCKTICK   = 150;        // speed of run - move things every n milliseconds
const   MAXSTEPS  = 2000;       // length of a run before final score(2000 = 10 minutes)


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  LIGHTCOLOR   = 0xffffff ;

const startRadiusConst    = MAXPOS * 0.8 ;    // distance from centre to start the camera at
const skyboxConst     = MAXPOS ;    // where to put skybox 
const maxRadiusConst      = MAXPOS * 5 ;    // maximum distance from camera we will render things  

var ballDirection = "";
var ballMoves = 0;


//--- 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;

const KICK_BALL       = 5; //Action for kicking the ball 1 space.
const KICK_BALL_LONG  = 6; //Action for kicking the ball 5 spaces.

const ENEMY_MOVE_UP   = 7; //Moves the goalkeeper up 1 space.
const ENEMY_MOVE_DOWN = 8; //Moves the goalkeeper down 1 space.

// contents of a grid square
const GRID_BLANK  = 0;
const GRID_WALL   = 1;

// --- some useful random functions  -------------------------------------------

function randomfloatAtoB ( A, B )      
{
 return ( A + ( Math.random() * (B-A) ) );
}

function randomintAtoB ( A, B )      
{
 return  ( Math.round ( randomfloatAtoB ( A, B ) ) );
}

function randomBoolean()       
{
 if ( Math.random() < 0.5 ) { return false; }
 else { return true; }
}

function randomPick ( a, b )
{
 if ( randomBoolean() ) 
  return a;
 else
  return b;
}


//---- start of World class -------------------------------------------------------
 
function World() { 
    
// most of World can be private 
// regular "var" syntax means private variables:


var GRID  = new Array(gridsize);      // can query GRID about whether squares are occupied, will in fact be initialised as a 2D array   
var WALLS   = new Array ( 4 * gridsize );   // need to keep handle to each wall block object so can find it later to paint it 
var theagent, theagent2, theenemy, theball;      

var agentRotation = 0;
var enemyRotation = 0;      // with 3D models, current rotation away from default orientation
var ballRotation = 0;

// enemy, agent and ball position on squares
var ei, ej, ai, aj, ai2, aj2, bi, bj;
var  step;
var goals1;
var goals2;
var self = this;            // needed for private fn to call public fn - see below  


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] = GRID_BLANK ;
        }
    }
}

function occupied ( i, j )    // is this square occupied
{
     if ( ( ei == i ) && ( ej == j ) ) return true;   // variable objects 
     //if ( ( ai == i ) && ( aj == j ) ) return true;  Commented out so that the ball can go through the agent in case it's stuck at the wall.
     if ( ( bi == i ) && ( bj == j ) ) return true;
     if ( GRID[i][j] == GRID_WALL ) return true;    // fixed objects   
     return false;
}


function translate ( x ) 
{
    return ( x - ( MAXPOS/2 ) );
}

function loadTextures()
{
    var manager = new THREE.LoadingManager();   
    var loader = new THREE.OBJLoader( manager );
      
    loader.load( "/uploads/kamalm3/F4UA9H5YXXC05ITBUVP40L8Y1.obj", buildenemy );

    // load OBJ plus MTL (plus TGA files) 
    
    //Loads the agent "Bart" model
    THREE.Loader.Handlers.add( /.tga$/i, new THREE.TGALoader() );
    var m = new THREE.MTLLoader();
    m.setTexturePath ( "/uploads/kamalm3/" );
    m.setPath        ( "/uploads/kamalm3/" );
  
    m.load( "F4UA9H5YXXC05ITBUVP40L8Y1.mtl", function( materials )
    {
        materials.preload();
        var o = new THREE.OBJLoader();
        o.setMaterials ( materials );
        o.setPath ( "/uploads/kamalm3/" );
        o.load( "F4UA9H5YXXC05ITBUVP40L8Y1.obj", function ( object )
        {
          addparker ( object );
        } );
    } );
    
    var x = new THREE.MTLLoader();
    x.setTexturePath ( "/uploads/kamalm3/" );
    x.setPath        ( "/uploads/kamalm3/" );
  
    x.load( "J36E4FWYUCI91JNCLVZDIW5DM.mtl", function( materials )
    {
        materials.preload();
        var o = new THREE.OBJLoader();
        o.setMaterials ( materials );
        o.setPath ( "/uploads/kamalm3/" );
        o.load( "J36E4FWYUCI91JNCLVZDIW5DM.obj", function ( object )
        {
          addparker2 ( object );
        } );
    } );

    //This loads the "advertisement board" around the pitch
    var loader1 = new THREE.TextureLoader(); 
    loader1.load ( '/uploads/kamalm3/wc.jpg',    function ( thetexture )
    {      
        thetexture.minFilter = THREE.LinearFilter;
        paintWalls ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
    } ); 
    
    //Texture for the football itself.
    var loader2 = new THREE.TextureLoader();
    loader2.load ( '/uploads/kamalm3/wcball2.jpg',    function ( thetexture )
    {      
        thetexture.minFilter = THREE.LinearFilter;
        theball.material = new THREE.MeshBasicMaterial( { map: thetexture } );
    } ); 
}

//To scale and add the agent to the world.
function addparker ( object )
{
  object.scale.multiplyScalar ( 100 );        
  theagent = object;
  object.rotation.y = (Math.PI /2);
  threeworld.scene.add( theagent ); 
}

function addparker2 ( object )
{
  object.scale.multiplyScalar ( 100 );        
  theagent2 = object;
  object.rotation.y = (Math.PI/2);
  threeworld.scene.add( theagent2 ); 
}
function initSkybox() 
{
// urban photographic skyboxes, credit:
// http://opengameart.org/content/urban-skyboxes

   var materialArray = [
  ( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/kamalm3/sansiro1.jpg" ), side: THREE.BackSide } ) ),
  ( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/kamalm3/sansiro1.jpg" ), side: THREE.BackSide } ) ),
  ( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/kamalm3/sky.jpg" ), side: THREE.BackSide } ) ),
  ( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/kieranturgoose1/modern-wall-and-floor-tile.jpg" ), side: THREE.BackSide } ) ),
  ( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/kamalm3/sansiro1.jpg" ), side: THREE.BackSide } ) ),
  ( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/kamalm3/sansiro1.jpg" ), side: THREE.BackSide } ) ),
  ];


    var skyGeometry = new THREE.CubeGeometry (skyboxConst, skyboxConst, skyboxConst); 
    var skyMaterial = new THREE.MeshFaceMaterial ( materialArray );
    var theskybox = new THREE.Mesh (skyGeometry, skyMaterial);
    threeworld.scene.add(theskybox);            // We are inside a giant cube
  
    //Floor geometry to use as the main floor. Textured with a football pitch .jpg
    //Co-ordinates of floor adjusted accordingly. 
    var floorMaterial = new THREE.MeshBasicMaterial( {map: THREE.ImageUtils.loadTexture('/uploads/kieranturgoose1/footballpitch.jpg'), side:THREE.DoubleSide} );
    var floorGeometry = new THREE.PlaneGeometry(skyboxConst, skyboxConst ,0, 0);
    var floor = new THREE.Mesh(floorGeometry, floorMaterial);
    floor.position.y = -50;
    floor.position.x = -50;
    floor.rotation.x = Math.PI / 2;
    threeworld.scene.add(floor);

}


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] = GRID_WALL ;     
            }
}


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] == GRID_WALL && j!=13 && j!=14 && j!=15 && j!=16 && j!=17)
            {
                var shape    = new THREE.BoxGeometry( squaresize, squaresize, 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 (goalkeeper) -----------------------------------


function drawEnemy()    // given ei, ej, draw it 
{
    if ( theenemy )
    {
        var x = translate ( ei * squaresize );    
        var z = translate ( ej * squaresize );    
        var y = -50;    
    
        theenemy.position.x = x;
        theenemy.position.y = y;
        theenemy.position.z = z;
     
        threeworld.lookat.copy ( theenemy.position );    // if camera moving, look back at where the enemy is  
        threeworld.lookat.y = ( squaresize * 1.5 );     // point camera higher up
    }
}

function buildenemy ( object ) 
{ 
    object.scale.multiplyScalar ( 4 );        // make 3d object n times bigger 
    object.traverse( paintEnemy );
    object.rotation.y = Math.PI /2;
    theenemy = object;
    threeworld.scene.add( theenemy ); 
}

function paintEnemy ( child ) 
{
    if ( child instanceof THREE.Mesh ) 
    {
        child.material.map = THREE.ImageUtils.loadTexture( "/uploads/starter/ghost.3.png" );
    }
}

function initLogicalEnemy()
{
    var i, j;
    do
    {
        i = randomintAtoB(1,gridsize-2);
        j = randomintAtoB(1,gridsize-2);
    }
    while ( occupied(i,j) );     // search for empty square 
        ei = 2;
        ej = 13;  //Starting point for the goalkeeper.
}

//Return the goalkeepers movements randomly between the length of the goal.
function jgetAction()
{
    if ( ej == 13 ) return (ENEMY_MOVE_UP);
    if ( ej == 14 || ej == 15 || ej == 16 ) return enemyGetAction();
    if ( ej == 17)  return (ENEMY_MOVE_DOWN); 
    //return ( ACTION_STAYSTILL );
}

//Called above to return one of the actions randomly when on co-ordinate: 14,15 or 16.
function enemyGetAction()
{
    return randomPick (ENEMY_MOVE_UP, ENEMY_MOVE_DOWN);
}

//How the enemy actions are defined.
function moveLogicalEnemy()
{
    var a = jgetAction();
    if ( a == ENEMY_MOVE_UP )
    {
        ej = ej+1;
    }
    else if ( a == ENEMY_MOVE_DOWN )
    {
        ej = ej-1;
    }

}

const INTERIMROT = 10;    // number of interim rotations drawn when model turns round

// all rotations positive 0 to 2 PI 

function rotateEnemyTowards ( newRotation )   
{
    if ( enemyRotation == newRotation ) return;
    // else 
    var x = ( enemyRotation + newRotation ) / 2; 
    theenemy.rotation.set ( 0, x, 0 );
    enemyRotation = x; 
}

// --- Ball functions -----------------------------------

//Initialising the ball.
function initThreeBall()
{
    var ball   = new THREE.SphereGeometry( squaresize/3, squaresize/3, squaresize/3);      
    theball = new THREE.Mesh(ball);
    theball.material.color.setHex( BLANKCOLOR  );  
    threeworld.scene.add( ball );
    drawBall();      
}

//Drawing the ball
function drawBall()   // given bi, bj, draw it 
{
    if(theball)
    {
        var x = translate (bi*squaresize);     
        var z = translate (bj*squaresize);    
        var y = -16;    
        
        theball.position.x = x;
        theball.position.y = y;
        theball.position.z = z;
        
        threeworld.scene.add( theball ); 
        
        threeworld.lookat.copy ( theball.position );    // if camera moving, look back at where the enemy is  
        threeworld.lookat.y = ( squaresize * 1.5 );     // point camera higher up 
    }
}

//Start position of the ball. Midspot of pitch.
function initLogicalBall()
{
    bi = 15;
    bj = 15;
}

//Movements of the ball defined.
function moveLogicalBall()
{
    var i = bi;
    var j = bj;

    //Moving the ball depending on it's direction. 
    if(ballDirection == "right")
    {
        i = bi+1; 
    }
    if(ballDirection == "left")
    {
        i = bi-1;
    }
    if(ballDirection == "up")
    {
        j = bj-1;
    }
    if(ballDirection == "down")
    {
        j = bj+1;
    }

    if(ballMoves > 0) //If there are still rolls left to be done, rotate accordinly. 
    {
        if( !occupied(i,j) )
        {
            //Rotate the ball to look like it is rolling. 
            if( bi < i) //move right
            {
                theball.rotation.z -= 0.75;
            }
            else if (bi > i) //move left
            {
                theball.rotation.z += 0.75;
            }
            else if( bj < j) //move down
            {
                theball.rotation.x += 0.75;
            }
            else if (bj > j) //move up
            {
                theball.rotation.x -= 0.75;
            }
            bi = i;
            bj = j;
            ballMoves--;
        }
        //If against the wall(occupied) but not at the enemy's space.
        else if(ei != bi || ej != bj)
        {
            if(ballDirection == "up")
            {
                ballDirection = "down";
            }
            else if(ballDirection == "down")
            { 
                ballDirection = "up";
            }
            else if(ballDirection == "right")
            {
                ballDirection = "left";
            }
            else if(ballDirection == "left")
            {
                ballDirection = "right";
            }
        }
    }
    //If shot is blocked the goalkeeper "clears" the ball 10 spaces.
    else if(bj == ej && bi == ei)
    {
        bi = bi+10;
        ballMoves = 0;
    }
}
// --- agent functions -----------------------------------


function drawAgent()  // given ai, aj, draw it 
{
    if ( theagent )
    {
        var x = translate ( ai * squaresize );    
        var z = translate ( aj * squaresize );    
        var y = 0;    

        theagent.position.x = x;
        theagent.position.y = y + 80;
        theagent.position.z = z;
        theagent.rotation.set(0,Math.PI * 2,0);
 
        threeworld.follow.copy ( theagent.position );    // follow vector = agent position (for camera following agent)
        threeworld.follow.y = ( squaresize * 1.5 );     // put camera higher up
    }
}

function drawAgent2()  // given ai, aj, draw it 
{
    if ( theagent2 )
    {
        var x = translate ( ai2 * squaresize );    
        var z = translate ( aj2 * squaresize );    
        var y = 0;    

        theagent2.position.x = x;
        theagent2.position.y = y + 80;
        theagent2.position.z = z;
        theagent2.rotation.set(0,Math.PI,0);
 
        threeworld.follow.copy ( theagent2.position );    // follow vector = agent position (for camera following agent)
        threeworld.follow.y = ( squaresize * 1.5 );     // put camera higher up
    }
}
function initLogicalAgent()
{
    var i, j;
    do
    {
        i = randomintAtoB(1,gridsize-2);
        j = randomintAtoB(1,gridsize-2);
    }
    while ( occupied(i,j) );     // search for empty square 
        ai = 26; //Starting point for Bart
        aj = 15;
}
function initLogicalAgent2()
{
    var i, j;
    do
    {
        i = randomintAtoB(1,gridsize-2);
        j = randomintAtoB(1,gridsize-2);
    }
    while ( occupied(i,j) );     // search for empty square 
        ai2 = 1; //Starting point for Bart
        aj2 = 15;
}
function keyHandler(e)    
// user control 
// Note that this.takeAction(a) is constantly running at same time, redrawing the screen.
{
    // Arrow keys 
    if (e.keyCode == 37)  moveLogicalAgent (ACTION_LEFT);
    if (e.keyCode == 38)  moveLogicalAgent (ACTION_DOWN);
    if (e.keyCode == 39)  moveLogicalAgent (ACTION_RIGHT);
    if (e.keyCode == 40)  moveLogicalAgent (ACTION_UP);
    if (e.keyCode == 75) moveLogicalAgent(KICK_BALL);      // k
    if (e.keyCode == 76) moveLogicalAgent(KICK_BALL_LONG); // l
}

function keyHandler2(e)    
// user control 
// Note that this.takeAction(a) is constantly running at same time, redrawing the screen.
{
    // WASD
    if (e.keyCode == 65)  moveLogicalAgent2 (ACTION_LEFT);
    if (e.keyCode == 87)  moveLogicalAgent2 (ACTION_DOWN);
    if (e.keyCode == 68)  moveLogicalAgent2 (ACTION_RIGHT);
    if (e.keyCode == 83)  moveLogicalAgent2 (ACTION_UP);
    if (e.keyCode == 70) moveLogicalAgent2(KICK_BALL);      // f
    if (e.keyCode == 71) moveLogicalAgent2(KICK_BALL_LONG); // g
}

function moveLogicalAgent( a )      // this is called by the infrastructure that gets action a from the Mind 
{ 
    var i = ai;
    var j = aj;     

    //Defines the actions for each of the key presses.
    if ( a == ACTION_LEFT ) i--;
    else if ( a == ACTION_RIGHT )   i++;
    else if ( a == ACTION_UP )  j++;
    else if ( a == ACTION_DOWN )  j--;
    //To kick the ball 1 space
    else if ( a == KICK_BALL)
    {
        //check if the ball is beside us
        
        if ((bi == ai+1) && (bj == aj)) //ball is to our immediate right
        {
            ballMoves = 1;
            ballDirection = "right"; //set direction
        }
        if ((bi == ai-1) && (bj == aj)) //ball is to our immediate left
        {
            ballMoves = 1;
            ballDirection = "left";
        }
        if ((bj == aj+1) && (bi == ai)) //ball is to our immediate underneath
        {
            ballMoves = 1;
            ballDirection = "down";
        }
        if ((bj == aj-1) && (bi == ai)) //ball is to our immediate above
        {
            ballMoves = 1;
            ballDirection = "up";
        }
    }
    //To kick the ball 5 spaces.
    else if ( a == KICK_BALL_LONG)
    {
        //check if the ball is beside us
        
        if ((bi == ai+1) && (bj == aj)) //ball is to our immediate right
        {
            ballMoves = 5;
            ballDirection = "right";
        }
        if ((bi == ai-1) && (bj == aj)) //ball is to our immediate left
        {
            ballMoves = 5;
            ballDirection = "left";
        }
        if ((bj == aj+1) && (bi == ai)) //ball is to our immediate underneath
        {
            ballMoves = 5;
            ballDirection = "down";
        }
        if ((bj == aj-1) && (bi == ai)) //ball is to our immediate above
        {
            ballMoves = 5;
            ballDirection = "up";
        }
    }
    if (!occupied(i,j)) 
    {
        //Rotates the agent upon key presses.
        if ( true  )
        {
            if ( a == ACTION_LEFT )   {  rotateAgentTowards ((Math.PI / 2)); }
            else if ( a == ACTION_RIGHT )  {  rotateAgentTowards (3* (Math.PI / 2)); }
            else if ( a == ACTION_UP )     {  rotateAgentTowards (Math.PI); }
            else if ( a == ACTION_DOWN )   {  rotateAgentTowards (0); }
        }
        ai = i;
        aj = j;
    }
}

function moveLogicalAgent2( a )      // this is called by the infrastructure that gets action a from the Mind 
{ 
    var i = ai2;
    var j = aj2;     

    //Defines the actions for each of the key presses.
    if ( a == ACTION_LEFT ) i--;
    else if ( a == ACTION_RIGHT )   i++;
    else if ( a == ACTION_UP )  j++;
    else if ( a == ACTION_DOWN )  j--;
    //To kick the ball 1 space
    else if ( a == KICK_BALL)
    {
        //check if the ball is beside us
        
        if ((bi == ai2+1) && (bj == aj)) //ball is to our immediate right
        {
            ballMoves = 1;
            ballDirection = "right"; //set direction
        }
        if ((bi == ai2-1) && (bj == aj2)) //ball is to our immediate left
        {
            ballMoves = 1;
            ballDirection = "left";
        }
        if ((bj == aj2+1) && (bi == ai2)) //ball is to our immediate underneath
        {
            ballMoves = 1;
            ballDirection = "down";
        }
        if ((bj == aj2-1) && (bi == ai2)) //ball is to our immediate above
        {
            ballMoves = 1;
            ballDirection = "up";
        }
    }
    //To kick the ball 5 spaces.
    else if ( a == KICK_BALL_LONG)
    {
        //check if the ball is beside us
        
        if ((bi == ai2+1) && (bj == aj2)) //ball is to our immediate right
        {
            ballMoves = 5;
            ballDirection = "right";
        }
        if ((bi == ai2-1) && (bj == aj2)) //ball is to our immediate left
        {
            ballMoves = 5;
            ballDirection = "left";
        }
        if ((bj == aj2+1) && (bi == ai2)) //ball is to our immediate underneath
        {
            ballMoves = 5;
            ballDirection = "down";
        }
        if ((bj == aj2-1) && (bi == ai2)) //ball is to our immediate above
        {
            ballMoves = 5;
            ballDirection = "up";
        }
    }
    
    if (!occupied(i,j)) 
    {
        //Rotates the agent upon key presses.
        if ( true  )
        {
            if ( a == ACTION_LEFT )   {  rotateAgentTowards ((Math.PI / 2)); }
            else if ( a == ACTION_RIGHT )  {  rotateAgentTowards (3* (Math.PI / 2)); }
            else if ( a == ACTION_UP )     {  rotateAgentTowards (Math.PI); }
            else if ( a == ACTION_DOWN )   {  rotateAgentTowards (0); }
        }
        ai2 = i;
        aj2 = j;
    }
}
function rotateAgentTowards ( newRotation )   
{
 if ( agentRotation == newRotation ) return;
 var x = ( agentRotation + newRotation ) / 2; 
 theagent.rotation.set ( 0, x, 0 );
 agentRotation = x; 
}

function rotateAgentTowards2 ( newRotation )   
{
 if ( agentRotation == newRotation ) return;
 var x = ( agentRotation + newRotation ) / 2; 
 theagent2.rotation.set ( 0, x, 0 );
 agentRotation = x; 
}
// --- score: -----------------------------------

//Counts goals scored when it reaches the goal area. Resets the agent and ball to origin position.
function goalScored()
{
  if(bi == 1 || bi == 27 && (bj == 13 || bj == 14 || bj == 15 || bj == 16 || bj == 17))
  {
    goals1++;
    bi = 14;
    bj = 15;
    ballMoves = 0;  //Resets to 0 so the ball stops dead upon reset to origin position.
    ai = 27;
    aj = 15;
    ai1 = 1;
    aj1 = 15;
    //Plays crowd cheer when goal is scored
    //var p = "<audio  id=theaudio  src=/uploads/kieranturgoose1/crowdcheersoundeffect.mp3 autoplay> </audio>" ;
    $("#user_span3").html( p );
  }
  return goals1;
}

function goalScored2()
{
  if(bi == 27 || bi == 27 && (bj == 13 || bj == 14 || bj == 15 || bj == 16 || bj == 17))
  {
    goals2++;
    bi = 14;
    bj = 15;
    ballMoves = 0;  //Resets to 0 so the ball stops dead upon reset to origin position.
    ai = 27;
    ai = 15;
    ai1 = 1;
    aj1 = 15;

    //Plays crowd cheer when goal is scored
    //var p = "<audio  id=theaudio  src=/uploads/kieranturgoose1/crowdcheersoundeffect.mp3 autoplay> </audio>" ;
    $("#user_span3").html( p );
  }
  return goals2;
}
//Updates the onscreen status.
function updateStatus()    
{
    var goals1 = goalScored();
    var goals2 = goalScored2();
    var status =  "Score goals by kicking the ball past the other player's goal. Player 1 controls: \n To move: Arrow Keys. \n K kicks one space. \n L kicks five spaces. \n... Player 2 controls: \n To move: WASD keys, \n  F kicks one space, \n G kicks five spaces \n  Step: " + step + " out of " + MAXSTEPS + ". Score " + goals1 + " : " + goals2;
    $("#user_span1").html( status );
}

//--- public functions / interface / API ----------------------------------------------------------


// must have this public variable:

this.endCondition;      // If set to true, run will end. 

this.newRun = function() 
{
    this.endCondition = false;
    step = 0;
    goals1 = 0;
    goals2 = 0;

    // define logical data structure for the World, even if no graphical representation:

    initGrid();
    initLogicalWalls(); 
    initLogicalAgent();
    initLogicalAgent2();
    initLogicalEnemy();
    initLogicalBall();

    if ( true  )
    {
            threeworld.init3d ( startRadiusConst, maxRadiusConst, SKYCOLOR  ); 


        // light
        var ambient = new THREE.AmbientLight();
        threeworld.scene.add( ambient );

        document.addEventListener("keydown",keyHandler); //Allows key presses
        document.addEventListener("keydown",keyHandler2); //Allows key presses

        var thelight = new THREE.DirectionalLight ( LIGHTCOLOR, 3 );
        thelight.position.set ( startRadiusConst, startRadiusConst, startRadiusConst );
        threeworld.scene.add(thelight);
    


        //Plays Match of the Day theme song throughout playthrough.
        var x = "<audio  id=theaudio  src=/uploads/kieranturgoose1/matchoftheday-themetune.mp3   autoplay loop> </audio>" ;
        $("#user_span2").html( x );
        
        initSkybox();
        initThreeWalls();
        initThreeBall();
        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);
    moveLogicalAgent2(a);
    moveLogicalBall();  
    if ( ( step % 2 ) === 0 )   // slow the enemy down to every nth step
    {
        moveLogicalEnemy();
    }

    if ( true  )
    {
        drawAgent();
        drawAgent2();
        drawEnemy();
        drawBall();
        updateStatus();
    }
    if(step == (MAXSTEPS-2)) //Plays the final whistle audio just before the run ends.
    {
        var p = "<audio  id=theaudio  src=/uploads/kieranturgoose1/refereewhistlesoundeffects.mp3	autoplay> </audio>" ;
        $("#user_span3").html( p );
    }
    if(step == MAXSTEPS)  //Updates the screen at the end of the game. 
    {
        var endGame =  "FULL TIME!         Final Score: " + goals1 + " : " + goals2; 
        $("#user_span1").html( endGame );
    }
};



this.endRun = function()
{
};


}

//---- end of World class -------------------------------------------------------