Code viewer for World: Lights out
    
    
    
    
    
    
    
    
    
    
    // =============================================================================================
    // More complex starter World for WWM
    // 3d-effect Maze World (really a 2-D problem)
    // Mark Humphrys, 2016.
    //
    // This more complex World shows:
    // - Skybox
    // - Internal maze (randomly drawn each time)
    // - Enemy actively chases agent
    // - Music/audio
    // - 2D world (clone this and set show3d = false)
    // - User keyboard control (clone this and comment out Mind actions to see)
    // =============================================================================================
    
    
    /*
     * Based on tigers lights out handheld game. Objective is turn all lights out in shortest amount of moves.
     * Flicking a light affects neighbouring ones ie :  flick [i,j] will xor [i+1,j], [i-1,j], [i,j+1], [i,j-1].
     * Based on 5x5 board, search space is 2^25 or roughly 32 million.
    */
    
    /*
            Pseudo
            
            problem = GIVEN
            solutions = [[0],[1]] // array of binary matrix answers (two entries, a zero and a one)
            for (problemSize = 1; problemSize <= 5; problemSize++) {
                newSolutions = [];
                foreach (solutions as oldSolution) {
                    candidateSolutions = arrayOfNByNMatriciesWithMatrixAtTopLeft(min(5,problemSize+1), oldSolution);
                    // size of candidateSolutions is 2^((problemSize+1)^2 - problemSize^2)
                    // except last round candidateSolutions == solutions
                    foreach (candidateSolutions as candidateSolution) {
                        candidateProblem = boardFromPressingButtonsInSolution(candidateSolution);
                        if (compareMatrix(problem, candidateProblem, 0, 0, problemSize, problemSize)==0)
                            newSolutions[] = candidateSolution;
                    }
                }
                solutions = newSolutions;
            }
            return solutions;
    
    
    */
    // World must define these:
     
    const	 	CLOCKTICK 	= 100;					// speed of run - move things every n milliseconds
    const		MAXSTEPS 	= 1000;					// length of a run before final score
     
    
    
