需求分析
-
生成地图。
- 将应用抽象成一个对象。
- 地图使用一个二维数组作为结构。
-
生成食物。
- 生成食物的范围。
- 食物不能和身体生成位置重合。
-
生成蛇,开始移动。
- 蛇碰到墙壁,计算出穿过墙的范围.
- 蛇碰到自己的身体,Game Over .
- 蛇吃到食物,长度加一,并生成新的食物
-
监听键盘事件。
- 对上下左右移动做出反应。
代码实现(也可以查看 github )
html (index.html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" href="./style/index.css">
</head>
<body>
<div class="wrap">
<div id="container">
</div>
</div>
<script src="./js/index.js" ></script>
</body>
</html>
css (./style/index.css)
* {
margin: 0;
padding: 0;
}
.wrap{
display: flex;
justify-content: center;
align-content: center;
}
#container{
width: 100%;
text-align: center;
}
#container div{
/* width: 20px;
height: 20px; */
float: left;
border: 1px solid #000;
}
js(./js/index.js)
(function(self){
function GreedySnake(gridXN,gridYN){
// 地图
this.gridXN = gridXN >= 10 ? gridXN : 10;
this.gridYN = gridYN >= 10 ? gridYN : 10;
this.gridSize = 20; //每个格子的固定大小
this.map = []; //保存地图中dom对象的二维数组
this.container = document.getElementById('container');
// 食物属性
this.food = null;
this.foodX = null;
this.foodY = null;
// 蛇
this.snake = [];//将蛇抽象成一个二维列表,列为身体长度,排为坐标数组
this.snakeLen = null ; //默认蛇长为3
// 初始蛇头坐标
this.snakeHead = []
// 蛇的移动方向
this.direction = 'top'
// 速度
this.speed = 1 ;
this.timer = null
}
GreedySnake.prototype.init = function(){
this.initMap();
this.createSnake();
this.createFood();
this.snakeMove()
this.listenKeyBoardEvents()
}
GreedySnake.prototype.restart = function(gridXN,gridYN){
// 地图
this.gridXN = null
this.gridYN = null
this.gridSize = null
this.map = null
this.container = null
// 食物属性
this.food = null;
this.foodX = null;
this.foodY = null;
// 蛇
this.snake = null;
this.snakeLen = null;
// 初始蛇头坐标
this.snakeHead = null;
// 蛇的移动方向
this.direction = null;
// 速度
this.speed = null;
}
// 初始化地图
GreedySnake.prototype.initMap = function(){
var gridYN = this.gridYN,
gridXN = this.gridXN,
w = gridXN * this.gridSize + gridXN + 1 ,
h = gridYN * this.gridSize + gridYN + 1;
this.container.style.width = w + "px";
this.container.style.height = h + 'px';
for(let y = 0 ; y < gridXN ; y++){
this.map[y] = [] //初始化二维数组
for(let x = 0 ; x < gridYN ; x++){
this.createGrid(x,y)
}
}
}
// 创建每一个格子,x轴格子索引,y轴格子索引
GreedySnake.prototype.createGrid = function(idxX,idxY){
// 当idxY > 0 时,对应的grid的向左移动1px,将格子之间的线重合;
// 当idxX > 0 时,对应的grid的向上移动1px,将格子之间的线重合;
let grid = document.createElement('div');
grid.style.width = this.gridSize + 'px';
grid.style.height = this.gridSize + "px";
this.map[idxY][idxX] = grid
if(idxX > 0 ){
grid.style.marginLeft = '-1px';
}
if(idxY > 0 ){
grid.style.marginTop = '-1px';
}
this.container.appendChild(grid);
}
// 创建食物:原理就是根据在范围内的格子数,生成两个随机数,作为元素的坐标
GreedySnake.prototype.createFood = function(){
var gridYN = this.gridYN,
gridXN = this.gridXN,
temp = null ,
flag = true;
this.foodX = Math.floor(Math.random() * gridXN); //缓存
this.foodY = Math.floor(Math.random() * gridYN);
for(var i = 0 ; i<this.snake.length ; i++){
temp = this.snake[i]
// 检测食物是否和蛇的身体重合
while(temp[0] == this.foodY && temp[1] == this.foodX){
flag = false
this.createFood();
}
}
if(flag){
this.food = this.map[this.foodY][this.foodX];
this.food.style.backgroundColor = '#f00';
}
}
// 生成蛇
GreedySnake.prototype.createSnake = function(){
this.snakeLen = 3 ; //默认蛇长为3
let i = this.snakeLen - 1;
for(i ; i >= 0; i--){
this.snake.push([this.gridYN - 1 - i ,this.gridXN - 1 - 3])
this.map[this.gridYN - 1 - i][this.gridXN - 1 - 3].style.backgroundColor = 'blue'
}
// 初始蛇头坐标
this.snakeHead = this.snake[0]
// console.log(this.snake)
}
// 开始移动,移动后蛇头坐标 +-1
GreedySnake.prototype.snakeMove = function(){
let _this = this,
sH = this.snakeHead,
y = null,
x = null,
tempH = null,
last = null,
alive = true
function common(){
_this.map[tempH[0]][tempH[1]].style.backgroundColor = 'blue'
_this.snake.unshift(tempH);
_this.snakeHead = tempH;
// 检测蛇头是否和蛇的身体相撞
for(var i = 1 ; i < _this.snake.length;i++){
if(_this.snakeHead[0] == _this.snake[i][0] && _this.snakeHead[1] == _this.snake[i][2]){
alert('GAME OVER');
alive = false
return false
}
}
// 当蛇吃到食物后再重新创建食物
if(sH[0] === _this.foodY && sH[1] === _this.foodX){
_this.createFood()
return false
}
last = _this.snake.pop();
_this.map[last[0]][last[1]].style.backgroundColor = ''
}
switch(this.direction){
case 'top':
y = sH[0] //缓存
tempH = [--y , sH[1] ];
if(tempH[0] < 0){
tempH = [this.gridYN - 1 , sH[1]]
}
common();
break;
case 'bottom':
y = sH[0]
tempH = [++y , sH[1] ];
// 边界判断
if(tempH[0] > this.gridYN - 1){
tempH = [ 0 , sH[1]]
}
common()
break;
case 'left':
x = sH[1]
tempH = [sH[0] , --x ];
if(tempH[1] < 0){
tempH = [ sH[0] , this.gridXN - 1]
}
common()
break;
case 'right':
x = sH[1]
tempH = [sH[0] , ++x ];
if(tempH[1] > this.gridXN - 1){
tempH = [ sH[0] , 0 ]
}
common()
break;
}
alive && setTimeout(function(){
_this.snakeMove()
},500 / this.speed)
}
// 注册键盘事件
GreedySnake.prototype.listenKeyBoardEvents = function(){
let _this = this
self.onkeyup = function(e){
let dir = _this.direction
switch(e.keyCode){
case 37:
if(dir != 'right'){
_this.direction = 'left';
}
break;
case 38:
if(dir != 'bottom'){
_this.direction = 'top';
}
break;
case 39:
if(dir != 'left'){
_this.direction = 'right';
}
break;
case 40:
if(dir != 'top'){
_this.direction = 'bottom';
}
break;
}
}
}
GreedySnake.prototype.print = function(){
return this.map
}
// 参数含义:x轴,y轴上的格子数量
var greedySnake = new GreedySnake(20,20);
greedySnake.init()
})(window)