贪吃蛇的游戏相信你们都玩过。在那个水果机尚未流行,人手一部诺基亚的时代,贪吃蛇是手机中的必备游戏。笔者闲的无聊的时候就拿出手机来玩上几局,挑战一下本身的记录。 后来上大学了,用c语言作过贪吃蛇的游戏,不过主要是经过函数来控制(PS:如今让我看代码都看不懂(⊙﹏⊙))。如今学习前端框架以后,经过jQuery来实现一个贪吃蛇的游戏效果,虽然游戏界面比(bu)较(ren)简(zhi)陋(shi),可是主要学习一下游戏中面向对象和由局部到总体的思想。javascript
在开始写代码前首先让咱们来构思一下总体游戏的实现过程: css
首先既然是贪吃蛇,那么游戏中确定要涉及到两个对象,一个是蛇的对象,另外一个是食物的对象。食物对象确定要有一个属性就是食物的坐标点,蛇对象有一个属性是一个数组,用来存放蛇身体全部的坐标点。html
另外全局须要有一个定时器来周期性的移动蛇的身体。因为蛇的身体弯弯曲曲有各类不一样的形状,所以咱们只处理蛇的头部和尾部,每次移动都根据移动的方向的不一样来添加新的头部,再把尾部擦去,看起来就像蛇在向前爬行同样。前端
因为蛇有移动的方向,所以咱们也须要在全局定义一个方向对象,对象中有上下左右所表明的值。同时,在蛇对象的属性中咱们也须要定义一个方向属性,用来表示当前蛇所移动的方向。java
在蛇向前爬行的过程当中,会遇到三种不一样的状况,须要进行不一样的判断检测。第一种状况是吃到了食物,这时候就须要向蛇的数组中添加食物的坐标点;第二种状况是碰到了本身的身体,第三种是碰到了边界,这两种状况都致使游戏结束;若是不是上面的三种状况,蛇就能够正常的移动。git
总体构思有了,下面就开始写代码了。github
首先整个游戏须要一个搭建活动的场景,咱们经过一个表格布局来做为整个游戏的背景。编程
<style type="text/css"> #pannel table{ border-collapse:collapse; } #pannel td{ width: 10px; height: 10px; border: 1px solid #000; } #pannel td.food{ background: green; } #pannel td.body{ background: #f60; } </style>
<div id="pannel">
</div>
<select name="" id="palSize">
<option value="10">10*10</option>
<option value="20">20*20</option>
<option value="40">30*30</option>
</select>
<select name="" id="palSpeed">
<option value="500">速度-慢</option>
<option value="250">速度-正常</option>
<option value="100">速度-快</option>
</select>
<button id="startBtn">开始</button>
复制代码
pannel就是咱们的幕布,咱们在这个里面用td标签来画上一个个的“像素点”。咱们用两种样式来表现不一样的对象,.body
表示蛇的身体的样式,.food
表示食物的样式。数组
var settings = {
// pannel面板的长度
pannelSize: 10,
// 贪吃蛇移动的速度
speed: 500,
// 贪吃蛇工做线程
workThread: null,
};
function setPannel(size){
var content = [];
content.push('<table>');
for(let i=0;i<size;i++){
content.push('<tr>');
for(let j=0;j<size;j++){
content.push('<td class="td_'+i+'_'+j+'"></td>');
}
content.push('</tr>');
}
content.push('</table>');
$('#pannel').html(content.join(''));
}
setPannel(settings.pannelSize);
复制代码
咱们定义了一个全局的settings用来存放全局性的变量,好比幕布的大小、蛇移动的速度和工做的线程。而后经过一个函数把幕布画了出来,最后的效果就是这样: bash
既然咱们的“舞台”已经搭建完了,怎么来定义咱们“演员”的位置和移动的方向呢。首先定义一个全局的方向变量,对应的数值就是咱们的上下左右方向键所表明的keyCode。
var Direction = {
UP: 38,
DOWN: 40,
LEFT: 37,
RIGHT: 39,
};
复制代码
咱们在上面画幕布的时候经过两次遍历画出了一个相似于中学里学的坐标系,有X轴和Y轴。若是每次都用{x:x,y:y}
来表示会很(mei)麻(bi)烦(ge),咱们能够定义一个坐标点对象。
function Position(x,y){
// 距离X轴长度,取值范围0~pannelSize-1
this.X = x || 0;
// 距离Y轴长度,取值范围0~pannelSize-1
this.Y = y || 0;
}
复制代码
既然定义好了坐标点对象,那么能够先来看一下简单的对象,就是咱们的食物(Food)对象,上面说了,它有一个重要的属性就是它的坐标点。
function Food(){
this.pos = null;
// 随机产生Food坐标点,避开蛇身
this.Create = function(){
if(this.pos){
this.handleDot(false, this.pos, 'food');
}
let isOk = true;
while(isOk){
let x = parseInt(Math.random()*settings.pannelSize),
y = parseInt(Math.random()*settings.pannelSize);
if(!$('.td_'+x+'_'+y).hasClass('body')){
isOk = false;
let pos = new Position(x, y);
this.handleDot(true, pos, 'food');
this.pos = pos;
}
}
};
// 画点
this.handleDot = function(flag, dot, className){
if(flag){
$('.td_'+dot.X+'_'+dot.Y).addClass(className);
} else {
$('.td_'+dot.X+'_'+dot.Y).removeClass(className);
}
};
}
复制代码
既然食物有了坐标点这个属性,那么咱们何时给他赋值呢?咱们知道Food是随机产生的,所以咱们定义了一个Create函数用来产生Food的坐标点。可是产生的坐标点又不能在蛇的身体上,因此经过一个while循环来产生坐标点,若是坐标点正确了,就终止循环。此外为了方便咱们统一处理坐标点的样式,所以定义了一个handleDot函数。
终于到了咱们的主咖,蛇。首先定义一下蛇基本的属性,最重要的确定是蛇的body属性,每次移动时,都须要对这个数组进行一些操做。其次是蛇的方向,咱们给它一个默认向下的方向。而后是食物,在蛇的构造函数中咱们传入食物对象,在后续移动时须要判断是否吃到食物。
function Snake(myFood){
// 蛇的身体
this.body = [];
// 蛇的方向
this.dir = Direction.DOWN;
// 蛇的食物
this.food = myFood;
// 创造蛇身
this.Create = function(){
let isOk = true;
while(isOk){
let x = parseInt(Math.random()*(settings.pannelSize-2))+1,
y = parseInt(Math.random()*(settings.pannelSize-2))+1;
console.log(x,y)
if(!$('.td_'+x+'_'+y).hasClass('food')){
isOk = false;
let pos = new Position(x, y);
this.handleDot(true, pos, 'body')
this.body.push(pos);
}
}
};
this.handleDot = function(flag, dot, className){
if(flag){
$('.td_'+dot.X+'_'+dot.Y).addClass(className);
} else {
$('.td_'+dot.X+'_'+dot.Y).removeClass(className);
}
};
}
复制代码
下面对蛇移动的过程进行处理,因为咱们每次都采用添头去尾
的方式移动,所以咱们每次只须要关注蛇的头和尾。咱们约定数组的第一个元素是头,最后一个元素是尾。
this.Move = function(){
let oldHead = Object.assign(new Position(), this.body[0]),
oldTail = Object.assign(new Position(), this.body[this.body.length - 1]),
newHead = Object.assign(new Position(), oldHead);
switch(this.dir){
case Direction.UP:
newHead.X = newHead.X - 1;
break;
case Direction.DOWN:
newHead.X = newHead.X + 1;
break;
case Direction.LEFT:
newHead.Y = newHead.Y - 1;
break;
case Direction.RIGHT:
newHead.Y = newHead.Y + 1;
break;
default:
break;
}
// 数组添头
this.body.unshift(newHead);
// 数组去尾
this.body.pop();
};
复制代码
这样咱们对蛇身数组就处理完了。可是咱们还须要对新的头(newHead)进行一些碰撞检测,判断新头部的位置上是否有其余东西(碰撞检测)。
// 食物检测
this.eatFood = function(){
let newHead = this.body[0];
if(newHead.X == this.food.pos.X&&newHead.Y == this.food.pos.Y){
return true;
} else {
return false;
}
};
// 边界检测
this.konckWall = function(){
let newHead = this.body[0];
if(newHead.X == -1 ||
newHead.Y == -1 ||
newHead.X == settings.pannelSize ||
newHead.Y == settings.pannelSize ){
return true;
} else {
return false;
}
};
// 蛇身检测
this.konckBody = function(){
let newHead = this.body[0],
flag = false;
this.body.map(function(elem, index){
if(index == 0)
return;
if(elem.X == newHead.X && elem.Y == newHead.Y){
flag = true;
}
});
return flag;
};
复制代码
所以咱们须要对Move函数进行一些扩充:
this.Move = function(){
// ...数组操做
if(this.eatFood()){
this.body.push(oldTail);
this.food.Create();
this.rePaint(true, newHead, oldTail);
} else if(this.konckWall() || this.konckBody()) {
this.Over();
} else {
this.rePaint(false, newHead, oldTail);
}
};
this.Over = function(){
clearInterval(settings.workThread);
console.log('Game Over');
};
this.rePaint = function(isEatFood, newHead, oldTail){
if(isEatFood){
// 加头
this.handleDot(true, newHead, 'body');
} else {
// 加头
this.handleDot(true, newHead, 'body');
// 去尾
this.handleDot(false, oldTail, 'body');
}
};
复制代码
由于在Move函数处理数组的后咱们的蛇身尚未从新绘制,所以咱们很巧妙地判断若是是吃到食物的状况,在数组中就把原来的尾部添加上,这样就达到了吃食物的效果。同时咱们定义一个rePaint函数进行页面的重绘。
咱们的“幕布”、“演员”和“动做指导”都已经到位,那么,咱们如今就须要一个“摄影机”进行拍摄,让它们都开始“干活”。
function Control(){
this.snake = null;
// 按钮的事件绑定
this.bindClick = function(){
var that = this;
$(document).on('keydown', function(e){
if(!that.snake)
return;
var canChangrDir = true;
switch(e.keyCode){
case Direction.DOWN:
if(that.snake.dir == Direction.UP){
canChangrDir = false;
}
break;
case Direction.UP:
if(that.snake.dir == Direction.DOWN){
canChangrDir = false;
}
break;
case Direction.LEFT:
if(that.snake.dir == Direction.RIGHT){
canChangrDir = false;
}
break;
case Direction.RIGHT:
if(that.snake.dir == Direction.LEFT){
canChangrDir = false;
}
break;
default:
canChangrDir = false;
break;
}
if(canChangrDir){
that.snake.dir = e.keyCode;
}
});
$('#palSize').on('change',function(){
settings.pannelSize = $(this).val();
setPannel(settings.pannelSize);
});
$('#palSpeed').on('change',function(){
settings.speed = $(this).val();
});
$('#startBtn').on('click',function(){
$('.food').removeClass('food');
$('.body').removeClass('body');
that.startGame();
});
};
// 初始化
this.init = function(){
this.bindClick();
setPannel(settings.pannelSize);
};
// 开始游戏
this.startGame = function(){
var food = new Food();
food.Create();
var snake = new Snake(food);
snake.Create();
this.snake =snake;
settings.workThread = setInterval(function(){
snake.Move();
},settings.speed);
}
this.init();
}
复制代码
咱们给document绑定一个keydown事件,当触发按键时改变蛇的移动方向,可是若是和当前蛇移动方向相反时就直接return。最后的效果以下:
实现了贪吃蛇的一些基本功能,好比移动、吃点、控制速度等,页面也比较的简单,就一个table、select和button。后期能够添加一些其余的功能,好比有计分、关卡等,也能够添加多个点,有的点吃完直接GameOver等等。