const  SCREENSHOT_STEP = 50;    
    
    
    
    //---- global constants: -------------------------------------------------------
    
    const gridsize = 7;						// number of squares along side of world	   
    
    const NOBOXES =  5;
    		// density of maze - number of internal boxes
    		// (bug) use trunc or can get a non-integer 
    
    const squaresize = 100;					// size of square in pixels
    const MAXPOS = gridsize * squaresize;		// length of one side in pixels 
    	
    const SKYCOLOR 	= 0xddffdd;				// a number, not a string 
    const BLANKCOLOR 	= SKYCOLOR ;			// make objects this color until texture arrives (from asynchronous file read)
    
    
    
    
    const show3d = true;						// Switch between 3d and 2d view (both using Three.js) 
     
    const startRadiusConst	 	= MAXPOS * 0.8 ;		// distance from centre to start the camera at
    const skyboxConst			= MAXPOS * 3 ;		// where to put skybox 
    const maxRadiusConst 		= MAXPOS * 10  ;		// 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)
    
    
    
    // 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; }
    }
    
    
    
    
    
    
    
    //---- start of World class -------------------------------------------------------
     
    function World() { 
    
    
    
    // most of World can be private 
    // regular "var" syntax means private variables:
    
    
    var BOXHEIGHT;		// 3d or 2d box height 
    
    
    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 handles to wall and maze objects so can find them later to paint them 
    var MAZE 	= new Array ( 25 );
    var BINARY = new Array(25);
    var theagent, theenemy;
      
    
    // enemy and agent position on squares
    var ei, ej, ai, aj;
    var min = 25;
    var badsteps;
    var goodsteps;
    var  step;
    
     	var self = this;						// needed for private fn to call public fn - see below  
    
     
    
    
    // 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] = GRID_BLANK ;
      }
     }
    }
    
    //game logic array, if 1 lit, if 0 unlit. Start off with fully lit board
    function initBinary()
    {
     for (var i = 0; i < 25 ; i++) 
     {
        BINARY[i] = 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;
    }
    
     
    // 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 ) );
    }
    
    
    
    
    
    //--- skybox ----------------------------------------------------------------------------------------------
    
    
    function initSkybox() 
    {
    
    // x,y,z positive and negative faces have to be in certain order in the array 
     
    // mountain skybox, credit:
    // http://stemkoski.github.io/Three.js/Skybox.html
    
      var materialArray = [
     	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/kinses38/moon4.jpg" ), side: THREE.BackSide } ) ), //moon
     	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/kinses38/moon5.jpg" ), side: THREE.BackSide } ) ),
     	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/kinses38/moon6.jpg" ), side: THREE.BackSide } ) ), //ceiling
     	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/kinses38/moon2.jpg" ), side: THREE.BackSide } ) ),//floor
     	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/kinses38/moon3.jpg" ), side: THREE.BackSide } ) ),
     	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/kinses38/moon1.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
      var spotLight = new THREE.SpotLight( 0xffffff,1 );
        spotLight.position.set( 100, 100, 100 );
        threeworld.scene.add( spotLight );
    
    }
    
    
    
    function loadTextures()
    {
    
     var loader1 = new THREE.TextureLoader();
     var loader2 = new THREE.TextureLoader();
     var loader3 = new THREE.TextureLoader();
     var loader4 = new THREE.TextureLoader();
     
                     loader1.load ( '/uploads/kinses38/metaltexture.jpg',		function ( thetexture ) {			 
                    		thetexture.minFilter = THREE.LinearFilter;
                    		paintWalls ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
                    	} ); 
    	
        
                    
                     loader2.load ( '/uploads/kinses38/lit.jpg',		function ( thetexture ) {			 
                    		thetexture.minFilter = THREE.LinearFilter;
                    		paintMazeRed ( new THREE.MeshBasicMaterial( { map: thetexture } ));
                     	} ); 
              
                     
                     loader3.load ( '/uploads/kinses38/unlit.jpg',		function ( thetexture ) {			 
                    		thetexture.minFilter = THREE.LinearFilter;
                    		paintMazeBlue ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
                     	} ); 
                     	

                     loader4.load ( '/uploads/starter/pacman.jpg',	function ( thetexture ) {			 
                    		thetexture.minFilter = THREE.LinearFilter;
                    		theagent.material =  new THREE.MeshBasicMaterial( { map: thetexture } );
                    	} ); 
               
            
        
    }
    
    
     
    
    
    // --- add fixed objects ---------------------------------------- 
       
     
    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, BOXHEIGHT, 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 )		 
    {
     for ( var i = 0; i < WALLS.length; i++ )
     { 
       if ( WALLS[i] )  WALLS[i].material = material;
     }
    }
    
    
    
    
    
    function initLogicalMaze()		 
    {
     for ( var c=1 ; c <= NOBOXES ; c++ )
     {
         for (var i = 1; i <= c; i++)
         {
             for (var j = 1; j <= c; j++)
            {
                GRID[i][j] = 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, BOXHEIGHT, squaresize );			 
      	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;	
     
     	threeworld.scene.add(thecube);
    	MAZE[t] = thecube;		// save it for later
    	t++; 
       }
    }
    
    
    function paintMazeRed ( material )		 
    { 
       for(var i = 0; i < MAZE.length; i++)
       if ( MAZE[i] && BINARY[i] == 1 )  MAZE[i].material = material;
     
    }
    function paintMazeBlue(material)
    {
        for(var i = 0; i < MAZE.length; i++)
       if ( MAZE[i] && BINARY[i] == 0 )  MAZE[i].material = material;
    }

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


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

 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 in random location:
 BINARY[0];
 
 
 
}

