设计模式项目应用深入二笔记-贪吃蛇

"Hello World, Hello Blog"

Posted by wudimingwo on December 15, 2018

uml类图

下面是抄写第一遍的源码, 是笔记一的基础上继续的. 整个项目的设计,非常的简洁,精彩. 可扩展性高, 确实对前面的设计模式有很大的理解应用作用.

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewprot" content="width=device-width, initial-scale=1.0"/>
        <meta http-equiv="X-UA-Compatible" content="ie=edge"/>
        <title>贪吃蛇</title>
        <style type="text/css">
            
        </style>
    </head>
    <body>
         <!--1. css reset-->
         <!--2. 抽取公共方法 根据经验 根据我实现树立好的类的关系-->
         
         <script src="common.js" type="text/javascript" charset="utf-8"></script>
         <script src="initVar.js" type="text/javascript" charset="utf-8"></script>
         <script src="Factory.js" type="text/javascript" charset="utf-8"></script>
         <script src="Ground.js" type="text/javascript" charset="utf-8"></script>
         <script src="Snake.js" type="text/javascript" charset="utf-8"></script>
         <script src="Game.js" type="text/javascript" charset="utf-8"></script>
        <script type="text/javascript">
          
        </script>
    </body>
</html>

common.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var tool = {
  inherit : function (target,origin) {// 继承
    // 原型的继承  圣杯模式
    var temp = function () {};
    temp.prototype = origin.prototype;
    target.prototype = new temp();
    target.prototype.constructor = target;
    target.prototype.uper = origin;
  },
  extends : function (origin) {//另一种继承  
    //继承函数, 实例又相同, 很巧妙.. 
    var result = function () {
      origin.apply(this,arguments);// 继承实例
    };
    this.inherit(result, origin);// 继承原型 
    return result;
  },
  single : function (origin) {// 单例 + 实例继承 + 原型继承
    var singleResult = (function () {
        var instance;
        return function () {
          if (typeof instance == 'object') {
            return instance;
          }
          origin && origin.apply(this,arguments);// 继承实例
            instance = this;
            return instance;
        }
    })();
    origin && this.inherit(singleResult,origin);// 继承原型
    return singleResult;
  }
}

initVar.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 场景 == 广场     宽度系数     高度系数

var XLEN = 30;
var YLEN = 30;
// 大写 : 基本不变的量. 游戏运行过程中.


// 方块 宽高
var SQUAREWIDTH = 20;
var SQUAREHEIGHT = 20;
// 广场     位置

var BASE_X_POINT = 200;
var BASE_Y_POINT = 100;

// 定义基类

function Square (x, y, width, height,viewContent) {// 传一个dom元素,用来渲染
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.viewContent = viewContent || document.createElement('div');
}

Square.prototype.touch = function () {
    console.log(0);
}
Square.prototype.update = function (x,y) {
	this.x = x;
	this.y = y;
	this.viewContent.style.left = x * SQUAREWIDTH + 'px';
	this.viewContent.style.top = y * SQUAREHEIGHT + 'px';
}

// 定义子类

var Floor = tool.extends(Square);

var Stone = tool.extends(Square);

var Food = tool.single(Square);

var Snake = tool.single(Square);

var SnakeHead = tool.single(Square);

var SnakeBody = tool.extends(Square);

var Ground = tool.single(Square);

var Game = tool.single();

// 用对象 集合所有的策略消息
var STRATEGYENUM =  {
  move : 'MOVE',
  eat : 'EAT',
  die : 'DIE'
}

Factory.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// 方块工厂 // 抽象工厂模式
function SquareFactory () {
    
}
// 这里要注意,
// 跟一般的抽象工厂不同, 这里的实例是 无法访问 SquareFactory.prototype 的,
// 所以想要给这些实例的原型上 定义属性或者行为, 应该去找 Square.prototype 上去定义.
SquareFactory.create = function (type,x,y,color) {
    // 先判断有没有流水线
    if(typeof SquareFactory.prototype[type] == "undefined"){
      throw "sorry"; 
    }
    if (SquareFactory.prototype[type].prototype.__proto__ != SquareFactory.prototype) {
        SquareFactory.prototype[type].prototype = new SquareFactory();
    }
    
    var newSquare = new SquareFactory.prototype[type](x,y,color);
    return newSquare;
    
}

SquareFactory.prototype.init = function (square, color, message) {// 上背景颜色
  var sv = square.viewContent;
  sv.style.position = "absolute";
  sv.style.width = square.width + 'px';
  sv.style.height = square.height + 'px';
  sv.style.left = square.x * SQUAREWIDTH + 'px';
  sv.style.top = square.y * SQUAREHEIGHT + 'px';
  sv.style.backgroundColor = color;
  square.touch = function () {
  	return message
  }
}

