index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>贪吃蛇</title>
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
<div id="map"></div>
<script src="js/index.js"></script>
</body>
</html>
style.css
#map{
width: 800px;
height: 600px;
background-color: #ccc;
position: relative;
}
index.js
tools.js
//自调用函数传入window的目的,是让变量可以被压缩
//防止undefined重命名,也可以被压缩
;(function(window,undefined){//形参
var Tools = {
getRandom:function(min,max){
return Math.floor(Math.random() * (max - min +1) + min);
}
}
//暴露Tools给window
window.Tools = Tools;
})(window,undefined)//实参
//_____________________Parent.js_____//这里不建议使用继承
;(function(window){
function Parent(options){
options = options || {};
this.width = options.width || 20;
this.height = options.height || 20;
}
Parent.prototype.test = function(){
console.log("test");
}
window.Parent = Parent;
})(window,undefined)
food.js
/*所有js文件书写代码,都是全局作用域。
为了避免命名冲突,可以使用构造函数,但是不同js文件也可能出现相同的函数名
function fn(){}
fn();
所以就要有另外解决命名冲突,自调用函数,开启一个新的作用域,避免命名冲突
(
(function(){
console.log("1");
})()
*/
//优化方法,remove别人访问不到,如果是Food对象的方法,别人是可以访问到的
;(function(window,undefined){
//局部作用域
var position = "absolute";
//记录上一次创建的食物,为了删除做准备
var elements = [];
function Food(options){
options = options || {};
this.x = options.x || 0;
this.y = options.y || 0;
// this.width = options.width || 20;
//this.height = options.height || 20;
//————————————————————————借用构造函数,此时this就是food对象
Parent.call(this,options);
this.color = options.color || "green";
}
//——————————————原型继承——————————————
Food.prototype = new Parent();
Food.prototype.constructor = Food;
//渲染
Food.prototype.render = function(map){ //创建div,给出样式
//删除之前创建的食物
//记忆之前创建的食物
remove();
//随机设置x,y的值
this.x = Tools.getRandom(0,map.offsetWidth/this.width - 1) * this.width;
this.y = Tools.getRandom(0,map.offsetHeight/this.height - 1) * this.height;
//动态创建div,页面上显示的食物
var div = document.createElement("div");
map.appendChild(div);
elements.push(div);//存放食物div
//div样式
div.style.position = position;
div.style.left = this.x + "px";
div.style.top = this.y + "px";
div.style.width = this.width + "px";
div.style.height = this.height + "px";
div.style.backgroundColor = this.color;
}
function remove(){
for(var i = elements.length - 1; i >= 0; i--){
//删除div
elements[i].parentNode.removeChild(elements[i]);
//删除数组中的元素
elements.splice(i,1);
}
}
//把Food构造函数,让外部可以访问
window.Food = Food;
})(window,undefined)
//测试
/*var map = document.getElementById('map');
var food = new Food();
food.render(map);*/
//snake.js
//自调用函数,会开启一个新的作用域,防止命名冲突
;(function(window,undefined){
var position = "absolute";
//存放之前创建的蛇
var elements = [];
function Snake(options){
options = options || {};
// this.width = options.width || 20;
// this.height = options.height || 20;
//_________________借用构造函数
Parent.call(this,options);
this.direction = options.direction || "right";
this.body = [
{x: 3, y: 2, color: "red"},
{x: 2, y: 2, color: "blue"},
{x: 1, y: 2, color: "blue"}
];
}
Snake.prototype = new Parent();
Snake.prototype.constructor = Snake;
Snake.prototype.render = function(map){
//把蛇身节点渲染到地图上
//删除之前创建的蛇
remove();
for(var i = 0,len = this.body.length; i < len; i++){
//蛇节
var object = this.body[i];
//
var div = document.createElement("div");
map.appendChild(div);
//记录当前蛇
elements.push(div);
//设置样式
div.style.position = position;
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.backgroundColor = object.color;
}
}
//私有成员
function remove(){
for(var i = elements.length - 1; i >= 0; i--){
//删除div
elements[i].parentNode.removeChild(elements[i]);
//删除数组元素
elements.splice(i,1);
}
}
//控制蛇移动的方法
Snake.prototype.move = function (food,map){
//控制蛇身体移动(当前蛇节-上一个蛇节的位置)把每一个蛇节映射到地图上
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;
}
//控制蛇头的方向
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;
}
// 2.3.1 判断蛇是否和食物的坐标重合
var headX = head.x * this.width;//蛇节的宽度this.width,move方法是蛇对象的方法,this就指向蛇对象
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
});
*/
var obj = {};
//对象拷贝
extend(last,obj);
this.body.push(obj);
//随机在地图上重新生成食物
food.render(map);
//render方法需要传入map,由于蛇对象无地图,Snake.prototype.render方法中有地图但获取不到,
//所以需要在Snake.prototype.move = function(food,map)在多传入一个参数
//move方法中增加一个参数,调用的位置也需要参数,game.js中要添加that.map至that.snake.move(that.food,that.map)
//做以上工作主要是为了在food.render()需要渲染到指定位置
}
}
function extend(parent,child){
for(var key in parent){
if(child[key]){
continue;
}
child[key] = parent[key];
}
}
window.Snake = Snake;
})(window,undefined)
/*测试以上函数是否有问题
var map = document.getElementById("map");
var snake = new Snake();
snake.render(map);
*/
///game.js
//使用自调用函数,创建新的局部作用域,防止命名冲突
;(function(window,undefined){
var that;//记录游戏对象,在runSnake()调用
function Game(){
this.food = new Food();
this.snake = new Snake();
this.map = map;
that = this;//将this值给that,可以在将来自调用函数的任何位置获取游戏对象
}
Game.prototype.start = function (){
//1.把蛇和食物对象,渲染到地图上来
this.food.render(this.map);
this.snake.render(this.map);
/*测试
this.snake.move();
this.snake.render(this.map);
this.snake.move();
this.snake.render(this.map);
*/
//2.开始游戏逻辑
//2.1让蛇移动
runSnake();
//2.2通过键盘控制蛇的移动
bindKey();
//2.3当蛇遇到食物,作相应的处理
//2.4蛇遇到边界游戏结束
}
//通过键盘控制蛇移动的方向
function bindKey(){
//document.onkeydown = function(){}
document.addEventListener("keydown",function(e){//function(e){}事件处理函数,this指触发事件的对象,指的是document
/* console.log(e.keyCode); //用于查看按键的编码数据
//left - 37; top - 38; right - 39; bottom - 40 */
switch(e.keyCode){
case 37:
this.snake.direction = "left";
break;
case 38:
this.snake.direction = "top";
break;
case 39:
this.snake.direction = "right";
break;
case 40:
this.snake.direction = "bottom";
break;
}
}.bind(that),false); //第三个参数为false是事件冒泡阶段,加上bind里第一个参数that就改变了function中的this对象
}
//私有函数,让蛇移动
function runSnake(){
var timerId = setInterval(function(){//如果不将that改为this,this默认指向window,将此函数内that全部改为this
//每隔150ms让蛇走一格
//在定时器中,this指向window对象,而不是game对象,this.snake不是指向game对象,而是指向window
//获取游戏对象中的蛇属性,利用that
this.snake.move(this.food,this.map);//判断是否吃到食物
this.snake.render(this.map); //将蛇渲染到地图上
//2.2通过键盘控制蛇的移动
//获取蛇头的坐标
var maxX = this.map.offsetWidth/that.snake.width; //一行有多少蛇节
var maxY = this.map.offsetHeight/that.snake.height;
var headX = this.snake.body[0].x;
var headY = this.snake.body[0].y;
if(headX < 0 || headX >= maxX || headY < 0 || headY >= maxY){
alert("game over!");
clearInterval(timerId);
}
}.bind(that),150); //bind(that)里存放当前游戏对象
}
//暴露构造函数给外部
window.Game = Game;
})(window,undefined);
//测试
/*var map = document.getElementById("map");
var game = new Game(map);
game.start();*/
/main.js
;(function(window,undefined){
var map = document.getElementById("map");
var game = new Game(map);//等价于 var game = new window.Game(map);只是windowkeyishenglue
game.start(window,undefined);
})()