通过设计模式写出的 demo
可拓展性强
// 符合单一职责原则
const tool = {
inherit: function (target, origin) {
const Fun = function () {
};
Fun.prototype = origin.prototype;
target.prototype = new Fun();
// 让子类的constructor重新指向自己,若不修改则会发现constructor指向的是父类的构造函数
target.prototype.constructor = target;
},
extend: function (origin) {
// 返回一个构造函数
const result = function () {
// 调用父类的构造函数来创建对象
origin.apply(this, arguments);
return this;
}
// 原型继承
this.inherit(result, origin);
return result;
},
single: function (origin) {
// 单例模式应用,例如 蛇头 食物等 无需创建第二个,因此使用单例模式
const singleResult = (function () {
let instance;
return function () {
if (typeof instance == 'object') {
return instance;
}
origin && origin.apply(this, arguments);
instance = this;
}
})();
origin && this.inherit(singleResult, origin);
return singleResult;
},
};
// 初始化定义一些变量
// dom 元素初始化
const restartBtn = document.getElementById('restart');
const startBtn = document.getElementById('start');
const speedUp = document.getElementById('speed-up');
const speedDown = document.getElementById('speed-down');
const speed = document.getElementById('speed');
// 游戏场景 宽度系数—控制每行有多少个方块 高度系数-控制一共多少行
const XLen = 30;
const YLen = 30;
// 每个方块的大小
const SquareSize = 20;
// 定义游戏地图的位置
const Base_X_Point = 100;
const Base_Y_Point = 100;
// 方向枚举
const DirectionNum = {
LEFT: {
x: -1,
y: 0,
rotate: 180, //蛇头在不同的方向中 应该进行旋转
},
RIGHT: {
x: 1,
y: 0,
rotate: 0,
},
UP: {
x: 0,
y: -1,
rotate: -90,
},
DOWN: {
x: 0,
y: 1,
rotate: 90,
},
};
// 定义蛇的移动事件间隔
let Level = 1;
let Interval = 300;
// 定义方块
function Square(x, y, width, height, dom) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.viewContent = dom || document.createElement('div');
}
Square.prototype.touch = function () {
};
// 单例模式下的 方块状态更新
Square.prototype.update = function (x, y) {
this.x = x;
this.y = y;
this.viewContent.style.left = x * SquareSize + 'px';
this.viewContent.style.top = y * SquareSize + 'px';
}
// 初始化构造函数
const Floor = tool.extend(Square);
const Stone = tool.extend(Square);
const Food = tool.single(Square);
const SnakeHead = tool.single(Square);
const SnakeBody = tool.extend(Square);
const Ground = tool.single(Square);
const Snake = tool.single();
const Game = tool.single();
// 蛇的状态枚举
const StrategyEnum = {
MOVE: 'move',
EAT: 'eat',
DIE: 'die',
};
function SquareFactory() {
}
SquareFactory.create = function (type, x, y, className) {
if (typeof SquareFactory.prototype[type] == 'undefined') {
throw new Error('no this type');
}
if (SquareFactory.prototype[type].prototype.__proto__ !== SquareFactory.prototype) {
SquareFactory.prototype[type].prototype = new SquareFactory();
}
return SquareFactory.prototype[type](x, y, className);
};
SquareFactory.prototype.init = function (square, className, message) {
square.viewContent.style.position = 'absolute';
square.viewContent.style.width = square.width + 'px';
square.viewContent.style.height = square.height + 'px';
square.type = className;
square.viewContent.className = className;
square.viewContent.style.left = square.x * SquareSize + 'px';
square.viewContent.style.top = square.y * SquareSize + 'px';
square.touch = function () {
return message;
};
};
SquareFactory.prototype.Floor = function (x, y, className) {
const oFloor = new Floor(x, y, SquareSize, SquareSize);
this.init(oFloor, className, StrategyEnum.MOVE);
return oFloor;
}
SquareFactory.prototype.Stone = function (x, y, className) {
const oStone = new Stone(x, y, SquareSize, SquareSize);
this.init(oStone, className, StrategyEnum.DIE);
return oStone;
}
SquareFactory.prototype.Food = function (x, y, className) {
const oFood = new Food(x, y, SquareSize, SquareSize);
this.init(oFood, className, StrategyEnum.EAT);
oFood.update(x, y);
return oFood;
}
SquareFactory.prototype.SnakeHead = function (x, y, className) {
const oSnakeHead = new SnakeHead(x, y, SquareSize, SquareSize);
this.init(oSnakeHead, className, StrategyEnum.DIE);
oSnakeHead.update(x, y);
return oSnakeHead;
}
SquareFactory.prototype.SnakeBody = function (x, y, className) {
const oSnakeBody = new SnakeBody(x, y, SquareSize, SquareSize);
this.init(oSnakeBody, className, StrategyEnum.DIE);
return oSnakeBody;
}
const oGround = new Ground(Base_X_Point, Base_Y_Point, XLen * SquareSize, YLen * SquareSize);
oGround.init = function () {
this.viewContent.style.position = 'absolute';
this.viewContent.style.left = '50%';
this.viewContent.style.top = '50%';
this.viewContent.style.transform = 'translateX(-50%) translateY(-50%)';
this.viewContent.style.height = this.height + 'px';
this.viewContent.style.width = this.width + 'px';
this.viewContent.style.backgroundColor = '#225675';
document.body.appendChild(this.viewContent);
this.SquareTable = [];
for (let y = 0; y < YLen; y++) {
this.SquareTable[y] = new Array(XLen);
for (let x = 0; x < XLen; x++) {
let newSquare = null;
if (x === 0 || y === 0 || x === XLen - 1 || y === YLen - 1) {
newSquare = SquareFactory.create('Stone', x, y, 'stone');
} else {
newSquare = SquareFactory.create('Floor', x, y, 'floor');
}
this.viewContent.appendChild(newSquare.viewContent);
this.SquareTable[y][x] = newSquare;
}
}
};
oGround.remove = function (x, y) {
const curSquare = this.SquareTable[y][x];
this.viewContent.removeChild(curSquare.viewContent);
this.SquareTable[y][x] = null;
};
oGround.append = function (square) {
this.viewContent.appendChild(square.viewContent);
this.SquareTable[square.y][square.x] = square;
};
const oSnake = new Snake();
oSnake.head = null;
oSnake.tail = null;
oSnake.init = function (ground) {
const snakeHead = SquareFactory.create('SnakeHead', 3, 1, 'snake-head');
const snakeBody1 = SquareFactory.create('SnakeBody', 2, 1, 'snake-body');
const snakeBody2 = SquareFactory.create('SnakeBody', 1, 1, 'snake-body');
// 显示初始的蛇
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);
// 形成双向链表
snakeHead.next = snakeBody1;
snakeHead.last = null;
snakeBody1.next = snakeBody2;
snakeBody1.last = snakeHead;
snakeBody2.next = null;
snakeBody2.last = snakeBody1;
oSnake.head = snakeHead;
oSnake.tail = snakeBody2;
// 默认的方向
this.direction = DirectionNum.RIGHT;
};
// 蛇触发的状态方法
oSnake.stategies = {
move(snake, square, ground, flag) {
// flag 表示是否吃到食物的移动 为true 表示吃到了食物,此时尾部不删除,若是没吃到食物,伤处尾部
// 在原蛇头处创建一个新的蛇身体
const newBody = SquareFactory.create('SnakeBody', snake.head.x, snake.head.y, 'snake-body');
newBody.next = snake.head.next;
snake.head.next.last = newBody;
newBody.last = null;
ground.remove(snake.head.x, snake.head.y);
ground.append(newBody);
// 新建蛇头
const newHead = SquareFactory.create('SnakeHead', square.x, square.y, 'snake-head');
newHead.viewContent.style.transform = 'rotate(' + snake.direction.rotate + 'deg)';
ground.remove(square.x, square.y);
ground.append(newHead);
newHead.next = newBody;
newHead.last = null;
newBody.last = newHead;
snake.head = newHead;
if (!flag) {
// 删除最后一节身体,添加地板
const newFloor = SquareFactory.create('Floor', snake.tail.x, snake.tail.y, 'floor');
ground.remove(snake.tail.x, snake.tail.y);
ground.append(newFloor);
snake.tail = snake.tail.last;
snake.tail.next = null;
}
},
eat(snake, square, ground) {
this.move(snake, square, ground, true);
oGame.score += 1;
createFood(oGround);
},
die() {
oGame.over();
},
};
// 做一个预判,以蛇头为参考,根据自身的方向,判断下一个碰到的方块是什么
oSnake.move = function (ground) {
const nextSquare = ground.SquareTable[this.head.y + this.direction.y][this.head.x + this.direction.x];
if (typeof nextSquare.touch === 'function') {
this.stategies[nextSquare.touch()](this, nextSquare, oGround);
}
};
const oGame = new Game();
oGame.timer = null;
oGame.score = 0;
// 游戏初始化
oGame.init = function () {
restartBtn.style.display = 'none';
oGame.score = 0;
oGround.init();
// next hit delay
document.onkeydown = function (e) {
if (e.key === 'ArrowLeft' && oSnake.direction !== DirectionNum.RIGHT) {
oSnake.direction = DirectionNum.LEFT;
} else if (e.key === 'ArrowUp' && oSnake.direction !== DirectionNum.DOWN) {
oSnake.direction = DirectionNum.UP;
} else if (e.key === 'ArrowDown' && oSnake.direction !== DirectionNum.UP) {
oSnake.direction = DirectionNum.DOWN;
} else if (e.key === 'ArrowRight' && oSnake.direction !== DirectionNum.LEFT) {
oSnake.direction = DirectionNum.RIGHT;
}
}
};
// 游戏开始
oGame.start = function () {
this.timer = setInterval(() => {
oSnake.move(oGround);
}, Interval);
};
// 游戏结束
oGame.over = function () {
clearInterval(this.timer);
alert(`最终得分为:${
this.score}`);
const restartBtn = document.getElementById('restart');
restartBtn.style.display = 'block';
restartBtn.parentNode.style.display = 'block';
};
function createFood(ground) {
let foodX = null;
let foodY = null;
let flag = true;
while (flag) {
foodX = Math.floor(Math.random() * 28 + 1);
foodY = Math.floor(Math.random() * 28 + 1);
if (ground.SquareTable[foodY][foodX].type === 'floor') {
flag = false;
break;
}
}
const oFood = SquareFactory.create('Food', foodX, foodY, 'food');
ground.remove(foodX, foodY);
ground.append(oFood);
}
oGame.init();
// 点击开始按钮
startBtn.onclick = function () {
oSnake.init(oGround);
createFood(oGround);
this.style.display = 'none';
this.parentNode.style.display = 'none';
oGame.start();
}
// 点击重新开始事件
restartBtn.onclick = function () {
oGame.init();
restartBtn.parentNode.style.display = 'none';
startBtn.onclick();
};
speed.innerHTML = Level + '';
// 加快速度
speedUp.onclick = function () {
if (Level < 8) {
Level += 1;
Interval -= 20;
}
speed.innerHTML = Level + '';
}
// 减慢速度
speedDown.onclick = function () {
if (Level > 1) {
Level -= 1;
Interval += 20;
}
speed.innerHTML = Level + '';
}
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Greedy-Snake-Protitle>
<link rel="stylesheet" href="src/CSS/index.css">
head>
<body>
<div class="btn-container">
<div class="speed-control">
<button id="speed-down">难度降低button>
<div id="speed">div>
<button id="speed-up">难度提升button>
div>
<button id="start">开始游戏button>
<button id="restart" style="display: none">重新开始button>
div>
<script src="./src/js/utils.js">script>
<script src="./src/js/initVar.js">script>
<script src="./src/js/SquareFactory.js">script>
<script src="./src/js/Ground.js">script>
<script src="./src/js/Snake.js">script>
<script src="./src/js/Game.js">script>
<script src="src/js/handleEvent.js">script>
body>
html>
:root, body {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
.btn-container {
position: relative;
height: 100%;
width: 100%;
z-index: 999;
top: 0;
right: 0;
left: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
}
.btn-container .speed-control {
display: flex;
justify-content: space-between;
position: absolute;
font-size: 30px;
width: 200px;
left: 50%;
top: 40%;
transform: translateX(-50%) translateY(-50%);
}
.btn-container #restart,
.btn-container #start {
position: absolute;
border-radius: 10px;
font-size: 30px;
width: 200px;
height: 50px;
left: 50%;
top: 50%;
transform: translateX(-50%) translateY(-50%);
}
.stone {
background-color: #7dd9ff;
}
.floor {
background-color: #225675;
}
.snake-head {
background-image: url(../assets/snakeHead.png);
background-size: cover;
border-top-right-radius: 30%;
border-bottom-right-radius: 30%;
}
.snake-body {
background-color: #9ddbb1;
border-radius: 50%;
}
.food {
background-image: url(../assets/food.png);
background-size: cover;
}