SquareFactory.prototype.Floor = function (x,y,color) {
    var floor = new Floor(x,y,SQUAREWIDTH,SQUAREHEIGHT);
    this.init(floor, color,STRATEGYENUM.move);
    return floor;// 因为return的不是 this, 所以即使new了 floor也不会继承该函数的 原型
}
SquareFactory.prototype.Stone = function (x,y,color) {
    var stone = new Stone(x,y,SQUAREWIDTH,SQUAREHEIGHT);
    this.init(stone, color,STRATEGYENUM.die);
  return stone;
}
SquareFactory.prototype.Food = function (x,y,color) {
    var food = new Food(x,y,SQUAREWIDTH,SQUAREHEIGHT);
    this.init(food, color,STRATEGYENUM.eat);
    food.update(x,y);
  return food;
}
SquareFactory.prototype.SnakeHead= function (x,y,color) {
    
    var snakehead = new SnakeHead(x,y,SQUAREWIDTH,SQUAREHEIGHT);
    // 每次返回的都是同一个
    this.init(snakehead, color,STRATEGYENUM.die);
    
    snakehead.update(x,y);
    // 但我们可以改变属性.
    // 感觉很巧妙 
    // 用一个create接口,完成了单例的同时,又改变了状态.
  return snakehead;
}
SquareFactory.prototype.SnakeBody= function (x,y,color) {
    var snakebody = new SnakeBody(x,y,SQUAREWIDTH,SQUAREHEIGHT);
    this.init(snakebody, color,STRATEGYENUM.die);
  return snakebody;
}

Ground.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
var ground = new Ground(BASE_X_POINT,BASE_Y_POINT,XLEN * SQUAREWIDTH,YLEN * SQUAREHEIGHT);


ground.init = function (){
  // 渲染广场
  this.viewContent.style.position = "absolute";
  this.viewContent.style.backgroundColor = '#0ff';
  this.viewContent.style.left = this.x + 'px';
  this.viewContent.style.top = this.y + 'px';
  this.viewContent.style.width = this.width + 'px';
  this.viewContent.style.height = this.height + 'px';
  document.body.appendChild(this.viewContent);
  
  
  //存储 管理 广场中所有方块对象
  // 二维数组  // x,y坐标本身是重要的参数
  this.SquareTable = [];
  
  
  for(var i = 0; i < YLEN;i++) {// 生成了二维数组.
    this.SquareTable[i] = new Array(XLEN);
    for(var j = 0; j < XLEN; j++) {// 创建方块.
      // 生成墙壁 y = 0 || y = YLEN - 1  || x = 0 || x = XLEN -1
      if( j == 0 || j == XLEN - 1 || i == 0 || i == YLEN -1) {
        // 创建石头
        var newSqaure = SquareFactory.create('Stone', j, i, 'black');
      } else {
        //创建地板
        var newSqaure = SquareFactory.create('Floor', j, i, 'orange');
      }
      this.SquareTable[i][j] = newSqaure;
      this.viewContent.appendChild(newSqaure.viewContent);
    }
  }
}

//拆方法
ground.remove = function (x, y) {
	// 根据坐标找到方块, 找到那个方块代表的数据
	var square = this.SquareTable[y][x];
	// 从视觉上抹除
	this.viewContent.removeChild(square.viewContent);
	// 抹除数据块.
	this.SquareTable[y][x] = null;
	//
}
// 安方法
ground.append = function (square) {
  // 从视觉上添加方块 
  this.viewContent.appendChild(square.viewContent);
  // 从数据上添加方块
  this.SquareTable[square.y][square.x] = square;
  
}



Snake.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
var snake = new Snake();

snake.head = null;
snake.tail = null;


var DIRECTIONENUM = {
  LEFT : {
    x : -1,
    y : 0
  },
  RIGHT : {
    x : 1,
    y : 0
  },
  UP : {
    x : 0,
    y : -1
  },
  DOWN : {
    x : 0,
    y : 1
  }
}

snake.init = function (ground) {
	// 创建蛇身 蛇头 渲染
	
	var SnakeHead = SquareFactory.create('SnakeHead', 3, 1,'red');
	var SnakeBody1 = SquareFactory.create('SnakeBody', 2, 1,'blue');
	var SnakeBody2 = SquareFactory.create('SnakeBody', 1, 1,'blue');
	
	
	//链表   一种数据结构  javascript 数据结构
	// 双向链表
	
	SnakeHead.next = SnakeBody1;
	SnakeHead.last = null;
	
	SnakeBody1.next = SnakeBody2;
	SnakeBody1.last = SnakeHead;
	
	SnakeBody2.next = null;
	SnakeBody2.last = SnakeBody1;
	
	
	// 记录
	this.head = SnakeHead;
	this.tail = SnakeBody2;
	
	
	// 渲染
	ground.remove(SnakeHead.x,SnakeHead.y);
	ground.append(SnakeHead);
	
	ground.remove(SnakeBody1.x,SnakeBody1.y);
  ground.append(SnakeBody1);
  
	ground.remove(SnakeBody2.x,SnakeBody2.y);
  ground.append(SnakeBody2);
	
	snake.direction = DIRECTIONENUM.RIGHT;
	
}

