- gf24240 的博客
《梦溪笔谈·娱乐》卷一:小恐龙
- 2025-3-29 15:51:18 @
小恐龙

“小恐龙”通常指的是谷歌浏览器(Google Chrome)中的隐藏彩蛋游戏——“恐龙游戏”(Chrome Dino/T-Rex Game)。当浏览器无法连接网络时(断网状态下),页面会显示一个像素风的小恐龙图标,并提示“未连接到互联网”。此时按下键盘的空格键或 ↑键 ,即可启动这款简单的跑酷游戏。
游戏玩法:
- 操作:按空格键或↑键让小恐龙跳跃,躲避仙人掌和飞行翼龙;↓键可以下蹲躲避高障碍物。
- 规则:随着时间推移,游戏速度会逐渐加快,难度增加。
- 彩蛋:即使有网络时,你也可以通过地址栏输入
chrome://dino
直接进入游戏。
html游戏
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>恐龙游戏(修复计分版)</title>
<style>
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f7f7f7;
font-family: 'Courier New', monospace;
overflow: hidden;
-webkit-tap-highlight-color: transparent;
}
#game-container {
position: relative;
width: 800px;
height: 300px;
background-color: white;
border: 2px solid #ddd;
box-shadow: 0 0 20px rgba(0,0,0,0.1);
overflow: hidden;
touch-action: manipulation;
}
canvas {
width: 100%;
height: 100%;
display: block;
}
#score {
position: absolute;
top: 10px;
right: 20px;
font-size: 20px;
color: #666;
z-index: 10;
user-select: none;
-webkit-user-select: none;
}
#high-score {
position: absolute;
top: 10px;
left: 20px;
font-size: 20px;
color: #666;
z-index: 10;
user-select: none;
-webkit-user-select: none;
}
#game-over {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255,255,255,0.8);
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 20;
}
#start-screen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255,255,255,0.9);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 30;
}
h1 {
color: #333;
margin-bottom: 10px;
font-size: 2.5em;
user-select: none;
-webkit-user-select: none;
}
p {
color: #666;
margin-bottom: 30px;
font-size: 1.2em;
user-select: none;
-webkit-user-select: none;
}
button {
padding: 12px 30px;
font-size: 1.2em;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
user-select: none;
-webkit-user-select: none;
touch-action: manipulation;
}
button:active {
transform: scale(0.98);
}
</style>
</head>
<body>
<div id="game-container">
<canvas id="game-canvas"></canvas>
<div id="score">00000</div>
<div id="high-score">00000</div>
<div id="game-over">
<h1>游戏结束!</h1>
<p>你的分数: <span id="final-score">00000</span></p>
<button id="restart-btn">再来一次</button>
</div>
<div id="start-screen">
<h1>恐龙游戏(修复计分版)</h1>
<p>按空格键或上箭头键跳跃,下箭头键下蹲</p>
<button id="start-btn">开始游戏</button>
</div>
</div>
<script>
// 游戏配置
const CONFIG = {
INITIAL_SPEED: 6,
SPEED_INCREMENT: 0.002,
OBSTACLE_INTERVAL: 1000,
MIN_GAP: 300,
JUMP_FORCE: 12,
GRAVITY: 0.4,
BIRD_HEIGHT: 70,
BIRD_PROBABILITY: 0.3,
CLOUD_PROBABILITY: 0.006,
MAX_CLOUDS: 6,
DAY_NIGHT_CYCLE: 10000,
SCORE_INCREMENT: 1 // 每帧增加的分数
};
// 游戏元素
const canvas = document.getElementById('game-canvas');
const ctx = canvas.getContext('2d');
const scoreElement = document.getElementById('score');
const highScoreElement = document.getElementById('high-score');
const finalScoreElement = document.getElementById('final-score');
const gameOverScreen = document.getElementById('game-over');
const startScreen = document.getElementById('start-screen');
const startBtn = document.getElementById('start-btn');
const restartBtn = document.getElementById('restart-btn');
// 设置画布大小
canvas.width = 800;
canvas.height = 300;
// 游戏变量
let score = 0;
let highScore = localStorage.getItem('dinoHighScore') || 0;
let gameSpeed = CONFIG.INITIAL_SPEED;
let isGameOver = false;
let isGameStarted = false;
let isDucking = false;
let animationId;
let clouds = [];
let obstacles = [];
let lastObstacleTime = 0;
let timeOfDay = 0;
let isNight = false;
let stars = [];
let lastScoreUpdateTime = 0;
// 初始化星星
function initStars() {
stars = [];
for (let i = 0; i < 30; i++) {
stars.push({
x: Math.random() * 800,
y: Math.random() * 150,
size: Math.random() * 1.5 + 0.5,
alpha: Math.random() * 0.5 + 0.5
});
}
}
// 游戏对象
const dino = {
x: 50,
y: 0,
width: 50,
height: 50,
duckWidth: 70,
duckHeight: 30,
velocityY: 0,
isJumping: false,
frame: 0,
frameCount: 0,
init: function() {
this.y = 300 - this.height - 20;
this.velocityY = 0;
this.isJumping = false;
this.frame = 0;
this.frameCount = 0;
isDucking = false;
},
update: function() {
this.velocityY += CONFIG.GRAVITY;
this.y += this.velocityY;
const groundY = 300 - (isDucking ? this.duckHeight : this.height) - 20;
if (this.y > groundY) {
this.y = groundY;
this.velocityY = 0;
this.isJumping = false;
}
if (!this.isJumping && !isDucking) {
this.frameCount++;
if (this.frameCount >= 5) {
this.frame = (this.frame + 1) % 2;
this.frameCount = 0;
}
}
},
draw: function() {
ctx.fillStyle = isNight ? '#e0e0e0' : '#535353';
if (isDucking) {
ctx.beginPath();
ctx.roundRect(this.x, this.y + this.height - this.duckHeight, this.duckWidth, this.duckHeight, 5);
ctx.fill();
if (this.frame === 0) {
ctx.fillRect(this.x + 15, this.y + this.height - 10, 20, 10);
ctx.fillRect(this.x + 45, this.y + this.height - 10, 20, 10);
} else {
ctx.fillRect(this.x + 10, this.y + this.height - 10, 20, 10);
ctx.fillRect(this.x + 40, this.y + this.height - 10, 20, 10);
}
} else {
ctx.beginPath();
ctx.roundRect(this.x, this.y, this.width, this.height, 5);
ctx.fill();
ctx.beginPath();
ctx.moveTo(this.x - 5, this.y + this.height - 15);
ctx.lineTo(this.x + 10, this.y + this.height - 5);
ctx.lineTo(this.x - 5, this.y + this.height + 5);
ctx.fill();
if (!this.isJumping) {
if (this.frame === 0) {
ctx.fillRect(this.x + 10, this.y + this.height - 10, 15, 15);
ctx.fillRect(this.x + 35, this.y + this.height - 5, 15, 10);
} else {
ctx.fillRect(this.x + 10, this.y + this.height - 5, 15, 10);
ctx.fillRect(this.x + 35, this.y + this.height - 10, 15, 15);
}
}
}
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(this.x + 35, this.y + 15, 5, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = isNight ? '#e0e0e0' : '#535353';
ctx.beginPath();
ctx.arc(this.x + 37, this.y + 15, 2, 0, Math.PI * 2);
ctx.fill();
},
jump: function() {
if (!this.isJumping && isGameStarted) {
this.velocityY = -CONFIG.JUMP_FORCE;
this.isJumping = true;
this.frame = 0;
this.frameCount = 0;
}
},
duck: function() {
if (!this.isJumping && isGameStarted) {
isDucking = true;
}
},
stand: function() {
isDucking = false;
}
};
// 障碍物类
class Obstacle {
constructor() {
this.type = Math.random() < CONFIG.BIRD_PROBABILITY ? 'bird' : 'cactus';
if (this.type === 'cactus') {
this.cactusType = Math.floor(Math.random() * 3) + 1;
if (this.cactusType === 1) {
this.width = 30;
this.height = 60;
} else if (this.cactusType === 2) {
this.width = 60;
this.height = 60;
} else {
this.width = 90;
this.height = 60;
}
this.y = 300 - this.height - 20;
} else {
this.width = 50;
this.height = 25;
this.y = 300 - CONFIG.BIRD_HEIGHT - 20;
}
this.x = 800;
this.frame = 0;
this.frameCount = 0;
this.scored = false; // 标记是否已经计分
}
update() {
this.x -= gameSpeed;
if (this.type === 'bird') {
this.frameCount++;
if (this.frameCount >= 5) {
this.frame = (this.frame + 1) % 2;
this.frameCount = 0;
}
}
}
draw() {
const obstacleColor = isNight ? '#a0a0a0' : '#535353';
const cactusColor = isNight ? '#3a5f3a' : '#2e8b57';
if (this.type === 'cactus') {
ctx.fillStyle = cactusColor;
if (this.cactusType === 1) {
ctx.beginPath();
ctx.roundRect(this.x, this.y, 20, this.height, 5);
ctx.fill();
ctx.fillRect(this.x - 5, this.y + 25, 5, 5);
} else if (this.cactusType === 2) {
ctx.beginPath();
ctx.roundRect(this.x, this.y, 20, this.height, 5);
ctx.roundRect(this.x + 30, this.y + 10, 20, this.height - 10, 5);
ctx.fill();
ctx.fillRect(this.x + 15, this.y + 25, 5, 5);
} else {
ctx.beginPath();
ctx.roundRect(this.x, this.y + 15, 20, this.height - 15, 5);
ctx.roundRect(this.x + 25, this.y, 20, this.height, 5);
ctx.roundRect(this.x + 50, this.y + 5, 20, this.height - 5, 5);
ctx.fill();
ctx.fillRect(this.x + 15, this.y + 30, 5, 5);
}
} else {
ctx.fillStyle = obstacleColor;
ctx.beginPath();
ctx.ellipse(this.x + 25, this.y + 12, 25, 12, 0, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(this.x + 45, this.y + 5, 7, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = isNight ? '#c0c0c0' : '#ff9900';
ctx.beginPath();
ctx.moveTo(this.x + 52, this.y + 5);
ctx.lineTo(this.x + 60, this.y + 5);
ctx.lineTo(this.x + 52, this.y + 10);
ctx.fill();
ctx.fillStyle = obstacleColor;
if (this.frame === 0) {
ctx.beginPath();
ctx.moveTo(this.x + 15, this.y + 10);
ctx.quadraticCurveTo(this.x, this.y - 5, this.x - 10, this.y + 5);
ctx.quadraticCurveTo(this.x, this.y + 15, this.x + 15, this.y + 15);
ctx.fill();
} else {
ctx.beginPath();
ctx.moveTo(this.x + 15, this.y + 10);
ctx.quadraticCurveTo(this.x, this.y + 25, this.x - 10, this.y + 15);
ctx.quadraticCurveTo(this.x, this.y + 5, this.x + 15, this.y + 5);
ctx.fill();
}
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(this.x + 48, this.y + 3, 2, 0, Math.PI * 2);
ctx.fill();
}
}
isOffScreen() {
return this.x + this.width < 0;
}
collidesWith(dino) {
const dinoLeft = dino.x;
const dinoRight = dino.x + (isDucking ? dino.duckWidth : dino.width);
const dinoTop = dino.y;
const dinoBottom = dino.y + (isDucking ? dino.duckHeight : dino.height);
const obsLeft = this.x;
const obsRight = this.x + this.width;
const obsTop = this.y;
const obsBottom = this.y + this.height;
return !(
dinoRight < obsLeft ||
dinoLeft > obsRight ||
dinoBottom < obsTop ||
dinoTop > obsBottom
);
}
}
// 云朵类
class Cloud {
constructor() {
this.width = 46 + Math.random() * 64;
this.height = 14 + Math.random() * 16;
this.x = 800;
this.y = 30 + Math.random() * 80;
this.speed = 0.2 + Math.random() * 0.8;
}
update() {
this.x -= this.speed;
}
draw() {
const cloudAlpha = isNight ? 0.4 : 0.8;
ctx.fillStyle = `rgba(255, 255, 255, ${cloudAlpha})`;
ctx.beginPath();
ctx.arc(this.x, this.y, this.height/2, 0, Math.PI * 2);
ctx.arc(this.x + this.width/3, this.y - this.height/4, this.height/2, 0, Math.PI * 2);
ctx.arc(this.x + this.width/1.5, this.y, this.height/2, 0, Math.PI * 2);
ctx.fill();
}
isOffScreen() {
return this.x + this.width < 0;
}
}
// 绘制星星
function drawStars() {
ctx.fillStyle = 'white';
stars.forEach(star => {
ctx.globalAlpha = star.alpha * (isNight ? 1 : 0);
const spikes = 5;
const outerRadius = star.size;
const innerRadius = star.size * 0.4;
let rot = Math.PI/2*3;
let x = star.x;
let y = star.y;
let step = Math.PI/spikes;
ctx.beginPath();
ctx.moveTo(star.x, star.y - outerRadius);
for(let i = 0; i < spikes; i++) {
x = star.x + Math.cos(rot) * outerRadius;
y = star.y + Math.sin(rot) * outerRadius;
ctx.lineTo(x, y);
rot += step;
x = star.x + Math.cos(rot) * innerRadius;
y = star.y + Math.sin(rot) * innerRadius;
ctx.lineTo(x, y);
rot += step;
}
ctx.lineTo(star.x, star.y - outerRadius);
ctx.closePath();
ctx.fill();
});
ctx.globalAlpha = 1;
}
// 更新昼夜周期
function updateDayNightCycle(timestamp) {
timeOfDay = (timestamp % CONFIG.DAY_NIGHT_CYCLE) / CONFIG.DAY_NIGHT_CYCLE;
isNight = timeOfDay > 0.5;
const dayColor = '#f7f7f7';
const nightColor = '#0a0a1a';
const r = Math.floor(parseInt(dayColor.substr(1, 2), 16) * (1 - timeOfDay) +
parseInt(nightColor.substr(1, 2), 16) * timeOfDay);
const g = Math.floor(parseInt(dayColor.substr(3, 2), 16) * (1 - timeOfDay) +
parseInt(nightColor.substr(3, 2), 16) * timeOfDay);
const b = Math.floor(parseInt(dayColor.substr(5, 2), 16) * (1 - timeOfDay) +
parseInt(nightColor.substr(5, 2), 16) * timeOfDay);
return `rgb(${Math.floor(r)}, ${Math.floor(g)}, ${Math.floor(b)})`;
}
// 绘制地面
function drawGround() {
const groundColor = isNight ? '#2a2a2a' : '#535353';
const lineColor = isNight ? '#3a3a3a' : '#636363';
ctx.fillStyle = groundColor;
ctx.fillRect(0, 280, 800, 20);
ctx.strokeStyle = lineColor;
ctx.lineWidth = 2;
for (let x = 0; x < 800; x += 40) {
ctx.beginPath();
ctx.moveTo(x, 285 + Math.sin(x * 0.05) * 3);
ctx.lineTo(x + 20, 285 + Math.sin((x + 20) * 0.05) * 3);
ctx.stroke();
}
}
// 游戏主循环
function gameLoop(timestamp) {
if (isGameOver) return;
const bgColor = updateDayNightCycle(timestamp);
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, 800, 300);
drawStars();
updateClouds();
drawClouds();
drawGround();
generateObstacle();
updateObstacles();
drawObstacles();
dino.update();
dino.draw();
// 更新游戏速度
gameSpeed += CONFIG.SPEED_INCREMENT;
// 修复计分问题:每帧增加固定分数,同时障碍物通过时也增加分数
if (timestamp - lastScoreUpdateTime > 100) { // 每100毫秒增加一次分数
score += CONFIG.SCORE_INCREMENT;
lastScoreUpdateTime = timestamp;
}
updateScoreDisplay();
animationId = requestAnimationFrame(gameLoop);
}
// 更新分数显示
function updateScoreDisplay() {
scoreElement.textContent = String(score).padStart(5, '0');
scoreElement.style.color = isNight ? '#e0e0e0' : '#666';
highScoreElement.style.color = isNight ? '#e0e0e0' : '#666';
}
// 开始游戏
function startGame() {
score = 0;
gameSpeed = CONFIG.INITIAL_SPEED;
isGameOver = false;
isGameStarted = true;
obstacles = [];
clouds = [];
dino.init();
lastObstacleTime = 0;
lastScoreUpdateTime = 0;
initStars();
scoreElement.textContent = '00000';
highScoreElement.textContent = String(highScore).padStart(5, '0');
startScreen.style.display = 'none';
gameOverScreen.style.display = 'none';
for (let i = 0; i < 2; i++) {
clouds.push(new Cloud());
clouds[i].x = Math.random() * 800;
}
animationId = requestAnimationFrame(gameLoop);
}
// 结束游戏
function endGame() {
isGameOver = true;
isGameStarted = false;
cancelAnimationFrame(animationId);
if (score > highScore) {
highScore = score;
localStorage.setItem('dinoHighScore', highScore);
highScoreElement.textContent = String(highScore).padStart(5, '0');
}
finalScoreElement.textContent = String(score).padStart(5, '0');
gameOverScreen.style.display = 'flex';
}
// 生成障碍物
function generateObstacle() {
const now = Date.now();
if (now - lastObstacleTime > CONFIG.OBSTACLE_INTERVAL &&
(obstacles.length === 0 ||
obstacles[obstacles.length-1].x < 800 - CONFIG.MIN_GAP)) {
obstacles.push(new Obstacle());
lastObstacleTime = now;
}
}
// 更新障碍物
function updateObstacles() {
for (let i = obstacles.length - 1; i >= 0; i--) {
obstacles[i].update();
// 检测障碍物是否通过恐龙(用于计分)
if (!obstacles[i].scored && obstacles[i].x + obstacles[i].width < dino.x) {
obstacles[i].scored = true;
score += 10; // 每个障碍物通过增加10分
}
if (obstacles[i].collidesWith(dino)) {
endGame();
return;
}
if (obstacles[i].isOffScreen()) {
obstacles.splice(i, 1);
}
}
}
// 绘制障碍物
function drawObstacles() {
obstacles.forEach(obstacle => obstacle.draw());
}
// 更新云朵
function updateClouds() {
for (let i = clouds.length - 1; i >= 0; i--) {
clouds[i].update();
if (clouds[i].isOffScreen()) {
clouds.splice(i, 1);
}
}
if (Math.random() < CONFIG.CLOUD_PROBABILITY && clouds.length < CONFIG.MAX_CLOUDS) {
clouds.push(new Cloud());
}
}
// 绘制云朵
function drawClouds() {
clouds.forEach(cloud => cloud.draw());
}
// 事件监听
function handleKeyDown(e) {
if (e.code === 'Space' || e.key === 'ArrowUp') {
e.preventDefault();
dino.jump();
}
if (e.key === 'ArrowDown') {
e.preventDefault();
dino.duck();
}
if (e.code === 'Space' && !isGameStarted) {
startGame();
}
}
function handleKeyUp(e) {
if (e.key === 'ArrowDown') {
e.preventDefault();
dino.stand();
}
}
function handleStartClick() {
if (!isGameStarted) {
startGame();
}
}
function handleRestartClick() {
startGame();
}
// 添加事件监听
document.addEventListener('keydown', handleKeyDown);
document.addEventListener('keyup', handleKeyUp);
startBtn.addEventListener('click', handleStartClick);
restartBtn.addEventListener('click', handleRestartClick);
// 添加触摸事件支持
document.addEventListener('touchstart', function(e) {
if (!isGameStarted) {
startGame();
} else {
dino.jump();
}
});
// 初始化游戏
dino.init();
initStars();
highScoreElement.textContent = String(highScore).padStart(5, '0');
// 确保按钮可以点击
startBtn.style.pointerEvents = 'auto';
restartBtn.style.pointerEvents = 'auto';
</script>
</body>
</html>