Code viewer for World: Sheepdog3(Submission) (clo...

// Cloned by Adam Gray on 8 Nov 2022 from World "Sheepdog3(Submission)" by Neil Geoghegan 
// Please leave this clone trail here.
 
/*
KNOWN BUGS
-----------
dog will pin sheep against wall forever
dog will get stuck at a wall if sheep is on the other side of it
gate sometimes closes on the sheep

*/



const	 	CLOCKTICK 	= 150;				// speed of run - move things every n milliseconds
const		MAXSTEPS 	= 1000;				// length of a run before final score




//---- global constants: -------------------------------------------------------


const gridsize = 20;					// 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 NOBOXES =  Math.trunc ( (gridsize * gridsize) / 15 );


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 * 3 ;		// where to put skybox
const maxRadiusConst 			= MAXPOS * 5 ;		// 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;




// contents of a grid square

const GRID_BLANK 	= 0;
const GRID_WALL 	= 1;
const GRID_MAZE 	= 2;







// --- 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() {
  //---My functions------------------------------------------------------------------
  function looseSheep(){
    if(zone(e1i,e1j) != 2) return true;
    if(zone(e2i,e2j) != 2) return true;
    if(zone(e3i,e3j) != 2) return true;
    return false;
  }
  function sheepCaught(){
    var count = 0;
    if(zone(e1i,e1j) == 2) count++;
    if(zone(e2i,e2j) == 2) count++;
    if(zone(e3i,e3j) == 2) count++;
    return count;
  }


  //raise and lower the gate logically
  function liftGate(){
    for(var i = 6; i <= 10; i++){
      MAZE[i].position.y = (MAZE[i].position.y)+100;
    }
    for(var i = 7; i < 12; i++){
      GRID[i][6] = GRID_BLANK ;
    }
    gateLifted = true;
  }
  function lowerGate(){
    for(var i = 6; i <= 10; i++){
      MAZE[i].position.y = (MAZE[i].position.y)-100;
    }
    for(var i = 7; i < 12; i++){
      GRID[i][6] = GRID_MAZE;
    }
    gateLifted = false;
  }

  //determine what zone you are curently in.
  function zone(i,j){
    if( (i => 1) && (i <= 6) && (j => 1) && (j <= 6 ) ) return 1
  	if( (i > 6) && (i <= 12) && (j => 1) && (j <= 6 ) ) return 2
  	if( (i > 12) && (i <= 18) && (j => 1) && (j <= 6 ) ) return 3

  	if( (i => 1) && (i <= 7) && (j > 6) && (j <= 12 ) ) return 4
  	if( (i > 7) && (i <= 11) && (j > 6) && (j <= 12 ) ) return 5
  	if( (i >= 12) && (i <= 18) &&(j > 6) && (j <= 12 ) ) return 6

  	if( (i => 1) && (i <= 7) && (j > 12) && (j <= 18 ) ) return 7
  	if( (i > 7) && (i <= 11) && (j > 12) && (j <= 18 ) ) return 8
  	if( (i >= 12) && (i <= 18) &&(j > 12) && (j <= 18 ) ) return 9
  }

var sheepCount = 0;
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 MAZE 	= new Array ( NOBOXES );
var theagent, theenemy1, theenemy2, theenemy3;

var agentRotation = 0;
var enemy1Rotation = 0;		  // with 3D models, current rotation away from default orientation
var enemy2Rotation = 0;
var enemy3Rotation = 0;

// enemy and agent position on squares

var ai, aj;
var e1i, e1j;
var e2i, e2j;
var e3i, e3j;
var gateLifted = false; //determine if gate is up/down
var badsteps;
var goodsteps;
var  step;

  	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 ( ( e1i == i ) && ( e1j == j ) ) return true;		// variable objects
 if ( ( ai == i ) && ( aj == j ) ) return true;
 if ( ( e2i == i ) && ( e2j == j ) ) return true;
 if ( ( e3i == i ) && ( e3j == j ) ) return true;
 	// variable objects

 if ( GRID[i][j] == GRID_WALL ) return true;		// fixed objects
 if ( GRID[i][j] == GRID_MAZE ) return true;

 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/geoghen4/sheep.obj", buildenemy1 );
 loader.load( "/uploads/geoghen4/sheep.obj", buildenemy2 );
 loader.load( "/uploads/geoghen4/sheep.obj", buildenemy3 );

// load simple OBJ
	 loader.load( "/uploads/geoghen4/dog.obj", buildagent );


 var loader1 = new THREE.TextureLoader();
 loader1.load ( '/uploads/geoghen4/fence.png',		function ( thetexture ) {
		thetexture.minFilter = THREE.LinearFilter;
		paintWalls ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
	} );

 var loader2 = new THREE.TextureLoader();
 loader2.load ( '/uploads/geoghen4/fence.png',		function ( thetexture ) {
		thetexture.minFilter = THREE.LinearFilter;
		paintMaze ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
 	} );

}



