JS viewer

Syntax-highlighted view of JS of World: Pacxon








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