Accéder au contenu principal

Snake game (pure JS)

Building a mini snake game in JS is one of these things every webcoder should be able to do. Since I hadn't tackled that "challenge" yet (and mostly because I was bored like hell and wanted to code something without using jQuery for a change), I decided to give it a shot. This is very basic, but it taught me a two interesting things I was unaware of:

  • it seems that a DIV cannot natively receives the focus, but a simple workaround to cope with this is to give the DIV a tabIndex value
  • if you duplicate an array (Array.splice(0)), references are maintained (so as far as I understand, if you modify the original array, the copy will be modified in sync...sounds pretty weird, but I must admit that there was something fishy with the values returned during my initial attempts to clone an array)

Anyway, you can find the result of that one-hour experiment at the following URL:
http://www.manuthommes.be/toolbox/snakejs/

Note: Left/Right arrows to turn.

Quick review of the code now:

/* let's define a few variables: the orientation of the snake (0-3), two 2D arrays to store the position of each part of the body (x and y coordinates) and the score */
        dir=0;
        snake=new Array(new Array());
        oldsnake=new Array(new Array());       
        snake[0][0]=160;
        snake[0][1]=120;
        score=0;
    
// main function -> create the board dynamically + the head of the snake
        function new_game(){
            b=document.createElement("div");
                b.id="board";
                b.tabIndex="1";
                document.getElementsByTagName("body")[0].appendChild(b);
            s=document.createElement("div");
                s.id="sn_head";
                document.getElementById("board").appendChild(s);   
            addBonus();
// here add an event listener on the board
            b.addEventListener("keydown",turn ,false);
            var tmr=setInterval(function(){
// every 150ms, move the snake
                move();
                }, 150);
// focus on the board to make sure the user can start playing right away!
            b.focus();
        }
        
// function invoked when the head of the snake collided with the bonus
// we get random X and Y positions and then create dynamically the object (div)
        function addBonus(){
            xpos=Math.floor((Math.random()*32))*10;
            ypos=Math.floor((Math.random()*24))*10;
            s=document.createElement("div");
                s.id="bonus";
                document.getElementById("board").appendChild(s);   
            document.getElementById("bonus").style.left=xpos+"px";           
            document.getElementById("bonus").style.top=ypos+"px";               
        }       

        function move(){         
// in the array oldsnake, we get the position of the head and other body parts BEFORE their position is updated
            oldsnake[0][0]=parseInt(document.getElementById("sn_head").style.left);
            oldsnake[0][1]=parseInt(document.getElementById("sn_head").style.top);   
            var allParts=document.getElementsByClassName("snake");           
            for (var i=1; i<allParts.length; i++) {
                oldsnake[i][0]=parseInt(allParts[i-1].style.left);
                oldsnake[i][1]=parseInt(allParts[i-1].style.top);   
            } 
// move the snake either to the left, to the right, up or down depending on the value of variable dir
            switch(dir){
                case 0: snake[0][0]=snake[0][0]-10;break;
                case 1: snake[0][1]=snake[0][1]-10;break;
                case 2: snake[0][0]=snake[0][0]+10;break;
                case 3: snake[0][1]=snake[0][1]+10;break;               
            }
// if the snake hits a border, move it the other side
            if(snake[0][0]<0)snake[0][0]=310; if(snake[0][0]>310)snake[0][0]=0;
            if(snake[0][1]<0)snake[0][1]=230; if(snake[0][1]>230)snake[0][1]=0;
// update the position of the head
            document.getElementById("sn_head").style.left=snake[0][0]+"px";           
            document.getElementById("sn_head").style.top=snake[0][1]+"px";
// collision with the bonus?
            testCollision();
// position the body parts:
// body part 1 goes at the position of the OLD head (before it was updated)
// body part 2 goes at the position of the OLD body part 1 (before it was updated)
// ...and so on and so forth until we've moved the whole snake        
            var allParts=document.getElementsByClassName("snake");
            for(var i=1;i<snake.length;i++){
                snake[i][0]=oldsnake[i-1][0];
                snake[i][1]=oldsnake[i-1][1];
                allParts[i-1].style.left=snake[i][0]+"px";
                allParts[i-1].style.top=snake[i][1]+"px";               
            }
        }
        