function buildenemy1 ( object )
{
	object.scale.multiplyScalar ( 100 );    	  // make 3d object n times bigger
	object.traverse( paintEnemy );
	theenemy1 = object;
	threeworld.scene.add( theenemy1 );
}
function buildenemy2 ( object )
{
	object.scale.multiplyScalar ( 100 );    	  // make 3d object n times bigger
	object.traverse( paintEnemy );
	theenemy2 = object;
	threeworld.scene.add( theenemy2 );
}
function buildenemy3 ( object )
{
	object.scale.multiplyScalar ( 100 );    	  // make 3d object n times bigger
	object.traverse( paintEnemy );
	theenemy3 = object;
	threeworld.scene.add( theenemy3 );
}

function paintEnemy ( child )
{
	if ( child instanceof THREE.Mesh )
	{
      	child.material.map = THREE.ImageUtils.loadTexture( "/uploads/geoghen4/wool.jppg" );
	}
}


function buildagent ( object )
{
      	  // make 3d object n times bigger
	object.traverse( paintAgent );
	theagent = object;
	threeworld.scene.add( theagent );
}

function paintAgent ( child )
{
	if ( child instanceof THREE.Mesh )
	{
       	child.material.map = THREE.ImageUtils.loadTexture( "/uploads/geoghen4/dogfurr.jpg" );
	}
}








