//=============================================================================================
//Introduction:
//This is my version of snake. The game based of the old nokio phone. The game starts of with
//the snake in the middle, one block in size. As you eat the sweet you get one block bigger.
//The challenge I had in this project was the snake moving. I ended up keeping track of
//the previous moves made by the snake and draw the snake then removing the snake then re-draw
//the snake in the moved position. As the snake gets long the hard it is on the computer.
//So i added in the maze to make it harder for the play to complete it. The snake moves with
//the up, down, right and left arrows. if I had more time I would have put in a mind for the snake
//to get to the food in the shortest distence. I would also have add in a model of the snake.
//============================================================================================
// =============================================================================================
// 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)
// =============================================================================================
// =============================================================================================
// Scoring:
// Based of the amount of sweets you eat
// Game will stay going until you crash into yourself or into the maze
// =============================================================================================
// 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
//---- global constants: -------------------------------------------------------
const gridsize = 30; // number of squares along side of world
const squaresize = 50; // 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 LIGHTCOLOR = 0xffffff ;
const show3d = false; // 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;
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 = [];
var theSnake, theSweet;
// Sweet and Snake position on squares
var ei, ej, ai, aj;
var right = 0;
var left = 0;
var down = 0;
var up = 0;
//array for blocks
var blocks = [];
//arrays for the snake position
var snakeTaili = [];
var snakeTailj = [];
//array for the maze
var walli = [14, 14, 13, 16, 16, 17, 14, 14, 13, 16, 16, 17, 7, 7, 7, 8, 9, 23, 22, 21, 23, 23, 7, 7, 7, 8, 9, 23, 23, 23, 22, 21];
var wallj = [14, 13, 14, 14, 13, 14, 16, 17, 16, 16, 17, 16, 7, 8, 9, 7, 7, 7, 7, 7, 8, 9, 23, 22, 21, 23, 23, 23, 22, 21, 23,23];
var snakeTotal = 1;
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 ;
}
}
}
function occupied(i, j){ // is this square occupied
if ((ai == i) && (aj == j)){
return true;
}
if(GRID[i][j] == GRID_WALL){
return true; // fixed objects
}
for(var z = 0; z < snakeTotal; z++){
if((snakeTaili[z] == i) && (snakeTailj[z] == j)){
return true;
}
}
for(var k = 0; k < walli.length; k++){
if((walli[k] == i) && (wallj[k] == j)){
return true;
}
}
return false;
}
function translate ( x ){
return (x - ( MAXPOS/2 ));
}
//--- skybox ----------------------------------------------------------------------------------------------
function initSkybox(){
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
}
// --- alternative skyboxes: ------------------------------
var materialArray = [
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/sky_pos_z.jpg" ), side: THREE.BackSide } ) ),
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/sky_neg_z.jpg" ), side: THREE.BackSide } ) ),
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/sky_pos_y.jpg" ), side: THREE.BackSide } ) ),
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/sky_neg_y.jpg" ), side: THREE.BackSide } ) ),
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/sky_pos_x.jpg" ), side: THREE.BackSide } ) ),
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/sky_neg_x.jpg" ), side: THREE.BackSide } ) ),
];
// --- asynchronous 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
// http://en.wikipedia.org/wiki/File:Inscription_displaying_apices_(from_the_shrine_of_the_Augustales_at_Herculaneum).jpg
// loader return can call private function
function loadTextures(){
var loader1 = new THREE.TextureLoader();
loader1.load ( '/uploads/starter/door.jpg', function ( thetexture ) {
thetexture.minFilter = THREE.LinearFilter;
paintWalls ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
} );
var loader2 = new THREE.TextureLoader();
loader2.load ( '/uploads/starter/latin.jpg', function ( thetexture ) {
thetexture.minFilter = THREE.LinearFilter;
paintMaze ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
} );
//This adds in the sweet
var m = new THREE.MTLLoader();
m.setTexturePath ( "/uploads/kingodhran/" );
m.setPath ( "/uploads/kingodhran/" );
m.load( "colorbomb.mtl", function( materials ) {
materials.preload();
var o = new THREE.OBJLoader();
o.setMaterials ( materials );
o.setPath ( "/uploads/kingodhran/" );
o.load( "colorbomb.obj", function ( object ) {
addparker ( object );
});
});
}
// --- 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 initThreeMaze(){
var t = 0;
for (var i = 0; i < walli.length; i++){
var shape = new THREE.BoxGeometry(squaresize, BOXHEIGHT, squaresize);
var thecube = new THREE.Mesh(shape);
thecube.material.color.setHex(BLANKCOLOR);
thecube.position.x = translate (walli[i] * squaresize);
thecube.position.z = translate (wallj[i] * squaresize);
thecube.position.y = 0;
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;
}
}
}
// --- Sweet functions -----------------------------------
//Credit to Sweet
//Comes from candy crush
//https://www.models-resource.com/mobile/candycrushsodasaga/model/12996/
function addparker ( object )
{
object.scale.multiplyScalar(12);
theSweet = object;
threeworld.scene.add(theSweet);
}
function drawSweet(){ // given ei, ej, draw it
var x = translate (ei * squaresize);
var z = translate (ej * squaresize);
var y = ( -0.5 * squaresize );
theSweet.position.x = x;
theSweet.position.y = y;
theSweet.position.z = z;
//threeworld.scene.add(theSweet);
threeworld.lookat.copy (theSweet.position); // if camera moving, look back at where the enemy is
}
function initLogicalSweet(){
// start in random location:
var i, j;
do{
i = randomintAtoB(1,gridsize-2);
j = randomintAtoB(1,gridsize-2);
} while (occupied(i,j)); // search for empty square
ei = i;
ej = j;
}
// --- Snake functions -----------------------------------
function drawSnake(){ // given ai, aj, draw it
var x = translate (ai * squaresize);
var z = translate (aj * squaresize);
var y = 0;
theSnake.position.x = x;
theSnake.position.y = y;
theSnake.position.z = z;
threeworld.scene.add(theSnake);
threeworld.follow.copy(theSnake.position); // follow vector = agent position (for camera following agent)
}
function initLogicalSnake(){
// start in center location:
ai = gridsize / 2;
aj = gridsize / 2;
}
function initThreeSnake(){
var shape = new THREE.BoxGeometry( squaresize, BOXHEIGHT, squaresize);
theSnake = new THREE.Mesh( shape );
theSnake.material.color.setHex( BLANKCOLOR );
blocks[0] = theSnake;
drawSnake();
}
function moveLogicalSnake(a) { // this is called by the infrastructure that gets action a from the Mind
//This iterats through the array and moves the steps forwars
for(var z = 0; z < snakeTotal - 1; z++){
snakeTaili[z] = snakeTaili[z + 1];
snakeTailj[z] = snakeTailj[z + 1];
}
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--;
}
//moves the snake through the walls
if(i == 29){
i = 1;
}
if(i == 0){
i = 28;
}
if(j == 0){
j = 28;
}
if(j == 29){
j = 1;
}
ai = i;
aj = j;
//Sets the end of the head to the end of the snake array
snakeTaili[snakeTotal - 1] = ai;
snakeTailj[snakeTotal - 1] = aj;
}
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(right == 0){
left = 1;
right = 0;
up = 0;
down = 0;
}
}
if(e.keyCode == 38){
//moveLogicalAgent (ACTION_DOWN);
if(up == 0){
left = 0;
right = 0;
up = 0;
down = 1;
}
}
if(e.keyCode == 39){
//moveLogicalAgent (ACTION_RIGHT);
if(left == 0){
left = 0;
right = 1;
up = 0;
down = 0;
}
}
if(e.keyCode == 40){
//moveLogicalAgent (ACTION_UP);
if(down == 0){
left = 0;
right = 0;
up = 1;
down = 0;
}
}
}
//This checks if the sweet is in the same spot as the snakes head
function foodHit(){
if(ai == ei && aj == ej){
return true;
}else {
return false;
}
}
//This just moves the sweet in a different spot (not remove)
function removeSweet(){
if(foodHit()){
snakeTotal++;
initLogicalSweet();
eatSound();
}
}
//This method draws the snake
//the for loop goes through the size
//the snakeTail arrays keeps track of the position
function drawBlock(){
for(var i = 0; i < snakeTotal; i++){
var shape = new THREE.BoxGeometry( squaresize, BOXHEIGHT, squaresize);
var snake = new THREE.Mesh( shape );
var x = translate (snakeTaili[i] * squaresize);
var z = translate (snakeTailj[i] * squaresize);
var y = 0;
snake.position.x = x;
snake.position.y = y;
snake.position.z = z;
blocks[i] = snake;
threeworld.scene.add(snake);
}
}
//This removes the snake
function removeBlock(){
for(var j = 0; j < snakeTotal; j++){
threeworld.scene.remove(blocks[j]);
}
}
//This Checks if the snake hit it's self or ran into a wall
function hitSnake(){
for(var z = 1; z < snakeTotal - 2; z++){
if((snakeTaili[z] == ai) && (snakeTailj[z] == aj)){
return true;
}
}
for(var k = 0; k < walli.length; k++){
if((walli[k] == ai) && (wallj[k] == aj)){
return true;
}
}
return false;
}
// --- score: -----------------------------------
function updateStatusBefore(a){
//Displays the Players Score
var status = " Score: <b> " + (snakeTotal - 1); // + " </b> x = (" + x.toString() + ") a = (" + a + ") ";
$("#user_span3").html( status );
}
//--- 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;
// for all runs:
initGrid();
initLogicalWalls();
initLogicalSnake();
initLogicalSweet();
// for graphical runs only:
if(true){
if(show3d){
BOXHEIGHT = squaresize;
threeworld.init3d ( startRadiusConst, maxRadiusConst, SKYCOLOR );
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);
} else {
BOXHEIGHT = 1;
threeworld.init2d ( startRadiusConst, maxRadiusConst, SKYCOLOR );
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);
}
initSkybox();
// Set up objects first:
initThreeWalls();
initThreeMaze();
initThreeSnake();
loadTextures();
document.onkeydown = keyHandler;
}
};
this.getState = function(){
var x = [ ai, aj, ei, ej ];
return ( x );
};
//Functions to keep the snake moving up
//The snake is removed, moves one step then re-drawen
function moveRight(){
removeBlock();
moveLogicalSnake (ACTION_RIGHT);
drawBlock();
}
function moveLeft(){
removeBlock();
moveLogicalSnake (ACTION_LEFT);
drawBlock();
}
function moveDown(){
removeBlock();
moveLogicalSnake (ACTION_DOWN);
drawBlock();
}
function moveUp(){
removeBlock();
moveLogicalSnake (ACTION_UP);
drawBlock();
}
//Main Game Play
this.takeAction = function (a){
if(true){
updateStatusBefore(a); // show status line before moves
}
//Stay Moving right
if(right > 0){
moveRight();
}
//Stay Moving Left
if(left > 0){
moveLeft();
}
//Stay Moving up
if(up > 0){
moveUp();
}
//Stay Moving down
if(down > 0){
moveDown();
}
if(true){
drawSnake();
drawSweet();
}
//If the snake runs into itself or int the maze
if(hitSnake()){
died();
this.endCondition = true;
}
//Will keep checking to see if the sweet has been eaten
removeSweet();
};
//End the game and writes the comment
this.endRun = function(){
$("#user_span6").html(" <font color=red> <B> You killed the snake. </B> </font> ");
};
}
//---- end of World class -------------------------------------------------------
// --- music and sound effects ----------------------------------------
// credits:
//Beep Sound
// https://www.youtube.com/watch?v=BEH3TOPBVRc
//GTA 5 wasted sound
// https://www.youtube.com/watch?v=XEm-anELm10
function eatSound(){
var x = "<audio src=/uploads/kingodhran/blooper-beep.mp3 autoplay> </audio>" ;
$("#user_span1").html( x );
}
function died(){
var x = "<audio src=/uploads/kingodhran/wasted.mp3 autoplay> </audio>" ;
$("#user_span1").html( x );
}