直接复制粘贴就能玩
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport"
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
<title>贪吃蛇</title>
<style>
a, abbr, acronym, address, applet, article, aside, audio, b, big, blockquote, body, canvas, caption, center, cite, code, dd, del, details, dfn, div, dl, dt, em, embed, fieldset, figcaption, figure, footer, form, h1, h2, h3, h4, h5, h6, header, hgroup, html, i, iframe, img, ins, kbd, label, legend, li, mark, menu, nav, object, ol, output, p, pre, q, ruby, s, samp, section, small, span, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, time, tr, tt, u, ul, var, video {
padding: 0;
margin: 0;
box-sizing: border-box;
}
html, body {
padding-top: 20px;
text-align: center;
background-color: #efefef;
}
canvas {
margin: 0 auto;
box-shadow: 0 0 40px #333;
}
.canvasContainer {
position: relative;
display: inline-block;
height: 600px;
}
.pageContainer {
position: relative;
}
.pageContainer > .masked {
position: absolute;
top: 0;
left: 0;
right: 0;
width: 100%;
height: 100%;
background-color: #00000020;
}
.masked > button {
outline: none;
padding: 10px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border: 1px solid #00000020;
border-radius: 5px;
color: #ffffff;
background-color: #cccccc;
font-size: 18px;
font-weight: 700;
cursor: pointer;
box-shadow: 0 0 10px 5px #00000020;
}
.currentScore {
position: absolute;
top: 10px;
left: 10px;
z-index: 999;
font-size: 20px;
}
.remote > div {
font-size: 20px;
}
/*移动端媒体查询样式*/
@media screen and (max-width: 1000px) {
html, body {
padding-top: 0;
width: 100%;
height: 100%;
}
canvas {
width: 100%;
height: 100%;
box-shadow: 0 0 0;
}
.pageContainer, .canvasContainer {
width: 100%;
height: 100%;
}
/*遥控器样式*/
.remote {
position: absolute;
right: 0;
bottom: 0;
background-color: #d1d1d180;
}
.remote > .left, .right, .top, .down {
position: absolute;
width: 0;
height: 0;
background-color: #ffffff50;
border-radius: 8px;
box-shadow: 0 0 10px 5px #00000010;
}
.remote > .left {
top: 50%;
transform: translateY(-50%);
border-top: 20px solid transparent;
border-bottom: 20px solid transparent;
border-right: 40px solid #00000020;
}
.remote > .top {
left: 50%;
transform: translateX(-50%);
border-left: 20px solid transparent;
border-right: 20px solid transparent;
border-bottom: 40px solid #00000020;
}
.remote > .right {
top: 50%;
transform: translateY(-50%);
border-top: 20px solid transparent;
border-bottom: 20px solid transparent;
border-left: 40px solid #00000020;
}
.remote > .down {
left: 50%;
transform: translateX(-50%);
border-left: 20px solid transparent;
border-right: 20px solid transparent;
border-top: 40px solid #00000020;
}
/*竖屏遥控器*/
@media (orientation: portrait ) {
.canvasContainer {
height: 80%;
}
.remote {
left: 0;
width: 100%;
height: 20%;
}
.remote > .top {
top: 5px;
}
.remote > .down {
bottom: 5px;
}
.remote > .left {
left: calc(50% - 80px);
}
.remote > .right {
right: calc(50% - 80px);
}
}
/*横屏遥控器*/
@media ( orientation: landscape ) {
html, body {
width: 100%;
height: 100%;
}
.canvasContainer {
display: block;
width: 80%;
height: 100%;
}
.remote {
top: 0;
width: 20%;
height: 100%;
}
.remote > .top {
top: 30%;
}
.remote > .down {
bottom: 30%;
}
.remote > .left {
left: 5px;
}
.remote > .right {
right: 5px;
}
}
}
</style>
</head>
<body>
<div class="pageContainer">
<div class="canvasContainer">
<canvas id="myCanvas" width="800" height="600">当前浏览器不支持canvas,请更新浏览器。</canvas>
<h4 class="currentScore">分数:0</h4>
</div>
<div class="remote"></div>
<div class="masked">
<button class="start">开始</button>
</div>
</div>
<script>
snakeInit();
function snakeInit() {
// 判断当前是否为移动端
let multiport = getMultiport();
if (multiport === 'mobile') {
let str = `
<div class="left"></div>
<div class="top"></div>
<div class="right"></div>
<div class="down"></div>
`;
const remote = document.querySelector('.remote');
remote.innerHTML = str;
// 获取移动端4个按钮
const left = document.querySelector('.left');
const top = document.querySelector('.top');
const right = document.querySelector('.right');
const down = document.querySelector('.down');
left.ontouchstart = () => snake.direction = 0;
top.ontouchstart = () => snake.direction = 1;
right.ontouchstart = () => snake.direction = 2;
down.ontouchstart = () => snake.direction = 3;
stopUserScalable();
}
const c = document.getElementById("myCanvas");
const ctx = c.getContext("2d");
const masked = document.querySelector('.masked');
// 是否吃到食物开关变量
let isEatFood = false;
// 当前分数变量
let currentScore = 0;
// 定时器开关变量
let timer = null;
// 操作DOM点击开始按钮隐藏遮罩层 并且开始定时器
const btnStart = document.querySelector('.start');
btnStart.onclick = () => {
masked.style.display = 'none';
timer = setInterval(animate, 200);
}
// 画矩形构造函数
function Rect(x, y, width, height, color) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.color = color;
}
// 矩形构造函数原型
Rect.prototype = {
constructor: Rect,
rDraw: function () {
ctx.beginPath();
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.width, this.height);
ctx.strokeRect(this.x, this.y, this.width, this.height);
}
}
// snake构造函数
function Snake() {
// 蛇头
this.head = new Rect(40, 60, 40, 40, 'pink');
// 蛇身
this.body = [];
let {x, y, width, height} = this.head;
const rect = new Rect(x - 40, y, width, height, 'skyblue');
this.body.push(rect);
// 控制蛇的方向 默认向右走
this.direction = 2;
}
// snake构造函数原型
Snake.prototype = {
constructor: Snake,
// 绘制蛇的方法
sDraw: function () {
// 绘制蛇头
this.head.rDraw();
// 遍历蛇身数组 调用绘制矩形的方法
this.body.forEach(reactItem => reactItem.rDraw());
},
// 蛇移动的方法
move: function () {
// 加头
let {x, y, width, height} = this.head;
const rect = new Rect(x, y, width, height, 'skyblue');
this.body.unshift(rect);
isEatFood === false ? this.body.pop() : isEatFood = false;
// 控制蛇行走的方向
switch (this.direction) {
case 0:
this.head.x -= this.head.width;
break;
case 1:
this.head.y -= this.head.height;
break;
case 2:
this.head.x += this.head.width;
break;
case 3:
this.head.y += this.head.height;
break;
}
// 判断是否撞到墙
if (this.head.x >= c.width || this.head.x < 0 || this.head.y >= c.height || this.head.y < 0) {
ctx.clearRect(0, 0, c.width, c.height)
clearInterval(timer);
timer = null;
alert(`Game Over~\n本局分数为${currentScore}分`);
window.location.reload();
}
// 判断蛇头和蛇身是否重叠
this.body.forEach(item => {
if (isRectHit(this.head, item)) {
clearInterval(timer);
timer = null;
alert(`Game Over~\n本局分数为${currentScore}分`);
window.location.reload();
}
})
}
}
// 监听键盘事件 如按下上下左右返回对应的direction 决定蛇行走的方向
document.onkeydown = (e) => {
switch (e.code) {
case 'ArrowLeft':
snake.direction = 0;
break;
case 'ArrowUp':
snake.direction = 1;
break;
case 'ArrowRight':
snake.direction = 2;
break;
case 'ArrowDown':
snake.direction = 3;
break;
}
}
// 创建蛇的实例对象
const snake = new Snake();
// 创造食物的返回值为一个Rect的实例对象
let food = randForFood();
// 游戏开始前先渲染一次
animate()
// 动画函数
function animate() {
ctx.clearRect(0, 0, c.width, c.height);
food.rDraw();
snake.move();
snake.sDraw();
// 判断是否吃到食物 碰撞检测
if (isRectHit(snake.head, food)) {
updateScore();
isEatFood = true;
food = randForFood();
}
}
// 随机创造食物函数
function randForFood() {
// 食物是否在蛇头或者蛇身上开关变量
let isInsSnake = true;
// 如果为true 则一直循环直到false
while (isInsSnake) {
let x = getRandInRange(0, (c.width - 40) / 40) * 40;
let y = getRandInRange(0, (c.height - 40) / 40) * 40;
// 食物矩形
const rect = new Rect(x, y, 40, 40, 'green');
// 判断食物是否与蛇头重叠
if (isRectHit(snake.head, rect)) {
isInsSnake = true;
continue;
}
isInsSnake = false;
// 判断食物是否与蛇身重叠
snake.body.forEach(item => {
if (isRectHit(item, rect)) {
isInsSnake = true;
}
})
return rect;
}
}
// 更新分数方法
function updateScore() {
let str = `分数:${++currentScore}`;
const score = document.querySelector('.currentScore');
score.innerHTML = str;
}
// 获取随机数方法
function getRandInRange(min, max) {
return Math.round(Math.random() * (max - min) + min);
}
// 碰撞检测方法
function isRectHit(rect1, rect2) {
const minX1 = rect1.x;
const minX2 = rect2.x;
const minY1 = rect1.y;
const minY2 = rect2.y;
const maxX1 = rect1.x + rect1.width;
const maxX2 = rect2.x + rect2.width;
const maxY1 = rect1.y + rect1.height;
const maxY2 = rect2.y + rect2.height;
// 判断矩形相交的最大/最小值
const minX = Math.max(minX1, minX2);
const minY = Math.max(minY1, minY2);
const maxX = Math.min(maxX1, maxX2);
const maxY = Math.min(maxY1, maxY2);
if (minX < maxX && minY < maxY) return true;
}
// ios阻止页面被双击放大和双指缩放
function stopUserScalable() {
let lastTouchEnd = 0;
document.addEventListener('touchstart', function (event) {
if (event.touches.length > 1) {
event.preventDefault();
}
});
document.addEventListener('touchend', function (event) {
const now = (new Date()).getTime();
if (now - lastTouchEnd <= 300) {
event.preventDefault();
}
lastTouchEnd = now;
}, false);
document.addEventListener('gesturestart', function (event) {
event.preventDefault();
});
}
// 判断当前处于移动端还是PC端
function getMultiport() {
if (navigator.userAgent.match(/(iPhone|iPad|ipod|Android|ios)/i)) {
return 'mobile';
}
return 'PC';
}
}
/* 贪吃蛇步骤
*
* 1. 画蛇
* 1.1 蛇头蛇身
*
* 2. 让蛇动起来
* 2.1 添加键盘事件
* 2.2 animate运动
*
* 3. 随机投放食物
* 3.1 坐标位置
* 3.2 食物是否投放到了蛇头和蛇身上(数组去重)
*
* 4. 吃食物
* 4.1 碰撞检测
* 4.2 将食物添加到蛇身上
*
* 5.边缘检测,判断游戏是否结束
* 5.1 碰撞检测
* */
</script>
</body>
</html>