function initSkybox()
{
// urban photographic skyboxes, credit:
// http://opengameart.org/content/urban-skyboxes

var materialArray = [
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/geoghen4/side3.png" ), side: THREE.BackSide } ) ),
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/geoghen4/side1.png" ), side: THREE.BackSide } ) ),
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/geoghen4/sky.png" ), side: THREE.BackSide } ) ),
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/geoghen4/ground.png" ), side: THREE.BackSide } ) ),
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/geoghen4/side2.png" ), side: THREE.BackSide } ) ),
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/geoghen4/side4.png" ), 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
}






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 )
   {
 	 var shape    = new THREE.BoxGeometry( squaresize, squaresize, 10 );
 	 var thecube  = new THREE.Mesh( shape );
	 thecube.material.color.setHex( 0xffffff  );
       if((i == 0) || (i == 19)){
      	 thecube.rotation.y =  Math.PI / 2;
       }

       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.material.side = THREE.DoubleSide;


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

function initLogicalMaze()
{

 for(var i = 1; i <= 6; i++){
   GRID[6][i] = GRID_MAZE ;
   GRID[12][i] = GRID_MAZE ;
 }
 for(var i = 7; i < 12; i++){
   GRID[i][6] = GRID_MAZE ;
 }

}

function initThreeMaze()
{
 var t = 0;
 for (var i = 0; i < gridsize ; i++)
  for (var j = 0; j < gridsize ; j++)
   if ( GRID[i][j] == GRID_MAZE )
   {
   	var shape    = new THREE.BoxGeometry( squaresize, squaresize, 10 );
  	var thecube  = new THREE.Mesh( shape );
	thecube.material.color.setHex( BLANKCOLOR  );

  	thecube.position.x = translate ( i * squaresize );
  	thecube.position.z = translate ( j * squaresize );
  //	thecube.position.y =  0;
    if((i == 6) || (i == 12)){
      thecube.rotation.y =  Math.PI / 2;

    }

    thecube.material.side = THREE.DoubleSide;

 	threeworld.scene.add(thecube);
	MAZE[t] = thecube;		// save it for later
	t++;
   }
}




function paintMaze ( material )
{
 for ( var i = 0; i < MAZE.length; i++ )
 {
   if ( MAZE[i] )  MAZE[i].material = material;
 }
}




// --- enemy functions -----------------------------------


function drawEnemys()		// given e1i, e1j, draw it
{
  if ( theenemy1 )
  {
    var x = translate ( e1i * squaresize );
    var z = translate ( e1j * squaresize );
    var y =   ( -1 * squaresize );

   theenemy1.position.x = x;
   theenemy1.position.y = y;
   theenemy1.position.z = z;

   threeworld.lookat.copy ( theenemy1.position );		// if camera moving, look back at where the enemy is
   threeworld.lookat.y = ( squaresize * 1.5 );     // point camera higher up
  }
  if ( theenemy2 )
  {
    var x = translate ( e2i * squaresize );
    var z = translate ( e2j * squaresize );
    var y =   ( -1 * squaresize );

   theenemy2.position.x = x;
   theenemy2.position.y = y;
   theenemy2.position.z = z;

   //threeworld.lookat.copy ( theenemy2.position );		// if camera moving, look back at where the enemy is
   //threeworld.lookat.y = ( squaresize * 1.5 );     // point camera higher up
  }
  if ( theenemy3 )
  {
    var x = translate ( e3i * squaresize );
    var z = translate ( e3j * squaresize );
    var y =   ( -1 * squaresize );

   theenemy3.position.x = x;
   theenemy3.position.y = y;
   theenemy3.position.z = z;

   //threeworld.lookat.copy ( theenemy2.position );		// if camera moving, look back at where the enemy is
   //threeworld.lookat.y = ( squaresize * 1.5 );     // point camera higher up
  }
}

function initLogicalEnemy()
{
// start in random location:
 var i, j;
 do
 {
  i = randomintAtoB(1,gridsize-2);
  j = randomintAtoB(1,gridsize-2);
 }
 while ( occupied(i,j) || zone(i,j) == 2 );  	  // search for empty square

 e1i = i;
 e1j = j;

 do{
  i = randomintAtoB(1,gridsize-2);
  j = randomintAtoB(1,gridsize-2);
 }
 while (  occupied(i,j) || zone(i,j) == 2  );  	  // search for empty square

 e2i = i;
 e2j = j;

 do{
  i = randomintAtoB(1,gridsize-2);
  j = randomintAtoB(1,gridsize-2);
 }
 while (  occupied(i,j) || zone(i,j) == 2 );  	  // search for empty square

 e3i = i;
 e3j = j;
}


function enemyGetAction(i,j)
{
    if(zone(i,j) == 5 && zone(ai,aj) == 5 && gateLifted == false){
      liftGate();

    }
    else if(zone(i,j) != 5 && zone(ai,aj) != 5 && gateLifted == true){

      lowerGate();
    }
    if(zone(i,j) ==  2){

		if(randomBoolean()){
			return ( ACTION_UP );
		}
		else{
			return randomPick ( ACTION_LEFT, ACTION_RIGHT );
		}
	}
	if( (aj === j) && ( (ai-1) === i ) ){

		return ( ACTION_LEFT );
	}
	else if( (aj === j) && ( (ai+1) === i ) ){

		return ( ACTION_RIGHT );

	}
	else if( (ai === i) && ( (aj-1) === j ) ){

		return (ACTION_UP);
	}
	else if( (ai === i) && ( (aj+1) === j ) ){

		return (ACTION_DOWN);
	}
	else{
		if(randomBoolean()){
      return randomPick(ACTION_DOWN, ACTION_UP)
    }
    else{
      return randomPick(ACTION_LEFT, ACTION_RIGHT)
    }
	}
}

function moveLogicalEnemy(id)
{

  switch(id) {
      case 1:
          var a = enemyGetAction(e1i,e1j);
          var i = e1i;
          var j = e1j;
          break;
      case 2:
          var a = enemyGetAction(e2i,e2j);
          var i = e2i;
          var j = e2j;
          break;
      default:
          var a = enemyGetAction(e3i,e3j);
          var i = e3i;
          var j = e3j;

  }

 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))
 {
  if ( true  )
  {

	      if ( a == ACTION_LEFT ) 	{  rotateEnemyTowards ( id, 3 * (Math.PI / 2) ); }
	 else if ( a == ACTION_RIGHT ) 	{  rotateEnemyTowards ( id, 1 * (Math.PI / 2) ); }
	 else if ( a == ACTION_UP ) 		{  rotateEnemyTowards ( id, 2 * (Math.PI / 2) ); }
	 else if ( a == ACTION_DOWN ) 	{  rotateEnemyTowards ( id, 0 * (Math.PI / 2) ); }
  }
  switch(id) {
      case 1:
          e1i = i;
          e1j = j;
          break;
      case 2:
          e2i = i;
          e2j = j;
          break;
      default:
          e3i = i;
          e3j = j;

  }
 }
}

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

