运行结果
知识点
- 利用面向对象的思想实现——一个食物对象、一个蛇对象、一个游戏总控对象。
- 在使用××.prototype= {}重写原型对象的时候,一定要加上一句constructor:该对象。不然会造成实例化出来的实例的constructor为object。
Food.prototype = { constructor: Food, };
- 在underscore中,使用_.random(a,b)即可获得a-b中的一个随机数。
- 在求食物的随机位置的时候,用到了panel.clientHeight/this.height - 1) * this.height。
原理是使用盒子的高度/小球的高度,可以算得最多放多少个小球。因为要控制小球不能超过边界,所以总数量要减去1,数量×高度即为随机位置的最大值。 - 在蛇对象中,用body数组存放蛇身体每一个部分对象。蛇的绘制过程就是遍历body,在面板上绘制。
- 蛇的移动分为两部分。
① 蛇节移动到前一个蛇节的位置。直到蛇头后一个蛇节移动到蛇头的位置。
② 根据direction判断蛇头如何移动。
注意:在游戏绘制的过程中,界面的每一次绘制都要删除之前的绘制,不然会叠加到一起。 - 在蛇的闭包中建一个局部数组,存储蛇对象,可以更加方便的删除操作。
- 只有在原型对象中的方法和属性,外界是可以调用的。
- 蛇的移动(动画)必然需要定时器协助。定时器的时间,即象征着刷新速度,也就是难度。
- this所在的函数在哪一个对象中,this就指向谁。单独写一个函数的时候,如果调用之前对象的this,需要备份指针(将对象的this赋值给另一个变量)。
- JavaScript原生的键盘按下事件(keydown) 中,事件有一个keyCode属性,其值代表按下的键。其中:37—left、38—top、39—right、40—bottom。
- 边界控制。通过判断蛇头与最大X和Y的关系,判断是否碰到边界。
- confirm()方法用于显示一个带有指定消息和确认及取消按钮的对话框。
- window.location.reload(); 重新加载当前文档
- window.close() 方法用于关闭浏览器窗口。
- 与食物的碰撞检测:如果蛇头和食物坐标重叠,将蛇尾添加到body中。并重新绘制一个食物点,将之前的食物删掉。
代码
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<style>
#panel {
width: 800px;
height: 600px;
background: skyblue;
position: relative;
margin: 60px auto;
border-radius: 10px;
border: 5px solid orange;
overflow: hidden;
}
</style>
</head>
<body>
<div id="panel"></div>
<script src="js/UnderScore.js"></script>
<script src="js/Foods.js"></script>
<script src="js/Snake.js"></script>
<script src="js/Game.js"></script>
<script>
var panel = document.getElementById('panel');
var game = new Game(panel);
game.render();
game.run();
</script>
</body>
</html>
Food.js
(function (window) {
// 全局指针
var self; // 记录当前游戏对象
/**
* 构造函数-游戏
* @param panel
* @constructor
*/
function Game(panel) {
this.food = new Food({});
this.snake = new Snake({});
this.panel = panel;
// 赋值
self = this;
}
Game.prototype = {
constructor: Game,
/**
* 初始化
*/
render: function () {
// 1.把蛇和食物对象,渲染到面板上
this.food.render(this.panel);
this.snake.render(this.panel);
},
run: function () {
// 1.蛇跑起来
snakeMove();
// 2.方向控制
dirControl();
}
};
/**
* 蛇开始移动
*/
function snakeMove() {
var timerId = setInterval(function () {
// 1. 蛇开始移动和绘制
self.snake.move(self.food, self.panel);
self.snake.render(self.panel);
// 2. 边界控制
var maxX = self.panel.clientWidth / self.snake.width;
var maxY = self.panel.clientHeight / self.snake.height;
var headX = self.snake.body[0].x;
var headY = self.snake.body[0].y;
console.log(self.panel.clientWidth, self.snake.width, self.snake.body[0].x);
// 2.1 水平方向控制
if (headX < 0 || headX >= maxX) {
clearInterval(timerId);
if(confirm('很遗憾,您输了!再来一次?')){
window.location.reload();
}else {
window.close();
}
}
// 2.2 垂直方向控制
if (headY < 0 || headY >= maxY) {
clearInterval(timerId);
if(confirm('很遗憾,您输了!再来一次?')){
window.location.reload();
}else {
window.close();
}
}
}, 100);
}
/**
* 方向控制
*/
function dirControl() {
document.addEventListener('keydown', function (e) {
console.log(e['keyCode']);
/*
37 - left
38 - top
39 - right
40 - bottom
*/
switch (e['keyCode']) {
case 37:
self.snake.direction = 'left';
break;
case 38:
self.snake.direction = 'top';
break;
case 39:
self.snake.direction = 'right';
break;
case 40:
self.snake.direction = 'bottom';
break;
}
}, false);
}
window.Game = Game;
})(window);
Snake.js
(function (window) {
// 食物数组
var snakeArr = [];
var remove = function() {
for (var i = snakeArr.length - 1; i >= 0; i--) {
// 1.删除div
snakeArr[i].remove();
// 2.删除数组中的元素
snakeArr.splice(i, 1);
}
};
/**
* 构造函数-蛇
* @param {Object}options
* @constructor
*/
function Snake(options) {
options = options || {};
// 1.蛇节的大小
this.width = options.width || 20;
this.height = options.height || 20;
// 2.蛇移动的方向
this.direction = options.direction || 'right';
// 3.蛇的身体和蛇头 (坐标基数、颜色)
this.body = [
{x: 2, y: 0, color: 'purple'},
{x: 1, y: 0, color: 'red'},
{x: 0, y: 0, color: 'red'}
];
}
Snake.prototype = {
constructor: Snake,
/**
* 绘制小蛇
*/
render: function (panel) {
// 1. 先删除
remove();
// 2. 把每一个蛇节渲染到地图上
for (var i = 0, len = this.body.length; i < len; i++) {
// 蛇节
var object = this.body[i];
var div = document.createElement('div');
// 设置样式
div.style.position = 'absolute';
div.style.width = this.width + 'px';
div.style.height = this.height + 'px';
div.style.left = object.x * this.width + 'px';
div.style.top = object.y * this.height + 'px';
div.style.borderRadius = this.width / 2 + 'px';
div.style.backgroundColor = object.color;
panel.appendChild(div);
// 记录当前蛇
snakeArr.push(div);
}
},
/**
* 移动小蛇
*/
move: function (food, panel) {
// 1. 控制蛇的身体移动(当前蛇节 到 上一个蛇节的位置)
for (var i = this.body.length - 1; i > 0; i--) {
this.body[i].x = this.body[i - 1].x;
this.body[i].y = this.body[i - 1].y;
}
// 2. 控制蛇头的移动
// 2.1 判断蛇移动的方向
var head = this.body[0];
switch(this.direction) {
case 'right':
head.x += 1;
break;
case 'left':
head.x -= 1;
break;
case 'top':
head.y -= 1;
break;
case 'bottom':
head.y += 1;
break;
}
// 3. 判断蛇头是否和食物的坐标重合
var headX = head.x * this.width;
var headY = head.y * this.height;
if (headX === food.x && headY === food.y) {
// 让蛇增加一节
// 获取蛇的最后一节
var last = this.body[this.body.length - 1];
this.body.push({
x: last.x,
y: last.y,
color: last.color
});
// 4. 随机在面板上重新生成食物
food.render(panel);
}
}
};
// 暴露构造函数给外部
window.Snake = Snake;
})(window);
Game.js
(function (window) {
// 全局指针
var self; // 记录当前游戏对象
/**
* 构造函数-游戏
* @param panel
* @constructor
*/
function Game(panel) {
this.food = new Food({});
this.snake = new Snake({});
this.panel = panel;
// 赋值
self = this;
}
Game.prototype = {
constructor: Game,
/**
* 初始化
*/
render: function () {
// 1.把蛇和食物对象,渲染到面板上
this.food.render(this.panel);
this.snake.render(this.panel);
},
run: function () {
// 1.蛇跑起来
snakeMove();
// 2.方向控制
dirControl();
}
};
/**
* 蛇开始移动
*/
function snakeMove() {
var timerId = setInterval(function () {
// 1. 蛇开始移动和绘制
self.snake.move(self.food, self.panel);
self.snake.render(self.panel);
// 2. 边界控制
var maxX = self.panel.clientWidth / self.snake.width;
var maxY = self.panel.clientHeight / self.snake.height;
var headX = self.snake.body[0].x;
var headY = self.snake.body[0].y;
console.log(self.panel.clientWidth, self.snake.width, self.snake.body[0].x);
// 2.1 水平方向控制
if (headX < 0 || headX >= maxX) {
clearInterval(timerId);
if(confirm('很遗憾,您输了!再来一次?')){
window.location.reload();
}else {
window.close();
}
}
// 2.2 垂直方向控制
if (headY < 0 || headY >= maxY) {
clearInterval(timerId);
if(confirm('很遗憾,您输了!再来一次?')){
window.location.reload();
}else {
window.close();
}
}
}, 100);
}
/**
* 方向控制
*/
function dirControl() {
document.addEventListener('keydown', function (e) {
console.log(e['keyCode']);
/*
37 - left
38 - top
39 - right
40 - bottom
*/
switch (e['keyCode']) {
case 37:
self.snake.direction = 'left';
break;
case 38:
self.snake.direction = 'top';
break;
case 39:
self.snake.direction = 'right';
break;
case 40:
self.snake.direction = 'bottom';
break;
}
}, false);
}
window.Game = Game;
})(window);