// TO-DO:
// -- When Ronaldo Scores, Messi moves to the centre circle but when Messi Scores, Ronaldo doesn't
// -- Still need to remove the goalkeeper code
// Done:
// -- Ronaldo doesn't Return to original position when a goal is scored:
// -- Ronaldo obj and mtl looks really good
// -- Score only has one count for all the goals scored: e.g. 5 : 0, messi scored 3, ronaldo scored 2
// -- It has something to do with the goals
// Issues:
// -- Ronaldo logicalAgent2 "thinks" the ball is one space up
// -- Goals still count if it's beyond the "goalposts"
// Cloned by Kline on 1 Dec 2022 from World "Football Model World (clone by Mustafa Kamal)" by Mustafa Kamal
// Please leave this clone trail here.
// Original Program -- Kieran Turgoose
const CLOCKTICK = 100; // 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.
// 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 score_on_left;
var score_on_right;
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 );
// load OBJ plus MTL (plus TGA files)
// Loads the Messi 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 );
} );
} );
// OBJ and MTL obtained by Mustafa
var x = new THREE.MTLLoader();
x.setTexturePath ( "/uploads/pepsio97/" );
x.setPath ( "/uploads/pepsio97/" );
x.load( "CR712345.mtl", function( materials )
{
materials.preload();
var o = new THREE.OBJLoader();
o.setMaterials ( materials );
o.setPath ( "/uploads/kamalm3/" );
o.load( "CR7123.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 ( 4 );
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;
}
}
const INTERIMROT = 10; // number of interim rotations drawn when model turns round
// --- 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 = 28; //Starting point for Messi (player1)
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 Ronaldo (player2)
aj2 = 16;
}
function keyHandler(e)
// user control
// Note that this.takeAction(a) is constantly running at same time, redrawing the screen.
// Controls Messi
{
// Arrow keys for movement
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.
// Controls Ronaldo
{
// WASD for movement
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.
// When Messi Scores, it should update score_on_left
function messiScores()
{
if(bi == 1 && (bj == 13 || bj == 14 || bj == 15 || bj == 16 || bj == 17))
{
score_on_right++;
bi = 14;
bj = 15;
ballMoves = 0; //Resets to 0 so the ball stops dead upon reset to origin position.
ai = 28;
aj = 15;
ai2 = 1;
aj2 = 16;
// Plays crowd cheer when goal is scored
var p = "<audio id=theaudio src=/uploads/kieranturgoose1/crowdcheersoundeffect.mp3 autoplay> </audio>" ;
$("#user_span3").html( p );
}
return score_on_right;
}
// When it hits Messi's goal, it should update score_on_right
// Advantageous to Ronaldo
function ronaldoScores()
{
if(bi == 27 && (bj == 13 || bj == 14 || bj == 15 || bj == 16 || bj == 17))
{
score_on_left++;
bi = 14;
bj = 15;
ballMoves = 0; //Resets to 0 so the ball stops dead upon reset to origin position.
ai = 28;
ai = 15;
ai2 = 1;
aj2 = 16;
// Plays crowd cheer when goal is scored
var p = "<audio id=theaudio src=/uploads/kieranturgoose1/crowdcheersoundeffect.mp3 autoplay> </audio>" ;
}
return score_on_left;
}
//Updates the onscreen status.
function updateStatus()
{
var score_on_right = messiScores();
var score_on_left = ronaldoScores();
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 " + score_on_left + " : " + score_on_right;
$("#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;
score_on_left = 0;
score_on_right = 0;
// define logical data structure for the World, even if no graphical representation:
initGrid();
initLogicalWalls();
initLogicalAgent();
initLogicalAgent2();
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>" ;
// var x = "<audio id=theaudio src=/uploads/kline101/sewyy.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() // added ai2, aj2
{
var x = [ ai, aj, ai2, aj2, 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();
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: " + score_on_left + " : " + score_on_right;
$("#user_span1").html( endGame );
}
};
this.endRun = function()
{
};
}
//---- end of World class -------------------------------------------------------