這樓用來發一些AI寫的小遊戲~
仿懷舊太空射擊遊戲

<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Dragon Space Shooter</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: radial-gradient(circle at top, #1a2342 0%, #090d18 55%, #03050a 100%);
color: #f3f4f6;
font-family: Arial, "Noto Sans TC", sans-serif;
}
.wrap {
text-align: center;
padding: 16px;
}
h1 {
margin: 0 0 10px;
font-size: 28px;
letter-spacing: 1px;
}
.hint {
margin: 0 0 12px;
color: #cbd5e1;
font-size: 14px;
}
canvas {
display: block;
margin: 0 auto;
border: 3px solid #7dd3fc;
border-radius: 12px;
background: linear-gradient(to bottom, #07111f, #08101a 45%, #03060d);
box-shadow: 0 0 24px rgba(125, 211, 252, 0.25);
image-rendering: pixelated;
}
.controls {
margin-top: 14px;
display: flex;
justify-content: center;
gap: 12px;
flex-wrap: wrap;
}
button {
border: none;
background: #f97316;
color: white;
padding: 10px 18px;
border-radius: 999px;
font-size: 15px;
cursor: pointer;
font-weight: bold;
box-shadow: 0 6px 14px rgba(249, 115, 22, 0.3);
}
button:hover { filter: brightness(1.08); }
.note {
margin-top: 10px;
font-size: 13px;
color: #94a3b8;
}
</style>
</head>
<body>
<div class="wrap">
<h1>🐉 Dragon Space Shooter</h1>
<p class="hint">操作:← → 移動,空白鍵發射火球</p>
<canvas id="game" width="480" height="640"></canvas>
<div class="controls">
<button id="restartBtn">重新開始</button>
</div>
<div class="note">復古 2D 太空射擊風格,單一 HTML 即可運行</div>
</div>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const restartBtn = document.getElementById('restartBtn');
const WIDTH = canvas.width;
const HEIGHT = canvas.height;
const keys = {
ArrowLeft: false,
ArrowRight: false,
Space: false,
};
let stars = [];
let player;
let bullets;
let enemies;
let score;
let gameOver;
let enemySpawnTimer;
let enemySpawnInterval;
let fireCooldown;
let animationId;
function resetGame() {
player = {
x: WIDTH / 2 - 20,
y: HEIGHT - 90,
width: 40,
height: 48,
speed: 5.5,
};
bullets = [];
enemies = [];
score = 0;
gameOver = false;
enemySpawnTimer = 0;
enemySpawnInterval = 42;
fireCooldown = 0;
stars = Array.from({ length: 70 }, () => ({
x: Math.random() * WIDTH,
y: Math.random() * HEIGHT,
size: Math.random() * 2 + 1,
speed: Math.random() * 1.4 + 0.6,
}));
}
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
function rectsOverlap(a, b) {
return (
a.x < b.x + b.width &&
a.x + a.width > b.x &&
a.y < b.y + b.height &&
a.y + a.height > b.y
);
}
function spawnEnemy() {
const size = 26 + Math.random() * 18;
enemies.push({
x: Math.random() * (WIDTH - size),
y: -size,
width: size,
height: size,
speed: 1.6 + Math.random() * 2.3 + Math.min(score * 0.02, 2.2),
wobble: Math.random() * Math.PI * 2,
wobbleSpeed: 0.03 + Math.random() * 0.04,
});
}
function shoot() {
bullets.push({
x: player.x + player.width / 2 - 4,
y: player.y + 8,
width: 8,
height: 18,
speed: 8.5,
});
}
function update() {
updateStars();
if (gameOver) return;
if (keys.ArrowLeft) player.x -= player.speed;
if (keys.ArrowRight) player.x += player.speed;
player.x = clamp(player.x, 0, WIDTH - player.width);
if (fireCooldown > 0) fireCooldown--;
if (keys.Space && fireCooldown <= 0) {
shoot();
fireCooldown = 10;
}
bullets.forEach((bullet) => {
bullet.y -= bullet.speed;
});
bullets = bullets.filter((bullet) => bullet.y + bullet.height > 0);
enemySpawnTimer++;
if (enemySpawnTimer >= enemySpawnInterval) {
spawnEnemy();
enemySpawnTimer = 0;
enemySpawnInterval = Math.max(18, 42 - Math.floor(score / 5));
}
enemies.forEach((enemy) => {
enemy.y += enemy.speed;
enemy.wobble += enemy.wobbleSpeed;
enemy.x += Math.sin(enemy.wobble) * 1.2;
enemy.x = clamp(enemy.x, 0, WIDTH - enemy.width);
});
for (let i = enemies.length - 1; i >= 0; i--) {
const enemy = enemies[i];
if (rectsOverlap(player, enemy)) {
gameOver = true;
break;
}
let destroyed = false;
for (let j = bullets.length - 1; j >= 0; j--) {
if (rectsOverlap(bullets[j], enemy)) {
bullets.splice(j, 1);
enemies.splice(i, 1);
score += 10;
destroyed = true;
break;
}
}
if (destroyed) continue;
}
enemies = enemies.filter((enemy) => enemy.y < HEIGHT + enemy.height);
}
function updateStars() {
for (const star of stars) {
star.y += star.speed;
if (star.y > HEIGHT) {
star.y = -star.size;
star.x = Math.random() * WIDTH;
star.size = Math.random() * 2 + 1;
star.speed = Math.random() * 1.4 + 0.6;
}
}
}
function drawBackground() {
ctx.clearRect(0, 0, WIDTH, HEIGHT);
for (const star of stars) {
ctx.fillStyle = '#ffffff';
ctx.fillRect(star.x, star.y, star.size, star.size);
}
}
function drawPlayer(p) {
const x = p.x;
const y = p.y;
// wings
ctx.fillStyle = '#7c3aed';
ctx.beginPath();
ctx.moveTo(x + 8, y + 22);
ctx.lineTo(x - 4, y + 40);
ctx.lineTo(x + 10, y + 36);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(x + p.width - 8, y + 22);
ctx.lineTo(x + p.width + 4, y + 40);
ctx.lineTo(x + p.width - 10, y + 36);
ctx.closePath();
ctx.fill();
// body
ctx.fillStyle = '#22c55e';
ctx.beginPath();
ctx.moveTo(x + p.width / 2, y);
ctx.lineTo(x + p.width - 2, y + 18);
ctx.lineTo(x + p.width - 7, y + 42);
ctx.lineTo(x + p.width / 2, y + p.height);
ctx.lineTo(x + 7, y + 42);
ctx.lineTo(x + 2, y + 18);
ctx.closePath();
ctx.fill();
// belly
ctx.fillStyle = '#fde68a';
ctx.beginPath();
ctx.moveTo(x + p.width / 2, y + 10);
ctx.lineTo(x + p.width - 11, y + 28);
ctx.lineTo(x + p.width / 2, y + p.height - 6);
ctx.lineTo(x + 11, y + 28);
ctx.closePath();
ctx.fill();
// horns
ctx.fillStyle = '#f59e0b';
ctx.fillRect(x + 10, y + 6, 4, 8);
ctx.fillRect(x + p.width - 14, y + 6, 4, 8);
// eyes
ctx.fillStyle = '#111827';
ctx.fillRect(x + 14, y + 18, 4, 4);
ctx.fillRect(x + p.width - 18, y + 18, 4, 4);
// fire tail glow
ctx.fillStyle = '#fb923c';
ctx.beginPath();
ctx.moveTo(x + p.width / 2, y + p.height);
ctx.lineTo(x + p.width / 2 - 6, y + p.height + 14);
ctx.lineTo(x + p.width / 2 + 6, y + p.height + 14);
ctx.closePath();
ctx.fill();
}
function drawBullets() {
bullets.forEach((bullet) => {
ctx.fillStyle = '#f97316';
ctx.fillRect(bullet.x, bullet.y, bullet.width, bullet.height);
ctx.fillStyle = '#fde68a';
ctx.fillRect(bullet.x + 2, bullet.y - 4, bullet.width - 4, 6);
});
}
function drawEnemy(enemy) {
const x = enemy.x;
const y = enemy.y;
const w = enemy.width;
const h = enemy.height;
ctx.fillStyle = '#ef4444';
ctx.beginPath();
ctx.moveTo(x + w / 2, y);
ctx.lineTo(x + w, y + h * 0.4);
ctx.lineTo(x + w * 0.8, y + h);
ctx.lineTo(x + w * 0.2, y + h);
ctx.lineTo(x, y + h * 0.4);
ctx.closePath();
ctx.fill();
ctx.fillStyle = '#fecaca';
ctx.fillRect(x + w * 0.28, y + h * 0.34, w * 0.14, h * 0.14);
ctx.fillRect(x + w * 0.58, y + h * 0.34, w * 0.14, h * 0.14);
ctx.fillStyle = '#7f1d1d';
ctx.fillRect(x + w * 0.42, y + h * 0.54, w * 0.16, h * 0.12);
}
function drawHUD() {
ctx.fillStyle = 'rgba(15, 23, 42, 0.7)';
ctx.fillRect(12, 12, 130, 42);
ctx.strokeStyle = '#7dd3fc';
ctx.strokeRect(12, 12, 130, 42);
ctx.fillStyle = '#e2e8f0';
ctx.font = 'bold 22px Arial';
ctx.fillText('Score: ' + score, 24, 40);
}
function drawGameOver() {
ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
ctx.fillRect(0, 0, WIDTH, HEIGHT);
ctx.fillStyle = '#fff';
ctx.textAlign = 'center';
ctx.font = 'bold 42px Arial';
ctx.fillText('GAME OVER', WIDTH / 2, HEIGHT / 2 - 24);
ctx.font = '24px Arial';
ctx.fillText('最終分數:' + score, WIDTH / 2, HEIGHT / 2 + 18);
ctx.font = '18px Arial';
ctx.fillText('按下「重新開始」再玩一次', WIDTH / 2, HEIGHT / 2 + 58);
ctx.textAlign = 'start';
}
function draw() {
drawBackground();
drawHUD();
drawBullets();
enemies.forEach(drawEnemy);
drawPlayer(player);
if (gameOver) {
drawGameOver();
}
}
function gameLoop() {
update();
draw();
animationId = requestAnimationFrame(gameLoop);
}
window.addEventListener('keydown', (e) => {
if (e.code === 'ArrowLeft' || e.code === 'ArrowRight' || e.code === 'Space') {
e.preventDefault();
}
if (e.code in keys) {
keys[e.code] = true;
}
});
window.addEventListener('keyup', (e) => {
if (e.code in keys) {
keys[e.code] = false;
}
});
restartBtn.addEventListener('click', () => {
resetGame();
});
resetGame();
cancelAnimationFrame(animationId);
gameLoop();
</script>
</body>
</html>
有 2 位朋友喜欢这篇文章:shiningdracon, 闪耀
←目前頭像感謝安雅贈圖。

Quit, dont quit... Noodles, dont noodles...
There is a saying: yesterday is history, tomorrow is a mystery, but today is a gift. That is why it is called the present.
离线
參考貪喫蛇遊戲


<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>東方龍貪吃蛇</title>
<style>
:root {
--bg1: #0f172a;
--bg2: #1e293b;
--panel: rgba(15, 23, 42, 0.82);
--gold: #fbbf24;
--gold2: #fde68a;
--jade: #34d399;
--danger: #fb7185;
--text: #e2e8f0;
--muted: #94a3b8;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
font-family: "Segoe UI", "Noto Sans TC", sans-serif;
color: var(--text);
background:
radial-gradient(circle at top, rgba(251, 191, 36, 0.18), transparent 28%),
radial-gradient(circle at bottom right, rgba(52, 211, 153, 0.14), transparent 26%),
linear-gradient(180deg, var(--bg2), var(--bg1));
padding: 20px;
}
.wrap {
width: min(92vw, 760px);
background: var(--panel);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 24px;
box-shadow: 0 24px 60px rgba(0,0,0,0.35);
backdrop-filter: blur(8px);
padding: 20px;
}
.topbar {
display: flex;
gap: 12px;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.title h1 {
margin: 0;
font-size: clamp(26px, 4vw, 36px);
color: var(--gold2);
letter-spacing: 1px;
}
.title p {
margin: 6px 0 0;
color: var(--muted);
font-size: 14px;
}
.stats {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.chip {
background: rgba(255,255,255,0.06);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 999px;
padding: 10px 14px;
font-weight: 700;
font-size: 14px;
}
.chip span {
color: var(--gold2);
margin-left: 6px;
}
.game-box {
position: relative;
display: grid;
place-items: center;
}
canvas {
width: min(92vw, 640px);
height: min(92vw, 640px);
max-width: 640px;
max-height: 640px;
aspect-ratio: 1 / 1;
background:
linear-gradient(180deg, rgba(15, 23, 42, 0.94), rgba(2, 6, 23, 0.96));
border-radius: 20px;
border: 2px solid rgba(251, 191, 36, 0.25);
box-shadow: inset 0 0 40px rgba(0, 0, 0, 0.35);
}
.overlay {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
}
.panel {
pointer-events: auto;
text-align: center;
width: min(84%, 420px);
padding: 24px 22px;
border-radius: 22px;
background: rgba(15, 23, 42, 0.9);
border: 1px solid rgba(255,255,255,0.08);
box-shadow: 0 16px 40px rgba(0,0,0,0.4);
}
.panel h2 {
margin: 0 0 10px;
color: var(--gold2);
font-size: 28px;
}
.panel p {
margin: 8px 0;
color: var(--text);
line-height: 1.6;
}
.actions {
display: flex;
gap: 12px;
justify-content: center;
flex-wrap: wrap;
margin-top: 16px;
}
button {
border: none;
border-radius: 999px;
padding: 12px 20px;
font-size: 15px;
font-weight: 800;
cursor: pointer;
color: #0f172a;
background: linear-gradient(135deg, var(--gold2), var(--gold));
box-shadow: 0 10px 24px rgba(251, 191, 36, 0.22);
transition: transform 0.15s ease, filter 0.15s ease;
}
button:hover {
transform: translateY(-1px);
filter: brightness(1.03);
}
.ghost {
background: rgba(255,255,255,0.08);
color: var(--text);
box-shadow: none;
border: 1px solid rgba(255,255,255,0.08);
}
.footer {
margin-top: 14px;
color: var(--muted);
font-size: 13px;
text-align: center;
line-height: 1.6;
}
.hidden {
display: none;
}
</style>
</head>
<body>
<div class="wrap">
<div class="topbar">
<div class="title">
<h1>東方龍貪吃蛇</h1>
<p>以方向鍵或 WASD 操控龍身,吃靈珠變長,別撞到自己或牆壁。</p>
</div>
<div class="stats">
<div class="chip">分數 <span id="score">0</span></div>
<div class="chip">最高分 <span id="best">0</span></div>
<div class="chip">速度 <span id="speed">1</span></div>
</div>
</div>
<div class="game-box">
<canvas id="game" width="640" height="640"></canvas>
<div class="overlay" id="startOverlay">
<div class="panel">
<h2>開始遊戲</h2>
<p>你將操控一條東方龍在夜空中穿梭,吞食靈珠成長。</p>
<p>操作:方向鍵 / WASD</p>
<div class="actions">
<button id="startBtn">開始</button>
</div>
</div>
</div>
<div class="overlay hidden" id="gameOverOverlay">
<div class="panel">
<h2>遊戲結束</h2>
<p>本次分數:<strong id="finalScore">0</strong></p>
<p id="finalMessage">龍撞上障礙了,再試一次。</p>
<div class="actions">
<button id="restartBtn">重新開始</button>
<button class="ghost" id="closeBtn">查看畫面</button>
</div>
</div>
</div>
</div>
<div class="footer">
小提醒:每吃 4 顆靈珠,速度會提升一級。<br />
直接把這個 HTML 存成檔案後,用瀏覽器打開即可遊玩。
</div>
</div>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const scoreEl = document.getElementById('score');
const bestEl = document.getElementById('best');
const speedEl = document.getElementById('speed');
const startOverlay = document.getElementById('startOverlay');
const gameOverOverlay = document.getElementById('gameOverOverlay');
const finalScoreEl = document.getElementById('finalScore');
const finalMessageEl = document.getElementById('finalMessage');
const startBtn = document.getElementById('startBtn');
const restartBtn = document.getElementById('restartBtn');
const closeBtn = document.getElementById('closeBtn');
const gridCount = 20;
const cell = canvas.width / gridCount;
let dragon = [];
let direction = { x: 1, y: 0 };
let nextDirection = { x: 1, y: 0 };
let orb = { x: 10, y: 10 };
let score = 0;
let best = Number(localStorage.getItem('dragonSnakeBest') || 0);
let level = 1;
let gameOver = false;
let started = false;
let frameTimer = 0;
let stepMs = 160;
let lastTime = 0;
bestEl.textContent = best;
function resetGame() {
dragon = [
{ x: 5, y: 10 },
{ x: 4, y: 10 },
{ x: 3, y: 10 },
{ x: 2, y: 10 }
];
direction = { x: 1, y: 0 };
nextDirection = { x: 1, y: 0 };
score = 0;
level = 1;
stepMs = 160;
gameOver = false;
scoreEl.textContent = score;
speedEl.textContent = level;
spawnOrb();
draw();
}
function spawnOrb() {
let valid = false;
while (!valid) {
orb = {
x: Math.floor(Math.random() * gridCount),
y: Math.floor(Math.random() * gridCount)
};
valid = !dragon.some(part => part.x === orb.x && part.y === orb.y);
}
}
function startGame() {
resetGame();
started = true;
startOverlay.classList.add('hidden');
gameOverOverlay.classList.add('hidden');
lastTime = performance.now();
frameTimer = 0;
requestAnimationFrame(loop);
}
function endGame(reason) {
gameOver = true;
started = false;
if (score > best) {
best = score;
localStorage.setItem('dragonSnakeBest', String(best));
bestEl.textContent = best;
}
finalScoreEl.textContent = score;
finalMessageEl.textContent = reason;
gameOverOverlay.classList.remove('hidden');
}
function update() {
direction = nextDirection;
const head = { ...dragon[0] };
const newHead = {
x: head.x + direction.x,
y: head.y + direction.y
};
if (
newHead.x < 0 || newHead.x >= gridCount ||
newHead.y < 0 || newHead.y >= gridCount
) {
endGame('東方龍撞上邊界了。');
return;
}
if (dragon.some(part => part.x === newHead.x && part.y === newHead.y)) {
endGame('東方龍撞到自己的龍身了。');
return;
}
dragon.unshift(newHead);
if (newHead.x === orb.x && newHead.y === orb.y) {
score += 10;
scoreEl.textContent = score;
if ((score / 10) % 4 === 0) {
level += 1;
speedEl.textContent = level;
stepMs = Math.max(70, stepMs - 12);
}
spawnOrb();
} else {
dragon.pop();
}
}
function drawBackground() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const bg = ctx.createLinearGradient(0, 0, 0, canvas.height);
bg.addColorStop(0, '#0f172a');
bg.addColorStop(1, '#020617');
ctx.fillStyle = bg;
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (let y = 0; y < gridCount; y++) {
for (let x = 0; x < gridCount; x++) {
ctx.fillStyle = (x + y) % 2 === 0 ? 'rgba(255,255,255,0.025)' : 'rgba(255,255,255,0.012)';
ctx.fillRect(x * cell, y * cell, cell, cell);
}
}
for (let i = 0; i < 24; i++) {
const sx = (i * 97) % canvas.width;
const sy = (i * 53) % canvas.height;
ctx.fillStyle = 'rgba(255,255,255,0.18)';
ctx.beginPath();
ctx.arc(sx, sy, 1.4, 0, Math.PI * 2);
ctx.fill();
}
}
function drawOrb() {
const cx = orb.x * cell + cell / 2;
const cy = orb.y * cell + cell / 2;
const glow = ctx.createRadialGradient(cx, cy, 2, cx, cy, cell * 0.45);
glow.addColorStop(0, 'rgba(253, 224, 71, 0.95)');
glow.addColorStop(1, 'rgba(253, 224, 71, 0)');
ctx.fillStyle = glow;
ctx.beginPath();
ctx.arc(cx, cy, cell * 0.48, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#fde68a';
ctx.beginPath();
ctx.arc(cx, cy, cell * 0.22, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#fbbf24';
ctx.lineWidth = 2;
ctx.stroke();
}
function drawDragon() {
dragon.forEach((part, index) => {
const px = part.x * cell;
const py = part.y * cell;
const isHead = index === 0;
if (isHead) {
const headGrad = ctx.createLinearGradient(px, py, px + cell, py + cell);
headGrad.addColorStop(0, '#fde68a');
headGrad.addColorStop(1, '#f59e0b');
ctx.fillStyle = headGrad;
roundRect(px + 3, py + 3, cell - 6, cell - 6, 10, true, false);
ctx.fillStyle = '#7c2d12';
const eyeOffsetX = direction.x === -1 ? 9 : direction.x === 1 ? cell - 13 : 10;
const eye2OffsetX = direction.x === -1 ? 9 : direction.x === 1 ? cell - 13 : cell - 14;
const eyeY = direction.y === 1 ? cell - 13 : 11;
ctx.beginPath();
ctx.arc(px + eyeOffsetX, py + eyeY, 2.3, 0, Math.PI * 2);
ctx.arc(px + eye2OffsetX, py + eyeY, 2.3, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#ef4444';
ctx.lineWidth = 2;
ctx.beginPath();
if (direction.x === 1) {
ctx.moveTo(px + cell - 4, py + cell / 2);
ctx.lineTo(px + cell + 6, py + cell / 2 - 3);
ctx.lineTo(px + cell + 9, py + cell / 2);
ctx.lineTo(px + cell + 6, py + cell / 2 + 3);
} else if (direction.x === -1) {
ctx.moveTo(px + 4, py + cell / 2);
ctx.lineTo(px - 6, py + cell / 2 - 3);
ctx.lineTo(px - 9, py + cell / 2);
ctx.lineTo(px - 6, py + cell / 2 + 3);
} else if (direction.y === -1) {
ctx.moveTo(px + cell / 2, py + 4);
ctx.lineTo(px + cell / 2 - 3, py - 6);
ctx.lineTo(px + cell / 2, py - 9);
ctx.lineTo(px + cell / 2 + 3, py - 6);
} else {
ctx.moveTo(px + cell / 2, py + cell - 4);
ctx.lineTo(px + cell / 2 - 3, py + cell + 6);
ctx.lineTo(px + cell / 2, py + cell + 9);
ctx.lineTo(px + cell / 2 + 3, py + cell + 6);
}
ctx.stroke();
ctx.strokeStyle = '#fef3c7';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(px + 8, py + 7);
ctx.lineTo(px + 11, py + 2);
ctx.moveTo(px + cell - 8, py + 7);
ctx.lineTo(px + cell - 11, py + 2);
ctx.stroke();
} else {
const t = index / dragon.length;
const bodyGrad = ctx.createLinearGradient(px, py, px + cell, py + cell);
bodyGrad.addColorStop(0, `rgba(52, 211, 153, ${0.95 - t * 0.25})`);
bodyGrad.addColorStop(1, `rgba(16, 185, 129, ${0.9 - t * 0.25})`);
ctx.fillStyle = bodyGrad;
roundRect(px + 4, py + 4, cell - 8, cell - 8, 9, true, false);
ctx.strokeStyle = 'rgba(253, 230, 138, 0.55)';
ctx.lineWidth = 1.6;
ctx.beginPath();
ctx.arc(px + cell / 2, py + cell / 2, cell * 0.18, Math.PI * 0.2, Math.PI * 1.8);
ctx.stroke();
}
});
}
function roundRect(x, y, w, h, r, fill, stroke) {
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
if (fill) ctx.fill();
if (stroke) ctx.stroke();
}
function draw() {
drawBackground();
drawOrb();
drawDragon();
}
function loop(timestamp) {
if (!started) return;
const delta = timestamp - lastTime;
lastTime = timestamp;
frameTimer += delta;
while (frameTimer >= stepMs && !gameOver) {
update();
frameTimer -= stepMs;
}
draw();
if (!gameOver) {
requestAnimationFrame(loop);
}
}
function setDirection(x, y) {
if (!started && startOverlay.classList.contains('hidden') === false) return;
if (direction.x === -x && direction.y === -y) return;
nextDirection = { x, y };
}
document.addEventListener('keydown', (e) => {
const key = e.key.toLowerCase();
if (["arrowup", "arrowdown", "arrowleft", "arrowright", "w", "a", "s", "d", " "].includes(key)) {
e.preventDefault();
}
if (!started && (key === ' ' || key === 'enter')) {
startGame();
return;
}
if (key === 'arrowup' || key === 'w') setDirection(0, -1);
else if (key === 'arrowdown' || key === 's') setDirection(0, 1);
else if (key === 'arrowleft' || key === 'a') setDirection(-1, 0);
else if (key === 'arrowright' || key === 'd') setDirection(1, 0);
});
startBtn.addEventListener('click', startGame);
restartBtn.addEventListener('click', startGame);
closeBtn.addEventListener('click', () => gameOverOverlay.classList.add('hidden'));
resetGame();
</script>
</body>
</html>
←目前頭像感謝安雅贈圖。

Quit, dont quit... Noodles, dont noodles...
There is a saying: yesterday is history, tomorrow is a mystery, but today is a gift. That is why it is called the present.
离线