Advertisement
HeroCC

Snake JS

Mar 17th, 2016
126
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*
  2. JavaScript Snake
  3. By Patrick Gillespie
  4. http://patorjk.com/games/snake
  5. */
  6.  
  7. /**
  8. * @module Snake
  9. * @class SNAKE
  10. */
  11.  
  12. var SNAKE = SNAKE || {};
  13.  
  14. /**
  15. * @method addEventListener
  16. * @param {Object} obj The object to add an event listener to.
  17. * @param {String} event The event to listen for.
  18. * @param {Function} funct The function to execute when the event is triggered.
  19. * @param {Boolean} evtCapturing True to do event capturing, false to do event bubbling.
  20. */
  21. SNAKE.addEventListener = (function() {
  22.     if (window.addEventListener) {
  23.         return function(obj, event, funct, evtCapturing) {
  24.             obj.addEventListener(event, funct, evtCapturing);
  25.         };
  26.     } else if (window.attachEvent) {
  27.         return function(obj, event, funct) {
  28.             obj.attachEvent("on" + event, funct);
  29.         };
  30.     }
  31. })();
  32.  
  33. /**
  34. * @method removeEventListener
  35. * @param {Object} obj The object to remove an event listener from.
  36. * @param {String} event The event that was listened for.
  37. * @param {Function} funct The function that was executed when the event is triggered.
  38. * @param {Boolean} evtCapturing True if event capturing was done, false otherwise.
  39. */
  40.  
  41. SNAKE.removeEventListener = (function() {
  42.     if (window.removeEventListener) {
  43.         return function(obj, event, funct, evtCapturing) {
  44.             obj.removeEventListener(event, funct, evtCapturing);
  45.         };
  46.     } else if (window.detachEvent) {
  47.         return function(obj, event, funct) {
  48.             obj.detachEvent("on" + event, funct);
  49.         };
  50.     }
  51. })();
  52.  
  53. /**
  54. * This class manages the snake which will reside inside of a SNAKE.Board object.
  55. * @class Snake
  56. * @constructor
  57. * @namespace SNAKE
  58. * @param {Object} config The configuration object for the class. Contains playingBoard (the SNAKE.Board that this snake resides in), startRow and startCol.
  59. */
  60. SNAKE.Snake = SNAKE.Snake || (function() {
  61.    
  62.     // -------------------------------------------------------------------------
  63.     // Private static variables and methods
  64.     // -------------------------------------------------------------------------
  65.    
  66.     var instanceNumber = 0;
  67.     var blockPool = [];
  68.    
  69.     var SnakeBlock = function() {
  70.         this.elm = null;
  71.         this.elmStyle = null;
  72.         this.row = -1;
  73.         this.col = -1;
  74.         this.xPos = -1000;
  75.         this.yPos = -1000;
  76.         this.next = null;
  77.         this.prev = null;
  78.     };
  79.    
  80.     // this function is adapted from the example at http://greengeckodesign.com/blog/2007/07/get-highest-z-index-in-javascript.html
  81.     function getNextHighestZIndex(myObj) {
  82.         var highestIndex = 0,
  83.             currentIndex = 0,
  84.             ii;
  85.         for (ii in myObj) {
  86.             if (myObj[ii].elm.currentStyle){  
  87.                 currentIndex = parseFloat(myObj[ii].elm.style["z-index"],10);
  88.             }else if(window.getComputedStyle) {
  89.                 currentIndex = parseFloat(document.defaultView.getComputedStyle(myObj[ii].elm,null).getPropertyValue("z-index"),10);  
  90.             }
  91.             if(!isNaN(currentIndex) && currentIndex > highestIndex){
  92.                 highestIndex = currentIndex;
  93.             }
  94.         }
  95.         return(highestIndex+1);  
  96.     }
  97.    
  98.     // -------------------------------------------------------------------------
  99.     // Contructor + public and private definitions
  100.     // -------------------------------------------------------------------------
  101.    
  102.     /*
  103.         config options:
  104.             playingBoard - the SnakeBoard that this snake belongs too.
  105.             startRow - The row the snake should start on.
  106.             startCol - The column the snake should start on.
  107.     */
  108.     return function(config) {
  109.    
  110.         if (!config||!config.playingBoard) {return;}
  111.    
  112.         // ----- private variables -----
  113.  
  114.         var me = this,
  115.             playingBoard = config.playingBoard,
  116.             myId = instanceNumber++,
  117.             growthIncr = 5,
  118.             moveQueue = [], // a queue that holds the next moves of the snake
  119.             currentDirection = 1, // 0: up, 1: left, 2: down, 3: right
  120.             columnShift = [0, 1, 0, -1],
  121.             rowShift = [-1, 0, 1, 0],
  122.             xPosShift = [],
  123.             yPosShift = [],
  124.             snakeSpeed = 75,
  125.             isDead = false,
  126.             isPaused = false;
  127.         function getMode (mode, speed) {
  128.     document.getElementById(mode).addEventListener('click', function () { snakeSpeed = speed; });
  129. }
  130.             getMode('Easy', 100);
  131.             getMode('Medium', 75);
  132.             getMode('Difficult', 50);
  133.         // ----- public variables -----
  134.         me.snakeBody = {};
  135.         me.snakeBody["b0"] = new SnakeBlock(); // create snake head
  136.         me.snakeBody["b0"].row = config.startRow || 1;
  137.         me.snakeBody["b0"].col = config.startCol || 1;
  138.         me.snakeBody["b0"].xPos = me.snakeBody["b0"].row * playingBoard.getBlockWidth();
  139.         me.snakeBody["b0"].yPos = me.snakeBody["b0"].col * playingBoard.getBlockHeight();
  140.         me.snakeBody["b0"].elm = createSnakeElement();
  141.         me.snakeBody["b0"].elmStyle = me.snakeBody["b0"].elm.style;
  142.         playingBoard.getBoardContainer().appendChild( me.snakeBody["b0"].elm );
  143.         me.snakeBody["b0"].elm.style.left = me.snakeBody["b0"].xPos + "px";
  144.         me.snakeBody["b0"].elm.style.top = me.snakeBody["b0"].yPos + "px";
  145.         me.snakeBody["b0"].next = me.snakeBody["b0"];
  146.         me.snakeBody["b0"].prev = me.snakeBody["b0"];
  147.        
  148.         me.snakeLength = 1;
  149.         me.snakeHead = me.snakeBody["b0"];
  150.         me.snakeTail = me.snakeBody["b0"];
  151.         me.snakeHead.elm.className = me.snakeHead.elm.className.replace(/\bsnake-snakebody-dead\b/,'');
  152.         me.snakeHead.elm.className += " snake-snakebody-alive";
  153.        
  154.         // ----- private methods -----
  155.        
  156.         function createSnakeElement() {
  157.             var tempNode = document.createElement("div");
  158.             tempNode.className = "snake-snakebody-block";
  159.             tempNode.style.left = "-1000px";
  160.             tempNode.style.top = "-1000px";
  161.             tempNode.style.width = playingBoard.getBlockWidth() + "px";
  162.             tempNode.style.height = playingBoard.getBlockHeight() + "px";
  163.             return tempNode;
  164.         }
  165.        
  166.         function createBlocks(num) {
  167.             var tempBlock;
  168.             var tempNode = createSnakeElement();
  169.  
  170.             for (var ii = 1; ii < num; ii++){
  171.                 tempBlock = new SnakeBlock();
  172.                 tempBlock.elm = tempNode.cloneNode(true);
  173.                 tempBlock.elmStyle = tempBlock.elm.style;
  174.                 playingBoard.getBoardContainer().appendChild( tempBlock.elm );
  175.                 blockPool[blockPool.length] = tempBlock;
  176.             }
  177.            
  178.             tempBlock = new SnakeBlock();
  179.             tempBlock.elm = tempNode;
  180.             playingBoard.getBoardContainer().appendChild( tempBlock.elm );
  181.             blockPool[blockPool.length] = tempBlock;
  182.         }
  183.        
  184.         // ----- public methods -----
  185.        
  186.         me.setPaused = function(val) {
  187.             isPaused = val;
  188.         };
  189.         me.getPaused = function() {
  190.             return isPaused;
  191.         };
  192.        
  193.         /**
  194.         * This method is called when a user presses a key. It logs arrow key presses in "moveQueue", which is used when the snake needs to make its next move.
  195.         * @method handleArrowKeys
  196.         * @param {Number} keyNum A number representing the key that was pressed.
  197.         */
  198.         /*
  199.             Handles what happens when an arrow key is pressed.
  200.             Direction explained (0 = up, etc etc)
  201.                     0
  202.                   3   1
  203.                     2
  204.         */
  205.         me.handleArrowKeys = function(keyNum) {
  206.             if (isDead || isPaused) {return;}
  207.            
  208.             var snakeLength = me.snakeLength;
  209.             var lastMove = moveQueue[0] || currentDirection;
  210.  
  211.             //console.log("lastmove="+lastMove);
  212.             //console.log("dir="+keyNum);
  213.            
  214.             switch (keyNum) {
  215.                 case 37:
  216.                 case 65:
  217.                     if ( lastMove !== 1 || snakeLength === 1 ) {
  218.                         moveQueue.unshift(3); //SnakeDirection = 3;
  219.                     }
  220.                     break;    
  221.                 case 38:
  222.                 case 87:
  223.                     if ( lastMove !== 2 || snakeLength === 1 ) {
  224.                         moveQueue.unshift(0);//SnakeDirection = 0;
  225.                     }
  226.                     break;    
  227.                 case 39:
  228.                 case 68:
  229.                     if ( lastMove !== 3 || snakeLength === 1 ) {
  230.                         moveQueue.unshift(1); //SnakeDirection = 1;
  231.                     }
  232.                     break;    
  233.                 case 40:
  234.                 case 83:
  235.                     if ( lastMove !== 0 || snakeLength === 1 ) {
  236.                         moveQueue.unshift(2);//SnakeDirection = 2;
  237.                     }
  238.                     break;  
  239.             }
  240.         };
  241.        
  242.         /**
  243.         * This method is executed for each move of the snake. It determines where the snake will go and what will happen to it. This method needs to run quickly.
  244.         * @method go
  245.         */
  246.         me.go = function() {
  247.        
  248.             var oldHead = me.snakeHead,
  249.                 newHead = me.snakeTail,
  250.                 myDirection = currentDirection,
  251.                 grid = playingBoard.grid; // cache grid for quicker lookup
  252.        
  253.             if (isPaused === true) {
  254.                 setTimeout(function(){me.go();}, snakeSpeed);
  255.                 return;
  256.             }
  257.        
  258.             me.snakeTail = newHead.prev;
  259.             me.snakeHead = newHead;
  260.        
  261.             // clear the old board position
  262.             if ( grid[newHead.row] && grid[newHead.row][newHead.col] ) {
  263.                 grid[newHead.row][newHead.col] = 0;
  264.             }
  265.        
  266.             if (moveQueue.length){
  267.                 myDirection = currentDirection = moveQueue.pop();
  268.             }
  269.        
  270.             newHead.col = oldHead.col + columnShift[myDirection];
  271.             newHead.row = oldHead.row + rowShift[myDirection];
  272.             newHead.xPos = oldHead.xPos + xPosShift[myDirection];
  273.             newHead.yPos = oldHead.yPos + yPosShift[myDirection];
  274.            
  275.             if ( !newHead.elmStyle ) {
  276.                 newHead.elmStyle = newHead.elm.style;
  277.             }
  278.            
  279.             newHead.elmStyle.left = newHead.xPos + "px";
  280.             newHead.elmStyle.top = newHead.yPos + "px";
  281.  
  282.             // check the new spot the snake moved into
  283.  
  284.             if (grid[newHead.row][newHead.col] === 0) {
  285.                 grid[newHead.row][newHead.col] = 1;
  286.                 setTimeout(function(){me.go();}, snakeSpeed);
  287.             } else if (grid[newHead.row][newHead.col] > 0) {
  288.                 me.handleDeath();
  289.             } else if (grid[newHead.row][newHead.col] === playingBoard.getGridFoodValue()) {
  290.                 grid[newHead.row][newHead.col] = 1;
  291.                 me.eatFood();
  292.                 setTimeout(function(){me.go();}, snakeSpeed);
  293.             }
  294.         };
  295.        
  296.         /**
  297.         * This method is called when it is determined that the snake has eaten some food.
  298.         * @method eatFood
  299.         */
  300.         me.eatFood = function() {
  301.             if (blockPool.length <= growthIncr) {
  302.                 createBlocks(growthIncr*2);
  303.             }
  304.             var blocks = blockPool.splice(0, growthIncr);
  305.            
  306.             var ii = blocks.length,
  307.                 index,
  308.                 prevNode = me.snakeTail;
  309.             while (ii--) {
  310.                 index = "b" + me.snakeLength++;
  311.                 me.snakeBody[index] = blocks[ii];
  312.                 me.snakeBody[index].prev = prevNode;
  313.                 me.snakeBody[index].elm.className = me.snakeHead.elm.className.replace(/\bsnake-snakebody-dead\b/,'')
  314.                 me.snakeBody[index].elm.className += " snake-snakebody-alive";
  315.                 prevNode.next = me.snakeBody[index];
  316.                 prevNode = me.snakeBody[index];
  317.             }
  318.             me.snakeTail = me.snakeBody[index];
  319.             me.snakeTail.next = me.snakeHead;
  320.             me.snakeHead.prev = me.snakeTail;
  321.  
  322.             playingBoard.foodEaten();
  323.         };
  324.        
  325.         /**
  326.         * This method handles what happens when the snake dies.
  327.         * @method handleDeath
  328.         */
  329.         me.handleDeath = function() {
  330.             function recordScore () {
  331.                 var highScore = localStorage.jsSnakeHighScore;
  332.                 if (highScore == undefined) localStorage.setItem('jsSnakeHighScore', me.snakeLength);
  333.                 if (me.snakeLength > highScore) {
  334.                     alert('Congratulations! You have beaten your previous high score, which was ' + highScore + '.');
  335.                         localStorage.setItem('jsSnakeHighScore', me.snakeLength);
  336.                 }
  337. }
  338.             recordScore();
  339.             me.snakeHead.elm.style.zIndex = getNextHighestZIndex(me.snakeBody);
  340.             me.snakeHead.elm.className = me.snakeHead.elm.className.replace(/\bsnake-snakebody-alive\b/,'')
  341.             me.snakeHead.elm.className += " snake-snakebody-dead";
  342.  
  343.             isDead = true;
  344.             playingBoard.handleDeath();
  345.             moveQueue.length = 0;
  346.         };
  347.  
  348.         /**
  349.         * This method sets a flag that lets the snake be alive again.
  350.         * @method rebirth
  351.         */  
  352.         me.rebirth = function() {
  353.             isDead = false;
  354.         };
  355.        
  356.         /**
  357.         * This method reset the snake so it is ready for a new game.
  358.         * @method reset
  359.         */        
  360.         me.reset = function() {
  361.             if (isDead === false) {return;}
  362.            
  363.             var blocks = [],
  364.                 curNode = me.snakeHead.next,
  365.                 nextNode;
  366.             while (curNode !== me.snakeHead) {
  367.                 nextNode = curNode.next;
  368.                 curNode.prev = null;
  369.                 curNode.next = null;
  370.                 blocks.push(curNode);
  371.                 curNode = nextNode;
  372.             }
  373.             me.snakeHead.next = me.snakeHead;
  374.             me.snakeHead.prev = me.snakeHead;
  375.             me.snakeTail = me.snakeHead;
  376.             me.snakeLength = 1;
  377.            
  378.             for (var ii = 0; ii < blocks.length; ii++) {
  379.                 blocks[ii].elm.style.left = "-1000px";
  380.                 blocks[ii].elm.style.top = "-1000px";
  381.                 blocks[ii].elm.className = me.snakeHead.elm.className.replace(/\bsnake-snakebody-dead\b/,'')
  382.                 blocks[ii].elm.className += " snake-snakebody-alive";
  383.             }
  384.            
  385.             blockPool.concat(blocks);
  386.             me.snakeHead.elm.className = me.snakeHead.elm.className.replace(/\bsnake-snakebody-dead\b/,'')
  387.             me.snakeHead.elm.className += " snake-snakebody-alive";
  388.             me.snakeHead.row = config.startRow || 1;
  389.             me.snakeHead.col = config.startCol || 1;
  390.             me.snakeHead.xPos = me.snakeHead.row * playingBoard.getBlockWidth();
  391.             me.snakeHead.yPos = me.snakeHead.col * playingBoard.getBlockHeight();
  392.             me.snakeHead.elm.style.left = me.snakeHead.xPos + "px";
  393.             me.snakeHead.elm.style.top = me.snakeHead.yPos + "px";
  394.         };
  395.        
  396.         // ---------------------------------------------------------------------
  397.         // Initialize
  398.         // ---------------------------------------------------------------------
  399.         createBlocks(growthIncr*2);
  400.         xPosShift[0] = 0;
  401.         xPosShift[1] = playingBoard.getBlockWidth();
  402.         xPosShift[2] = 0;
  403.         xPosShift[3] = -1 * playingBoard.getBlockWidth();
  404.        
  405.         yPosShift[0] = -1 * playingBoard.getBlockHeight();
  406.         yPosShift[1] = 0;
  407.         yPosShift[2] = playingBoard.getBlockHeight();
  408.         yPosShift[3] = 0;
  409.     };
  410. })();
  411.  
  412. /**
  413. * This class manages the food which the snake will eat.
  414. * @class Food
  415. * @constructor
  416. * @namespace SNAKE
  417. * @param {Object} config The configuration object for the class. Contains playingBoard (the SNAKE.Board that this food resides in).
  418. */
  419.  
  420. SNAKE.Food = SNAKE.Food || (function() {
  421.    
  422.     // -------------------------------------------------------------------------
  423.     // Private static variables and methods
  424.     // -------------------------------------------------------------------------
  425.    
  426.     var instanceNumber = 0;
  427.    
  428.     function getRandomPosition(x, y){
  429.         return Math.floor(Math.random()*(y+1-x)) + x;
  430.     }
  431.    
  432.     // -------------------------------------------------------------------------
  433.     // Contructor + public and private definitions
  434.     // -------------------------------------------------------------------------
  435.    
  436.     /*
  437.         config options:
  438.             playingBoard - the SnakeBoard that this object belongs too.
  439.     */
  440.     return function(config) {
  441.        
  442.         if (!config||!config.playingBoard) {return;}
  443.  
  444.         // ----- private variables -----
  445.  
  446.         var me = this;
  447.         var playingBoard = config.playingBoard;
  448.         var fRow, fColumn;
  449.         var myId = instanceNumber++;
  450.  
  451.         var elmFood = document.createElement("div");
  452.         elmFood.setAttribute("id", "snake-food-"+myId);
  453.         elmFood.className = "snake-food-block";
  454.         elmFood.style.width = playingBoard.getBlockWidth() + "px";
  455.         elmFood.style.height = playingBoard.getBlockHeight() + "px";
  456.         elmFood.style.left = "-1000px";
  457.         elmFood.style.top = "-1000px";
  458.         playingBoard.getBoardContainer().appendChild(elmFood);
  459.        
  460.         // ----- public methods -----
  461.        
  462.         /**
  463.         * @method getFoodElement
  464.         * @return {DOM Element} The div the represents the food.
  465.         */        
  466.         me.getFoodElement = function() {
  467.             return elmFood;  
  468.         };
  469.        
  470.         /**
  471.         * Randomly places the food onto an available location on the playing board.
  472.         * @method randomlyPlaceFood
  473.         */    
  474.         me.randomlyPlaceFood = function() {
  475.             // if there exist some food, clear its presence from the board
  476.             if (playingBoard.grid[fRow] && playingBoard.grid[fRow][fColumn] === playingBoard.getGridFoodValue()){
  477.                 playingBoard.grid[fRow][fColumn] = 0;
  478.             }
  479.  
  480.             var row = 0, col = 0, numTries = 0;
  481.  
  482.             var maxRows = playingBoard.grid.length-1;
  483.             var maxCols = playingBoard.grid[0].length-1;
  484.            
  485.             while (playingBoard.grid[row][col] !== 0){
  486.                 row = getRandomPosition(1, maxRows);
  487.                 col = getRandomPosition(1, maxCols);
  488.  
  489.                 // in some cases there may not be any room to put food anywhere
  490.                 // instead of freezing, exit out
  491.                 numTries++;
  492.                 if (numTries > 20000){
  493.                     row = -1;
  494.                     col = -1;
  495.                     break;
  496.                 }
  497.             }
  498.  
  499.             playingBoard.grid[row][col] = playingBoard.getGridFoodValue();
  500.             fRow = row;
  501.             fColumn = col;
  502.             elmFood.style.top = row * playingBoard.getBlockHeight() + "px";
  503.             elmFood.style.left = col * playingBoard.getBlockWidth() + "px";
  504.         };
  505.     };
  506. })();
  507.  
  508. /**
  509. * This class manages playing board for the game.
  510. * @class Board
  511. * @constructor
  512. * @namespace SNAKE
  513. * @param {Object} config The configuration object for the class. Set fullScreen equal to true if you want the game to take up the full screen, otherwise, set the top, left, width and height parameters.
  514. */
  515.  
  516. SNAKE.Board = SNAKE.Board || (function() {
  517.  
  518.     // -------------------------------------------------------------------------
  519.     // Private static variables and methods
  520.     // -------------------------------------------------------------------------
  521.  
  522.     var instanceNumber = 0;
  523.  
  524.     // this function is adapted from the example at http://greengeckodesign.com/blog/2007/07/get-highest-z-index-in-javascript.html
  525.     function getNextHighestZIndex(myObj) {
  526.         var highestIndex = 0,
  527.             currentIndex = 0,
  528.             ii;
  529.         for (ii in myObj) {
  530.             if (myObj[ii].elm.currentStyle){  
  531.                 currentIndex = parseFloat(myObj[ii].elm.style["z-index"],10);
  532.             }else if(window.getComputedStyle) {
  533.                 currentIndex = parseFloat(document.defaultView.getComputedStyle(myObj[ii].elm,null).getPropertyValue("z-index"),10);  
  534.             }
  535.             if(!isNaN(currentIndex) && currentIndex > highestIndex){
  536.                 highestIndex = currentIndex;
  537.             }
  538.         }
  539.         return(highestIndex+1);  
  540.     }
  541.  
  542.     /*
  543.         This function returns the width of the available screen real estate that we have
  544.     */
  545.     function getClientWidth(){
  546.         var myWidth = 0;
  547.         if( typeof window.innerWidth === "number" ) {
  548.             myWidth = window.innerWidth;//Non-IE
  549.         } else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
  550.             myWidth = document.documentElement.clientWidth;//IE 6+ in 'standards compliant mode'
  551.         } else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
  552.             myWidth = document.body.clientWidth;//IE 4 compatible
  553.         }
  554.         return myWidth;
  555.     }
  556.     /*
  557.         This function returns the height of the available screen real estate that we have
  558.     */
  559.     function getClientHeight(){
  560.         var myHeight = 0;
  561.         if( typeof window.innerHeight === "number" ) {
  562.             myHeight = window.innerHeight;//Non-IE
  563.         } else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
  564.             myHeight = document.documentElement.clientHeight;//IE 6+ in 'standards compliant mode'
  565.         } else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
  566.             myHeight = document.body.clientHeight;//IE 4 compatible
  567.         }
  568.         return myHeight;
  569.     }
  570.  
  571.     // -------------------------------------------------------------------------
  572.     // Contructor + public and private definitions
  573.     // -------------------------------------------------------------------------
  574.    
  575.     return function(inputConfig) {
  576.    
  577.         // --- private variables ---
  578.         var me = this,
  579.             myId = instanceNumber++,
  580.             config = inputConfig || {},
  581.             MAX_BOARD_COLS = 250,
  582.             MAX_BOARD_ROWS = 250,
  583.             blockWidth = 20,
  584.             blockHeight = 20,
  585.             GRID_FOOD_VALUE = -1, // the value of a spot on the board that represents snake food, MUST BE NEGATIVE
  586.             myFood,
  587.             mySnake,
  588.             boardState = 1, // 0: in active; 1: awaiting game start; 2: playing game
  589.             myKeyListener,
  590.             isPaused = false,//note: both the board and the snake can be paused
  591.             // Board components
  592.             elmContainer, elmPlayingField, elmAboutPanel, elmLengthPanel, elmWelcome, elmTryAgain, elmPauseScreen;
  593.        
  594.         // --- public variables ---
  595.         me.grid = [];
  596.        
  597.         // ---------------------------------------------------------------------
  598.         // private functions
  599.         // ---------------------------------------------------------------------
  600.        
  601.         function createBoardElements() {
  602.             elmPlayingField = document.createElement("div");
  603.             elmPlayingField.setAttribute("id", "playingField");
  604.             elmPlayingField.className = "snake-playing-field";
  605.            
  606.             SNAKE.addEventListener(elmPlayingField, "click", function() {
  607.                 elmContainer.focus();
  608.             }, false);
  609.            
  610.             elmPauseScreen = document.createElement("div");
  611.             elmPauseScreen.className = "snake-pause-screen";
  612.             elmPauseScreen.innerHTML = "<div style='padding:10px;'>[Paused]<p/>Press [space] to unpause.</div>";
  613.            
  614.             elmAboutPanel = document.createElement("div");
  615.             elmAboutPanel.className = "snake-panel-component";
  616.             elmAboutPanel.innerHTML = "<a href='http://patorjk.com/blog/software/' class='snake-link'>more patorjk.com apps</a> - <a href='https://github.com/patorjk/JavaScript-Snake' class='snake-link'>source code</a>";
  617.            
  618.             elmLengthPanel = document.createElement("div");
  619.             elmLengthPanel.className = "snake-panel-component";
  620.             elmLengthPanel.innerHTML = "Length: 1";
  621.            
  622.             elmWelcome = createWelcomeElement();
  623.             elmTryAgain = createTryAgainElement();
  624.            
  625.             SNAKE.addEventListener( elmContainer, "keyup", function(evt) {
  626.                 if (!evt) var evt = window.event;
  627.                 evt.cancelBubble = true;
  628.                 if (evt.stopPropagation) {evt.stopPropagation();}
  629.                 if (evt.preventDefault) {evt.preventDefault();}
  630.                 return false;
  631.             }, false);
  632.            
  633.             elmContainer.className = "snake-game-container";
  634.            
  635.             elmPauseScreen.style.zIndex = 10000;
  636.             elmContainer.appendChild(elmPauseScreen);
  637.             elmContainer.appendChild(elmPlayingField);
  638.             elmContainer.appendChild(elmAboutPanel);
  639.             elmContainer.appendChild(elmLengthPanel);
  640.             elmContainer.appendChild(elmWelcome);
  641.             elmContainer.appendChild(elmTryAgain);
  642.            
  643.             mySnake = new SNAKE.Snake({playingBoard:me,startRow:2,startCol:2});
  644.             myFood = new SNAKE.Food({playingBoard: me});
  645.            
  646.             elmWelcome.style.zIndex = 1000;
  647.         }
  648.         function maxBoardWidth() {
  649.             return MAX_BOARD_COLS * me.getBlockWidth();  
  650.         }
  651.         function maxBoardHeight() {
  652.             return MAX_BOARD_ROWS * me.getBlockHeight();
  653.         }
  654.        
  655.         function createWelcomeElement() {
  656.              var tmpElm = document.createElement("div");
  657.             tmpElm.id = "sbWelcome" + myId;
  658.             tmpElm.className = "snake-welcome-dialog";
  659.            
  660.             var welcomeTxt = document.createElement("div");
  661.             var fullScreenText = "";
  662.             if (config.fullScreen) {
  663.                 fullScreenText = "On Windows, press F11 to play in Full Screen mode.";  
  664.             }
  665.             welcomeTxt.innerHTML = "JavaScript Snake<p></p>Use the <strong>arrow keys</strong> on your keyboard to play the game. " + fullScreenText + "<p></p>";
  666.             var welcomeStart = document.createElement("button");
  667.             welcomeStart.appendChild(document.createTextNode("Play Game"));
  668.             var loadGame = function() {
  669.                 SNAKE.removeEventListener(window, "keyup", kbShortcut, false);
  670.                 tmpElm.style.display = "none";
  671.                 me.setBoardState(1);
  672.                 me.getBoardContainer().focus();
  673.             };
  674.            
  675.             var kbShortcut = function(evt) {
  676.                 if (!evt) var evt = window.event;
  677.                 var keyNum = (evt.which) ? evt.which : evt.keyCode;
  678.                 if (keyNum === 32 || keyNum === 13) {
  679.                     loadGame();
  680.                 }
  681.             };
  682.             SNAKE.addEventListener(window, "keyup", kbShortcut, false);
  683.             SNAKE.addEventListener(welcomeStart, "click", loadGame, false);
  684.            
  685.             tmpElm.appendChild(welcomeTxt);
  686.             tmpElm.appendChild(welcomeStart);
  687.             return tmpElm;
  688.         }
  689.        
  690.         function createTryAgainElement() {
  691.             var tmpElm = document.createElement("div");
  692.             tmpElm.id = "sbTryAgain" + myId;
  693.             tmpElm.className = "snake-try-again-dialog";
  694.            
  695.             var tryAgainTxt = document.createElement("div");
  696.             tryAgainTxt.innerHTML = "JavaScript Snake<p></p>You died :(.<p></p>";
  697.             var tryAgainStart = document.createElement("button");
  698.             tryAgainStart.appendChild( document.createTextNode("Play Again?"));
  699.            
  700.             var reloadGame = function() {
  701.                 tmpElm.style.display = "none";
  702.                 me.resetBoard();
  703.                 me.setBoardState(1);
  704.                 me.getBoardContainer().focus();
  705.             };
  706.            
  707.             var kbTryAgainShortcut = function(evt) {
  708.                 if (boardState !== 0 || tmpElm.style.display !== "block") {return;}
  709.                 if (!evt) var evt = window.event;
  710.                 var keyNum = (evt.which) ? evt.which : evt.keyCode;
  711.                 if (keyNum === 32 || keyNum === 13) {
  712.                     reloadGame();
  713.                 }
  714.             };
  715.             SNAKE.addEventListener(window, "keyup", kbTryAgainShortcut, true);
  716.            
  717.             SNAKE.addEventListener(tryAgainStart, "click", reloadGame, false);
  718.             tmpElm.appendChild(tryAgainTxt);
  719.             tmpElm.appendChild(tryAgainStart);
  720.             return tmpElm;
  721.         }
  722.         // ---------------------------------------------------------------------
  723.         // public functions
  724.         // ---------------------------------------------------------------------
  725.        
  726.         me.setPaused = function(val) {
  727.             isPaused = val;
  728.             mySnake.setPaused(val);
  729.             if (isPaused) {
  730.                 elmPauseScreen.style.display = "block";
  731.             } else {
  732.                 elmPauseScreen.style.display = "none";
  733.             }
  734.         };
  735.         me.getPaused = function() {
  736.             return isPaused;
  737.         };
  738.        
  739.         /**
  740.         * Resets the playing board for a new game.
  741.         * @method resetBoard
  742.         */  
  743.         me.resetBoard = function() {
  744.             SNAKE.removeEventListener(elmContainer, "keydown", myKeyListener, false);
  745.             mySnake.reset();
  746.             elmLengthPanel.innerHTML = "Length: 1";
  747.             me.setupPlayingField();
  748.         };
  749.         /**
  750.         * Gets the current state of the playing board. There are 3 states: 0 - Welcome or Try Again dialog is present. 1 - User has pressed "Start Game" on the Welcome or Try Again dialog but has not pressed an arrow key to move the snake. 2 - The game is in progress and the snake is moving.
  751.         * @method getBoardState
  752.         * @return {Number} The state of the board.
  753.         */  
  754.         me.getBoardState = function() {
  755.             return boardState;
  756.         };
  757.         /**
  758.         * Sets the current state of the playing board. There are 3 states: 0 - Welcome or Try Again dialog is present. 1 - User has pressed "Start Game" on the Welcome or Try Again dialog but has not pressed an arrow key to move the snake. 2 - The game is in progress and the snake is moving.
  759.         * @method setBoardState
  760.         * @param {Number} state The state of the board.
  761.         */  
  762.         me.setBoardState = function(state) {
  763.             boardState = state;
  764.         };
  765.         /**
  766.         * @method getGridFoodValue
  767.         * @return {Number} A number that represents food on a number representation of the playing board.
  768.         */  
  769.         me.getGridFoodValue = function() {
  770.             return GRID_FOOD_VALUE;
  771.         };
  772.         /**
  773.         * @method getPlayingFieldElement
  774.         * @return {DOM Element} The div representing the playing field (this is where the snake can move).
  775.         */
  776.         me.getPlayingFieldElement = function() {
  777.             return elmPlayingField;
  778.         };
  779.         /**
  780.         * @method setBoardContainer
  781.         * @param {DOM Element or String} myContainer Sets the container element for the game.
  782.         */
  783.         me.setBoardContainer = function(myContainer) {
  784.             if (typeof myContainer === "string") {
  785.                 myContainer = document.getElementById(myContainer);  
  786.             }
  787.             if (myContainer === elmContainer) {return;}
  788.             elmContainer = myContainer;
  789.             elmPlayingField = null;
  790.            
  791.             me.setupPlayingField();
  792.         };
  793.         /**
  794.         * @method getBoardContainer
  795.         * @return {DOM Element}
  796.         */
  797.         me.getBoardContainer = function() {
  798.             return elmContainer;
  799.         };
  800.         /**
  801.         * @method getBlockWidth
  802.         * @return {Number}
  803.         */
  804.         me.getBlockWidth = function() {
  805.             return blockWidth;  
  806.         };
  807.         /**
  808.         * @method getBlockHeight
  809.         * @return {Number}
  810.         */
  811.         me.getBlockHeight = function() {
  812.             return blockHeight;  
  813.         };
  814.         /**
  815.         * Sets up the playing field.
  816.         * @method setupPlayingField
  817.         */
  818.         me.setupPlayingField = function () {
  819.            
  820.             if (!elmPlayingField) {createBoardElements();} // create playing field
  821.            
  822.             // calculate width of our game container
  823.             var cWidth, cHeight;
  824.             if (config.fullScreen === true) {
  825.                 cTop = 0;
  826.                 cLeft = 0;
  827.                 cWidth = getClientWidth()-5;
  828.                 cHeight = getClientHeight()-5;
  829.                 document.body.style.backgroundColor = "#FC5454";
  830.             } else {
  831.                 cTop = config.top;
  832.                 cLeft = config.left;
  833.                 cWidth = config.width;
  834.                 cHeight = config.height;
  835.             }
  836.            
  837.             // define the dimensions of the board and playing field
  838.             var wEdgeSpace = me.getBlockWidth()*2 + (cWidth % me.getBlockWidth());
  839.             var fWidth = Math.min(maxBoardWidth()-wEdgeSpace,cWidth-wEdgeSpace);
  840.             var hEdgeSpace = me.getBlockHeight()*3 + (cHeight % me.getBlockHeight());
  841.             var fHeight = Math.min(maxBoardHeight()-hEdgeSpace,cHeight-hEdgeSpace);
  842.            
  843.             elmContainer.style.left = cLeft + "px";
  844.             elmContainer.style.top = cTop + "px";
  845.             elmContainer.style.width = cWidth + "px";
  846.             elmContainer.style.height = cHeight + "px";
  847.             elmPlayingField.style.left = me.getBlockWidth() + "px";
  848.             elmPlayingField.style.top  = me.getBlockHeight() + "px";
  849.             elmPlayingField.style.width = fWidth + "px";
  850.             elmPlayingField.style.height = fHeight + "px";
  851.            
  852.             // the math for this will need to change depending on font size, padding, etc
  853.             // assuming height of 14 (font size) + 8 (padding)
  854.             var bottomPanelHeight = hEdgeSpace - me.getBlockHeight();
  855.             var pLabelTop = me.getBlockHeight() + fHeight + Math.round((bottomPanelHeight - 30)/2) + "px";
  856.            
  857.             elmAboutPanel.style.top = pLabelTop;
  858.             elmAboutPanel.style.width = "450px";
  859.             elmAboutPanel.style.left = Math.round(cWidth/2) - Math.round(450/2) + "px";
  860.            
  861.             elmLengthPanel.style.top = pLabelTop;
  862.             elmLengthPanel.style.left = cWidth - 120 + "px";
  863.            
  864.             // if width is too narrow, hide the about panel
  865.             if (cWidth < 700) {
  866.                 elmAboutPanel.style.display = "none";
  867.             } else {
  868.                 elmAboutPanel.style.display = "block";
  869.             }
  870.            
  871.             me.grid = [];
  872.             var numBoardCols = fWidth / me.getBlockWidth() + 2;
  873.             var numBoardRows = fHeight / me.getBlockHeight() + 2;
  874.            
  875.             for (var row = 0; row < numBoardRows; row++) {
  876.                 me.grid[row] = [];
  877.                 for (var col = 0; col < numBoardCols; col++) {
  878.                     if (col === 0 || row === 0 || col === (numBoardCols-1) || row === (numBoardRows-1)) {
  879.                         me.grid[row][col] = 1; // an edge
  880.                     } else {
  881.                         me.grid[row][col] = 0; // empty space
  882.                     }
  883.                 }
  884.             }
  885.            
  886.             myFood.randomlyPlaceFood();
  887.            
  888.             // setup event listeners
  889.             function getMode (mode, speed) {
  890.     document.getElementById(mode).addEventListener('click', function () { snakeSpeed = speed; });
  891. }
  892.             getMode('Easy', 100);
  893.             getMode('Medium', 75);
  894.             getMode('Difficult', 50);
  895.             myKeyListener = function(evt) {
  896.                 if (!evt) var evt = window.event;
  897.                 var keyNum = (evt.which) ? evt.which : evt.keyCode;
  898.  
  899.                 if (me.getBoardState() === 1) {
  900.                     if ( !(keyNum >= 37 && keyNum <= 40) && !(keyNum === 87 || keyNum === 65 || keyNum === 83 || keyNum === 68)) {return;} // if not an arrow key, leave
  901.                    
  902.                     // This removes the listener added at the #listenerX line
  903.                     SNAKE.removeEventListener(elmContainer, "keydown", myKeyListener, false);
  904.                    
  905.                     myKeyListener = function(evt) {
  906.                         if (!evt) var evt = window.event;
  907.                         var keyNum = (evt.which) ? evt.which : evt.keyCode;
  908.                        
  909.                         //console.log(keyNum);
  910.                         if (keyNum === 32) {
  911.                             me.setPaused(!me.getPaused());
  912.                         }
  913.                        
  914.                         mySnake.handleArrowKeys(keyNum);
  915.                        
  916.                         evt.cancelBubble = true;
  917.                         if (evt.stopPropagation) {evt.stopPropagation();}
  918.                         if (evt.preventDefault) {evt.preventDefault();}
  919.                         return false;
  920.                     };
  921.                     SNAKE.addEventListener( elmContainer, "keydown", myKeyListener, false);
  922.                    
  923.                     mySnake.rebirth();
  924.                     mySnake.handleArrowKeys(keyNum);
  925.                     me.setBoardState(2); // start the game!
  926.                     mySnake.go();
  927.                 }
  928.                
  929.                 evt.cancelBubble = true;
  930.                 if (evt.stopPropagation) {evt.stopPropagation();}
  931.                 if (evt.preventDefault) {evt.preventDefault();}
  932.                 return false;
  933.             };
  934.            
  935.             // Search for #listenerX to see where this is removed
  936.             SNAKE.addEventListener( elmContainer, "keydown", myKeyListener, false);
  937.         };
  938.        
  939.         /**
  940.         * This method is called when the snake has eaten some food.
  941.         * @method foodEaten
  942.         */
  943.         me.foodEaten = function() {
  944.             elmLengthPanel.innerHTML = "Length: " + mySnake.snakeLength;
  945.             myFood.randomlyPlaceFood();
  946.         };
  947.        
  948.         /**
  949.         * This method is called when the snake dies.
  950.         * @method handleDeath
  951.         */
  952.         me.handleDeath = function() {
  953.             var index = Math.max(getNextHighestZIndex( mySnake.snakeBody), getNextHighestZIndex( {tmp:{elm:myFood.getFoodElement()}} ));
  954.             elmContainer.removeChild(elmTryAgain);
  955.             elmContainer.appendChild(elmTryAgain);
  956.             elmTryAgain.style.zIndex = index;
  957.             elmTryAgain.style.display = "block";
  958.             me.setBoardState(0);
  959.         };
  960.        
  961.         // ---------------------------------------------------------------------
  962.         // Initialize
  963.         // ---------------------------------------------------------------------
  964.  
  965.         config.fullScreen = (typeof config.fullScreen === "undefined") ? false : config.fullScreen;        
  966.         config.top = (typeof config.top === "undefined") ? 0 : config.top;
  967.         config.left = (typeof config.left === "undefined") ? 0 : config.left;
  968.         config.width = (typeof config.width === "undefined") ? 400 : config.width;        
  969.         config.height = (typeof config.height === "undefined") ? 400 : config.height;
  970.        
  971.         if (config.fullScreen) {
  972.             SNAKE.addEventListener(window,"resize", function() {
  973.                 me.setupPlayingField();
  974.             }, false);
  975.         }
  976.        
  977.         me.setBoardState(0);
  978.        
  979.         if (config.boardContainer) {
  980.             me.setBoardContainer(config.boardContainer);
  981.         }
  982.        
  983.     }; // end return function
  984. })();
  985. function getHighScore () {
  986.     document.getElementById('high-score').addEventListener('click', function () {
  987.         if (localStorage.jsSnakeHighScore == undefined) alert('You have not played this game yet!');
  988.         else
  989.     alert('Your current high score is ' + localStorage.jsSnakeHighScore + '.'); });
  990. }
  991. getHighScore();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement