用js做个简易的贪吃蛇游戏
代码结构:
主要思路:
1:将草地(蛇运动容器)分成一个个网格
// 生成网格
buildGid () {
let col = Math.floor(this.options.grasswidth / this.options.snodesize); // 列数
let row = Math.floor(this.options.grassheight / this.options.snodesize); // 行数
for (let i = 0; i < row; i++) {
for(let j = 0; j < col; j++) {
this.grassgrid.x.push(i * this.options.snodesize); // 加入每个小格子
this.grassgrid.y.push(j * this.options.snodesize); // 加入每个小格子
this.grassgrid.el.push((i * this.options.snodesize) + '_' + (j * this.options.snodesize)); // 加入每个小格子
}
}
}
2:随机生成食物,食物位置为随机蛇没有占据的网格
// 随机出现食物
addfood (){
// 获取蛇的网格
this.getSnakeGrid()
let arr = { x: [], y: [], el: [] };
arr.el = this.grassgrid.el.filter((item, index) => {
if (!this.snakegrid.el.includes(item)) {
arr.x.push(this.grassgrid.x[index])
arr.y.push(this.grassgrid.y[index])
return true
}
return false
});
if (arr.x.length) {
let index = Math.floor(Math.random() * arr.x.length);
if (this.food) {
this.food.style.left = arr.x[index] + 'px';
this.food.style.top = arr.y[index] + 'px';
} else {
let foodel = document.createElement('div');
foodel.style.left = arr.x[index] + 'px';
foodel.style.top = arr.y[index] + 'px';
foodel.className = 'food'
this.food = this.grassel.appendChild(foodel);
}
}
}
3:蛇爬动,蛇snake为连续的一小格一小格,每次爬一小格,蛇头(第一小格)根据当前方向cudire设置移动位置,蛇身体(除第一个小格)移动位置为上一个小格的位置
// 蛇爬动
snakeClimb () {
this.crossBorder(); // 判断蛇头是否碰壁
this.crossSelf(); // 判断蛇头是否碰到身体
if (!this.gameover && !this.isplaying) { // 如果游戏没有结束且蛇没有在爬动
this.isplaying = true
let snakenode = this.grassel.querySelectorAll('.snake-node'); // 蛇的所有关节小格
let preposition = { x: 0, y: 0 }; // 记录上一个蛇关节的位置
for (let i = 0; i < snakenode.length; i++) {
let item = snakenode[i]
if (i == 0) { // 如果是蛇头
switch(this.cudire) {
case 'r': // 如果向右移动,则蛇头小格left增加一小格宽度
item.style.left = (item.offsetLeft + this.options.snodesize) + 'px';
break;
case 'l': // 如果向左移动,则蛇头小格left减少一小格宽度
item.style.left = (item.offsetLeft - this.options.snodesize) + 'px';
break;
case 't': // 如果向上移动,则蛇头小格top减少一小格宽度
item.style.top = (item.offsetTop - this.options.snodesize) + 'px';
break;
case 'b': // 如果向下移动,则蛇头小格top增加一小格宽度
item.style.top = (item.offsetTop + this.options.snodesize) + 'px';
break;
}
} else { // 蛇身体关节小格移动位置为上一个关节小格的位置
item.style.left = preposition.x + 'px';
item.style.top = preposition.y + 'px';
}
preposition = { x: item.offsetLeft, y: item.offsetTop }
}
}
}
4:修改蛇爬动方向,监听鼠标点击草地位置,通过判断当前蛇爬行方向、蛇头位置和鼠标位置比较判断蛇改变的方向:如果是水平方向移动,则方向只能改为上下方向;如果是垂直方向移动,则方向只能是左右方向
// 转变方向
snakeTurn () {
this.grassel.addEventListener('click', function (e) {
let selnode = this.grassel.querySelector('.snake-node');
let x = e.offsetX; // 鼠标x坐标
let y = e.offsetY; // 鼠标y坐标
// 如果是水平方向移动,则方向只能改为上下方向;如果是垂直方向移动,则方向只能是左右方向
if ((this.cudire == 'r' || this.cudire == 'l') && (selnode.offsetTop >= y || selnode.offsetTop + this.options.snodesize <= y)) {
if (y > selnode.offsetTop) {
this.cudire = 'b'
} else {
this.cudire = 't'
}
} else if ((this.cudire == 't' || this.cudire == 'b') && (selnode.offsetLeft >= x || selnode.offsetLeft + this.options.snodesize <= x)) {
if (x > selnode.offsetLeft) {
this.cudire = 'r'
} else {
this.cudire = 'l'
}
}
if (this.gamestart && !this.isplaying) {
this.snakeClimb();
}
}.bind(this), false)
}
4:蛇吃食物,判断蛇头和食物的位置相同,为碰到食物,食物修改位置,在蛇尾处增加一小关节小格
// 判断是否碰到食物
getFood () {
let sel = this.grassel.querySelector('.snake-node');
if (sel.offsetLeft == this.food.offsetLeft && sel.offsetTop == this.food.offsetTop) {
return true
} else {
return false
}
}
// 吃东西
eatfood () {
this.addSnakeNodes(); // 增加蛇关节
this.addfood(); // 改变食物位置
}
最后完整代码
<!DOCTYPE html>
<html>
<head>
<title>贪吃蛇</title>
</head>
<body>
<script>
class RetroSnaker {
constructor (options) {
let defoptions = {
container: document.body,
grasswidth: 300,
grassheight: 300,
snodesize: 50,
speed: 0.3
}
options || (options = {});
this.options = Object.assign({}, defoptions, options); // 基础配置
this.grassgrid = { x: [], y: [], el: [] }; // 草地网格
this.snakegrid = { x: [], y: [], el: [] }; // 蛇的网格
this.cudire = 'r'; // 方向 r:向右;l:向左;t:向上;b:向下。
this.isplaying = false; // 是否在运动中
this.gameover = false; // 游戏是否结束
this.gamestart = false; // 游戏是否开始
this.grassel = null; // 草地
this.snake = null; // 蛇
this.startbtn = null; // 开始游戏按钮
this.food = null; // 食物
this.tipcon = null; // 提示框
this.init();
}
init () {
this.buildGrass();
this.addSnakeNodes();
this.buildGid();
this.addfood();
this.startbtn.addEventListener('click', function () {
this.startbtn.innerText = "重新开始";
if (this.gamestart) {
this.reStart();
}
this.play();
}.bind(this), false)
this.snakeTurn ();
}
// 创建元素
buildGrass () {
if (!document.querySelector('head style#style-retrosnaker')) {
let styleel = document.createElement('style');
styleel.innerHTML = `
.grass{
position: relative;
width: ${this.options.grasswidth}px;
height: ${this.options.grassheight}px;
background: green;
margin: 0px auto;
overflow: hidden;
}
.snake{
}
.snake-node{
left: 0px;
top: 0px;
transition: all ${this.options.speed}s;
transition-timing-function: linear;
}
.snake-node,
.food{
position: absolute;
width: ${this.options.snodesize}px;
height: ${this.options.snodesize}px;
background: red;
pointer-events: none;
}
.food{
background: yellow;
}
.opbtns-box{
position: relative;
margin-top: 20px;
display: flex;
justify-content: center;
}
.opbtns-start{
position: relative;
font-size: 24px;
}
.tipcon{
position: absolute;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, .6);
display: none;
font-size: 24px;
justify-content: center;
align-items: center;
color: #fff;
letter-spacing: .2em;
font-weight: bold;
z-index: 1;
}
`;
styleel.id = 'style-retrosnaker';
document.querySelector('head').appendChild(styleel);
}
let gel = document.createElement('div');
gel.className = 'grass'
this.grassel = this.options.container.appendChild(gel);
let sel = document.createElement('div');
sel.className = 'snake';
this.snake = this.grassel.appendChild(sel);
let btns = document.createElement('div');
btns.className = "opbtns-box";
btns = this.options.container.appendChild(btns)
let startbtn = document.createElement('button');
startbtn.className = "opbtns-start";
startbtn.innerText = "开始游戏";
this.startbtn = btns.appendChild(startbtn);
let tipcon = document.createElement('div');
tipcon.className = 'tipcon';
this.tipcon = this.grassel.appendChild(tipcon)
}
// 生成蛇节点
addSnakeNodes () {
this.grassel || this.buildGrass();
let selnodes = this.grassel.querySelectorAll('.snake-node');
let x = 0;
let y = 0;
if (selnodes && selnodes.length) {
let lastnode = selnodes[selnodes.length - 1];
x = lastnode.offsetLeft;
y = lastnode.offsetTop;
switch(this.cudire) {
case 'r':
x -= this.options.snodesize;
break;
case 'l':
x += this.options.snodesize;
break;
case 't':
y += this.options.snodesize;
break;
case 'b':
y -= this.options.snodesize;
break;
}
}
let snakenode = document.createElement('div');
snakenode.className = 'snake-node';
snakenode.setAttribute('style', `left: ${x}px; top: ${y}px;`)
this.snake.appendChild(snakenode);
}
// 生成网格
buildGid () {
let col = Math.floor(this.options.grasswidth / this.options.snodesize); // 列数
let row = Math.floor(this.options.grassheight / this.options.snodesize); // 行数
for (let i = 0; i < row; i++) {
for(let j = 0; j < col; j++) {
this.grassgrid.x.push(i * this.options.snodesize); // 加入每个小格子
this.grassgrid.y.push(j * this.options.snodesize); // 加入每个小格子
this.grassgrid.el.push((i * this.options.snodesize) + '_' + (j * this.options.snodesize)); // 加入每个小格子
}
}
}
// 获取蛇的网格
getSnakeGrid () {
let els = this.grassel.querySelectorAll('.snake-node');
let obj= { x: [], y: [], el: [] }
els.forEach(item => {
obj.x.push(item.offsetLeft); // 加入每个小格子
obj.y.push(item.offsetTop); // 加入每个小格子
obj.el.push(item.offsetLeft + '_' + item.offsetTop); // 加入每个小格子
})
this.snakegrid = obj;
}
// 随机出现食物
addfood (){
// 获取蛇的网格
this.getSnakeGrid()
let arr = { x: [], y: [], el: [] };
arr.el = this.grassgrid.el.filter((item, index) => {
if (!this.snakegrid.el.includes(item)) {
arr.x.push(this.grassgrid.x[index])
arr.y.push(this.grassgrid.y[index])
return true
}
return false
});
if (arr.x.length) {
let index = Math.floor(Math.random() * arr.x.length);
if (this.food) {
this.food.style.left = arr.x[index] + 'px';
this.food.style.top = arr.y[index] + 'px';
} else {
let foodel = document.createElement('div');
foodel.style.left = arr.x[index] + 'px';
foodel.style.top = arr.y[index] + 'px';
foodel.className = 'food'
this.food = this.grassel.appendChild(foodel);
}
}
}
// 判断是否碰壁
crossBorder () {
if (this.grassgrid.el.length && (this.grassgrid.el.length == this.snakegrid.el.length)) {
this.gameover = true;
console.log('游戏胜利');
this.showTip('游戏胜利');
return
}
let dir = this.cudire
let el = this.grassel.querySelector('.snake-node')
if (dir == 'r' && el.offsetLeft >= this.grassgrid.x[this.grassgrid.x.length - 1] ||
dir == 'l' && el.offsetLeft <= 0 ||
dir == 'b' && el.offsetTop >= this.grassgrid.y[this.grassgrid.y.length - 1] ||
dir == 't' && el.offsetTop <= 0
) {
this.gameover = true
this.showTip('游戏结束');
console.log('游戏结束')
}
}
// 判断是否碰到身体
crossSelf () {
this.getSnakeGrid();
for (let i = 1; i < this.snakegrid.el.length; i++) {
if (this.snakegrid.el[i] == this.snakegrid.el[0]) {
this.gameover = true
console.log('游戏结束')
this.showTip('游戏结束');
break
}
}
}
// 蛇爬动
snakeClimb () {
this.crossBorder(); // 判断蛇头是否碰壁
this.crossSelf(); // 判断蛇头是否碰到身体
if (!this.gameover && !this.isplaying) { // 如果游戏没有结束且蛇没有在爬动
this.isplaying = true
let snakenode = this.grassel.querySelectorAll('.snake-node'); // 蛇的所有关节小格
let preposition = { x: 0, y: 0 }; // 记录上一个蛇关节的位置
for (let i = 0; i < snakenode.length; i++) {
let item = snakenode[i]
if (i == 0) { // 如果是蛇头
switch(this.cudire) {
case 'r': // 如果向右移动,则蛇头小格left增加一小格宽度
item.style.left = (item.offsetLeft + this.options.snodesize) + 'px';
break;
case 'l': // 如果向左移动,则蛇头小格left减少一小格宽度
item.style.left = (item.offsetLeft - this.options.snodesize) + 'px';
break;
case 't': // 如果向上移动,则蛇头小格top减少一小格宽度
item.style.top = (item.offsetTop - this.options.snodesize) + 'px';
break;
case 'b': // 如果向下移动,则蛇头小格top增加一小格宽度
item.style.top = (item.offsetTop + this.options.snodesize) + 'px';
break;
}
} else { // 蛇身体关节小格移动位置为上一个关节小格的位置
item.style.left = preposition.x + 'px';
item.style.top = preposition.y + 'px';
}
preposition = { x: item.offsetLeft, y: item.offsetTop }
}
}
}
// 转变方向
snakeTurn () {
this.grassel.addEventListener('click', function (e) {
let selnode = this.grassel.querySelector('.snake-node');
let x = e.offsetX; // 鼠标x坐标
let y = e.offsetY; // 鼠标y坐标
// 如果是水平方向移动,则方向只能改为上下方向;如果是垂直方向移动,则方向只能是左右方向
if ((this.cudire == 'r' || this.cudire == 'l') && (selnode.offsetTop >= y || selnode.offsetTop + this.options.snodesize <= y)) {
if (y > selnode.offsetTop) {
this.cudire = 'b'
} else {
this.cudire = 't'
}
} else if ((this.cudire == 't' || this.cudire == 'b') && (selnode.offsetLeft >= x || selnode.offsetLeft + this.options.snodesize <= x)) {
if (x > selnode.offsetLeft) {
this.cudire = 'r'
} else {
this.cudire = 'l'
}
}
if (this.gamestart && !this.isplaying) {
this.snakeClimb();
}
}.bind(this), false)
}
// 判断是否碰到食物
getFood () {
let sel = this.grassel.querySelector('.snake-node');
if (sel.offsetLeft == this.food.offsetLeft && sel.offsetTop == this.food.offsetTop) {
return true
} else {
return false
}
}
// 吃东西
eatfood () {
this.addSnakeNodes(); // 增加蛇关节
this.addfood(); // 改变食物位置
}
// 开始爬动
play () {
this.gamestart = true;
if (!this.isplaying) {
this.snakeClimb();
}
function run (e) {
e.preventDefault();
this.isplaying = false;
if (this.getFood()) {
this.eatfood();
}
this.snakeClimb();
}
this.grassel.querySelector('.snake-node').removeEventListener('transitionend', run.bind(this), false);
this.grassel.querySelector('.snake-node').addEventListener('transitionend', run.bind(this), false);
}
// 重置游戏
reStart () {
this.snakegrid = { x: [], y: [], el: [] }
this.cudire = 'r';
this.isplaying = false;
this.gameover = false;
this.gamestart = false;
this.snake.innerHTML = '';
this.addSnakeNodes();
this.addfood();
this.hideTip()
}
// 显示提示框
showTip (msg) {
this.tipcon.style.display = 'flex';
this.tipcon.innerText = msg;
}
// 隐藏提示框
hideTip () {
this.tipcon.style.display = 'none';
this.tipcon.innerText = '';
}
}
let snake = new RetroSnaker();
</script>
</body>
</html>