介绍一个由本人制作的贪吃蛇小游戏。这个游戏采用了前端BootStrap框架和jQuery框架,因此移动端和电脑端都能兼容。虽然界面比较简陋,但是基本功能比较齐全,如果有bug欢迎各位能给我提出来,希望大家能喜欢这个小游戏!
项目结构:
虽然是个动态web项目,但我是用纯前端技术完成的,只是使用的工具是eclipse而已,大家大可放心运行在自己的浏览器上。
运行效果
注意,历史最高分我采用了webstorage,所以可以做到数据持久化,但有的浏览器可能不支持,我也没有继续深究下去。音效功能尚未实现,有兴趣的读者可以实现一下。
index.html
<!DOCTYPE html><html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<link href="css/bootstrap.min.css" rel="stylesheet" type="text/css">
<script type="text/javascript" src="jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="bootstrap.min.js"></script>
<script type="text/javascript" src="my.js"></script>
<style type="text/css">
#tab{
background-color:black;
margin: 0;
}
.glyphicon{
font-size:150%;
}
</style>
</head>
<body >
<div class="container-fluid">
<!--标题面板行-->
<div class="row bg-primary">
<div class="col-xs-12">
<h1 class="text-center">贪吃蛇</h1>
</div>
</div>
<!--记录面板行-->
<div class="row bg-info">
<!--历史记录行-->
<div class="row">
<div class="col-xs-6">
<span class="h1">历史记录</span>
</div>
<div class="col-xs-4 col-xs-offset-2">
<span id="his-score" class="h1">00</span>
</div>
</div>
<!--本次得分行-->
<div class="row">
<div class="col-xs-6">
<span class="h1">本次得分</span>
</div>
<div class="col-xs-4 col-xs-offset-2">
<span id="cur-score" class="h1">00</span>
</div>
</div>
</div>
<!--游戏面板行-->
<div class="row">
<!--22行20列-->
<table id="tab" class="table table-bordered">
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
</table>
</div>
<!--功能面板行-->
<div class="row bg-info">
<!--功能按钮列-->
<div class="col-xs-8 row">
<div class="col-xs-2">
<button id="start-game" class="btn btn-primary">重新开始</button>
<button id="pause" class="btn btn-danger"><span class="glyphicon glyphicon-pause"></span></button>
</div>
<div class="col-xs-2 col-xs-offset-4">
<button class="btn btn-info">
<span>音效 </span><span class="glyphicon glyphicon-volume-up"></span>
</button>
<div class="btn-group dropup">
<button class="btn btn-success" data-toggle="dropdown">
<span id="speed-span">中</span><span class="caret"></span>
</button>
<ul id="speed-menu" class="dropdown-menu" style="min-width:50px">
<li class="text-center">快</li>
<li class="text-center">中</li>
<li class="text-center">慢</li>
</ul>
</div>
</div>
</div>
<!--方向按钮列-->
<div class="col-xs-4">
<!--向上按钮,独占一行-->
<div class="row">
<div class="col-xs-4 col-xs-offset-4">
<button id="up" type="button" class="btn">
<span class="glyphicon glyphicon-triangle-top"></span>
</button>
</div>
</div>
<!--向左和向右按钮,占一行-->
<div class="row">
<div class="col-xs-2">
<button id="left" type="button" class="btn">
<span class="glyphicon glyphicon-triangle-left"></span>
</button>
</div>
<div class="col-xs-2 col-xs-offset-5">
<button id="right" type="button" class="btn">
<span class="glyphicon glyphicon-triangle-right"></span>
</button>
</div>
</div>
<!--向下按钮,独占一行-->
<div class="row">
<div class="col-xs-4 col-xs-offset-4">
<button id="down" type="button" class="btn">
<span class="glyphicon glyphicon-triangle-bottom"></span>
</button>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
my.js
var px;//蛇身x坐标数组var py;//蛇身y坐标数组
var nt;//蛇身作一次运动后的新头坐标数组
var ow;//蛇身作一次运动后的旧尾坐标数组
var ot;//蛇身作一次运动后的旧头坐标数组
var l;//蛇身长度
var fx;//食物x坐标
var fy;//食物y坐标
var flag;//表示生成的随机食物是否与蛇身重复的标志
var d;//表示当前蛇身运动方向
var timer;//蛇身运动的定时器
var flag1;//表示蛇头坐标是否和蛇身坐标出现重复的标志
var score;//分数,吃一个食物加10分
var hisScore;//历史最高分
var speed;
//返回一个随机数
function getRandom(a){
return Math.floor(Math.random()*a);
}
//生成与蛇身不重复的随机食物
function getFood(){
do{
//获取食物坐标
fx=getRandom(19);
fy=getRandom(21);
flag=0;//表示食物与蛇身不重复
for(var i=0;i<l;i++){
//逐一判断是否有重复
if(px[i]==fx && py[i]==fy){
flag=1;//表示食物与蛇身重复了
break;
}
}
}while(flag==1);
$("tr:eq("+fy+")>td:eq("+fx+")").css("background-color","red");
}
//判断蛇头是否撞墙或者撞自己
function isDead(){
flag1=0;
//判断蛇头是否撞了自己
for(var i=1;i<l;i++){
if(px[0]==px[i] && py[0]==py[i]){
flag1=1;//出现重复,表示蛇头撞到了蛇身
break;
}
}
//判断蛇头是否撞墙或撞了自己
if(px[0]<0 || px[0]>19 || py[0]<0 || py[0]>21 || flag1==1){
endGame();
return true;
}
}
//结束游戏
function endGame(){
//移除蛇身运动定时器
window.clearInterval(timer);
alert('游戏结束')
//消除已经死掉的蛇身
for(var i=0;i<l;i++){
$("tr:eq("+py[i]+")>td:eq("+px[i]+")").css("background-color","black");
}
//消除未吃掉的食物
$("tr:eq("+fy+")>td:eq("+fx+")").css("background-color","black");
//数据持久化
if(score>parseInt(localStorage.getItem("key"))){
window.localStorage.setItem("key",String(score));
}
//一次游戏结束,初始化游戏
init();
}
//判断蛇头是否吃到食物
function isEat(){
if(px[0]==fx && py[0]==fy){
//加分
score+=10;
$("#cur-score").text(score);
//蛇身要加1
l++;
//蛇身长一节,思路是让已经被去掉的旧尾再回来
$("tr:eq("+ow[1]+")>td:eq("+ow[0]+")").css("background-color","white");
//蛇身x坐标数组添加新蛇尾x坐标
px.push(ow[0]);
//蛇身y坐标数组添加新蛇尾y坐标
py.push(ow[1]);
//表示吃到了食物,获取新的随机食物
getFood();
}
}
//蛇身作一次运动
function runOnce(){
//根据四种不同的运动方向获得新头坐标数组
if(d=='left'){
nt=[px[0]-1,py[0]];
}else if(d=='right'){
nt=[px[0]+1,py[0]];
}else if(d=='up'){
nt=[px[0],py[0]-1];
}else{
nt=[px[0],py[0]+1];
}
//旧尾坐标数组
ow=[px[l-1],py[l-1]];
//旧头坐标数组
ot=[px[0],py[0]];
//刷新蛇身x坐标数组、y坐标数组
px.pop();
py.pop();
px.unshift(nt[0]);
py.unshift(nt[1]);
//去掉旧尾
$("tr:eq("+ow[1]+")>td:eq("+ow[0]+")").css("background-color","black");
//如果蛇死了,就不要让后面的代码继续执行,否则会出bug
if(isDead()==true){
return;
}
//注意,要先去尾再显头,否则特殊情况下没效果
//显示新头
$("tr:eq("+py[0]+")>td:eq("+px[0]+")").css("background-color","green");
//旧头由绿变白
$("tr:eq("+ot[1]+")>td:eq("+ot[0]+")").css("background-color","white");
isEat();
}
//重新开始游戏
function restart(){
var a=window.confirm('确定开始新的游戏?');
if(a==true){
endGame();
}
}
//暂停游戏
function pause(){
if($("#pause>span").hasClass("glyphicon-pause")){
//移除定时器
window.clearInterval(timer);
//改变图标
$("#pause>span").removeClass("glyphicon-pause");
$("#pause>span").addClass("glyphicon-play");
//暂定状态下重新启用速度下拉菜单事件
changeSpeed();
}else{
//重新上定时器
timer=window.setInterval(function(){
runOnce();
}, speed);
//改变图片
$("#pause>span").removeClass("glyphicon-play");
$("#pause>span").addClass("glyphicon-pause");
//移除速度下单菜单事件
$('#speed-menu>li').each(function(){
$(this).unbind();
});
}
}
//蛇身运动
function run(){
//改变重新开始按钮绑定的事件
$("#start-game").unbind();
$("#start-game").bind("click",restart);
//给暂停按钮绑定事件
$("#pause").bind("click",pause);
//移除速度下单菜单事件
$('#speed-menu>li').each(function(){
$(this).unbind();
});
timer=window.setInterval(function(){
runOnce();
}, speed);
}
//改变蛇身运动方向
function changeFx(a){
//将传过来的方向参数赋给d
d=a;
//根据具体的蛇身运动方向动态地给方向按钮绑定或解绑事件
if(d=='left' || d=='right'){
$("#left").unbind();
$("#right").unbind();
$("#up").bind("click",function(){changeFx('up')});
$("#down").bind("click",function(){changeFx('down')});
}else{
$("#left").bind("click",function(){changeFx('left')});
$("#right").bind("click",function(){changeFx('right')});
$("#up").unbind();
$("#down").unbind();
}
}
//给速度下拉菜单注册事件
function changeSpeed(){
$('#speed-menu>li:eq(0)').bind("click",function(){
speed=200;
$("#speed-span").text('快')
});
$('#speed-menu>li:eq(1)').bind("click",function(){
speed=400;
$("#speed-span").text('中')
});
$('#speed-menu>li:eq(2)').bind("click",function(){
speed=600;
$("#speed-span").text('慢')
});
}
//游戏初始化
function init(){
//初始化蛇身x坐标数组,索引为0即为蛇头x坐标
px=[11,10,9,8];
//初始化蛇身y坐标数组,索引为0即为蛇尾y坐标
py=[10,10,10,10];
//初始化蛇身长度
l=4;
//初始化蛇头
$("tr:eq("+py[0]+")>td:eq("+px[0]+")").css("background-color","green");
//初始化蛇身
for(var i=1;i<l;i++){
$("tr:eq("+py[i]+")>td:eq("+px[i]+")").css("background-color","white");
}
//初始化食物
getFood();
//初始化分数
score=0;
$("#cur-score").text("00");
//从本地存储获取历史最高分
hisScore=localStorage.getItem("key");
if(hisScore!=null){
$("#his-score").text(hisScore);
}
//初始化速度
var s=$("#speed-span").text();
if(s='快'){
speed=200;
}else if(s='中'){
speed=400;
}else{
speed=600;
}
//初始化蛇身运动方向
d='right';
//初始化向上和向下方向按钮
$("#up").bind("click",function(){changeFx('up')});
$("#down").bind("click",function(){changeFx('down')});
//给开始游戏按钮绑定事件
$("#start-game").unbind();
$("#start-game").bind("click",run);
//移除暂停按钮的事件
$("#pause").unbind();
//初始化暂停按钮图标
$("#pause>span").removeClass("glyphicon-play");
$("#pause>span").addClass("glyphicon-pause");
changeSpeed();
}
$(function(){
//初始化游戏
init();
});