hypeorcode
Network offOpen alone (new tab)

Run details

Model
xingchen-agi/xing4.0-29b-a4b:free
Route
OpenRouter API, one user message
Reasoning
provider default, 20,273 reasoning tokens
Limits
60 min · 235,929 output tokens
Date
21 Oct 2026
Time
11 min 5 s
Size
21 kB
Tokens
85 in, 42,630 out
List price
free (list price $0)
Paid
$0 (free route)

Raw files: output · meta.json

Exact prompt

Make a Breakout game I can play right now with the keyboard or the mouse. Give it several levels, a score and some juice.

It must be one complete, self-contained HTML file: inline all CSS and JavaScript, and make no network requests of any kind (no CDNs, web fonts or remote images). Reply with the file in a single ```html code block.

Source

21 kB 576 lines Raw

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width,width=device-width,initial-scale=1.0">
<title>BRICKBREAK — Play Now</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
html,body{width:100%;height:100%;overflow:hidden}
body{background:#0a0a0a;display:flex;flex-direction:column;justify-content:center;align-items:center;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;user-select:none;-webkit-user-select:none;touch-action:none}
#wrap{position:relative;width:min(900px,96vh);aspect-ratio:3/2}
canvas{display:block;width:100%;height:100%;background:#0f0f12;border-radius:16px;box-shadow:0 0 80px rgba(34,211,238,.06),0 20px 60px rgba(0,0,0,.5)}
</style>
</head>
<body>
<div id="wrap"><canvas id="game" width="900" height="600"></canvas></div>
<script>
// ==================== CONFIGURATION ====================
var CW=900,CH=600;
var COLS=10,BRICK_W=(CW-100-4*(COLS-1))/COLS,BRICK_H=22,BRICK_GAP_Y=6,BRICK_OX=50,BRICK_OY=55;
var PADDLE_W=110,PADDLE_H=14,PADDLE_Y=CH-42;
var BALL_R=7,BALL_BASE=4.5;
var COLORS=['#f87171','#fb923c','#facc15','#4ade88','#22d3ee','#c084fc'];
var SPECIAL='#FFD700';
var LIVES_START=3;
var NUM_LEVELS=5;

// ==================== AUDIO SYSTEM ====================
var actx=null;
function initAudio(){
  if(!actx){try{actx=new(window.AudioContext||window.webkitAudioContext)()}catch(e){}}
  if(actx&&actx.state==='suspended')actx.resume();
}
function sfx(freq,dur,type,vol,sweep){
  if(!actx)return;
  type=type||'square';vol=vol||.06;sweep=sweep||0;
  var o=actx.createOscillator(),g=actx.createGain();
  o.type=type;o.frequency.setValueAtTime(freq,actx.currentTime);
  if(sweep)o.frequency.exponentialRampToValueAtTime(Math.max(1,freq+sweep),actx.currentTime+dur);
  g.gain.setValueAtTime(vol,actx.currentTime);
  g.gain.exponentialRampToValueAtTime(.001,actx.currentTime+dur);
  o.connect(g);g.connect(actx.destination);
  o.start();o.stop(actx.currentTime+dur);
}
var SFX={
  paddle:function(){sfx(240,.07,'square',.05)},
  wall:function(){sfx(160,.04,'sine',.03)},
  brick:function(){sfx(420+.3*combo,.05,'square',.04)},
  brickSpecial:function(){sfx(600,.08,'square',.05,200)},
  lifeLost:function(){sfx(300,.12,'sawtooth',.05);sfx(180,.2,'sawtooth',.04)},
  levelClear:function(){[400,523,659,880].forEach(function(f,i){setTimeout(function(){sfx(f,.14,'triangle',.07)},i*.09)})},
  gameOver:function(){[400,350,300,200].forEach(function(f,i){setTimeout(function(){sfx(f,.18,'sawtooth',.06)},i*.12)})},
  victory:function(){[523,659,784,1047].forEach(function(f,i){setTimeout(function(){sfx(f,.16,'triangle',.08)},i*.1)})},
  click:function(){sfx(600,.05,'square',.03)},
  blip:function(){sfx(800,.04,'sine',.03)}
};

// ==================== UTILITIES ====================
function rr(ctx,x,y,w,h,r){
  ctx.beginPath();ctx.moveTo(x+r,y);ctx.lineTo(x+w-r,y);
  ctx.quadraticCurveTo(x+w,y,x+w,y+r);ctx.lineTo(x+w,y+h-r);
  ctx.quadraticCurveTo(x+w,y+h,x+w-r,y+h);ctx.lineTo(x+r,y+h);
  ctx.quadraticCurveTo(x,y+h,x,y+h-r);ctx.lineTo(x,y+r);
  ctx.quadraticCurveTo(x,y,x+r,y,x+r,y);ctx.closePath();
}
function shade(hex,pct){
  var n=parseInt(hex.slice(1),16);
  var r=Math.max(0,Math.min(255,(n>>16)+pct));
  var g=Math.max(0,Math.min(255,((n>>8)&255)+pct));
  var b=Math.max(0,Math.min(255,(n&255)+pct));
  return('#'+((r<<16)|(g<<8)|b).toString(16).padStart(6,'0'));
}

// ==================== GAME STATE ====================
var state='TITLE';
var score=0,highScore=+(localStorage.getItem('bb_hs')||0),lives=LIVES_START,combo=0,comboTimer=0;
var currentLevel=0,ballLaunched=false;
var shakeAmt=0,shakeDur=0;
var particles=[],floaters=[];
var bricks=[];
var keys={};
var input={left:false,right:false,mouseX:CW/2,mouseActive:false};
var timeVal=0;

// ==================== ENTITIES ====================
function Paddle(){
  this.w=PADDLE_W;this.h=PADDLE_H;this.x=CW/2-this.w/2;this.y=PADDLE_Y;this.vx=0;this.flash=0;
}
Paddle.prototype.update=function(dt){
  if(input.mouseActive){
    var t=input.mouseX-this.w/2;
    this.x+=(t-this.x)*Math.min(1,dt*.35);
  }else{
    if(keys['ArrowLeft']||keys['a'])this.vx=-8;
    else if(keys['ArrowRight']||keys['d'])this.vx=8;
    else this.vx*=Math.pow(.001,dt);
  }
  this.x+=this.vx*dt;
  this.x=Math.max(8,Math.min(CW-this.w-8,this.x));
  if(this.flash>0)this.flash-=.5;
};
Paddle.prototype.draw=function(ctx){
  ctx.save();
  ctx.shadowBlur=16;ctx.shadowColor='#22d3ee';
  var g=ctx.createLinearGradient(this.x,this.y,this.x,this.y+this.h);
  g.addColorStop(0,'#67e8f9');g.addColorStop(.5,'#22d3ee');g.addColorStop(1,'#0891b2');
  ctx.fillStyle=g;rr(ctx,this.x,this.y,this.w,this.h,7);ctx.fill();
  ctx.shadowBlur=0;
  ctx.fillStyle='rgba(255,255,255,.2)';rr(ctx,this.x+2,this.y+2,this.w-4,3,2);ctx.fill();
  if(this.flash>0){ctx.fillStyle='rgba(255,255,255,.25)';rr(ctx,this.x,this.y,this.w,this.h,7);ctx.fill()}
  ctx.restore();
};

function Ball(){
  this.r=BALL_R;this.speed=BALL_BASE;this.trail=[];this.flash=0;this.vxPrev=0;
  this.reset();
}
Ball.prototype.reset=function(){
  this.x=CW/2;this.y=PADDLE_Y-this.r-1;
  this.dx=(Math.random()>.5?1:-1)*this.speed;
  this.dy=-this.speed;this.trail=[];
};
Ball.prototype.update=function(dt){
  this.trail.push({x:this.x,y:this.y});
  if(this.trail.length>10)this.trail.shift();
  this.x+=this.dx;this.y+=this.dy;
};
Ball.prototype.bouncePaddle=function(p){
  var rel=(this.x-(p.x+p.w/2))/(p.w/2);
  var maxA=Math.PI*.38;var a=rel*maxA;
  var sp=Math.hypot(this.dx,this.dy);
  this.dx=sp*Math.sin(a)+(p.vx-p.vxPrev)*.15;
  this.dy=-Math.abs(sp*Math.cos(a));
  this.flash=6;
};
Ball.prototype.bounceWall=function(){
  if(this.x-this.r<0){this.x=this.r;this.dx=Math.abs(this.dx);SFX.wall()}
  if(this.x+this.r>CW){this.x=CW-this.r;this.dx=-Math.abs(this.dx);SFX.wall()}
  if(this.y-this.r<0){this.y=this.r;this.dy=Math.abs(this.dy);SFX.wall()}
};
Ball.prototype.minVertical=function(sp){
  var mn=sp*.3;
  if(Math.abs(this.dy)<mn){
    this.dy=Math.sign(this.dy)*mn;
    this.dx=Math.sign(this.dx)*Math.sqrt(Math.max(0,sp*sp-mn*mn));
  }
};

function Brick(x,y,rowIdx,isSpecial){
  this.x=x;this.y=y;this.w=BRICK_W;this.h=BRICK_H;
  this.isSpecial=!!isSpecial;
  this.color=isSpecial?SPECIAL:COLORS[rowIdx%COLORS.length];
  this.alive=true;this.shake=0;
}
Brick.prototype.draw=function(ctx){
  if(!this.alive)return;
  var sx=this.shake>0?(Math.random()-.5)*this.shake:0;
  ctx.save();ctx.translate(this.x+sx,this.y);
  ctx.shadowBlur=this.isSpecial?18:8;ctx.shadowColor=this.color;
  var g=ctx.createLinearGradient(0,0,0,this.h);
  if(this.isSpecial){
    g.addColorStop(0,'#fef08a');g.addColorStop(.4,'#fde047');g.addColorStop(1,'#ca8a04');
  }else{
    g.addColorStop(0,'#ffffff');g.addColorStop(.35,this.color);g.addColorStop(1,shade(this.color,-30));
  }
  ctx.fillStyle=g;rr(ctx,0,0,this.w,this.h,4);ctx.fill();
  ctx.shadowBlur=0;
  if(!this.isSpecial){
    ctx.fillStyle='rgba(255,255,255,.22)';rr(ctx,2,2,this.w-4,this.h*.3,2);ctx.fill();
  }
  if(this.isSpecial){
    ctx.fillStyle='rgba(120,53,15,.5)';
    ctx.font='bold 11px sans-serif';ctx.textAlign='center';ctx.textBaseline='middle';
    ctx.fillText('\u2605',this.w/2,this.h/2+1);
  }
  ctx.strokeStyle='rgba(0,0,0,.25)';ctx.lineWidth=1;rr(ctx,0.5,0.5,this.w-1,this.h-1,3.5);ctx.stroke();
  ctx.restore();
  if(this.shake>0)this.shake-=.4;
};

// ==================== LEVEL DATA ====================
var LEVELS=[
  // L1
  [[1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1]],
  // L2
  [[0,0,0,0,1,1,1,1,0,0],[1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1],[0,0,0,0,1,1,1,1,0,0]],
  // L3 Pyramid
  [[0,0,0,0,1,1,0,0,0,0],[0,0,0,1,1,1,1,0,0,0],[0,0,1,1,1,1,1,1,0,0],[0,1,1,1,1,1,1,1,1,0],[1,1,1,1,1,1,1,1,1,1]],
  // L4 Diamond
  [[0,0,0,0,1,1,0,0,0,0],[0,0,0,1,1,1,1,0,0,0],[0,0,1,1,1,1,1,1,0,0],[0,1,1,1,1,1,1,1,1,0],[0,0,1,1,1,1,1,1,0,0],[0,0,0,1,1,1,1,0,0,0],[0,0,0,0,1,1,0,0,0,0]],
  // L5 Cage with specials
  [[2,2,1,1,1,1,1,1,2,2],[2,0,0,0,1,1,1,0,0,2],[2,0,1,1,1,1,1,1,0,2],[2,0,0,0,0,0,0,0,0,2],[2,1,1,1,1,1,1,1,1,2],[2,0,0,0,0,0,0,0,0,2],[2,2,1,1,1,1,1,1,2,2]]
];

// ==================== PARTICLES & EFFECTS ====================
function spawnParticles(x,y,col,count,big){
  for(var i=0;i<count;i++){
    var a=Math.random()*Math.PI*2;
    var sp=1+Math.random()*(big?1:4);
    particles.push({
      x:x,y:y,
      vx:Math.cos(a)*sp,vy:Math.sin(a)*sp-1,
      col:col,
      life:20+Math.random()*25,ml:45,
      sz:2+Math.random()*4,
      circ:Math.random()<.3
    });
  }
}
function spawnFloater(x,y,txt,col){floaters.push({x:x,y:y,txt:txt,col:col,life:55,ml:55,vy:-1.2})}
function addShake(a,d){shakeAmt=Math.max(shakeAmt,a);shakeDur=Math.max(shakeDur,d)}

// ==================== GAME LOGICS ====================
function startGame(){
  score=0;lives=LIVES_START;currentLevel=0;combo=0;comboTimer=0;
  loadLevel(0);state='PLAYING';
}
function loadLevel(idx){
  var lay=LEVELS[idx];bricks=[];
  for(var r=0;r<lay.length;r++){
    for(var c=0;c<lay[r].length;c++){
      if(lay[r][c]){
        var x=BRICK_OX+c*(BRICK_W+4);
        var y=BRICK_OY+r*(BRICK_H+BRICK_GAP_Y);
        var sp=lay[r][c]>1.5;
        bricks.push(new Brick(x,y,r,sp));
      }
    }
  }
  ball.speed=BALL_BASE*(1+idx*.14);
  ball.reset();ballLaunched=false;
}
function launchBall(){if(!ballLaunched&&state==='PLAYING'){ballLaunched=true;SFX.click()}}
function loseBall(){
  lives--;ballLaunched=false;ball.reset();
  addShake(8,12);SFX.lifeLost();
  if(lives<=0){
    state='GAME_OVER';
    if(score>highScore){highScore=score;localStorage.setItem('bb_hs',score)}
  }
}
function nextLevel(){
  currentLevel++;
  if(currentLevel>=NUM_LEVELS){
    state='VICTORY';
    if(score>highScore){highScore=score;localStorage.setItem('bb_hs',score)}
  }else{
    loadLevel(currentLevel);state='PLAYING';
  }
}

// ==================== UPDATE ====================
function update(dt){
  timeVal+=dt*.01;
  // particles always update
  for(var i=particles.length-1;i>=0;i--){
    var p=particles[i];
    p.x+=p.vx*dt;p.y+=p.vy*dt;p.vy+=.15*dt;p.vx*=.97;p.life-=dt;
    if(p.life<=0)particles.splice(i,1);
  }
  // floaters always update
  for(var j=floaters.length-1;j>=0;j--){
    var f=floaters[j];
    f.y+=f.vy*dt;f.vy*=.96;f.life-=dt;
    if(f.life<=0)floaters.splice(j,1);
  }
  // shake always updates
  if(shakeDur>0){shakeDur-=dt;if(shakeDur<=0)shakeAmt=0}

  if(state==='PLAYING'){
    paddle.update(dt);
    if(!ballLaunched){
      ball.x=paddle.x+paddle.w/2;
      ball.y=paddle.y-ball.r-1;
    }else{
      ball.update(dt);
      ball.bounceWall();
      // paddle collision
      if(ball.y+ball.r>=paddle.y&&ball.y-ball.r<=paddle.y+paddle.h&&
         ball.x>=paddle.x&&ball.x<=paddle.x+paddle.w&&ball.dy>0){
        ball.bouncePaddle(paddle);
        addShake(3,5);SFX.paddle();
      }
      // brick collisions
      for(var bi=0;bi<bricks.length;bi++){
        var b=bricks[bi];
        if(!b.alive)continue;
        if(ball.x+ball.r>b.x&&ball.x-ball.r<b.x+b.w&&
           ball.y+ball.r>b.y&&ball.y-ball.r<b.y+b.h){
          var ol=(ball.x+ball.r)-b.x;
          var or=(b.x+b.w)-(ball.x-ball.r);
          var ot=(ball.y+ball.r)-b.y;
          var ob=(b.y+b.h)-(ball.y-ball.r);
          var m=Math.min(ol,or,ot,ob);
          if(m===ol||m===or)ball.dx=-ball.dx;
          else ball.dy=-ball.dy;
          ball.minVertical(ball.speed);
          b.alive=false;
          combo++;comboTimer=120;
          var pts=b.isSpecial?30:10;
          score+=pts*Math.max(1,combo);
          var col=b.isSpecial?SPECIAL:b.color;
          spawnParticles(b.x+b.w/2,b.y+b.h/2,col,b.isSpecial?16:8,b.isSpecial);
          addShake(b.isSpecial?8:4,b.isSpecial?10:6);
          if(b.isSpecial)SFX.brickSpecial();else SFX.brick();
          if(combo>1)spawnFloater(b.x+b.w/2,b.y,'+'+pts*combo,col);
          else spawnFloater(b.x+b.w/2,b.y,'+'+pts,col);
          break;
        }
      }
      if(ball.y-ball.r>CH){loseBall();return}
      // level complete
      var allDead=true;
      for(var k=0;k<bricks.length;k++){if(bricks[k].alive){allDead=false;break}}
      if(allDead){
        var bonus=lives*50;score+=bonus;
        state='LEVEL_CLEAR';SFX.levelClear();
        addShake(6,15);
        spawnFloater(CW/2,CH/2-10,'LEVEL '+currentLevel+' CLEAR! +'+bonus,'#22d3ee');
      }
    }
    // combo timer
    if(comboTimer>0){comboTimer-=dt;if(comboTimer<=0)combo=0}
  }
}

// ==================== RENDER ====================
var canvas=document.getElementById('game'),ctx=canvas.getContext('2d');

function drawBG(){
  var g=ctx.createLinearGradient(0,0,0,CH);
  g.addColorStop(0,'#0a0a0a');g.addColorStop(1,'#0f0f12');
  ctx.fillStyle=g;ctx.fillRect(0,0,CW,CH);
  // subtle grid
  ctx.strokeStyle='rgba(255,255,255,.015)';ctx.lineWidth=1;
  for(var x=0;x<CW;x+=40){ctx.beginPath();ctx.moveTo(x,0);ctx.lineTo(x,CH);ctx.stroke()}
  for(var y=0;y<CH;y+=40){ctx.beginPath();ctx.moveTo(0,y);ctx.lineTo(CW,y);ctx.stroke()}
}

function drawTitle(){
  var t=Date.now()/1000;
  ctx.save();ctx.textAlign='center';ctx.textBaseline='middle';
  // decorative bricks at bottom
  var cols=['#f87171','#fb923c','#facc15','#4ade80','#22d3ee'];
  for(var i=0;i<15;i++){
    var bx=80+i*55,by=CH-105+Math.sin(i*1.3)*5;
    ctx.fillStyle=cols[i%5];rr(ctx,bx,by,50,20,4);ctx.fill();
  }
  // title glow layers
  for(var gl=3;gl>0;gl--){
    ctx.shadowBlur=gl*15;ctx.shadowColor='#22d3ee';
    ctx.fillStyle='#22d3ee';ctx.font='bold 58px sans-serif';
    ctx.fillText('BRICKBREAK',CW/2,180+Math.sin(t*2)*4);
  }
  ctx.shadowBlur=0;
  // main title
  ctx.fillStyle='#fff';ctx.font='bold 58px sans-serif';
  ctx.fillText('BRICKBREAK',CW/2,180);
  // subtitle
  ctx.fillStyle='rgba(255,255,255,.4)';ctx.font='16px sans-serif';
  ctx.fillText('A Breakout Experience',CW/2,215);
  // blink prompt
  if(Math.sin(t*3)>0){
    ctx.fillStyle='#EAB308';ctx.font='bold 20px sans-serif';
    ctx.fillText('PRESS ENTER TO START',CW/2,345);
  }
  // controls
  ctx.fillStyle='rgba(255,255,255,.35)';ctx.font='14px sans-serif';
  ctx.fillText('\u2190 \u2192 or A D to move  |  SPACE or CLICK to launch  |  P to pause',CW/2,435);
  // high score
  ctx.fillStyle='rgba(255,255,255,.5)';ctx.font='16px sans-serif';
  ctx.fillText('BEST: '+(highScore>0?highScore:0),CW/2,475);
  ctx.restore();
}

function drawHUD(){
  ctx.save();
  // score
  ctx.fillStyle='#F9FAFB';ctx.font='bold 22px sans-serif';ctx.textAlign='left';ctx.textBaseline='alphabetic';
  ctx.fillText('SCORE: '+score,16,32);
  // level
  ctx.textAlign='center';ctx.fillStyle='rgba(255,255,255,.7)';ctx.font='14px sans-serif';
  ctx.fillText('LEVEL '+currentLevel+'/'+NUM_LEVELS,CW/2,30);
  // lives as hearts
  ctx.textAlign='right';ctx.fillStyle='rgba(255,255,255,.7)';ctx.font='14px sans-serif';
  ctx.fillText('LIVES:',CW-16,32);
  for(var i=0;i<lives;i++){
    ctx.fillStyle='#f87171';ctx.beginPath();ctx.arc(CW-22-i*22,26,7,0,Math.PI*2);ctx.fill();
    ctx.fillStyle='rgba(255,255,255,.3)';ctx.beginPath();ctx.arc(CW-22-i*22-2,24,2,0,Math.PI*2);ctx.fill();
  }
  // combo
  if(combo>1&&comboTimer>0){
    ctx.textAlign='center';ctx.globalAlpha=Math.min(1,comboTimer/30);
    ctx.fillStyle='#EAB308';ctx.font='bold 22px sans-serif';
    ctx.fillText(combo+'x COMBO!',CW/2,58);
    ctx.globalAlpha=1;
  }
  // ready prompt
  if(!ballLaunched&&state==='PLAYING'){
    ctx.textAlign='center';ctx.globalAlpha=.5+.5*Math.sin(Date.now()/200);
    ctx.fillStyle='#fff';ctx.font='bold 16px sans-serif';
    ctx.fillText('PRESS SPACE OR CLICK TO LAUNCH',CW/2,CH-22);
    ctx.globalAlpha=1;
  }
  ctx.restore();
}

function drawGame(dt){
  ctx.save();
  if(shakeAmt>0){ctx.translate((Math.random()-.5)*shakeAmt,(Math.random()-.5)*shakeAmt)}
  // bricks
  for(var i=0;i<bricks.length;i++)bricks[i].draw(ctx);
  // paddle
  paddle.draw(ctx);
  // ball trail
  for(var ti=0;ti<ball.trail.length;ti++){
    var tr=ball.trail[ti],fa=(ti/ball.trail.length)*.4;
    ctx.globalAlpha=fa;ctx.fillStyle='#fff';
    ctx.beginPath();ctx.arc(tr.x,tr.y,ball.r*(ti/ball.trail.length*.6+.2),0,Math.PI*2);ctx.fill();
  }
  ctx.globalAlpha=1;
  // ball
  ctx.shadowBlur=12;ctx.shadowColor='#fff';
  ctx.fillStyle='#fff';ctx.beginPath();ctx.arc(ball.x,ball.y,ball.r,0,Math.PI*2);ctx.fill();
  ctx.shadowBlur=0;
  ctx.fillStyle='#0f0f12';ctx.beginPath();ctx.arc(ball.x,ball.y,ball.r*.4,0,Math.PI*2);ctx.fill();
  if(ball.flash>0){
    ctx.fillStyle='rgba(255,255,255,.35)';ctx.beginPath();ctx.arc(ball.x,ball.y,ball.r+2,0,Math.PI*2);ctx.fill();
  }
  // particles
  for(var pi=0;pi<particles.length;pi++){
    var p=particles[pi];
    ctx.globalAlpha=p.life/p.ml;ctx.fillStyle=p.col;
    if(p.circ){ctx.beginPath();ctx.arc(p.x,p.y,p.sz*(p.life/p.ml),0,Math.PI*2);ctx.fill()}
    else{ctx.fillRect(p.x-p.sz/2,p.y-p.sz/2,p.sz,p.sz)}
  }
  ctx.globalAlpha=1;
  // floaters
  ctx.textAlign='center';ctx.font='bold 17px sans-serif';
  for(var fi=0;fi<floaters.length;fi++){
    var f=floaters[fi];
    ctx.globalAlpha=f.life/f.ml;ctx.fillStyle=f.col;
    ctx.fillText(f.txt,f.x,f.y);
  }
  ctx.globalAlpha=1;ctx.textAlign='left';
  ctx.restore();
  drawHUD();
}

function drawOverlay(){
  if(state==='TITLE'){drawTitle();return}
  if(state==='PAUSED'){
    drawGame(0);
    ctx.fillStyle='rgba(0,0,0,.6)';ctx.fillRect(0,0,CW,CH);
    ctx.fillStyle='#fff';ctx.font='bold 40px sans-serif';ctx.textAlign='center';ctx.textBaseline='middle';
    ctx.fillText('PAUSED',CW/2,CH/2-10);
    ctx.font='18px sans-serif';ctx.fillStyle='rgba(255,255,255,.6)';
    ctx.fillText('PRESS P TO RESUME',CW/2,CH/2+30);
    ctx.textAlign='left';ctx.textBaseline='alphabetic';return;
  }
  if(state==='LEVEL_CLEAR'){
    drawGame(0);
    ctx.fillStyle='rgba(0,0,0,.5)';ctx.fillRect(0,0,CW,CH);
    ctx.fillStyle='#22d3ee';ctx.font='bold 36px sans-serif';ctx.textAlign='center';ctx.textBaseline='middle';
    ctx.fillText('LEVEL '+currentLevel+' CLEAR!',CW/2,CH/2-20);
    ctx.fillStyle='#fff';ctx.font='20px sans-serif';
    ctx.fillText('ENTER FOR NEXT LEVEL',CW/2,CH/2+30);
    ctx.textAlign='left';ctx.textBaseline='alphabetic';return;
  }
  if(state==='GAME_OVER'){
    drawGame(0);
    ctx.fillStyle='rgba(0,0,0,.7)';ctx.fillRect(0,0,CW,CH);
    ctx.fillStyle='#f87171';ctx.font='bold 42px sans-serif';ctx.textAlign='center';ctx.textBaseline='middle';
    ctx.fillText('GAME OVER',CW/2,CH/2-30);
    ctx.fillStyle='#fff';ctx.font='20px sans-serif';
    ctx.fillText('FINAL SCORE: '+score,CW/2,CH/2+5);
    if(score>=highScore&&score>0){
      ctx.fillStyle='#EAB308';ctx.font='bold 18px sans-serif';
      ctx.fillText('NEW HIGH SCORE!',CW/2,CH/2+35);
    }
    ctx.fillStyle='rgba(255,255,255,.5)';ctx.font='16px sans-serif';
    ctx.fillText('ENTER TO RETRY',CW/2,CH/2+65);
    ctx.textAlign='left';ctx.textBaseline='alphabetic';return;
  }
  if(state==='VICTORY'){
    drawGame(0);
    ctx.fillStyle='rgba(0,0,0,.4)';ctx.fillRect(0,0,CW,CH);
    var v=Date.now()/300;
    ctx.fillStyle='#facc15';ctx.font='bold 46px sans-serif';ctx.textAlign='center';ctx.textBaseline='middle';
    ctx.shadowBlur=20+Math.sin(v)*8;ctx.shadowColor='#facc15';
    ctx.fillText('VICTORY!',CW/2,CH/2-20);
    ctx.shadowBlur=0;
    ctx.fillStyle='#fff';ctx.font='20px sans-serif';
    ctx.fillText('ALL LEVELS COMPLETE',CW/2,CH/2+20);
    ctx.fillText('FINAL SCORE: '+score,CW/2,CH/2+48);
    ctx.fillStyle='rgba(255,255,255,.5)';ctx.font='16px sans-serif';
    ctx.fillText('ENTER TO PLAY AGAIN',CW/2,CH/2+78);
    ctx.textAlign='left';ctx.textBaseline='alphabetic';return;
  }
}

// ==================== INPUT HANDLERS ====================
document.addEventListener('keydown',function(e){
  initAudio();
  var k=e.key;
  if(k==='Enter'){
    if(state==='TITLE')startGame();
    else if(state==='LEVEL_CLEAR')nextLevel();
    else if(state==='GAME_OVER'||state==='VICTORY')startGame();
    e.preventDefault();
  }
  if(k===' '&&state==='TITLE'){startGame();e.preventDefault()}
  if(k===' '&&state==='PLAYING'&&!ballLaunched){launchBall();e.preventDefault()}
  if((k==='p'||k==='P')&&(state==='PLAYING'||state==='PAUSED')){
    state=(state==='PLAYING')?'PAUSED':'PLAYING';e.preventDefault();
  }
  keys[k]=true;
  if(['ArrowLeft','ArrowRight','ArrowUp','ArrowDown',' '].indexOf(k)>=0)e.preventDefault();
});
document.addEventListener('keyup',function(e){keys[e.key]=false});

function getMousePos(e){
  var r=canvas.getBoundingClientRect();
  return{x:(e.clientX-r.left)*CW/r.width,y:(e.clientY-r.top)*CH/r.height};
}
canvas.addEventListener('mousemove',function(e){
  var p=getMousePos(e);input.mouseX=p.x;input.mouseActive=true;
});
canvas.addEventListener('mousedown',function(e){
  initAudio();
  if(state==='TITLE')startGame();
  else if(state==='PLAYING'&&!ballLaunched)launchBall();
  else if(state==='LEVEL_CLEAR')nextLevel();
  else if(state==='GAME_OVER'||state==='VICTORY')startGame();
});
canvas.addEventListener('touchstart',function(e){
  e.preventDefault();initAudio();
  var t=e.touches[0],r=canvas.getBoundingClientRect();
  input.mouseX=(t.clientX-r.left)*CW/r.width;input.mouseActive=true;
  if(state==='TITLE')startGame();
  else if(state==='PLAYING'&&!ballLaunched)launchBall();
  else if(state==='LEVEL_CLEAR')nextLevel();
  else if(state==='GAME_OVER'||state==='VICTORY')startGame();
},{passive:false});
canvas.addEventListener('touchmove',function(e){
  e.preventDefault();
  var t=e.touches[0],r=canvas.getBoundingClientRect();
  input.mouseX=(t.clientX-r.left)*CW/r.width;
},{passive:false});

// ==================== GAME LOOP ====================
var lastT=0;
function loop(ts){
  if(!lastT)lastT=ts;
  var dt=Math.min((ts-lastT)/16.67,2.5);
  lastT=ts;
  update(dt);
  drawBG();
  if(state==='PLAYING'||state==='PAUSED'||state==='LEVEL_CLEAR'||state==='GAME_OVER'||state==='VICTORY'){
    drawGame(dt);
  }else{
    drawTitle();
  }
  drawOverlay();
  requestAnimationFrame(loop);
}

// ==================== INITIALIZE ====================
var paddle=new Paddle();
var ball=new Ball();
// init paddle vxPrev
ball.vxPrev=ball.vx;
requestAnimationFrame(loop);
</script>
</body>
</html>

Compare with

Xing 4.0: other attempts

  • Xing 4.0, Pelican: Queued · next run 22 Oct, 05:17 UTC
  • Xing 4.0, Logo: Queued · next run 22 Oct, 05:17 UTC
  • Xing 4.0, About: Queued · next run 22 Oct, 05:17 UTC