<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>贪吃蛇游戏</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%);
}
canvas {
border: 1px solid #000;
}
#menu {
height: 50px;
width: 600px;
margin-bottom: 5px;
border-radius: 5px;
background-image: radial-gradient(circle 248px at center, #16d9e3 0%, #30c7ec 47%, #46aef7 100%);
-webkit-box-shadow: 6px 6px 15px -6px rgba(13,13,13,0.51);
box-shadow: 6px 6px 15px -6px rgba(13,13,13,0.51);
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
}
.button {
display: inline-block;
padding: 8px 16px;
margin: 5px;
background-color: #738aff; /* 深蓝色 */
color: #ffffff; /* 白色文字 */
text-align: center;
text-decoration: none;
font-size: 14px;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.button:hover {
background-color: #6a71dd;
}
/* 背景遮罩 */
.overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
}
/* 提示窗 */
.modal {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
z-index: 1001;
}
/* 按钮 */
.modal button {
margin-top: 10px;
float: right;
}
#gameInfo div {
color: white;
}
#leftBtnGroup {
height: 320px;
width: 120px;
border-radius: 5px;
margin-top: 40px;
margin-right: 25px;
display: flex;
justify-content: space-around;
flex-direction: column;
align-content: center;
align-items: center;
}
#rightBtnGroup {
height: 320px;
width: 120px;
border-radius: 5px;
margin-top: 40px;
margin-left: 25px;
display: flex;
justify-content: space-around;
flex-direction: column;
align-content: center;
align-items: center;
}
.directionBtn {
display: flex;
height: 150px;
width: 100px;
background-color: #f9fafb;
font-size: 45px;
border-radius: 25px;
box-shadow: inset 7px 7px 14px #e8e9e9, inset -7px -7px 14px #ffffff;
align-items: center;
justify-content: center;
}
.directionBtn:hover {
background: linear-gradient(145deg, #e0e1e2, #ffffff);
box-shadow: 7px 7px 14px #e8e9e9,
-7px -7px 14px #ffffff;
}
</style>
</head>
<body>
<div id="leftBtnGroup">
<div class="directionBtn" id="upBtn" onclick="updateDirection(38)">
↑
</div>
<div class="directionBtn" id="leftBtn" onclick="updateDirection(37)">
←
</div>
</div>
<div>
<div id="menu">
<div id="gameInfo">
<div id="score">得分: 0</div>
<div id="runTime">00:00:000</div>
</div>
<div>
<button id="startBtn" class="button" onclick="start()">开始游戏</button>
<button id="pauseBtn" class="button" onclick="pause()">暂 停</button>
</div>
</div>
<canvas id="gameCanvas" width="600" height="320"></canvas>
</div>
<div id="rightBtnGroup">
<div class="directionBtn" id="bottomBtn" onclick="updateDirection(40)">
↓
</div>
<div class="directionBtn" id="rightBtn" onclick="updateDirection(39)">
→
</div>
</div>
<!-- 遮罩层 -->
<div class="overlay" id="overlay"></div>
<!-- 提示窗 -->
<div class="modal" id="modal">
<p id="msg">这是一个提示窗!</p>
<button id="closeModalBtn" onclick="restart()">重新开始</button>
</div>
<script type='text/javascript'>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const box = 10;
const canvasWidth = canvas.width / box;
const canvasHeight = canvas.height / box;
// 定时器id
let intervalId;
// 记录是否暂停
let isPaused = false;
// 蛇
let snake = [];
// 页面中食物数量
let footNum = 5;
// 食物
let food = [];
// 当前蛇头方向
let direction = 'RIGHT';
// 后续方向
let nextDirection = "RIGHT";
// 速度
let speed = 0.1;
// 分数
let score = 0;
// 游戏状态
let gameStatus = 0;
// 时间功能
let timer;
let isRunning = false;
let startTime;
let elapsedTime = 0;
function init() {
// 清空数组
snake.length = 0;
food.length = 0;
score = 0;
// 方向
direction = 'RIGHT';
nextDirection = "RIGHT";
// 初始化蛇
snake[0] = { x: 9, y: 10, direction: "RIGHT" };
snake[1] = { x: 8, y: 10, direction: "RIGHT" };
snake[2] = { x: 7, y: 10, direction: "RIGHT" };
snake[3] = { x: 6, y: 10, direction: "RIGHT" };
// 随机食物
generateFood();
// 绑定键盘事件
document.addEventListener('keydown', changeDirection);
// 定时刷新页面
if (intervalId) clearInterval(intervalId);
// 刷新页面
//intervalId = requestAnimationFrame(updateGame)
intervalId = setInterval(updateGame, 10);
}
// 更新游戏状态
function updateGame() {
// 整数 才可执行下一步动作
if (isInteger(snake[0].x) && isInteger(snake[0].y)) {
if (direction !== nextDirection) {
const newDirection = nextDirection;
if (newDirection === 'LEFT' && direction !== 'RIGHT') {
direction = newDirection;
} else if (newDirection === 'RIGHT' && direction !== 'LEFT') {
direction = newDirection;
} else if (newDirection === 'UP' && direction !== 'DOWN') {
direction = newDirection;
} else if (newDirection === 'DOWN' && direction !== 'UP') {
direction = newDirection;
}
}
}
var xValue = 0;
var yValue = 0;
// 确定本次移动后的蛇头坐标
if (direction === 'LEFT') xValue = - speed;
if (direction === 'UP') yValue = - speed;
if (direction === 'RIGHT') xValue = + speed;
if (direction === 'DOWN') yValue = + speed;
// 深拷贝蛇对象
var lastSnake = snake.map(segment => ({ ...segment }));
// 蛇身 每次移动都是后面的 使用前面的坐标
for (var i = lastSnake.length-1; i > 0; i--) {
// 如果是刚吃完食物则蛇尾先不动
if (i === lastSnake.length -1 && lastSnake[lastSnake.length - 1].x === Math.floor(lastSnake[lastSnake.length-2].x) && lastSnake[lastSnake.length - 1].y === Math.floor(lastSnake[lastSnake.length-2].y)) {
continue;
}
var moveX = 0;
var moveY = 0;
// 如果前面的蛇身方向为上下
if (lastSnake[i - 1].direction === "UP" || lastSnake[i - 1].direction === "DOWN") {
moveX = getMoveDirection(lastSnake[i - 1].x, lastSnake[i].x);
if (moveX === 0) {
moveY = getMoveDirection(lastSnake[i - 1].y, lastSnake[i].y);
}
} else {
moveY = getMoveDirection(lastSnake[i - 1].y, lastSnake[i].y);
if (moveY === 0) {
moveX = getMoveDirection(lastSnake[i - 1].x, lastSnake[i].x);
}
}
var newX = Number((lastSnake[i].x + moveX).toFixed(10));
var newY = Number((lastSnake[i].y + moveY).toFixed(10));
var newDir = lastSnake[i].direction;
// 如果是整数说明 可能需要调整方向 蛇神需要使用前面的方向
if (isInteger(newX) && isInteger(newY)) {
newDir = lastSnake[i-1].direction;
}
lastSnake[i] = {
x: newX,
y: newY,
direction: newDir
}
}
// 由于函数执行会消耗时间 所以蛇定位数据会出现不准确的情况 所以在这里更新为准确数据
lastSnake[0] = {
x: Number((lastSnake[0].x + xValue).toFixed(10)),
y: Number((lastSnake[0].y + yValue).toFixed(10)),
direction: direction
};
snake = lastSnake;
// 检查蛇是否撞到边界或自己
if (snake[0].x >= canvasWidth || snake[0].x < 0 || snake[0].y >= canvasHeight || snake[0].y < 0 || collision(snake[0], snake.slice(1))) {
clearInterval(intervalId);
stopTimer();
showModal("Game Over! 最终得分: " + score);
return;
}
// 吃到食物
for (let i = 0; i < footNum; i++) {
if (snake[0].x === food[i].x && snake[0].y === food[i].y) {
score++;
snake.push({...snake[snake.length - 1]}); // 增加蛇的长度
generateFood(i);
}
}
// 绘制画布
draw();
if (intervalId) clearInterval(intervalId);
intervalId = setInterval(updateGame, 10);
}
// 生成食物
function generateFood(footIndex) {
// 食物被吃后 补充食物
if (isInteger(footIndex) && footIndex >= 0 && footIndex < 5) {
food[footIndex] = {
x: Math.floor(Math.random() * canvasWidth),
y: Math.floor(Math.random() * canvasHeight),
foodShape: Math.floor(Math.random() * (3 - 1 + 1)) + 1
}
}
// 初始
else {
for (let i = 0; i < footNum; i++) {
food[i] = {
x: Math.floor(Math.random() * canvasWidth),
y: Math.floor(Math.random() * canvasHeight),
foodShape: Math.floor(Math.random() * (3 - 1 + 1)) + 1
}
}
}
}
// 按钮切换方向
function updateDirection(btn) {
const event = {
keyCode: btn
}
changeDirection(event);
}
// 改变蛇的移动方向
function changeDirection(event) {
if (event.keyCode === 37 && direction !== 'RIGHT') nextDirection = 'LEFT';
if (event.keyCode === 38 && direction !== 'DOWN') nextDirection = 'UP';
if (event.keyCode === 39 && direction !== 'LEFT') nextDirection = 'RIGHT';
if (event.keyCode === 40 && direction !== 'UP') nextDirection = 'DOWN';
}
// 计算移动方向
const getMoveDirection = (previous, current) => {
const diff = Number((previous - current).toFixed(10));
if (diff > 0) return speed;
if (diff === 0) return 0;
return -speed;
};
// 检查是否碰到自己
function collision(head, array) {
for (let i = 0; i < array.length; i++) {
if (head.x === array[i].x && head.y === array[i].y) return true;
}
return false;
}
// 绘制游戏画面
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制蛇
for (let i = snake.length-1; i >= 0; i--) {
ctx.fillStyle = i === 0 ? 'green' : 'lightgreen';
ctx.fillRect(snake[i].x * box, snake[i].y * box, box, box);
// 处于蛇神转弯处把 转弯处的节点点亮
if (i !== 0) {
if ((!isInteger(snake[i].x) && !isInteger(snake[i-1].y)) || (!isInteger(snake[i].y) && !isInteger(snake[i-1].x))) {
var curveX = isInteger(snake[i].x) ? snake[i].x : snake[i-1].x;
var curveY = isInteger(snake[i].y) ? snake[i].y : snake[i-1].y;
ctx.fillRect(curveX * box, curveY * box, box, box);
}
}
}
// 绘制食物
ctx.fillStyle = 'red';
for (let i = 0; i < footNum; i++) {
switch (food[i].foodShape) {
case 1:
ctx.beginPath();
ctx.arc(food[i].x * box + box / 2, food[i].y * box + box / 2, box / 2, 0, 2 * Math.PI);
ctx.fill();
break;
case 2:
ctx.fillRect(food[i].x * box, food[i].y * box, box, box);
break;
case 3:
ctx.beginPath();
ctx.moveTo(food[i].x * box + box / 2, food[i].y * box);
ctx.lineTo(food[i].x * box + box, food[i].y * box + box);
ctx.lineTo(food[i].x * box, food[i].y * box + box);
ctx.closePath();
ctx.fill();
break;
}
}
// 绘制得分
updateScore(score);
}
// 判断是否为整数
const isInteger = (value) => !isNaN(value) && parseInt(value) === Number(value);
// 打开提示弹窗
function showModal(msg) {
const overlay = document.getElementById('overlay');
const modal = document.getElementById('modal');
const msgP = document.getElementById('msg');
msgP.innerText = msg;
overlay.style.display = 'block';
modal.style.display = 'block';
}
// 关闭提示弹窗
function closeModal() {
const overlay = document.getElementById('overlay');
const modal = document.getElementById('modal');
overlay.style.display = 'none';
modal.style.display = 'none';
}
// 启动游戏
function start() {
if (gameStatus === 0) {
// 清楚之前的定时器
if (intervalId) clearInterval(intervalId);
// 初始化游戏
init();
// 计时开始
startTimer();
// 游戏运行状态
gameStatus = 1;
let startBtn = document.getElementById("startBtn");
startBtn.innerText = "重新开始";
}
else {
restart();
}
}
//重新开始
function restart() {
closeModal();
init();
resetTimer();
}
// 暂停/继续
function pause() {
// 暂停
if (intervalId && !isPaused) {
const paragraph = document.getElementById('pauseBtn');
paragraph.innerHTML = '继 续';
clearInterval(intervalId);
isPaused = true;
// 暂停计时
stopTimer();
return;
}
// 继续
if (intervalId && isPaused) {
const paragraph = document.getElementById('pauseBtn');
paragraph.innerHTML = '暂 停';
if (intervalId) clearInterval(intervalId);
// 定时刷新页面
intervalId = setInterval(updateGame, 10);
isPaused = false;
startTimer();
}
}
function startTimer() {
if (isRunning) return;
isRunning = true;
startTime = Date.now() - elapsedTime;
timer = requestAnimationFrame(updateTime);
}
function updateTime() {
if (!isRunning) return;
elapsedTime = Date.now() - startTime;
updateDisplayTime();
timer = requestAnimationFrame(updateTime);
}
function stopTimer() {
if (!isRunning) return;
isRunning = false;
cancelAnimationFrame(timer);
}
function resetTimer() {
stopTimer();
elapsedTime = 0;
updateDisplayTime();
startTimer();
}
// 时间显示
function updateDisplayTime() {
const timeDisplay = document.getElementById('runTime');
timeDisplay.textContent = formatTime(elapsedTime);
}
// 得分显示
function updateScore(score) {
const timeDisplay = document.getElementById('score');
timeDisplay.textContent = "得分: " + score;
}
// 时间格式化
function formatTime(ms) {
const hours = String(Math.floor(ms / 3600000)).padStart(2, '0');
const minutes = String(Math.floor((ms % 3600000) / 60000)).padStart(2, '0');
const seconds = String(Math.floor((ms % 60000) / 1000)).padStart(2, '0');
const milliseconds = String(ms % 1000).padStart(3, '0');
return `${hours}:${minutes}:${seconds}:${milliseconds}`;
}
</script>
</body>
</html>
丝滑的贪吃蛇
最新推荐文章于 2024-11-16 14:48:02 发布