功能:蛇吃食物有四种情况:走到的地方有食物;走到的地方没有食物;走到的地方是墙壁;走到的地方是自己的身体。吃到食物后,蛇的身体会变长;碰到墙壁或咬到自己 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>
发表回复