pzg's blog

网页版贪吃蛇

  功能:蛇吃食物有四种情况:走到的地方有食物;走到的地方没有食物;走到的地方是墙壁;走到的地方是自己的身体。吃到食物后,蛇的身体会变长;碰到墙壁或咬到自己 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节的蛇体,按倒退键时结束游戏的错误;

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

<HTML>
<head>
<meta http-equiv=”Content-Type” content=”text/html; charset=gb2312″>
<title>贪吃蛇</title>
<style>
  body{font-size: 12px;}

  .food
  {
    background: url(”images/food1.gif”) no-repeat;
    /*background-color:green;*/
    overflow:hidden;
    position:absolute;
  }

  .snake_head
  {
    background-color: red;
    border:3px solid gold;
    position:absolute;
  }

  .snake_body
  {
    background-color: orange;
    border:2px dotted white;
    position:absolute;
  }

  .snake_tail
  {
    background-color: peachpuff;
    border:2px dotted white;
    position:absolute;
  }

  .grid
  {
    border-style:solid;
    border-color:#0000ff;
    border-top-width:0;
    border-right-width:1;
    border-bottom-width:1;
    border-left-width:0;
    padding:0;
  }

  .mainmap
  {
    position:absolute;
    border-style:outset;
    border-color:#0000ff;
    border-width:5;
  }

  .table
  {
    position:absolute;
    overflow:hidden;
    border-collapse:collapse;
  }
</style>
</head>

<BODY>
<div id=”help”>
  红色方块表示蛇头,按方向键控制蛇吃食物<br><br>
  行数:
  <select id=”rows”>
    <option>25</option>
    <script language=”javascript”>
      for(var ct=10;ct<=30;ct++)
        document.write(’<option>’ + ct + ‘</option>’);
    </script>
  </select>
  <br>
  列数:
  <select id=”cells”>
    <option>30</option>
    <script language=”javascript”>
      for(var ct=10;ct<=40;ct++)
        document.write(’<option>’ + ct + ‘</option>’);
    </script>
  </select>
  <br>
  <button onclick=”reCreateMap()”> 设置 </button>
  <br><br>
  【初始化:】F5 功能键<br>
  【暂停:】S 字母键<br><br>
  【显示/隐藏网格:】G 字母键<br><br>
  【加速:】PageUP 键<br>
  【减速:】PageDown 键<br>
</div>
【速度:】<span id=”speed” style=”color:red”></span>

<br><br><div id=”mapxy”></div>
<div
  <br><br>
  蛇只能吃食物,<br>不能触墙,<br>也不能咬自己
</div>