function initThreeAgent()
{
 var shape    = new THREE.BoxGeometry( squaresize, BOXHEIGHT, 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;
 if(BINARY[i] == 0 || i < 4)
 {
     i++;
 }
 else
    i;
 
}
    
    function keyHandler(e)		
    // user control 
    // Note that this.takeAction(a) is constantly running at same time, redrawing the screen.
    {
        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	);
    }
    
    
    
    
    
    // --- 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 lightsOff()			// agent is blocked on all sides, run over
    {
        for(var i = 0; i < BINARY.length; i++)
        {
            if(BINARY[i] == 1)
            {
                return false;
            }
            
        }
        return true;
    } 
    
    
    function updateStatusBefore(a)
    // this is called before anyone has moved on this step, agent has just proposed an action
    // update status to show old state and proposed move 
    {
        var x = 0
        x = BINARY.reduce(add, 0);

        function add(a, b) 
        {
            return a + b;
        }
          
        
        if(x < min)
        {
            min = x;
        }

     var status = " Step: <b> " + step + " </b> &nbsp; Lights lit = (" + x.toString() + "), Least amount of lights on = (" + min.toString() + ")";
    
     $("#user_span3").html( status );
    }
    
    
    function   updateStatusAfter()		// agent and enemy have moved, can calculate score
    {
     // new state after both have moved
     var y = self.getState();
     var score = self.getScore();
    
     
    }
 
    //updateBoard after toggling a light according to game behaviour
    function toggleLight(i)
    {
        //just xors a cross shape with binary[i] as the center, with 1
        //tacky but works.
        BINARY[i] = BINARY[i]^1;
        BINARY[i-1] = BINARY[i-1]^1;
        BINARY[i+1] = BINARY[i+1]^1;
        
        if(i+5 > 24)
            BINARY[(i+5)-25] = BINARY[(i+5)-25]^1;
        else
            BINARY[i+5] = BINARY[i+5]^1;
        if(i-5 < 0)
            BINARY[(i-5)+25] = BINARY[(i-5)+25]^1;
        else
        BINARY[i-5] = BINARY[i-5]^1;
       
        
    }
    
    
    
    
    //--- public functions / interface / API ----------------------------------------------------------
    
    
    	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;
    
    
     // for all runs:
    
     	initGrid();
    	initLogicalWalls(); 
    	initLogicalMaze();
    	initBinary();
    	initLogicalAgent();
    
     // for graphical runs only:
    
      if ( true  )
      {
    	if ( show3d )
    	{
    	 BOXHEIGHT = squaresize;
    	 threeworld.init3d ( startRadiusConst, maxRadiusConst, SKYCOLOR  ); 	
    	}	     
    	else
    	{
    	 BOXHEIGHT = 1;
    	 threeworld.init2d ( startRadiusConst, maxRadiusConst, SKYCOLOR  ); 		     
    	}
    	
    	
    	initSkybox();
     	initMusic();
        
    	// Set up objects first:
    
    	initThreeWalls(); 
    	initThreeMaze();
    	initThreeAgent();
        
    	loadTextures();	
    	
    
    	document.onkeydown = keyHandler;	 
      }
    
};
    
    
    
    
    this.getState = function()
    {
        /*
        var lit = 0;
        //read in board here, check current lit squares
        for(var i = 0; i < BINARY.length; i++)
        {
            if(BINARY[i] == 1)
                lit++;
        }
        return lit;
        */
        var x = (BINARY);
        return x;
    };
    

    
    this.takeAction = function ( a )
    {
      step++;
    
      if ( true  )
       updateStatusBefore(a);			// show status line before moves 
       toggleLight(a);
        console.log(BINARY)
    
      if ( badstep() )
       badsteps++;
      else
       goodsteps++;
    
      if ( true  )
      {
       //reload the lights that have changed
       loadTextures();
       updateStatusAfter();			// show status line after moves  
      }
      
      if(lightsOff())
      {
          this.endCondition = true;
          if( true)
          {
              musicPause();
              soundAlarm();
          }
      }
    
    };
    
    
    
    this.endRun = function()
    {
     if ( true  )
     {
      musicPause(); 
      if ( this.endCondition )
        $("#user_span6").html( " &nbsp; <font color=green> <B> All Lights out. </B> </font>   "  );
      else
        $("#user_span6").html( " &nbsp; <font color=red> <B> Run over, took too long. </B> </font>   "  );
     }
    };
    
    
    this.getScore = function()
    {
     return ( ( goodsteps / step ) * 100 );
    };
    
    
    }
    
    //---- end of World class -------------------------------------------------------
    
    
    
    
    
    
    
    
    // --- music and sound effects ----------------------------------------
    // credits:
    //https://www.youtube.com/watch?v=sZHqJILbF8A Pilotpriest | White Blazer
    // https://www.youtube.com/watch?v=-YCN-a0NsNk Final Fantasy 7 fanfare
    
    
    
    function initMusic()
    {
    	// put music element in one of the spans
      	var x = "<audio  id=theaudio  src=http://www.student.computing.dcu.ie/~kinses38/PilotPriest_whiteblazer.mp3   autoplay loop> </audio>" ;
      	$("#user_span1").html( x );
    } 
     
    
    function musicPlay()  
    {
    	// jQuery does not seem to parse pause() etc. so find the element the old way:
     	document.getElementById('theaudio').play();
    }
    
    
    function musicPause() 
    {
     	document.getElementById('theaudio').pause();
    }
    
    
    function soundAlarm()
    {
     	var x = "<audio    src=http://www.student.computing.dcu.ie/~kinses38/FFVII_fanfair.mp3   autoplay  > </audio>";
      	$("#user_span2").html( x );
    }