Chrome 断网才能玩的小恐龙,被做成了单文件 HTML:不用断网、不用装任何东西,保存成 .html 双击就能玩。零外部依赖,一个文件不到 30KB。
玩法
- 跳跃:空格 / ↑ / 鼠标点击
- 下蹲:↓(空中按下会快速落地,躲低空鸟用)
- 手机:点屏幕上半跳、下半蹲
- 最高分:存 localStorage,关了浏览器也在
跑 700 分昼夜会切换一次,撞到障碍物结束,死了点一下重来。
实现
- Canvas +
requestAnimationFrame游戏循环 - 恐龙、仙人掌、飞鸟全是字符串矩阵定义的像素精灵,
fillRect逐像素画 - 音效用 Web Audio API 现场合成(振荡器 + 噪声),没有音频文件
- 速度随分数递增,封顶 13
完整代码
保存为 dino.html,双击运行:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chrome Dino Game</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{background:#f7f7f7;display:flex;justify-content:center;align-items:center;height:100vh;overflow:hidden;font-family:'Courier New',monospace}
canvas{image-rendering:pixelated;image-rendering:crisp-edges;max-width:100vw;max-height:100vh}
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
(function(){
"use strict";
const canvas=document.getElementById('c');
const ctx=canvas.getContext('2d');
// --- Constants ---
const W=800,H=200;
const GROUND_Y=160;
const GRAVITY=0.6;
const JUMP_FORCE=-10;
const INITIAL_SPEED=6;
const MAX_SPEED=13;
const SPEED_INCREMENT=0.001;
const PIXEL=2;
canvas.width=W;
canvas.height=H;
// --- Pixel Sprite System ---
function drawSprite(sprite,x,y,color,scale){
scale=scale||PIXEL;
ctx.fillStyle=color;
for(let row=0;row<sprite.length;row++){
for(let col=0;col<sprite[row].length;col++){
if(sprite[row][col]==='1'){
ctx.fillRect(x+col*scale,y+row*scale,scale,scale);
}
}
}
}
// --- Sprite Data ---
// T-Rex standing/running
const DINO_RUN1=[
"0000000011111100",
"0000000111111110",
"0000000110111111",
"0000000111111110",
"0000000111111100",
"0000000111110000",
"0000000111111000",
"1000001111111000",
"1000011111111000",
"1100111111110000",
"1111111111100000",
"0111111111100000",
"0011111111000000",
"0001111110000000",
"0000111100000000",
"0000110110000000",
"0000110011000000",
"0000100001000000",
];
const DINO_RUN2=[
"0000000011111100",
"0000000111111110",
"0000000110111111",
"0000000111111110",
"0000000111111100",
"0000000111110000",
"0000000111111000",
"1000001111111000",
"1000011111111000",
"1100111111110000",
"1111111111100000",
"0111111111100000",
"0011111111000000",
"0001111110000000",
"0000111100000000",
"0000011011000000",
"0000110001000000",
"0000010000000000",
];
const DINO_JUMP=[
"0000000011111100",
"0000000111111110",
"0000000110111111",
"0000000111111110",
"0000000111111100",
"0000000111110000",
"0000000111111000",
"1000001111111000",
"1000011111111000",
"1100111111110000",
"1111111111100000",
"0111111111100000",
"0011111111000000",
"0001111110000000",
"0000110110000000",
"0000110011000000",
"0000000000000000",
"0000000000000000",
];
const DINO_DUCK1=[
"0000000000000000000001111100",
"0000000000000000000011111110",
"0000000000000000000110111111",
"0000000000000000000111111110",
"0000000000000000000111111100",
"0000000000000000000111110000",
"1111110000111111111111100000",
"1111111001111111111110000000",
"1111111111111111111100000000",
"0111111111111111110000000000",
"0001111111111110000000000000",
"0000011111001100000000000000",
"0000011000001100000000000000",
"0000010000000000000000000000",
];
const DINO_DUCK2=[
"0000000000000000000001111100",
"0000000000000000000011111110",
"0000000000000000000110111111",
"0000000000000000000111111110",
"0000000000000000000111111100",
"0000000000000000000111110000",
"1111110000111111111111100000",
"1111111001111111111110000000",
"1111111111111111111100000000",
"0111111111111111110000000000",
"0001111111111110000000000000",
"0000001100011100000000000000",
"0000011000011000000000000000",
"0000000000001000000000000000",
];
const DINO_DEAD=[
"0000000011111100",
"0000000111111110",
"0000000110111111",
"0000000111111110",
"0000000111111100",
"0000000111110000",
"0000000111111000",
"1000001111111000",
"1000011111111000",
"1100111111110000",
"1111111111100000",
"0111111111100000",
"0011111111000000",
"0001111110000000",
"0000110110000000",
"0000110011000000",
"0000100001000000",
"0000000000000000",
];
// Eye patch for dead dino (X eyes)
const DINO_EYE_DEAD=[
"101",
"010",
"101",
];
// Cactus small
const CACTUS_SMALL=[
"000010000",
"000010000",
"000010000",
"100010010",
"100010010",
"100010010",
"100010010",
"100110010",
"100110010",
"101110010",
"101110010",
"111110110",
"111110110",
"011110110",
"001110100",
"000110000",
"000110000",
"000110000",
"000110000",
"000110000",
];
// Cactus large
const CACTUS_LARGE=[
"0000010000000",
"0000010000000",
"0000010000000",
"0000010000000",
"1000010000010",
"1000010000010",
"1000010000010",
"1000010000010",
"1000010000010",
"1000010001010",
"1000010001010",
"1000010101010",
"1000010101010",
"1010010101010",
"1010010101010",
"1010010101010",
"1011010101110",
"1011010101110",
"1111011101110",
"1111011101110",
"0111011101100",
"0111011101100",
"0011011101000",
"0001011001000",
"0001001000000",
"0001001000000",
"0001001000000",
"0001001000000",
"0001001000000",
"0001001000000",
];
// Bird (Pterodactyl) frame 1 - wings up
const BIRD1=[
"000000000000000000100000",
"000000000000000001100000",
"000000000000000011100000",
"000000000000000111000000",
"000000000000001110000000",
"000000000000011100000000",
"000000000000111000000000",
"000000000001110000000000",
"111111111111100000000000",
"111111111111111111111110",
"011111111111111111111100",
"000111111111111111111000",
"000001111111111111100000",
"000000011111111100000000",
"000000000111110000000000",
"000000000001000000000000",
];
// Bird frame 2 - wings level
const BIRD2=[
"000000000000000000000000",
"000000000000000000000000",
"000000000000000000000000",
"000000000000000000000000",
"000000000000000000000000",
"000000000000000000000000",
"000000000000000000000000",
"000000000000000000000000",
"111111111111100000000000",
"111111111111111111111110",
"011111111111111111111100",
"000111111111111111111000",
"000001111111111111100000",
"000000011111111100000000",
"000000000111110000000000",
"000000000001000000000000",
];
// Bird frame 3 - wings down
const BIRD3=[
"000000000000000000000000",
"000000000000000000000000",
"000000000000000000000000",
"000000000000000000000000",
"000000000000000000000000",
"000000000000000000000000",
"000000000000000000000000",
"000000000000000000000000",
"111111111111100000000000",
"111111111111111111111110",
"011111111111111111111100",
"000111111111111111111000",
"000001111111111111100000",
"000000011111111100000000",
"000000000111110000000000",
"000000000111000000000000",
"000000001110000000000000",
"000000011100000000000000",
"000000111000000000000000",
"000001110000000000000000",
];
// Cloud
const CLOUD=[
"00000111000000000",
"00001111100000000",
"00011111111001100",
"00111111111111110",
"01111111111111111",
"11111111111111111",
"00111111111111110",
];
// Game Over text pixels (simplified block letters)
const GAMEOVER_G=[
"1111",
"1000",
"1011",
"1001",
"1111",
];
const GAMEOVER_A=[
"0110",
"1001",
"1111",
"1001",
"1001",
];
const GAMEOVER_M=[
"10001",
"11011",
"10101",
"10001",
"10001",
];
const GAMEOVER_E=[
"1111",
"1000",
"1110",
"1000",
"1111",
];
const GAMEOVER_O=[
"0110",
"1001",
"1001",
"1001",
"0110",
];
const GAMEOVER_V=[
"10001",
"10001",
"01010",
"01010",
"00100",
];
const GAMEOVER_R=[
"1110",
"1001",
"1110",
"1010",
"1001",
];
// Restart icon
const RESTART=[
"001111100",
"011000110",
"110000011",
"100000001",
"100000000",
"100000000",
"110000011",
"011000110",
"001111100",
];
// --- Audio Engine (Web Audio API) ---
let audioCtx=null;
function getAudioCtx(){
if(!audioCtx) audioCtx=new(window.AudioContext||window.webkitAudioContext)();
return audioCtx;
}
function playJumpSound(){
try{
const ac=getAudioCtx();
const osc=ac.createOscillator();
const gain=ac.createGain();
osc.connect(gain);
gain.connect(ac.destination);
osc.type='square';
osc.frequency.setValueAtTime(600,ac.currentTime);
osc.frequency.exponentialRampToValueAtTime(900,ac.currentTime+0.05);
osc.frequency.exponentialRampToValueAtTime(500,ac.currentTime+0.1);
gain.gain.setValueAtTime(0.15,ac.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01,ac.currentTime+0.15);
osc.start(ac.currentTime);
osc.stop(ac.currentTime+0.15);
}catch(e){}
}
function playScoreSound(){
try{
const ac=getAudioCtx();
const osc=ac.createOscillator();
const gain=ac.createGain();
osc.connect(gain);
gain.connect(ac.destination);
osc.type='square';
osc.frequency.setValueAtTime(500,ac.currentTime);
osc.frequency.exponentialRampToValueAtTime(700,ac.currentTime+0.05);
gain.gain.setValueAtTime(0.1,ac.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01,ac.currentTime+0.1);
osc.start(ac.currentTime);
osc.stop(ac.currentTime+0.1);
// second tone
const osc2=ac.createOscillator();
const gain2=ac.createGain();
osc2.connect(gain2);
gain2.connect(ac.destination);
osc2.type='square';
osc2.frequency.setValueAtTime(700,ac.currentTime+0.08);
osc2.frequency.exponentialRampToValueAtTime(900,ac.currentTime+0.13);
gain2.gain.setValueAtTime(0.1,ac.currentTime+0.08);
gain2.gain.exponentialRampToValueAtTime(0.01,ac.currentTime+0.2);
osc2.start(ac.currentTime+0.08);
osc2.stop(ac.currentTime+0.2);
}catch(e){}
}
function playHitSound(){
try{
const ac=getAudioCtx();
// noise burst
const bufferSize=ac.sampleRate*0.15;
const buffer=ac.createBuffer(1,bufferSize,ac.sampleRate);
const data=buffer.getChannelData(0);
for(let i=0;i<bufferSize;i++){
data[i]=(Math.random()*2-1)*Math.pow(1-i/bufferSize,2);
}
const src=ac.createBufferSource();
src.buffer=buffer;
const gain=ac.createGain();
gain.gain.setValueAtTime(0.3,ac.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01,ac.currentTime+0.15);
src.connect(gain);
gain.connect(ac.destination);
src.start(ac.currentTime);
// low thud
const osc=ac.createOscillator();
const g2=ac.createGain();
osc.connect(g2);
g2.connect(ac.destination);
osc.type='sine';
osc.frequency.setValueAtTime(150,ac.currentTime);
osc.frequency.exponentialRampToValueAtTime(50,ac.currentTime+0.2);
g2.gain.setValueAtTime(0.3,ac.currentTime);
g2.gain.exponentialRampToValueAtTime(0.01,ac.currentTime+0.2);
osc.start(ac.currentTime);
osc.stop(ac.currentTime+0.2);
}catch(e){}
}
// --- Game State ---
let gameState='idle'; // idle, running, dead
let speed=INITIAL_SPEED;
let score=0;
let highScore=parseInt(localStorage.getItem('dinoHighScore'))||0;
let frameCount=0;
let nightMode=false;
let nightAlpha=0; // 0=day, 1=full night
let nightTarget=0;
let flashTimer=0;
// --- Dino ---
const dino={
x:50,
y:GROUND_Y,
w:16*PIXEL,
h:18*PIXEL,
vy:0,
jumping:false,
ducking:false,
animFrame:0,
animTimer:0,
reset(){
this.y=GROUND_Y;
this.vy=0;
this.jumping=false;
this.ducking=false;
this.animFrame=0;
this.animTimer=0;
},
jump(){
if(!this.jumping){
this.vy=JUMP_FORCE;
this.jumping=true;
this.ducking=false;
playJumpSound();
}
},
duck(active){
if(this.jumping){
if(active) this.vy=Math.max(this.vy,4); // fast fall
return;
}
this.ducking=active;
},
update(){
if(this.jumping){
this.vy+=GRAVITY;
this.y+=this.vy;
if(this.y>=GROUND_Y){
this.y=GROUND_Y;
this.vy=0;
this.jumping=false;
}
}
this.animTimer++;
if(this.animTimer>6){
this.animTimer=0;
this.animFrame=1-this.animFrame;
}
// update hitbox
if(this.ducking){
this.w=26*PIXEL;
this.h=14*PIXEL;
}else{
this.w=16*PIXEL;
this.h=18*PIXEL;
}
},
getHitbox(){
if(this.ducking){
return{x:this.x+4,y:this.y-14*PIXEL+4,w:this.w-8,h:14*PIXEL-6};
}
return{x:this.x+4,y:this.y-18*PIXEL+4,w:this.w-8,h:18*PIXEL-6};
},
draw(color){
const fg=color||getFgColor();
if(gameState==='dead'){
drawSprite(DINO_DEAD,this.x,this.y-18*PIXEL,fg);
// draw X eyes
ctx.fillStyle=nightMode&&nightAlpha>0.5?'#333':'#fff';
const eyeX=this.x+8*PIXEL;
const eyeY=this.y-18*PIXEL+3*PIXEL;
ctx.fillRect(eyeX,eyeY,PIXEL,PIXEL);
ctx.fillRect(eyeX+2*PIXEL,eyeY,PIXEL,PIXEL);
ctx.fillRect(eyeX+PIXEL,eyeY+PIXEL,PIXEL,PIXEL);
ctx.fillRect(eyeX,eyeY+2*PIXEL,PIXEL,PIXEL);
ctx.fillRect(eyeX+2*PIXEL,eyeY+2*PIXEL,PIXEL,PIXEL);
return;
}
if(this.ducking){
const sprite=this.animFrame===0?DINO_DUCK1:DINO_DUCK2;
drawSprite(sprite,this.x,this.y-14*PIXEL,fg);
}else if(this.jumping){
drawSprite(DINO_JUMP,this.x,this.y-18*PIXEL,fg);
}else{
const sprite=this.animFrame===0?DINO_RUN1:DINO_RUN2;
drawSprite(sprite,this.x,this.y-18*PIXEL,fg);
}
}
};
// --- Obstacles ---
let obstacles=[];
let obstacleTimer=0;
const MIN_OBSTACLE_GAP=50;
function spawnObstacle(){
const r=Math.random();
let obs;
if(score>300&&r<0.25){
// bird
const heights=[GROUND_Y-15*PIXEL,GROUND_Y-30*PIXEL,GROUND_Y-50*PIXEL];
const birdY=heights[Math.floor(Math.random()*heights.length)];
obs={type:'bird',x:W+10,y:birdY,w:24*PIXEL,h:16*PIXEL,animFrame:0,animTimer:0};
}else if(r<0.6){
obs={type:'cactus_small',x:W+10,y:GROUND_Y-20*PIXEL+2,w:9*PIXEL,h:20*PIXEL-2};
}else{
obs={type:'cactus_large',x:W+10,y:GROUND_Y-30*PIXEL+2,w:13*PIXEL,h:30*PIXEL-2};
}
obstacles.push(obs);
}
function updateObstacles(){
obstacleTimer++;
const minGap=Math.max(MIN_OBSTACLE_GAP,Math.floor(100-speed*4));
if(obstacleTimer>minGap+Math.random()*40){
spawnObstacle();
obstacleTimer=0;
}
for(let i=obstacles.length-1;i>=0;i--){
obstacles[i].x-=speed;
if(obstacles[i].type==='bird'){
obstacles[i].animTimer++;
if(obstacles[i].animTimer>10){
obstacles[i].animTimer=0;
obstacles[i].animFrame=(obstacles[i].animFrame+1)%3;
}
}
if(obstacles[i].x<-100){
obstacles.splice(i,1);
}
}
}
function drawObstacles(){
const fg=getFgColor();
for(const obs of obstacles){
if(obs.type==='cactus_small'){
drawSprite(CACTUS_SMALL,obs.x,obs.y,fg);
}else if(obs.type==='cactus_large'){
drawSprite(CACTUS_LARGE,obs.x,obs.y,fg);
}else if(obs.type==='bird'){
const sprites=[BIRD1,BIRD2,BIRD3];
drawSprite(sprites[obs.animFrame],obs.x,obs.y,fg);
}
}
}
function getObstacleHitbox(obs){
if(obs.type==='bird'){
return{x:obs.x+4,y:obs.y+4,w:obs.w-8,h:obs.h-8};
}
return{x:obs.x+2,y:obs.y+2,w:obs.w-4,h:obs.h-2};
}
// --- Ground ---
let groundOffset=0;
const groundBumps=[];
for(let i=0;i<200;i++){
groundBumps.push({x:Math.random()*W*2,h:Math.random()*3+1,w:Math.random()*4+1});
}
function drawGround(){
const fg=getFgColor();
ctx.fillStyle=fg;
// main ground line
ctx.fillRect(0,GROUND_Y+2,W,1);
// ground texture
for(const bump of groundBumps){
const bx=((bump.x-groundOffset)%(W*2)+W*2)%(W*2)-W*0.5;
if(bx>-10&&bx<W+10){
ctx.fillRect(bx,GROUND_Y+4+Math.random()*2,bump.w,1);
}
}
groundOffset+=speed;
}
// --- Clouds ---
let clouds=[];
let cloudTimer=0;
function spawnCloud(){
clouds.push({x:W+10,y:20+Math.random()*60,speed:0.5+Math.random()*0.5});
}
function updateClouds(){
cloudTimer++;
if(cloudTimer>100+Math.random()*100){
spawnCloud();
cloudTimer=0;
}
for(let i=clouds.length-1;i>=0;i--){
clouds[i].x-=clouds[i].speed;
if(clouds[i].x<-40){
clouds.splice(i,1);
}
}
}
function drawClouds(){
const col=nightMode&&nightAlpha>0.5?'rgba(200,200,200,0.3)':'rgba(200,200,200,0.8)';
for(const c of clouds){
drawSprite(CLOUD,c.x,c.y,col,2);
}
}
// --- Stars (night) ---
const stars=[];
for(let i=0;i<20;i++){
stars.push({x:Math.random()*W,y:Math.random()*100,size:Math.random()>0.5?2:1,blink:Math.random()*100});
}
function drawStars(){
if(nightAlpha<0.3)return;
const alpha=Math.min(1,(nightAlpha-0.3)/0.7);
ctx.fillStyle=`rgba(255,255,255,${alpha*0.8})`;
for(const s of stars){
const blink=Math.sin((frameCount+s.blink)*0.05)>0;
if(blink||s.size===2){
ctx.fillRect(s.x,s.y,s.size,s.size);
}
}
}
// --- Moon ---
function drawMoon(){
if(nightAlpha<0.3)return;
const alpha=Math.min(1,(nightAlpha-0.3)/0.7);
ctx.fillStyle=`rgba(255,255,240,${alpha})`;
// simple circle
const mx=W-80,my=30,mr=15;
ctx.beginPath();
ctx.arc(mx,my,mr,0,Math.PI*2);
ctx.fill();
// crater
ctx.fillStyle=`rgba(200,200,180,${alpha*0.5})`;
ctx.beginPath();
ctx.arc(mx-4,my-3,3,0,Math.PI*2);
ctx.fill();
ctx.beginPath();
ctx.arc(mx+5,my+4,2,0,Math.PI*2);
ctx.fill();
}
// --- Collision Detection ---
function checkCollision(){
const dHB=dino.getHitbox();
for(const obs of obstacles){
const oHB=getObstacleHitbox(obs);
if(dHB.x<oHB.x+oHB.w&&dHB.x+dHB.w>oHB.x&&dHB.y<oHB.y+oHB.h&&dHB.y+dHB.h>oHB.y){
return true;
}
}
return false;
}
// --- Score ---
function drawScore(){
const fg=getFgColor();
ctx.fillStyle=fg;
ctx.font='bold 13px "Courier New",monospace';
ctx.textAlign='right';
const scoreStr=String(Math.floor(score)).padStart(5,'0');
if(highScore>0){
const hiStr='HI '+String(highScore).padStart(5,'0')+' '+scoreStr;
ctx.fillText(hiStr,W-15,22);
}else{
ctx.fillText(scoreStr,W-15,22);
}
}
// --- Flash effect on score milestone ---
function drawFlash(){
if(flashTimer>0){
ctx.fillStyle=`rgba(255,255,255,${flashTimer/20})`;
ctx.fillRect(0,0,W,H);
flashTimer--;
}
}
// --- Night/Day ---
function getBgColor(){
if(nightAlpha<=0)return'#f7f7f7';
const r=Math.round(247*(1-nightAlpha)+50*nightAlpha);
const g=Math.round(247*(1-nightAlpha)+50*nightAlpha);
const b=Math.round(247*(1-nightAlpha)+70*nightAlpha);
return`rgb(${r},${g},${b})`;
}
function getFgColor(){
if(nightAlpha<=0)return'#535353';
const r=Math.round(83*(1-nightAlpha)+240*nightAlpha);
const g=Math.round(83*(1-nightAlpha)+240*nightAlpha);
const b=Math.round(83*(1-nightAlpha)+240*nightAlpha);
return`rgb(${r},${g},${b})`;
}
function updateNightMode(){
// switch every 700 points
const phase=Math.floor(score/700);
nightTarget=phase%2===1?1:0;
if(nightAlpha<nightTarget)nightAlpha=Math.min(nightTarget,nightAlpha+0.005);
if(nightAlpha>nightTarget)nightAlpha=Math.max(nightTarget,nightAlpha-0.005);
nightMode=nightAlpha>0.5;
}
// --- Game Over / Restart ---
function drawGameOver(){
const fg=getFgColor();
// "GAME OVER" text using pixel letters
const letters=[GAMEOVER_G,GAMEOVER_A,GAMEOVER_M,GAMEOVER_E,null,GAMEOVER_O,GAMEOVER_V,GAMEOVER_E,GAMEOVER_R];
let startX=W/2-80;
const startY=60;
ctx.fillStyle=fg;
for(const letter of letters){
if(letter===null){startX+=8;continue;}
for(let r=0;r<letter.length;r++){
for(let c=0;c<letter[r].length;c++){
if(letter[r][c]==='1'){
ctx.fillRect(startX+c*3,startY+r*3,3,3);
}
}
}
startX+=(letter[0].length+1)*3;
}
// restart button
const rx=W/2-10,ry=100;
ctx.fillStyle=fg;
// circular arrow icon
ctx.beginPath();
ctx.arc(rx+9,ry+9,8,0,Math.PI*2);
ctx.lineWidth=2;
ctx.strokeStyle=fg;
ctx.stroke();
// arrow head
ctx.beginPath();
ctx.moveTo(rx+14,ry+3);
ctx.lineTo(rx+18,ry+5);
ctx.lineTo(rx+14,ry+7);
ctx.fillStyle=fg;
ctx.fill();
// clear inside
ctx.fillStyle=getBgColor();
ctx.beginPath();
ctx.arc(rx+9,ry+9,6,0,Math.PI*2);
ctx.fill();
// inner arrow
ctx.fillStyle=fg;
ctx.beginPath();
ctx.moveTo(rx+5,ry+4);
ctx.lineTo(rx+13,ry+4);
ctx.lineTo(rx+13,ry+7);
ctx.lineTo(rx+5,ry+7);
ctx.fill();
}
// --- Start Screen ---
function drawStartScreen(){
const fg=getFgColor();
ctx.fillStyle=fg;
ctx.font='bold 14px "Courier New",monospace';
ctx.textAlign='center';
ctx.fillText('Press SPACE or Tap to Start',W/2,H/2-10);
// draw static dino
drawSprite(DINO_RUN1,dino.x,dino.y-18*PIXEL,fg);
}
// --- Input ---
const keys={};
document.addEventListener('keydown',function(e){
if(e.code==='Space'||e.code==='ArrowUp'){
e.preventDefault();
keys.jump=true;
handleInput();
}
if(e.code==='ArrowDown'){
e.preventDefault();
keys.duck=true;
if(gameState==='running')dino.duck(true);
}
});
document.addEventListener('keyup',function(e){
if(e.code==='Space'||e.code==='ArrowUp'){
keys.jump=false;
}
if(e.code==='ArrowDown'){
keys.duck=false;
if(gameState==='running')dino.duck(false);
}
});
// Touch support
canvas.addEventListener('touchstart',function(e){
e.preventDefault();
const touch=e.touches[0];
const rect=canvas.getBoundingClientRect();
const y=touch.clientY-rect.top;
const relY=y/rect.height;
if(relY>0.6){
// bottom area = duck
if(gameState==='running')dino.duck(true);
}else{
handleInput();
}
});
canvas.addEventListener('touchend',function(e){
dino.duck(false);
});
// Mouse click
canvas.addEventListener('click',function(e){
handleInput();
});
function handleInput(){
if(gameState==='idle'){
startGame();
}else if(gameState==='running'){
dino.jump();
}else if(gameState==='dead'){
resetGame();
}
}
// --- Game Loop ---
function startGame(){
gameState='running';
score=0;
speed=INITIAL_SPEED;
obstacles=[];
obstacleTimer=0;
frameCount=0;
nightAlpha=0;
nightTarget=0;
flashTimer=0;
dino.reset();
}
function resetGame(){
startGame();
}
function die(){
gameState='dead';
playHitSound();
const finalScore=Math.floor(score);
if(finalScore>highScore){
highScore=finalScore;
localStorage.setItem('dinoHighScore',String(highScore));
}
}
let lastTime=0;
function gameLoop(timestamp){
const dt=timestamp-lastTime;
lastTime=timestamp;
// Clear
ctx.fillStyle=getBgColor();
ctx.fillRect(0,0,W,H);
frameCount++;
if(gameState==='idle'){
drawGround();
drawClouds();
drawStartScreen();
requestAnimationFrame(gameLoop);
return;
}
if(gameState==='running'){
// Update
speed=Math.min(MAX_SPEED,INITIAL_SPEED+score*0.002);
score+=speed*0.02;
// Score milestone sound
if(Math.floor(score)%100===0&&Math.floor(score)>0&&Math.floor(score)!==Math.floor(score-speed*0.02)){
playScoreSound();
flashTimer=15;
}
updateNightMode();
dino.update();
updateObstacles();
updateClouds();
if(checkCollision()){
die();
}
}
// Draw
drawStars();
drawMoon();
drawClouds();
drawGround();
drawObstacles();
dino.draw();
drawScore();
drawFlash();
if(gameState==='dead'){
drawGameOver();
}
requestAnimationFrame(gameLoop);
}
// --- Scale canvas for display ---
function resizeCanvas(){
const maxW=window.innerWidth*0.95;
const maxH=window.innerHeight*0.9;
const scale=Math.min(maxW/W,maxH/H);
canvas.style.width=Math.floor(W*scale)+'px';
canvas.style.height=Math.floor(H*scale)+'px';
}
window.addEventListener('resize',resizeCanvas);
resizeCanvas();
// --- Start ---
requestAnimationFrame(gameLoop);
})();
</script>
</body>
</html>