javascript贪吃蛇二(面向对象实现)

一 效果图

二 代码实现

将面向过程中的相关的名词抽取出来。
        有一个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();

猜你喜欢

转载自my.oschina.net/lightled/blog/1825465