// 引入策略处理
snake.strategies = {
  MOVE : function (snake, square, ground, fromEat) {
  	// 实现运动
  	// 新建蛇身
  	var newBody = SquareFactory.create('SnakeBody',snake.head.x,snake.head.y,'blue');
  	// 先连再断, 否则找不到..
  	newBody.next = snake.head.next;
  	newBody.next.last = newBody;
  	newBody.last = null;
  	
  	ground.remove(snake.head.x,snake.head.y);
  	ground.append(newBody);
  	
  	// 新建蛇头
  	// 实际上返回的还是原来的蛇头,因为我们用的就是单例模式
  	// 所以通过创建的方式, 实际上坐标是不会变的.
  	// 此时我们就不能通过创建, 而是更改属性来达到这个目的.
  	var newHead = SquareFactory.create('SnakeHead',square.x, square.y, 'red');
  	newHead.next = newBody;
  	newHead.last = null;
  	newBody.last = newHead;
  	
  	
  	ground.remove(newHead.x,newHead.y);
  	ground.append(newHead);
  	
  	snake.head = newHead;
  	
  	// 删除蛇尾
  	if (!fromEat) {
  	  var floor = SquareFactory.create('Floor',snake.tail.x,snake.tail.y,'orange');
  	
  	  ground.remove(snake.tail.x,snake.tail.y);
  	  ground.append(floor);
  	
  	  snake.tail = snake.tail.last;
  	}
  	
  },
  EAT : function (snake, square, ground) {
  	// 实现吃
  	this.MOVE(snake, square, ground, true);
  	game.score++;
  	console.log(game.score);
    CreateFood(ground);
    
    // 吃的越多,速度越快.
    game.iSpeedInterval -= 20;
    game.start();
  },
  DIE : function () {
  	// game over
  	game.over();
  }
}


// 做一个预判
snake.move = function (ground) {
	// this.head.x this.head.y 
	var square = ground.SquareTable[this.head.y + this.direction.y][this.head.x + this.direction.x];
	
	if (typeof square.touch == "function") {
		this.strategies[ square.touch() ](this,square,ground);// 注意此时this 指向不是 this, 而是strategies
	}
	
}




Game.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
var game = new Game();

game.timer = null;

game.iSpeedInterval = 200;

game.score = 0;

game.init = function () {
	ground.init();
	snake.init(ground);
	
	// 绑定事件 监控
	
	document.onkeydown = function (e) {
		// e.which   37 left 38 up 39 right 40 down
		
		if (e.which == 37 && snake.direction !== DIRECTIONENUM.RIGHT) {
			snake.direction = DIRECTIONENUM.LEFT;
		}else if (e.which == 39 && snake.direction !== DIRECTIONENUM.LEFT) {
			snake.direction = DIRECTIONENUM.RIGHT;
		}else if (e.which == 38 && snake.direction !== DIRECTIONENUM.DOWN) {
			snake.direction = DIRECTIONENUM.UP;
		}else if (e.which == 40 && snake.direction !== DIRECTIONENUM.UP) {
			snake.direction = DIRECTIONENUM.DOWN;
		}
	}
}
	game.start = function () {
		clearInterval(game.timer);
		game.timer = setInterval(function () {
		  snake.move(ground)
		},game.iSpeedInterval);
	}

    game.over = function () {
    	clearInterval(game.timer);
    	alert(game.score);
    }

    function CreateFood (ground) {
    	
    	var flag = true;
    	
    	while (flag){
    	  var x = 1 + parseInt(Math.random() * 28);
    	  var y = 1 + parseInt(Math.random() * 28);
    		var ok = true;
    		// 遍历蛇, 链表的作用显现
    		for(var node = snake.head; node ; node = node.next) {
    		  if (node.x == x && node.y == y) {
    		  	ok = false;
    		  	break;
    		  }
    		  if (ok) {
    		  	flag = false;
    		  }
    		}
    	}
    	
    	
    	var food = SquareFactory.create('Food', x, y,'green');
    	ground.remove(food.x, food.y);
    	ground.append(food);
    }


game.init();
game.start();
CreateFood(ground);