/* if X/Y of snake head equals X/Y of bonus, the latter is removed, the score is increased, the snake gets bigger and we update the score board */
        function testCollision(){
            if(snake[0][0]==xpos && snake[0][1]==ypos){
                document.getElementById("board").removeChild(document.getElementById("bonus"));
                addBonus(); score=score+10;
                growBody();
                document.getElementById("scoreboard").innerHTML="Score:&nbsp;"+score;
            }
        }
        
// add a new body part and update the arrays (initialize a new item at the end of the arrays)
        function growBody(){
            s=document.createElement("div");
                s.className="snake";
                document.getElementById("board").appendChild(s);
            snake[snake.length]=[];   
            oldsnake[oldsnake.length]=[];           
        }
        
// the function called by the event listener
// if key pressed = 37 (left arrow), direction is decreased by 1 (reset to 3 if it's below 0, which would be a value non processed)
// if key pressed = 38 (rightarrow), direction is increased by 1 (reset to 0 if it's greater than 3, which would be a value non processed)    
    function turn(ev){
            if(ev.keyCode==37){
                dir-=1;
                if(dir<0)dir=3;
            }
            if(ev.keyCode==39){
                dir+=1;
                if(dir>3)dir=0;
            }   
        }

To be implemented: collision of the head with the body (basically going through the array of body parts and testing their X/Y coordinates with the ones of the head), potentially decreasing the timer interval when the score gets bigger, fancy stuff...hf :)

Commentaires

Posts les plus consultés de ce blog

jQuarax - a parallax plugin

This plugin is still in the making and complete documentation (as well as a downloadable package) will be available later. Sample code and quick variable explanation further below. jQuarax-latest.js:  var jquarax = {   load: loader }; function loader(){   jQuery(document).ready(function(){     // parallax script     $(".jquarax-div-container").scroll(function(){       scrollPos = parseInt($(".jquarax-div-container").scrollTop());       scrollPosPC = (100/parseInt($(".jquarax-div-container").css("height")))*(parseInt($(".jquarax-div-container").scrollTop()));       $(".jquarax-object").each(function(e){         var unit = $(this).attr("frmode");          if(unit=="%"){scrollPos=scrollPosPC;}         var startIt = parseInt($(this).attr("frstart"));  ...

Date format matching regex

Regex is a must-learn for any programmer, plus it is always interesting to see how an apparently complex check can be done with a basic rule (...or how a simple check sometimes requires a long and obscure rule, but that's the whole fun of it :P). Here's a short and useful regex to match a date field (for instance when submitting a form). // expected field value format: YYYY-MM-DD var e = document.getElementById("myDateField").value; var m = e.match(/(20\d\d|19\d\d)\-(0\d|1[012])\-([012]\d|3[01])/); Breaking it down: / (20\d\d|19\d\d) \- (0\d|1[012]) \- ([012]\d|3[01]) / 1. Year (20\d\d|19\d\d) Trying to match either "20" followed by 2 digits (2000, 2001,2002,...2099) or "19" and then 2 digits (1900...1999). Would not match: 20a0, 201, 20AA 2. Month (0\d|1[012]) Trying to match either "0" followed by one digit (01...09) or "1" followed by 0, 1 or 2 (hence only 10,11 or 12). We assume here that the month must ...

Reordering Javascript functions

If you have already injected some Javascript dynamically in a page (possibly because you cannot modify the original scripts directly), you may have realised the order in which Javascript functions are triggered is dependent on the order of declaration. Hence, if you try and bind a new "onclick" handler on a button for which another function has already been bound, the latter will be executed before yours. Here is a simple trick to reorder the order of execution.  Say your original code looks like so: <script> function showAlert(message){      alert(message); } </script> [...] <input type=text id="myButton" onClick="showAlert(this.value);" /> You can 't modify the HTML part and yet y ou want to execute a new function of your own before the alert message of showAlert is sent, for instance the following one: function changeMessage(newMessage){       document.getElementById("myButton").value=newMessage; ...