网页游戏“贪食蛇”改进

    在很早学 QuickBasic 的时候,就有一个蛇吃食物的游戏,当时是作为 Basic 的一个示例提供的。当时只顾得玩了,竟没想到要把那些高人写的代码好好分析一下。现在做 Web 开发的培训,有一个用 Javascript 写的类似的小游戏,就花了大半天好好看了一下代码,觉得写的不错。

    功能:蛇吃食物有四种情况:走到的地方有食物;走到的地方没有食物;走到的地方是墙壁;走到的地方是自己的身体。吃到食物后,蛇的身体会变长;碰到墙壁或咬到自己 Game Over,询问是否重新开始。

    整个游戏是在一个<div>框子(地图)里展开的,蛇有地图里的一系列<div>构成,初始状态(刚刚打开页面)蛇是一个<div>框,蛇头和蛇尾是在一起的,就是这个<div>框,以后吃了食物后<div>个数会变多,蛇就会长长。食物用一个<span>框表示,初始时和蛇一样其位置是随机产生的。蛇和食物的坐标用<div>和<span>的绝对位置表示。

    算法的关键是当蛇移动到新的位置后,判断前述的几种情况,做出相应的处理。而二维数组 Map[][] 就是判断的依据。二维数组 Map[][]通过其元素的值来表示蛇身、食物和空地。蛇身、食物和空地的值分别为’S'、’F’ 和 ‘0′。

    用 Javascript 编程和其他语言的一个大的区别就是键盘的控制代码不同。js是通过 Key=event.keyCode 语句获取键盘码,再在多分支 switch 结构里分别处理。看完这些代码就会对浏览器里的键盘控制有所了解了。

    当然该段代码的最大价值还是让我们了解到网页游戏的大概写法,而算法其实是最重要的,把一个游戏的设想变为一堆包含许多的函数的结构化的代码,是值得我们借鉴和学习的。这里的难点是确定蛇是怎么移动的。

    代码里有我的注释,结合我的以上大概分析介绍差不多能看懂了。

    题外问题:

    本人在调试的时候打算给原来的代码加一段地图内方格显示功能,就写了个ShowGrid()函数,采用代码生成方格。但由于在2层循环内完成方格显示,效率十分低下,生成15*10的方格需要大约6秒。生成30*20 的方格竟然需要几分钟。基本不能采用此方法。改进以后,不是生成多个包含单个单元格的表格,而是生成一个包含若干单元格的表格,结果效率有巨大的提高。

    改进后,有如下功能:
    - 可改变单元格数;
    - 可控制暂停;
    - 速度控制功能;
    - 蛇头、蛇身和蛇尾形状美化;
    - 可控制显示和隐藏背景图片、方格线;
    - 修正了长度等于或大于2节的蛇体,按倒退键时结束游戏的错误;

    进一步改进建议:
    - 控制蛇头的方向,可用动画图片代替蛇头。
    - 蛇身和蛇尾也可用图片代替。
    - 增加声音;
    - 增加换肤功能:背景、方格、蛇的皮肤可定制;
    - 自动演示功能;
    ……

 

  
  
  
  
  1. <HTML> 
  2. <head> 
  3. <meta http-equiv=”Content-Type” content=”text/html; charset=gb2312> 
  4. <title>贪吃蛇</title> 
  5. <style> 
  6.   body{font-size: 12px;}  
  7.  
  8.   .food  
  9.   {  
  10.     background: url(”images/food1.gif”) no-repeat;  
  11.     /*background-color:green;*/  
  12.     overflow:hidden;  
  13.     position:absolute;  
  14.   }  
  15.  
  16.   .snake_head  
  17.   {  
  18.     background-color: red;  
  19.     border:3px solid gold;  
  20.     position:absolute;  
  21.   }  
  22.  
  23.   .snake_body  
  24.   {  
  25.     background-color: orange;  
  26.     border:2px dotted white;  
  27.     position:absolute;  
  28.   }  
  29.  
  30.   .snake_tail  
  31.   {  
  32.     background-color: peachpuff;  
  33.     border:2px dotted white;  
  34.     position:absolute;  
  35.   }  
  36.  
  37.   .grid  
  38.   {  
  39.     border-style:solid;  
  40.     border-color:#0000ff;  
  41.     border-top-width:0;  
  42.     border-right-width:1;  
  43.     border-bottom-width:1;  
  44.     border-left-width:0;  
  45.     padding:0;  
  46.   }  
  47.  
  48.   .mainmap  
  49.   {  
  50.     position:absolute;  
  51.     border-style:outset;  
  52.     border-color:#0000ff;  
  53.     border-width:5;  
  54.   }  
  55.  
  56.   .table  
  57.   {  
  58.     position:absolute;  
  59.     overflow:hidden;  
  60.     border-collapse:collapse;  
  61.   }  
  62. </style> 
  63. </head> 
  64.  
  65. <BODY> 
  66. <div id=”help”> 
  67.   红色方块表示蛇头,按方向键控制蛇吃食物<br><br> 
  68.   行数:  
  69.   <select id=”rows”> 
  70.     <option>25</option> 
  71.     <script language=”javascript”> 
  72.       for(var ct=10;ct<=30;ct++)  
  73.         document.write(’<option>’ + ct + ‘</option>’);  
  74.     </script> 
  75.   </select> 
  76.   <br> 
  77.   列数:  
  78.   <select id=”cells”> 
  79.     <option>30</option> 
  80.     <script language=”javascript”> 
  81.       for(var ct=10;ct<=40;ct++)  
  82.         document.write(’<option>’ + ct + ‘</option>’);  
  83.     </script> 
  84.   </select> 
  85.   <br> 
  86.   <button onclick=”reCreateMap()”> 设置 </button> 
  87.   <br><br> 
  88.   【初始化:】F5 功能键<br> 
  89.   【暂停:】S 字母键<br><br> 
  90.   【显示/隐藏网格:】G 字母键<br><br> 
  91.   【加速:】PageUP 键<br> 
  92.   【减速:】PageDown 键<br> 
  93. </div> 
  94. 【速度:】<span id=”speed” style=”color:red”></span> 
  95.  
  96. <br><br><div id=”mapxy”></div> 
  97. <div 
  98.   <br><br> 
  99.   蛇只能吃食物,<br>不能触墙,<br>也不能咬自己  
  100. </div> 
  101.  
  102. <script langyage=”javascript”> 
  103.   var Rows = document.getElementById(’rows’).options[0].text;  
  104.   var Cells = document.getElementById(’cells’).options[0].text;  
  105.   var Num   = 20;  //正方形格子的边长  
  106.   var SpeedUp = 5000;  
  107.   var Times = 200;  
  108.   var Start = 0;  
  109.   var ShowGrid = true;  
  110.   var ShowBackground  = true;  
  111.  
  112.   var BorderWidth = 5;  
  113.   var MainMap = null;  
  114.   var AllDiv  = new Array();  
  115.   var AllSpan = new Array();  
  116.   var Sx = Sy = 0;  
  117.   var Map = null;  
  118.   var GoX,GoY,LastX,LastY;  
  119.   var moving = null;  
  120.   var AllDiv=nullAllSpan=null;  
  121.  
  122.   //重新创建地图  
  123.   function reCreateMap()  
  124.   {  
  125.     Rows = document.getElementById(’rows’).options[document.getElementById(’rows’).selectedIndex].text;  
  126.     Cells = document.getElementById(’cells’).options[document.getElementById(’cells’).selectedIndex].text;  
  127.     document.body.removeChild(MainMap);  
  128.     CreateMap();  
  129.   }  
  130.  
  131.   //创建地图  
  132.   function CreateMap()  
  133.   {  
  134.     BW = eval(Cells * Num + 2 * BorderWidth);  //宽度  
  135.     BH = eval(Rows * Num + 2 * BorderWidth);   //高度  
  136.     //document.body.innerHTML+=’<div id=MainMap style=position:absolute;left:’+(document.body.clientWidth-BW)/2+’;top:’+(document.body.clientHeight-BH)/2+’;width:’+BW+’;height:’+BH+’;border-width:’+BorderWidth+’;></div>’  
  137.     MainMap = document.createElement(’div’);  
  138.     MainMap.className = ‘mainmap’;  
  139.     if(ShowBackground)  
  140.       MainMap.style.backgroundImage = “url(’images/bgpic.jpg’)”;  
  141.     MainMap.style.left = (document.body.clientWidth - BW) / 2;  
  142.     MainMap.style.top  = (document.body.clientHeight - BH) / 2;  
  143.     MainMap.style.width  = BW;  
  144.     MainMap.style.height = BH;  
  145.     document.body.appendChild(MainMap);  
  146.  
  147.     //创建全局数组Map[]  
  148.     /*  
  149.     Map = new Array() //创建全局数组Map[]  
  150.     for(y=0; y<Rows; y++)  
  151.     {  
  152.       Map[y]=new Array() //创建全局二维数组Map[][],初始值为’0′  
  153.       for(x=0; x<Cells; x++)  
  154.         Map[y][x] = ‘0′  //’0′值表示“空地”  
  155.     }  
  156.     */  
  157.     Map = new Array();  
  158.     for(y = 0; y < Rows; y++)  
  159.     {  
  160.       //创建全局二维数组Map[][],初始值为’0′  
  161.       Map.push(new Array());  
  162.       for( x = 0; x < Cells; x++)  
  163.         Map[y].push(’0′);  //’0′值表示“空地”  
  164.     }  
  165.     //显示地图内格子  
  166.     if (ShowGrid)  
  167.       MainMap.appendChild(CreateGrid(’ ‘));  
  168.  
  169.     //创建全局变量Sx,赋予随机数  
  170.     Sx = parseInt(Math.random() * Cells);  
  171.  
  172.     //创建全局变量Sy,赋予随机数  
  173.     Sy = parseInt(Math.random() * Rows);  
  174.  
  175.     //生成蛇 - div  
  176.     CreateSnake();  
  177.  
  178.     //生成食物 - span  
  179.     CreatFood();  
  180.  
  181.     //创建全局数组AllDiv,保存着蛇身各节<div>。AllDiv[0]为蛇尾。MainMap是div的ID  
  182.     AllDiv  = MainMap.getElementsByTagName(’div’);  //等价于AllDiv = MainMap.all.tags(’DIV’)  
  183.     //创建全局数组AllSpan,始终只有一个元素AllSpan[0]  
  184.     AllSpan = MainMap.getElementsByTagName(’span’);  //等价于AllSpan = MainMap.all.tags(’SPAN’)  
  185.   }  
  186.  
  187.   //创建地图内格子(Rows*Cells 大小的 Table)  
  188.   function CreateGrid(celltext)  
  189.   {  
  190.     var table = document.createElement(’table’);  
  191.     table.className = ‘table’;  
  192.     table.style.left  = 0;  
  193.     table.style.top  = 0;  
  194.     table.setAttribute(’id’,'grid’);  
  195.     table.setAttribute(’border’,'0′);  //table.border = ‘0′;  
  196.     table.setAttribute(’cellspacing’,'0′);  
  197.     table.setAttribute(’cellpadding’,'0′);  
  198.     var tbody = document.createElement(”tbody”);  
  199.     table.insertBefore(tbody, null);  
  200.     for (var i=0; i<Rows; i++)  
  201.     {  
  202.       var tr = document.createElement(’tr’);  
  203.       for(var j=0; j<Cells; j++)  
  204.       {  
  205.         var td = document.createElement(’td’);  
  206.         td.className = ‘grid’;  
  207.         td.width  = Num;  //td.style.width  = Num;  
  208.         td.height  = Num;  //td.style.height  = Num;  
  209.         var text = document.createTextNode(celltext);  
  210.         td.insertBefore(text, null);  
  211.         tr.insertBefore(td, null);  
  212.       }  
  213.       tbody.insertBefore(tr, null);  
  214.     }  
  215.     return table;  
  216.   }  
  217.  
  218.   //创建蛇的位置,赋予值’S’  
  219.   function CreateSnake()  
  220.   {  
  221.     //<div>表示蛇身,通过调用本函数,可以累加到若干个,蛇身变长  
  222.     //注意 y 和 x 是“蛇”<div> 的自定义属性。一直保存着蛇身体各节的Map坐标,与Map[][]联系  
  223.     //初始时,蛇头、蛇尾是同一个位置  
  224.     var div = document.createElement(’div’);  
  225.     div.className = ’snake_head’;  
  226.     div.setAttribute(’x',Sx);  
  227.     div.setAttribute(’y',Sy);  
  228.     div.style.left = Sx * Num;  
  229.     div.style.top  = Sy * Num;  
  230.     div.style.width  = Num;  
  231.     div.style.height = Num;  
  232.     MainMap.appendChild(div);  
  233.  
  234.     Map[Sy][Sx] = ‘S’;  //Snake首字母  
  235.  
  236.     //蛇身长度  
  237.     //document.all.mapxy.innerHTML = AllDiv.length;  
  238.     if (AllDiv!=null)  
  239.     {  
  240.       if (AllDiv.length>1)  
  241.       {  
  242.         AllDiv[0].className = ’snake_tail’; //蛇尾  
  243.         for (var i=1;i<AllDiv.length-1;i++) //蛇身  
  244.           AllDiv[i].className = ’snake_body’;  
  245.       }  
  246.     }  
  247.   }  
  248.  
  249.   //创建食物的位置,赋予初始值’F’  
  250.   //食物的初始位置不能与蛇的初始位置相同,只能在空地放置食物。  
  251.   //若随机产生的2位置相同,则递归执行,直到不相同为止  
  252.   function CreatFood()  
  253.   {  
  254.     Fx = parseInt(Math.random() * Cells);  
  255.     Fy = parseInt(Math.random() * Rows);  
  256.     if(Map[Fy][Fx] == ‘0′)  //如果是空地  
  257.     {  
  258.       MainMap.appendChild(CreatSpan(Fx * Num,Fy * Num,Num));  
  259.       Map[Fy][Fx] = ‘F’;  //Food首字母  
  260.     }  
  261.     else  
  262.     {  
  263.       CreatFood(); //递归  
  264.     }  
  265.   }  
  266.  
  267.   //生成食物的span  
  268.   function CreatSpan(l,t,num)  
  269.   {  
  270.     var span = document.createElement(’span’);  
  271.     span.style.left = l;  
  272.     span.style.top  = t;  
  273.     span.style.width  = num;  
  274.     span.style.height = num;  
  275.     span.className = ‘food’;  
  276.     return span;  
  277.   }  
  278.  
  279.   //主移动–判断蛇头前面的是什么  
  280.   function Move()  
  281.   {  
  282.     //自动行走,Map[Sy][Sx]为蛇头前面位置  
  283.     Sx += GoX;  
  284.     Sy += GoY;  
  285.  
  286.     //碰墙,重新开始  
  287.     if(Sy < 0 || Sy >= Rows)  
  288.     {  
  289.       Move1();  
  290.     }  
  291.     else  
  292.     {  
  293.       SnakeFront = Map[Sy][Sx];  
  294.       switch(SnakeFront)  
  295.       {  
  296.         case ‘0′:  //蛇前是空地  
  297.           Move2();  
  298.           break;  
  299.         case ‘F’:  //蛇前面是食物  
  300.           Move3();  
  301.           break;  
  302.         case ‘S’:  //蛇前面是自己的身体  
  303.           Move1();  
  304.           break;  
  305.         default:  //啥都不是就是超出地图范围game over  
  306.           Move1();  
  307.       }  
  308.     }  
  309.   }  
  310.  
  311.   //重新开始  
  312.   function Move1()  
  313.   {  
  314.     if(confirm(”Game Over,重新开始?”))  
  315.     {  
  316.       //window.location.reload();  
  317.       reCreateMap();  
  318.       Start = 0;  
  319.     }  
  320.     else  
  321.       window.close();  
  322.   }  
  323.  
  324.   //蛇行走到的当前位置是空地时  
  325.   function Move2()  
  326.   {  
  327.     //蛇走开后,把原位置设置为’0′,表示是空地  
  328.     //把蛇数组当前元素删除,在下面的 CreateSnake()语句重新生成  
  329.     Map[AllDiv[0].getAttribute(’y')][AllDiv[0].getAttribute(’x')] = ‘0′;  
  330.     var fatherNode = AllDiv[0].parentNode;  //AllDiv[0].removeNode(true);  
  331.     fatherNode.removeChild(AllDiv[0]);  
  332.  
  333.     //在新的位置生成蛇的<div> 
  334.     CreateSnake();  
  335.     //再次移动  
  336.     moving = setTimeout(’Move()’,Times);  
  337.   }  
  338.  
  339.   //蛇行走到的当前位置是食物时  
  340.   function Move3()  
  341.   {  
  342.     //蛇数组当前元素不删除,<div>累加一次,蛇长长一节  
  343.     CreateSnake();  
  344.     //把食物数组当前元素删除,在下面的 CreatFood()语句重新生成  
  345.     var parentNode = AllSpan[0].parentNode;  //AllSpan[0].removeNode(true)  
  346.     parentNode.removeChild(AllSpan[0]);  
  347.     //再次随机生成食物  
  348.     CreatFood();  
  349.     //再次移动  
  350.     moving = setTimeout(’Move()’,Times);  
  351.   }  
  352.  
  353.   //蛇行加速  
  354.   function oTimes(step)  
  355.   {  
  356.     if(step>0 && Times>30)  
  357.       Times -step;  
  358.     if(step<0 && Times<400)  
  359.       Times -step;  
  360.     document.getElementById(’speed’).innerHTML = Times;  
  361.     //If(Times > 5) setTimeout(’oTimes()’, SpeedUp);  
  362.   }  
  363.  
  364.   //绑定键盘事件  
  365.   document.onkeydown = KeyDown;  
  366.  
  367.   //按键  
  368.   function KeyDown(e)  
  369.   {  
  370.     if(e)  
  371.       Key = e.keyCode;  
  372.     else  
  373.       Key = window.event.keyCode  
  374.     switch(Key)  
  375.     {  
  376.       case 37: //左方向键  
  377.         Dir(-1,0);  
  378.         break  
  379.       case 39: //右方向键  
  380.         Dir(1,0);  
  381.         break  
  382.       case 38: //上方向键  
  383.         Dir(0,-1);  
  384.         break  
  385.       case 40: //下方向键  
  386.         Dir(0,1);  
  387.         break  
  388.     case 33: //PageUp - speed up  
  389.         oTimes(5);  
  390.      break;  
  391.     case 34: //PageDown - speed down  
  392.         oTimes(-5);  
  393.      break;  
  394.     case 66: // B - Show/Hidden Background  
  395.       ShowBackground = !ShowBackground;  
  396.       if(ShowBackground)  
  397.       {  
  398.         MainMap.style.backgroundImage = “url(’images/bgpic.jpg’)”;  
  399.         //MainMap.style.backgroundRepeat=”repeat-x repeat-y”;  
  400.         //MainMap.style.backgroundRepeat=”no-repeat”;  
  401.       }  
  402.       else  
  403.         MainMap.style.backgroundImage = “url(”)”;  
  404.      break;  
  405.     case 71: // G - Show/Hidden Grid  
  406.       ShowGrid = !ShowGrid;  
  407.       if(ShowGrid)  
  408.         MainMap.appendChild(CreateGrid(’ ‘));  
  409.       else  
  410.           MainMap.removeChild(document.getElementById(’grid’));  
  411.      break;  
  412.     case 83: // s - stop  
  413.      clearTimeout(moving);  
  414.      Start = 0;  
  415.      break;  
  416.     }  
  417.     return false  
  418.   }  
  419.  
  420.   function Dir(x,y)  
  421.   {  
  422.     GoX = x;  
  423.     GoY = y;  
  424.     if( Start == 0 )  
  425.     {  
  426.       LastX = x;  
  427.       LastY = y;  
  428.       clearTimeout(moving);  
  429.       Start = 1;  
  430.       Move();  
  431.     }  
  432.     else  
  433.     {  
  434.       /* 增加的反方向判断 */  
  435.       //检查上次行动方向和本次行动方向,如果方向正好相反那么还是保持上次的前进方向  
  436.       if (GoX + LastX == 0 && GoY + LastY == 0)  
  437.       {  
  438.         GoX = LastX;  
  439.         GoY = LastY;  
  440.       }  
  441.       else  
  442.       { //如果不一样则记住本次的行动方向  
  443.         LastX = GoX;  
  444.         LastY = GoY;  
  445.       }  
  446.     }  
  447.   }  
  448.  
  449.   //绘制地图  
  450.   CreateMap();  
  451.   document.getElementById(’speed’).innerHTML = Times;  
  452. </script> 
  453. </BODY> 
  454. </HTML> 

 

你可能感兴趣的:(游戏,网页,改进,休闲,贪食蛇)