上水课闲的无聊至极的时候拿DeepSeek生成的一个小游戏,觉得蛮好玩的就挂上了。
玩法操作:点击屏幕或按空格控制重力方向,躲开红色障碍挑战高分。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>重力翻转</title>
<style>
*{margin:0;padding:0;box-sizing:border-box;}
html,body{
width:100%;height:100%;overflow:hidden;
background:#080810;
touch-action:none;
-webkit-user-select:none;user-select:none;
-webkit-tap-highlight-color:transparent;
}
canvas{display:block;position:fixed;inset:0;}
</style>
</head>
<body>
<canvas id="game"></canvas>
<script>
(() => {
"use strict";
const cvs = document.getElementById('game');
const ctx = cvs.getContext('2d');
let W = 0, H = 0, DPR = 1;
let ceilY = 80, groundY = 700;
const player = { x: 0, y: 0, vy: 0, size: 26 };
let state = 'menu'; // menu | playing | dead
let grav = 1; // 1 = 向下, -1 = 向上
let obstacles = [];
let particles = [];
let score = 0;
let best = 0;
let elapsed = 0;
let spawnTimer = 0;
let speed = 380;
let shake = 0;
let gridOffset = 0;
let lastSide = 'floor';
let deadTime = 0;
try { best = parseInt(localStorage.getItem('gravityFlipBest') || '0', 10) || 0; } catch (e) { best = 0; }
/* ---------------- 尺寸 ---------------- */
function resize() {
DPR = Math.min(window.devicePixelRatio || 1, 2);
W = window.innerWidth;
H = window.innerHeight;
cvs.width = Math.floor(W * DPR);
cvs.height = Math.floor(H * DPR);
cvs.style.width = W + 'px';
cvs.style.height = H + 'px';
ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
const pad = Math.max(60, Math.min(H * 0.13, 110));
ceilY = pad;
groundY = H - pad;
player.size = Math.max(18, Math.min(30, (groundY - ceilY) * 0.055));
player.x = Math.min(W * 0.25, 200);
if (state === 'menu') {
player.y = groundY - player.size;
} else {
player.y = Math.max(ceilY, Math.min(player.y, groundY - player.size));
}
}
/* ---------------- 工具 ---------------- */
function rrect(x, y, w, h, r) {
r = Math.min(r, w / 2, h / 2);
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();
}
function centerText(text, y, size, color, weight, glow) {
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = (weight || '700') + ' ' + size +
'px system-ui,-apple-system,"PingFang SC","Microsoft YaHei",sans-serif';
if (glow) { ctx.shadowColor = glow; ctx.shadowBlur = 24; }
ctx.fillStyle = color;
ctx.fillText(text, W / 2, y);
ctx.restore();
}
/* ---------------- 游戏逻辑 ---------------- */
function reset() {
player.x = Math.min(W * 0.25, 200);
player.y = groundY - player.size;
player.vy = 0;
grav = 1;
obstacles.length = 0;
particles.length = 0;
score = 0;
elapsed = 0;
speed = 380;
spawnTimer = 1.15;
shake = 0;
lastSide = 'floor';
deadTime = 0;
state = 'playing';
}
function spawnObstacle() {
const corridor = groundY - ceilY;
const h = corridor * 0.52;
const w = Math.max(30, Math.min(52, W * 0.04));
let side;
if (Math.random() < 0.65) {
side = lastSide === 'floor' ? 'ceil' : 'floor';
} else {
side = lastSide;
}
lastSide = side;
obstacles.push({ x: W + 30, w: w, h: h, side: side, scored: false });
}
function collide(o) {
const ps = player.size;
let oy, oh = o.h;
if (o.side === 'floor') oy = groundY - oh;
else oy = ceilY;
return player.x < o.x + o.w &&
player.x + ps > o.x &&
player.y < oy + oh &&
player.y + ps > oy;
}
function burst(x, y, n, power, colors) {
for (let i = 0; i < n; i++) {
const a = Math.random() * Math.PI * 2;
const sp = power * (0.3 + Math.random());
const life = 0.4 + Math.random() * 0.5;
particles.push({
x: x, y: y,
vx: Math.cos(a) * sp,
vy: Math.sin(a) * sp,
life: life, maxLife: life,
r: 1.2 + Math.random() * 3,
color: colors[(Math.random() * colors.length) | 0]
});
}
}
function die() {
state = 'dead';
deadTime = 0;
shake = 20;
if (score > best) {
best = score;
try { localStorage.setItem('gravityFlipBest', String(best)); } catch (e) {}
}
burst(player.x + player.size / 2, player.y + player.size / 2, 46, 380, ['#00f0ff', '#ff2e63', '#ffffff']);
}
function flip() {
if (state === 'playing') {
grav *= -1;
player.vy = 0;
shake = Math.max(shake, 7);
burst(player.x + player.size / 2, player.y + player.size / 2, 12, 220, ['#00f0ff', '#9dfcff']);
} else if (state === 'menu') {
reset();
} else if (state === 'dead' && deadTime > 0.4) {
reset();
}
}
/* ---------------- 更新 ---------------- */
function update(dt) {
gridOffset = (gridOffset + (state === 'playing' ? speed : 150) * dt) % 100000;
if (shake > 0) shake = Math.max(0, shake - dt * 48);
if (state === 'dead') deadTime += dt;
// 粒子
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.vx * dt;
p.y += p.vy * dt;
p.vx *= 0.96;
p.vy *= 0.96;
p.vy += 220 * dt * 0.15;
p.life -= dt;
if (p.life <= 0) particles.splice(i, 1);
}
if (state !== 'playing') return;
elapsed += dt;
speed = 380 + Math.min(400, elapsed * 13);
/* --- 玩家物理 --- */
const G = 7200;
player.vy += G * grav * dt;
player.vy = Math.max(-3600, Math.min(3600, player.vy));
player.y += player.vy * dt;
if (player.y < ceilY) { player.y = ceilY; player.vy = 0; }
const botLim = groundY - player.size;
if (player.y > botLim) { player.y = botLim; player.vy = 0; }
/* --- 障碍移动 --- */
for (let i = 0; i < obstacles.length; i++) obstacles[i].x -= speed * dt;
/* --- 计分 / 回收 --- */
for (let i = obstacles.length - 1; i >= 0; i--) {
const o = obstacles[i];
if (!o.scored && o.x + o.w < player.x) {
o.scored = true;
score++;
}
if (o.x + o.w < -60) obstacles.splice(i, 1);
}
/* --- 生成 --- */
spawnTimer -= dt;
if (spawnTimer <= 0) {
spawnObstacle();
spawnTimer = Math.max(0.52, 0.95 - elapsed * 0.009);
}
/* --- 拖尾粒子 --- */
const cx = player.x + player.size / 2;
const cy = player.y + player.size / 2;
for (let i = 0; i < 2; i++) {
const life = 0.35 + Math.random() * 0.2;
particles.push({
x: cx + (Math.random() - 0.5) * player.size,
y: cy + (Math.random() - 0.5) * player.size,
vx: -speed * 0.22 + (Math.random() - 0.5) * 70,
vy: (Math.random() - 0.5) * 70 - grav * 40,
life: life, maxLife: life,
r: 0.8 + Math.random() * 2.2,
color: '#00f0ff'
});
}
/* --- 碰撞 --- */
for (let i = 0; i < obstacles.length; i++) {
if (collide(obstacles[i])) { die(); break; }
}
}
/* ---------------- 绘制 ---------------- */
function drawBg() {
const g = ctx.createLinearGradient(0, 0, 0, H);
g.addColorStop(0, '#080810');
g.addColorStop(0.5, '#0d0d1c');
g.addColorStop(1, '#080810');
ctx.fillStyle = g;
ctx.fillRect(0, 0, W, H);
const gs = 64;
const off = gridOffset % gs;
ctx.strokeStyle = 'rgba(90,120,255,0.075)';
ctx.lineWidth = 1;
ctx.beginPath();
for (let x = -off; x < W + gs; x += gs) {
ctx.moveTo(x, ceilY);
ctx.lineTo(x, groundY);
}
ctx.stroke();
}
function drawWalls() {
// 天花板
let g = ctx.createLinearGradient(0, 0, 0, ceilY);
g.addColorStop(0, 'rgba(70,100,255,0.22)');
g.addColorStop(1, 'rgba(70,100,255,0.015)');
ctx.fillStyle = g;
ctx.fillRect(0, 0, W, ceilY);
ctx.save();
ctx.shadowColor = '#4d6bff';
ctx.shadowBlur = 16;
ctx.fillStyle = '#5f7dff';
ctx.fillRect(0, ceilY - 2, W, 2);
ctx.restore();
// 地面
g = ctx.createLinearGradient(0, groundY, 0, H);
g.addColorStop(0, 'rgba(70,100,255,0.22)');
g.addColorStop(1, 'rgba(70,100,255,0.015)');
ctx.fillStyle = g;
ctx.fillRect(0, groundY, W, H - groundY);
ctx.save();
ctx.shadowColor = '#4d6bff';
ctx.shadowBlur = 16;
ctx.fillStyle = '#5f7dff';
ctx.fillRect(0, groundY, W, 2);
ctx.restore();
}
function drawObstacles() {
for (let i = 0; i < obstacles.length; i++) {
const o = obstacles[i];
let oy;
if (o.side === 'floor') oy = groundY - o.h;
else oy = ceilY;
ctx.save();
ctx.shadowColor = '#ff2e63';
ctx.shadowBlur = 22;
const g = ctx.createLinearGradient(0, oy, 0, oy + o.h);
if (o.side === 'floor') {
g.addColorStop(0, '#ff2e63');
g.addColorStop(1, '#3d0b1c');
} else {
g.addColorStop(0, '#3d0b1c');
g.addColorStop(1, '#ff2e63');
}
ctx.fillStyle = g;
rrect(o.x, oy, o.w, o.h, 6);
ctx.fill();
// 尖端高光
ctx.shadowBlur = 0;
ctx.fillStyle = 'rgba(255,225,240,0.9)';
if (o.side === 'floor') ctx.fillRect(o.x + 4, oy, o.w - 8, 3);
else ctx.fillRect(o.x + 4, oy + o.h - 3, o.w - 8, 3);
ctx.restore();
}
}
function drawPlayer() {
const p = player;
ctx.save();
ctx.shadowColor = '#00f0ff';
ctx.shadowBlur = 26;
const g = ctx.createLinearGradient(p.x, p.y, p.x + p.size, p.y + p.size);
g.addColorStop(0, '#9ffcff');
g.addColorStop(1, '#00b8d4');
ctx.fillStyle = g;
rrect(p.x, p.y, p.size, p.size, 7);
ctx.fill();
ctx.shadowBlur = 0;
ctx.fillStyle = 'rgba(255,255,255,0.85)';
rrect(p.x + p.size * 0.3, p.y + p.size * 0.3, p.size * 0.4, p.size * 0.4, 3);
ctx.fill();
ctx.restore();
}
function drawParticles() {
for (let i = 0; i < particles.length; i++) {
const p = particles[i];
const a = Math.max(0, p.life / p.maxLife);
ctx.globalAlpha = a * 0.85;
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, p.r * (0.4 + a * 0.6), 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1;
}
function drawHud() {
if (state !== 'playing') return;
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = '700 ' + Math.min(44, W * 0.08) +
'px system-ui,-apple-system,"PingFang SC",sans-serif';
ctx.shadowColor = 'rgba(0,240,255,0.85)';
ctx.shadowBlur = 20;
ctx.fillStyle = 'rgba(255,255,255,0.95)';
ctx.fillText(String(score), W / 2, ceilY / 2 + 2);
ctx.restore();
}
function drawMenu() {
ctx.fillStyle = 'rgba(6,6,14,0.7)';
ctx.fillRect(0, 0, W, H);
const t = performance.now() / 1000;
const pulse = 0.55 + 0.45 * Math.sin(t * 3);
centerText('重 力 翻 转', H * 0.34, Math.min(52, W * 0.1), '#ffffff', '800', '#00f0ff');
centerText('点击屏幕 / 空格 → 翻转重力', H * 0.46, Math.min(19, W * 0.045), 'rgba(200,220,255,0.9)', '500');
centerText('躲开红色障碍,活得越久越好', H * 0.52, Math.min(15, W * 0.037), 'rgba(150,170,210,0.65)', '400');
ctx.save();
ctx.globalAlpha = pulse;
centerText('点 击 开 始', H * 0.68, Math.min(24, W * 0.052), '#00f0ff', '700', '#00f0ff');
ctx.restore();
}
function drawDead() {
ctx.fillStyle = 'rgba(255,46,99,' + Math.max(0, 0.28 - deadTime * 0.6) + ')';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = 'rgba(6,6,14,0.72)';
ctx.fillRect(0, 0, W, H);
centerText('撞 到 了', H * 0.33, Math.min(46, W * 0.09), '#ff2e63', '800', '#ff2e63');
centerText('得分 ' + score, H * 0.45, Math.min(30, W * 0.062), '#ffffff', '700');
centerText('最高 ' + best, H * 0.52, Math.min(18, W * 0.042), 'rgba(160,180,220,0.85)', '500');
if (deadTime > 0.4) {
const pulse = 0.55 + 0.45 * Math.sin(performance.now() / 200);
ctx.save();
ctx.globalAlpha = pulse;
centerText('点击重新开始', H * 0.68, Math.min(22, W * 0.05), '#00f0ff', '600', '#00f0ff');
ctx.restore();
}
}
function draw() {
ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
ctx.clearRect(0, 0, W, H);
ctx.save();
if (shake > 0.3) {
ctx.translate((Math.random() - 0.5) * shake, (Math.random() - 0.5) * shake);
}
drawBg();
drawWalls();
drawObstacles();
drawParticles();
if (state !== 'dead') drawPlayer();
ctx.restore();
drawHud();
if (state === 'menu') drawMenu();
else if (state === 'dead') drawDead();
}
/* ---------------- 主循环 ---------------- */
let lastT = 0;
function loop(t) {
if (!lastT) lastT = t;
let dt = (t - lastT) / 1000;
lastT = t;
if (dt > 0.05) dt = 0.05;
if (dt < 0) dt = 0;
update(dt);
draw();
requestAnimationFrame(loop);
}
/* ---------------- 输入 ---------------- */
window.addEventListener('pointerdown', function (e) {
e.preventDefault();
flip();
}, { passive: false });
window.addEventListener('keydown', function (e) {
if (e.repeat) return;
if (e.code === 'Space' || e.code === 'ArrowUp' || e.code === 'KeyW' || e.code === 'Enter') {
e.preventDefault();
flip();
}
});
window.addEventListener('contextmenu', function (e) { e.preventDefault(); });
let rzTimer = null;
window.addEventListener('resize', function () {
clearTimeout(rzTimer);
rzTimer = setTimeout(resize, 80);
});
/* ---------------- 启动 ---------------- */
resize();
requestAnimationFrame(loop);
})();
</script>
</body>
</html>
相关推荐
基于C++的无限扫雷
109 浏览