一 效果图
二 代码实现
将面向过程中的相关的名词抽取出来。
有一个Snake类 ,由多个方块(Block)组成,需要依赖BlockData类。
有一个Food类。由一个方块组成。需要依赖BlockData类对象。
有一个Table表格类,由400个方块组成。需要动态创建Block对象。赋值给BlockData对象。
有一个 BLock方块类,包含由td的dom对象,位置对象Location,修改颜色的方法等。
有一个位置Location类、MyEventObject事件类、Speed速度类。没了。
实用面向对象,并不见得代码量会减少。但是能够使得某些紧密耦合的部分最大程度松耦合。同时也容易扩展。
同样是两个文件:index2.html和way2.js
index2.html如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>贪吃蛇2</title>
<style type="text/css">
.container{
width:400px;
height:400px;
margin:60px auto;
padding:0px 0px 0px 0px;
border:1px solid black;
}
.container table{
width:100%;
height:100%;
border:none;
outline:none;
}
</style>
</head>
<body>
<!-- 阶段2 面向对象 -->
<div class="container" id="container2" ></div>
<script type="text/javascript" src="way2.js"></script>
</body>
</html>
way2.js如下:
(function(fn) {
//配置类
var Conf={
m:20,
n:20,
snakeColor:"blue",
foodColor:"red",
map:{37:"left",38:"up",39:"right",40:"down",}//方向。为了省去switch。
};
//事件工厂对象。为了解耦而设计的。
var EventFactory = {
bindKeyEvent : function() {
document.body.onkeydown = function(e) {
var keycode;
if ((keycode = e.keyCode) || (keycode = e.keyWhich)) {
if (keycode >= 37 && keycode <= 40) {
EventFactory.postMessage("onmove", Conf.map[keycode]);
}
}
}
},
//为了实现不同对象之间的信息传递并且降低对象之间的耦合。采用注册、监听的方式来进行信息传递。
callbackMethodMap:{},
registerMessage:function(eventtype,callback){
if(!EventFactory.callbackMethodMap[eventtype])EventFactory.callbackMethodMap[eventtype]=[];
EventFactory.callbackMethodMap[eventtype].push({instance:this,fnc:callback});
},
//调用者有可能是
postMessage:function(eventtype,data,notloopback){
var methodlist = EventFactory.callbackMethodMap[eventtype];
if(methodlist){
var e = new MyEventObject();
e.type = eventtype;
e.data=data;
e.target = this;
for(var i=0;i<methodlist.length;i++){
//为true 表示 是否不调用注册者。
if(notloopback ){
if(methodlist[i].instance !== this)
methodlist[i].fnc.call(methodlist[i].instance,e);
}else{
methodlist[i].fnc.call(methodlist[i].instance,e);
}
}
}
}
}
//位置类。
function Location(x,y){
var _x = x;
var _y = y;
this.setX = function(x) {
_x = x;
}
this.getX = function() {
return _x;
}
this.setY = function(y) {
_y = y;
}
this.getY = function() {
return _y;
}
this.isRepeat = function(loc){
return this.getX()==loc.getX() && this.getY() == loc.getY();
}
}
// 蛇类
function Snake(blockData) {
var _blockData = blockData;//BlockData对象。
var _newbody=[];//新的身体。
var _clearpart = [];//旧的身体。需要清除的部分。
var _repaintpart=[];//重绘部分。
var _sp = new Speed();//创建速度对象。
var _timerId = null;//定时器ID
var _movedir;//运行方向。
var _autoModeFlag = false;//能否自动运行。如果用户在手动输入过程中。禁用自动运行。
this.init=function(){
var empty_block = _blockData.getRandomEmptyBlock();//获取一张空白蛇身。
_newbody.push(empty_block);//加入蛇身。
_repaintpart.push(empty_block);//将蛇头加入重绘部分。
empty_block.setUsed(true);//设置方块为已占用状态。
this.paint();//
this.postMessage("onautomove");
}
this.paint=function(){
for(var i=0;i<_clearpart.length;i++){
_clearpart[i].clear();
}
for(var i=0;i<_repaintpart.length;i++){
_repaintpart[i].setColor(Conf.snakeColor);
}
_clearpart=[];//清空
_repaintpart =[];//清空
}
//私有。 调用时需要绑定this。否则里面用this会指向window对象。
var move=function(){
var head = _newbody[0];//蛇头
var tail = _newbody.pop();//尾巴
tail.setUsed(false);//清除占用。
var newhead = _blockData.nextBlock(head,_movedir);
newhead.setUsed(true);//占用。
//默认情况下,每移动一次。尾部需要清空。新的头部需要重绘。
_clearpart.push( tail );
_newbody.unshift( newhead );
_repaintpart.push( newhead );
//检测是否死亡。(只有一种情况。蛇头咬到自己。撞墙不死。)
EventFactory.postMessage("oncheckdie",newhead.getLocation());
//触发 吃到食物检测事件。
EventFactory.postMessage("oneatfood",newhead.getLocation());
this.paint();
}
//清空定时器。
var clearTimer=function(){
if(_timerId!=null)clearTimeout(_timerId);
}
//开启定时器。
var startTimer=function(){
EventFactory.postMessage("onautomove");
}
/****************事件注册*************/
//方向移动事件。
this.registerMessage("onmove", function(e){
_movedir = e.data;//取出运行方向。保存。实时更改。
clearTimer();
_autoModeFlag = false;
move.call(this);
_autoModeFlag = true;
startTimer();
});
//自动运行。
this.registerMessage("onautomove",function(e){
clearTimer();
var _this = this;
_timerId = setTimeout(function(){
if(_movedir && _autoModeFlag){
move.call(_this);
}
EventFactory.postMessage("onautomove");
}, _sp.getSpeed());
});
//得分检测事件。
this.registerMessage("ongainscores",function(e){
if(e.data){//如果吃到食物。
var tail = _clearpart.pop();//尾部不用清除。
tail.setUsed(true);
_newbody.push(tail);//还原尾部,相当于在尾部增加一块。
//触发 吃到食物之后的事件。
EventFactory.postMessage("onaftereatfood",null);
//加速。
_sp.speedUp();
}
});
//检测死亡
this.registerMessage("oncheckdie", function(e){
if(_newbody.length>1){
for(var i=1;i<_newbody.length;i++){
if(_newbody[i].getLocation().isRepeat(e.data)){
if(confirm("生死权在你手上。死就点确定。不死就取消。") ){
window.location.reload();
}
}
}
}
});
}
// 食物类
function Food(blockData) {
var _blockData = blockData;//BlockData对象。
var newblock = null;//新的食物方块。
var oldblock = null;//旧的食物方块。
this.init=function(){
newblock = _blockData.getRandomEmptyBlock();//
newblock.setUsed(true);//设置方块为已占用状态。
this.paint();//绘出舍身。
}
this.paint=function(){
oldblock && oldblock.clear();
newblock && newblock.setColor(Conf.foodColor);
}
/****************事件注册*************/
//检测是否吃到食物 事件
this.registerMessage("oneatfood", function(e){
EventFactory.postMessage("ongainscores",e.data == newblock.getLocation());
});
//吃到食物后 事件
this.registerMessage("onaftereatfood", function(){
oldblock = newblock;
newblock = _blockData.getRandomEmptyBlock();//
newblock.setUsed(true);//设置方块为已占用状态。
//oldblock.setUsed(false); //不能释放位置。因为旧的食物位置被新的蛇头占用了。
this.paint();//
})
}
//速度类。
function Speed(){
var _ori_speed = 2000;//默认2s
var _max_speed = 50;//最快速度 100ms
var _step = 50; //部增 50ms
var _current_count = 0; //当前计数。
var _speed_up_limit = 3;//缝3进1。
var _current_index = 0;//缝3进1。
var _stopSpeedUp = false;//停止增长。
this.getSpeed=function(){
if(_stopSpeedUp)return _max_speed;
var t = _ori_speed - _current_count*_step;
if(t<=_max_speed){
t =_max_speed;
_stopSpeedUp = true;
}
return t;
};
this.speedUp=function(){
if(_stopSpeedUp)return;
_current_index++;
if(_current_index == _speed_up_limit){
_current_index = 0;
_current_count++;
}
}
}
// 表格类 行和列。
function Table() {
// 初始化方法。
this.init = function(m,n,containerid,blockdatas) {
var container = document.getElementById(containerid);
var table = document.createElement("table");
for (var i = 0; i <m; i++) {
var tr = document.createElement("tr");
for (var j = 0; j < n; j++) {
var td = document.createElement("td");
tr.appendChild(td);
var block = new Block(new Location(j,i), td);// 400个格子。
blockdatas.add(block);
}
table.appendChild(tr);
}
container.appendChild(table);
}
}
// 方块类。
function Block(loca, td) {
var location = loca;
var _td = td;// 方块的td对象。
var _isused = false;//是否被占用。所谓的占用。就是被蛇身、或者食物的位置占用。
this.setUsed=function(flag){
_isused = flag;
}
this.isUsed=function(){
return _isused;
}
this.getLocation=function(){
return location;
}
this.setColor = function(color) {
_td.style.backgroundColor = color;
}
this.clear = function() {
_td.style.backgroundColor = "";
}
/**
* 是否到达边缘。
*/
this.isNextToEdge=function(side){
switch (side) {
case "left": return location.getX()-1 < 0;
case "up": return location.getY()-1 < 0;
case "right":return location.getX()+1 >= Conf.m;
case "down": return location.getY()+1 >= Conf.n;
}
return false;
}
}
//数据类
function BlockData(){
var blocks=[];//二维数组。存放Block对象。
this.add=function(block){
var i = block.getLocation().getX(),j = block.getLocation().getY();
if(!blocks[i])blocks[i]=[];
blocks[i][j]=block;
}
this.get=function(location){
return blocks[location.getX()][location.getY()];
}
//获取随机的空白。
this.getRandomEmptyBlock=function(){
//从剩余的空表中随机抽选。
var rest = [];
for(var i=0;i<blocks.length;i++){
for(var j=0;j<blocks[i].length;j++){
var block = blocks[i][j];
if( ! block.isUsed() ){
rest.push( block );
}
}
}
var index = parseInt(Math.random()*rest.length);
return rest[index];
}
/**
* 根据当前方块和方位。获取下一个方块。
*/
this.nextBlock=function(bk,side){
var flag = bk.isNextToEdge(side);
switch (side) {
case "left":
if( flag ){
return blocks[Conf.m-1][bk.getLocation().getY()];
}else{
return blocks[bk.getLocation().getX()-1][bk.getLocation().getY()];
}
case "right":
if( flag ){
return blocks[0][bk.getLocation().getY()];
}else{
return blocks[bk.getLocation().getX()+1][bk.getLocation().getY()];
}
case "up":
if( flag ){
return blocks[bk.getLocation().getX()][Conf.n-1];
}else{
return blocks[bk.getLocation().getX()][bk.getLocation().getY()-1];
}
case "down":
if( flag ){
return blocks[bk.getLocation().getX()][0];
}else{
return blocks[bk.getLocation().getX()][bk.getLocation().getY()+1];
}
}
}
}
//让snake、food 类 拥有这两个方法。因为他们需要相互消息通信。
Snake.prototype.registerMessage = EventFactory.registerMessage;
Food.prototype.registerMessage = EventFactory.registerMessage;
Snake.prototype.postMessage = EventFactory.postMessage;
Food.prototype.postMessage = EventFactory.postMessage;
//事件对象类。
function MyEventObject(){
this.type=null;//事件类型。
this.data=null;//数据。
this.target=null;//
}
var obj={};
obj[fn]=function(){
//1创建数据对象。
var listdatas = new BlockData();
//2创建表格 并初始化。
var table = new Table();
table.init(20,20,"container2",listdatas);
//3 创建snake
var snake = new Snake(listdatas);
snake.init();
//4 创建食物对象
var food = new Food(listdatas);
food.init();
//5 绑定事件。 需要依赖
EventFactory.bindKeyEvent();
}
return obj;
})("main").main();