<script langyage=”javascript”>
  var Rows = document.getElementById(’rows’).options[0].text;
  var Cells = document.getElementById(’cells’).options[0].text;
  var Num   = 20;  //正方形格子的边长
  var SpeedUp = 5000;
  var Times = 200;
  var Start = 0;
  var ShowGrid = true;
  var ShowBackground  = true;

  var BorderWidth = 5;
  var MainMap = null;
  var AllDiv  = new Array();
  var AllSpan = new Array();
  var Sx = Sy = 0;
  var Map = null;
  var GoX,GoY,LastX,LastY;
  var moving = null;
  var AllDiv=null, AllSpan=null;

  //重新创建地图
  function reCreateMap()
  {
    Rows = document.getElementById(’rows’).options[document.getElementById(’rows’).selectedIndex].text;
    Cells = document.getElementById(’cells’).options[document.getElementById(’cells’).selectedIndex].text;
    document.body.removeChild(MainMap);
    CreateMap();
  }

  //创建地图
  function CreateMap()
  {
    BW = eval(Cells * Num + 2 * BorderWidth);  //宽度
    BH = eval(Rows * Num + 2 * BorderWidth);   //高度
    //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>’
    MainMap = document.createElement(’div’);
    MainMap.className = ‘mainmap’;
    if(ShowBackground)
      MainMap.style.backgroundImage = “url(’images/bgpic.jpg’)”;
    MainMap.style.left = (document.body.clientWidth – BW) / 2;
    MainMap.style.top  = (document.body.clientHeight – BH) / 2;
    MainMap.style.width  = BW;
    MainMap.style.height = BH;
    document.body.appendChild(MainMap);

    //创建全局数组Map[]
    /*
    Map = new Array() //创建全局数组Map[]
    for(y=0; y<Rows; y++)
    {
      Map[y]=new Array() //创建全局二维数组Map[][],初始值为’0′
      for(x=0; x<Cells; x++)
        Map[y][x] = ‘0′  //’0′值表示“空地”
    }
    */
    Map = new Array();
    for(y = 0; y < Rows; y++)
    {
      //创建全局二维数组Map[][],初始值为’0′
      Map.push(new Array());
      for( x = 0; x < Cells; x++)
        Map[y].push(’0′);  //’0′值表示“空地”
    }
    //显示地图内格子
    if (ShowGrid)
      MainMap.appendChild(CreateGrid(’ ‘));

    //创建全局变量Sx,赋予随机数
    Sx = parseInt(Math.random() * Cells);

    //创建全局变量Sy,赋予随机数
    Sy = parseInt(Math.random() * Rows);

    //生成蛇 – div
    CreateSnake();

    //生成食物 – span
    CreatFood();

    //创建全局数组AllDiv,保存着蛇身各节<div>。AllDiv[0]为蛇尾。MainMap是div的ID
    AllDiv  = MainMap.getElementsByTagName(’div’);  //等价于AllDiv = MainMap.all.tags(’DIV’)
    //创建全局数组AllSpan,始终只有一个元素AllSpan[0]
    AllSpan = MainMap.getElementsByTagName(’span’);  //等价于AllSpan = MainMap.all.tags(’SPAN’)
  }

  //创建地图内格子(Rows*Cells 大小的 Table)
  function CreateGrid(celltext)
  {
    var table = document.createElement(’table’);
    table.className = ‘table’;
    table.style.left  = 0;
    table.style.top  = 0;
    table.setAttribute(’id’,’grid’);
    table.setAttribute(’border’,’0′);  //table.border = ‘0′;
    table.setAttribute(’cellspacing’,’0′);
    table.setAttribute(’cellpadding’,’0′);
    var tbody = document.createElement(”tbody”);
    table.insertBefore(tbody, null);
    for (var i=0; i<Rows; i++)
    {
      var tr = document.createElement(’tr’);
      for(var j=0; j<Cells; j++)
      {
        var td = document.createElement(’td’);
        td.className = ‘grid’;
        td.width  = Num;  //td.style.width  = Num;
        td.height  = Num;  //td.style.height  = Num;
        var text = document.createTextNode(celltext);
        td.insertBefore(text, null);
        tr.insertBefore(td, null);
      }
      tbody.insertBefore(tr, null);
    }
    return table;
  }

  //创建蛇的位置,赋予值’S’
  function CreateSnake()
  {
    //<div>表示蛇身,通过调用本函数,可以累加到若干个,蛇身变长
    //注意 y 和 x 是“蛇”<div> 的自定义属性。一直保存着蛇身体各节的Map坐标,与Map[][]联系
    //初始时,蛇头、蛇尾是同一个位置
    var div = document.createElement(’div’);
    div.className = ’snake_head’;
    div.setAttribute(’x’,Sx);
    div.setAttribute(’y’,Sy);
    div.style.left = Sx * Num;
    div.style.top  = Sy * Num;
    div.style.width  = Num;
    div.style.height = Num;
    MainMap.appendChild(div);

    Map[Sy][Sx] = ‘S’;  //Snake首字母

    //蛇身长度
    //document.all.mapxy.innerHTML = AllDiv.length;
    if (AllDiv!=null)
    {
      if (AllDiv.length>1)
      {
        AllDiv[0].className = ’snake_tail’; //蛇尾
        for (var i=1;i<AllDiv.length-1;i++) //蛇身
          AllDiv[i].className = ’snake_body’;
      }
    }
  }

  //创建食物的位置,赋予初始值’F’
  //食物的初始位置不能与蛇的初始位置相同,只能在空地放置食物。
  //若随机产生的2位置相同,则递归执行,直到不相同为止
  function CreatFood()
  {
    Fx = parseInt(Math.random() * Cells);
    Fy = parseInt(Math.random() * Rows);
    if(Map[Fy][Fx] == ‘0′)  //如果是空地
    {
      MainMap.appendChild(CreatSpan(Fx * Num,Fy * Num,Num));
      Map[Fy][Fx] = ‘F’;  //Food首字母
    }
    else
    {
      CreatFood(); //递归
    }
  }

  //生成食物的span
  function CreatSpan(l,t,num)
  {
    var span = document.createElement(’span’);
    span.style.left = l;
    span.style.top  = t;
    span.style.width  = num;
    span.style.height = num;
    span.className = ‘food’;
    return span;
  }

  //主移动–判断蛇头前面的是什么
  function Move()
  {
    //自动行走,Map[Sy][Sx]为蛇头前面位置
    Sx += GoX;
    Sy += GoY;

    //碰墙,重新开始
    if(Sy < 0 || Sy >= Rows)
    {
      Move1();
    }
    else
    {
      SnakeFront = Map[Sy][Sx];
      switch(SnakeFront)
      {
        case ‘0′:  //蛇前是空地
          Move2();
          break;
        case ‘F’:  //蛇前面是食物
          Move3();
          break;
        case ‘S’:  //蛇前面是自己的身体
          Move1();
          break;
        default:  //啥都不是就是超出地图范围game over
          Move1();
      }
    }
  }

  //重新开始
  function Move1()
  {
    if(confirm(”Game Over,重新开始?”))
    {
      //window.location.reload();
      reCreateMap();
      Start = 0;
    }
    else
      window.close();
  }

  //蛇行走到的当前位置是空地时
  function Move2()
  {
    //蛇走开后,把原位置设置为’0′,表示是空地
    //把蛇数组当前元素删除,在下面的 CreateSnake()语句重新生成
    Map[AllDiv[0].getAttribute(’y’)][AllDiv[0].getAttribute(’x’)] = ‘0′;
    var fatherNode = AllDiv[0].parentNode;  //AllDiv[0].removeNode(true);
    fatherNode.removeChild(AllDiv[0]);

    //在新的位置生成蛇的<div>
    CreateSnake();
    //再次移动
    moving = setTimeout(’Move()’,Times);
  }

  //蛇行走到的当前位置是食物时
  function Move3()
  {
    //蛇数组当前元素不删除,<div>累加一次,蛇长长一节
    CreateSnake();
    //把食物数组当前元素删除,在下面的 CreatFood()语句重新生成
    var parentNode = AllSpan[0].parentNode;  //AllSpan[0].removeNode(true)
    parentNode.removeChild(AllSpan[0]);
    //再次随机生成食物
    CreatFood();
    //再次移动
    moving = setTimeout(’Move()’,Times);
  }

  //蛇行加速
  function oTimes(step)
  {
    if(step>0 && Times>30)
      Times -= step;
    if(step<0 && Times<400)
      Times -= step;
    document.getElementById(’speed’).innerHTML = Times;
    //If(Times > 5) setTimeout(’oTimes()’, SpeedUp);
  }

  //绑定键盘事件
  document.onkeydown = KeyDown;

  //按键
  function KeyDown(e)
  {
    if(e)
      Key = e.keyCode;
    else
      Key = window.event.keyCode
    switch(Key)
    {
      case 37: //左方向键
        Dir(-1,0);
        break
      case 39: //右方向键
        Dir(1,0);
        break
      case 38: //上方向键
        Dir(0,-1);
        break
      case 40: //下方向键
        Dir(0,1);
        break
    case 33: //PageUp – speed up
        oTimes(5);
     break;
    case 34: //PageDown – speed down
        oTimes(-5);
     break;
    case 66: // B – Show/Hidden Background
      ShowBackground = !ShowBackground;
      if(ShowBackground)
      {
        MainMap.style.backgroundImage = “url(’images/bgpic.jpg’)”;
        //MainMap.style.backgroundRepeat=”repeat-x repeat-y”;
        //MainMap.style.backgroundRepeat=”no-repeat”;
      }
      else
        MainMap.style.backgroundImage = “url(”)”;
     break;
    case 71: // G – Show/Hidden Grid
      ShowGrid = !ShowGrid;
      if(ShowGrid)
        MainMap.appendChild(CreateGrid(’ ‘));
      else
          MainMap.removeChild(document.getElementById(’grid’));
     break;
    case 83: // s – stop
     clearTimeout(moving);
     Start = 0;
     break;
    }
    return false
  }

  function Dir(x,y)
  {
    GoX = x;
    GoY = y;
    if( Start == 0 )
    {
      LastX = x;
      LastY = y;
      clearTimeout(moving);
      Start = 1;
      Move();
    }
    else
    {
      /* 增加的反方向判断 */
      //检查上次行动方向和本次行动方向,如果方向正好相反那么还是保持上次的前进方向
      if (GoX + LastX == 0 && GoY + LastY == 0)
      {
        GoX = LastX;
        GoY = LastY;
      }
      else
      { //如果不一样则记住本次的行动方向
        LastX = GoX;
        LastY = GoY;
      }
    }
  }

  //绘制地图
  CreateMap();
  document.getElementById(’speed’).innerHTML = Times;
</script>
</BODY>
</HTML>

《 “网页版贪吃蛇” 》 有 2 条评论

  1. lwy 的头像

    我更换了域名,烦请博主更新友情链接
    博名:Lwy’s Dreamland
    新域名 l-wy.cn

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注