用原生js做个贪吃蛇
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#bg {
width: 600px;
height: 600px;
border: solid 5px black;
background: aliceblue;
position: relative;
overflow: hidden;
}
.body {
width: 20px;
height: 20px;
background: blue;
position: absolute;
}
.head {
background: red;
}
.head::before {
content: '';
display: block;
width: 1200px;
height: 0;
border-top: solid 1px red;
position: absolute;
top: 10px;
left: -600px;
}
.head::after {
content: '';
display: block;
width: 0;
height: 1200px;
border-left: solid 1px red;
position: absolute;
top: -600px;
left: 10px;
}
.food {
background: green;
border-radius: 10px;
}
</style>
</head>
<body>
<div id="bg"></div>
</body>
<script src="snake.js"></script>
</html>
const bg = document.getElementById('bg');
class Snake {
head = new SnakeHead(0, 0);
dir = {
x: 1,
y: 0,
};
body = [];
food;
timer;
get allBody() {
return [...this.body, this.head];
}
nextPosition() {
let nextX = this.dir.x + this.head.x;
let nextY = this.dir.y + this.head.y;
nextX = nextX < 0 ? 29 : nextX < 30 ? nextX : 0;
nextY = nextY < 0 ? 29 : nextY < 30 ? nextY : 0;
return [nextX, nextY];
}
move() {
const [nextX, nextY] = this.nextPosition();
if (this.body.some(x => x.x === nextX && x.y === nextY)) {
return this.gameOver();
}
if (nextX === this.food.x && nextY === this.food.y) {
return this.eat();
}
if (this.body.length) {
const lastBody = this.body.splice(0, 1)[0];
lastBody.x = this.head.x;
lastBody.y = this.head.y;
this.body.push(lastBody);
}
this.head.x = nextX;
this.head.y = nextY;
}
eat() {
this.food.x = this.head.x;
this.food.y = this.head.y;
this.head.x += this.dir.x;
this.head.y += this.dir.y;
this.body.push(this.food);
this.food.toBody();
this.addFood();
}
addFood() {
const xCount = this.allBody.reduce((m, x) => {
if (m[x.x]) {
m[x.x]++;
} else {
m[x.x] = 1;
}
return m;
}, {});
const someX = [];
for (let i = 0; i < 30; ++i) {
if (!xCount[i] || xCount[i] < 30) {
someX.push(i);
}
}
const randomX = someX[Math.floor(Math.random() * someX.length)];
const bodyOnTargetX = this.allBody.filter(x => x.x === randomX).map(x => x.y);
const someY = [];
for (let i = 0; i < 30; ++i) {
if (!bodyOnTargetX.includes(i)) {
someY.push(i);
}
}
const randomY = someY[Math.floor(Math.random() * someY.length)];
this.food = new Food(randomX, randomY);
}
run() {
this.addFood();
this.timer = setInterval(() => this.move(), 300);
document.onkeydown = e => {
const firstBody = this.body[this.body.length - 1];
switch (e.key) {
case 'ArrowUp':
if (firstBody && firstBody.y < this.head.y) return;
if (this.dir.x === 0 && this.dir.y === -1) {
return this.move();
}
this.dir.x = 0;
this.dir.y = -1;
return;
case 'ArrowDown':
if (firstBody && firstBody.y > this.head.y) return;
if (this.dir.x === 0 && this.dir.y === 1) {
return this.move();
}
this.dir.x = 0;
this.dir.y = 1;
return;
case 'ArrowLeft':
if (firstBody && firstBody.x < this.head.x) return;
if (this.dir.x === -1 && this.dir.y === 0) {
return this.move();
}
this.dir.x = -1;
this.dir.y = 0;
return;
case 'ArrowRight':
if (firstBody && firstBody.x > this.head.x) return;
if (this.dir.x === 1 && this.dir.y === 0) {
return this.move();
}
this.dir.x = 1;
this.dir.y = 0;
return;
}
}
}
gameOver() {
clearInterval(this.timer);
document.onkeydown = null;
alert('game over');
}
}
class SnakeBody {
constructor(x, y) {
this.body = document.createElement('div');
this.x = x;
this.y = y;
bg.append(this.body);
this.body.classList.add('body');
}
get x() {
return this._x;
}
set x(val) {
this._x = val;
this.body.style.left = val * 20 + 'px';
}
get y() {
return this._y;
}
set y(val) {
this._y = val;
this.body.style.top = val * 20 + 'px';
}
}
class SnakeHead extends SnakeBody {
constructor(x, y) {
super(x, y);
this.body.classList.add('head');
}
}
class Food extends SnakeBody {
constructor(x, y) {
super(x, y);
this.body.classList.add('food');
}
toBody() {
this.body.classList.remove('food');
}
}
new Snake().run();