function rotateEnemyTowards (id,  newRotation )
{
 switch(id){
   case 1:
     if ( enemy1Rotation == newRotation ) return;
     // else
     var x = ( enemy1Rotation + newRotation ) / 2;
     theenemy1.rotation.set ( 0, x, 0 );
     enemy1Rotation = x;
     break;
   case 2:
     if ( enemy2Rotation == newRotation ) return;
     // else
     var x = ( enemy2Rotation + newRotation ) / 2;
     theenemy2.rotation.set ( 0, x, 0 );
     enemy2Rotation = x;
     break;
  case 3:
      if ( enemy3Rotation == newRotation ) return;
      // else
      var x = ( enemy3Rotation + newRotation ) / 2;
      theenemy3.rotation.set ( 0, x, 0 );
      enemy3Rotation = x;

 }

}

// --- agent functions -----------------------------------


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

 theagent.position.x = x;
 theagent.position.y = y;
 theagent.position.z = z;

 threeworld.follow.copy ( theagent.position );		// follow vector = agent position (for camera following agent)
 threeworld.follow.y = ( squaresize * 1.5 );     // put camera higher up
}
}


function initLogicalAgent()
{
// start in random location:

 ai = 9;
 aj = 4;
}



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) )
 {
  if ( true  )
  {
	// if going to actually move, then turn body towards move
	// rotate by some amount of radians from the normal position
	// in degrees: +0, +90, +180, +270

	      if ( a == ACTION_LEFT ) 	{  rotateAgentTowards ( 3 * (Math.PI / 2) ); }
	 else if ( a == ACTION_RIGHT ) 	{  rotateAgentTowards ( 1 * (Math.PI / 2) ); }
	 else if ( a == ACTION_UP ) 		{  rotateAgentTowards ( 2 * (Math.PI / 2) ); }
	 else if ( a == ACTION_DOWN ) 	{  rotateAgentTowards ( 0 * (Math.PI / 2) ); }
  }
  ai = i;
  aj = j;
  if(zone(ai,aj) == 2 && gateLifted == false){
    liftGate();
  }
 }
}



function rotateAgentTowards ( newRotation )
{
 if ( agentRotation == newRotation ) return;
 // else
 var x = ( agentRotation + newRotation ) / 2;
 theagent.rotation.set ( 0, x, 0 );
 agentRotation = x;
}







// --- score: -----------------------------------


function badstep()			// is the enemy within one square of the agent
{
 if ( ( Math.abs(e1i - ai) < 2 ) && ( Math.abs(e1j - aj) < 2 ) ) return true;
 else return false;
}


function   updateStatus()
{

 var status =  sheepCaught() + " sheep caught out of " + 3 ;

 $("#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;
  badsteps = 0;
  goodsteps = 0;
  step = 0;


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

 	initGrid();
	initLogicalWalls();
	initLogicalMaze();

	initLogicalAgent();
	initLogicalEnemy();
  //initLogicalEnemy2();


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


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


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



		// music
  		var x = "<audio  id=theaudio  src=http://student.computing.dcu.ie/~geoghen4/bennyhill.mp3   autoplay loop> </audio>" ;
  		$("#user_span2").html( x );

		// music credit
		// http://www.dl-sounds.com/royalty-free/suspense-strings/



        	initSkybox();
	    		initThreeWalls();
	    	initThreeMaze();

	  loadTextures();			// will return sometime later, but can go ahead and render now
  }
};



this.getState = function()
{
 var x = [ ai, aj, e1i, e1j, e2i, e2j, e3i, e3j ];
  return ( x );
};


this.takeAction = function ( a )
{
  step++;
  if(sheepCaught() == 3 && zone(ai,aj) == 8){
    lowerGate();
    this.endCondition = true;

  }
  moveLogicalAgent(a);

    if ( ( step % 2 ) == 0 ){
      moveLogicalEnemy(1);
      moveLogicalEnemy(2);
      moveLogicalEnemy(3);
    }		// slow the enemy down to every nth step


  if ( badstep() )
   badsteps++;
  else
   goodsteps++;

  if ( true  )
  {
   drawAgent();
   drawEnemys();
   //drawEnemy2();
   updateStatus();
  }

};

this.endRun = function()
{
  console.log("Run Ended");
};


this.getScore = function()
{
 return goodsteps;
};


}

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