← back to Model Arena
data/artifacts/04692f72ef1d/grok.html
351 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tetris</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{background:#0f0f1a;color:#e0e0e0;font-family:'Segoe UI',system-ui,sans-serif;display:flex;justify-content:center;align-items:center;min-height:100vh;overflow:hidden}
.wrap{display:flex;gap:24px;align-items:flex-start;padding:20px}
canvas#game{background:#1a1a2e;border:3px solid #4a4a6a;border-radius:4px;box-shadow:0 0 30px rgba(80,80,180,.3)}
.side{display:flex;flex-direction:column;gap:16px;min-width:140px}
.panel{background:#1a1a2e;border:2px solid #4a4a6a;border-radius:8px;padding:14px;text-align:center}
.panel h2{font-size:11px;text-transform:uppercase;letter-spacing:2px;color:#8888aa;margin-bottom:8px}
.panel .val{font-size:28px;font-weight:700;color:#fff;font-variant-numeric:tabular-nums}
canvas#next{display:block;margin:0 auto;background:#12122a;border-radius:4px}
.controls{font-size:12px;line-height:1.8;color:#8888aa;text-align:left}
.controls kbd{display:inline-block;background:#2a2a4a;border:1px solid #5a5a7a;border-radius:3px;padding:1px 6px;font-size:11px;color:#ccc;font-family:inherit;min-width:22px;text-align:center}
.overlay{position:fixed;inset:0;background:rgba(0,0,0,.75);display:flex;justify-content:center;align-items:center;z-index:10;opacity:0;pointer-events:none;transition:opacity .25s}
.overlay.show{opacity:1;pointer-events:auto}
.modal{background:#1a1a2e;border:3px solid #6a6a9a;border-radius:12px;padding:36px 48px;text-align:center;box-shadow:0 0 60px rgba(100,100,200,.4)}
.modal h1{font-size:36px;margin-bottom:8px;color:#fff}
.modal p{color:#aaa;margin-bottom:20px;font-size:15px}
.modal .score-line{font-size:18px;color:#ddd;margin-bottom:24px}
.modal button{background:linear-gradient(135deg,#5a5aee,#8a4aee);color:#fff;border:none;padding:12px 36px;font-size:16px;border-radius:8px;cursor:pointer;font-weight:600;letter-spacing:1px;transition:transform .1s,box-shadow .1s}
.modal button:hover{transform:scale(1.05);box-shadow:0 4px 20px rgba(100,80,220,.5)}
.modal button:active{transform:scale(.98)}
#pause-ov .modal h1{color:#ffcc44}
#go-ov .modal h1{color:#ff4466}
</style>
</head>
<body>
<div class="wrap">
<canvas id="game" width="300" height="600"></canvas>
<div class="side">
<div class="panel"><h2>Score</h2><div class="val" id="score">0</div></div>
<div class="panel"><h2>Lines</h2><div class="val" id="lines">0</div></div>
<div class="panel"><h2>Level</h2><div class="val" id="level">1</div></div>
<div class="panel"><h2>Next</h2><canvas id="next" width="120" height="120"></canvas></div>
<div class="panel controls">
<kbd>←</kbd><kbd>→</kbd> Move<br>
<kbd>↓</kbd> Soft drop<br>
<kbd>↑</kbd> Rotate<br>
<kbd>Space</kbd> Hard drop<br>
<kbd>P</kbd> Pause
</div>
</div>
</div>
<div class="overlay show" id="start-ov">
<div class="modal">
<h1>TETRIS</h1>
<p>Clear lines, don't stack out!</p>
<button id="start-btn">Play</button>
</div>
</div>
<div class="overlay" id="pause-ov">
<div class="modal">
<h1>PAUSED</h1>
<p>Press P or click to resume</p>
<button id="resume-btn">Resume</button>
</div>
</div>
<div class="overlay" id="go-ov">
<div class="modal">
<h1>GAME OVER</h1>
<div class="score-line">Score: <strong id="final-score">0</strong></div>
<button id="restart-btn">Play Again</button>
</div>
</div>
<script>
(function(){
const COLS=10,ROWS=20,BLOCK=30;
const canvas=document.getElementById('game');
const ctx=canvas.getContext('2d');
const nextCanvas=document.getElementById('next');
const nctx=nextCanvas.getContext('2d');
const COLORS={
I:'#00f0f0',O:'#f0f000',T:'#a000f0',S:'#00f000',
Z:'#f00000',J:'#0000f0',L:'#f0a000'
};
const SHAPES={
I:[[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]],
O:[[1,1],[1,1]],
T:[[0,1,0],[1,1,1],[0,0,0]],
S:[[0,1,1],[1,1,0],[0,0,0]],
Z:[[1,1,0],[0,1,1],[0,0,0]],
J:[[1,0,0],[1,1,1],[0,0,0]],
L:[[0,0,1],[1,1,1],[0,0,0]]
};
const PIECE_TYPES=['I','O','T','S','Z','J','L'];
let grid,piece,nextPiece,score,lines,level,dropInterval,dropTimer,lastTime,paused,gameOver,started,animId;
function createGrid(){
const g=[];
for(let r=0;r<ROWS;r++)g.push(new Array(COLS).fill(null));
return g;
}
function randomType(){return PIECE_TYPES[Math.floor(Math.random()*7)];}
function spawnPiece(type){
const shape=SHAPES[type].map(r=>r.slice());
const w=shape[0].length;
return{type,shape,x:Math.floor((COLS-w)/2),y:0,color:COLORS[type]};
}
function rotate(shape){
const N=shape.length;
const res=Array.from({length:N},()=>new Array(N).fill(0));
for(let r=0;r<N;r++)for(let c=0;c<N;c++)res[c][N-1-r]=shape[r][c];
return res;
}
function collides(p,ox,oy,shape){
const s=shape||p.shape;
for(let r=0;r<s.length;r++){
for(let c=0;c<s[r].length;c++){
if(!s[r][c])continue;
const nx=p.x+c+ox,ny=p.y+r+oy;
if(nx<0||nx>=COLS||ny>=ROWS)return true;
if(ny>=0&&grid[ny][nx])return true;
}
}
return false;
}
function merge(p){
p.shape.forEach((row,r)=>{
row.forEach((v,c)=>{
if(v){
const y=p.y+r,x=p.x+c;
if(y>=0&&y<ROWS&&x>=0&&x<COLS)grid[y][x]=p.color;
}
});
});
}
function clearLines(){
let cleared=0;
for(let r=ROWS-1;r>=0;r--){
if(grid[r].every(c=>c!==null)){
grid.splice(r,1);
grid.unshift(new Array(COLS).fill(null));
cleared++;
r++;
}
}
if(cleared>0){
const pts=[0,100,300,500,800];
score+=pts[cleared]*level;
lines+=cleared;
level=Math.floor(lines/10)+1;
dropInterval=Math.max(80,1000-(level-1)*80);
updateHUD();
}
}
function hardDrop(){
let dist=0;
while(!collides(piece,0,1)){piece.y++;dist++;}
score+=dist*2;
lockPiece();
}
function lockPiece(){
merge(piece);
clearLines();
piece=nextPiece;
nextPiece=spawnPiece(randomType());
drawNext();
if(collides(piece,0,0)){
gameOver=true;
cancelAnimationFrame(animId);
document.getElementById('final-score').textContent=score;
document.getElementById('go-ov').classList.add('show');
}
}
function drawBlock(c,x,y,color,size){
const s=size||BLOCK;
const g=c.createLinearGradient(x,y,x+s,y+s);
g.addColorStop(0,lighten(color,40));
g.addColorStop(0.4,color);
g.addColorStop(1,darken(color,30));
c.fillStyle=g;
c.fillRect(x+1,y+1,s-2,s-2);
c.fillStyle='rgba(255,255,255,0.25)';
c.fillRect(x+2,y+2,s-4,Math.floor(s/4));
c.strokeStyle='rgba(0,0,0,0.3)';
c.lineWidth=1;
c.strokeRect(x+1,y+1,s-2,s-2);
}
function lighten(hex,amt){
const n=parseInt(hex.slice(1),16);
let r=Math.min(255,(n>>16)+amt),g=Math.min(255,((n>>8)&0xff)+amt),b=Math.min(255,(n&0xff)+amt);
return`#${((r<<16)|(g<<8)|b).toString(16).padStart(6,'0')}`;
}
function darken(hex,amt){
const n=parseInt(hex.slice(1),16);
let r=Math.max(0,(n>>16)-amt),g=Math.max(0,((n>>8)&0xff)-amt),b=Math.max(0,(n&0xff)-amt);
return`#${((r<<16)|(g<<8)|b).toString(16).padStart(6,'0')}`;
}
function drawGhost(){
let gy=0;
while(!collides(piece,0,gy+1))gy++;
if(gy===0)return;
piece.shape.forEach((row,r)=>{
row.forEach((v,c)=>{
if(v){
const x=(piece.x+c)*BLOCK,y=(piece.y+r+gy)*BLOCK;
ctx.fillStyle='rgba(255,255,255,0.08)';
ctx.fillRect(x+1,y+1,BLOCK-2,BLOCK-2);
ctx.strokeStyle='rgba(255,255,255,0.2)';
ctx.lineWidth=1;
ctx.strokeRect(x+2,y+2,BLOCK-4,BLOCK-4);
}
});
});
}
function draw(){
ctx.fillStyle='#1a1a2e';
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.strokeStyle='rgba(255,255,255,0.03)';
ctx.lineWidth=1;
for(let r=0;r<=ROWS;r++){ctx.beginPath();ctx.moveTo(0,r*BLOCK);ctx.lineTo(COLS*BLOCK,r*BLOCK);ctx.stroke();}
for(let c=0;c<=COLS;c++){ctx.beginPath();ctx.moveTo(c*BLOCK,0);ctx.lineTo(c*BLOCK,ROWS*BLOCK);ctx.stroke();}
for(let r=0;r<ROWS;r++)for(let c=0;c<COLS;c++)if(grid[r][c])drawBlock(ctx,c*BLOCK,r*BLOCK,grid[r][c]);
if(piece&&!gameOver){
drawGhost();
piece.shape.forEach((row,r)=>{
row.forEach((v,c)=>{
if(v){
const y=piece.y+r;
if(y>=0)drawBlock(ctx,(piece.x+c)*BLOCK,y*BLOCK,piece.color);
}
});
});
}
}
function drawNext(){
nctx.fillStyle='#12122a';
nctx.fillRect(0,0,nextCanvas.width,nextCanvas.height);
if(!nextPiece)return;
const s=nextPiece.shape;
const bs=24;
const w=s[0].length*bs,h=s.length*bs;
const ox=(nextCanvas.width-w)/2,oy=(nextCanvas.height-h)/2;
s.forEach((row,r)=>{
row.forEach((v,c)=>{
if(v)drawBlock(nctx,ox+c*bs,oy+r*bs,nextPiece.color,bs);
});
});
}
function updateHUD(){
document.getElementById('score').textContent=score;
document.getElementById('lines').textContent=lines;
document.getElementById('level').textContent=level;
}
function gameLoop(time){
if(gameOver||paused)return;
animId=requestAnimationFrame(gameLoop);
const delta=time-lastTime;
if(delta>=dropInterval){
lastTime=time;
if(!collides(piece,0,1)){piece.y++;}
else{lockPiece();}
}
draw();
}
function startGame(){
grid=createGrid();
score=0;lines=0;level=1;
dropInterval=1000;
paused=false;gameOver=false;started=true;
piece=spawnPiece(randomType());
nextPiece=spawnPiece(randomType());
updateHUD();
drawNext();
lastTime=performance.now();
document.getElementById('start-ov').classList.remove('show');
document.getElementById('go-ov').classList.remove('show');
document.getElementById('pause-ov').classList.remove('show');
cancelAnimationFrame(animId);
animId=requestAnimationFrame(gameLoop);
}
function togglePause(){
if(!started||gameOver)return;
paused=!paused;
if(paused){
cancelAnimationFrame(animId);
document.getElementById('pause-ov').classList.add('show');
}else{
document.getElementById('pause-ov').classList.remove('show');
lastTime=performance.now();
animId=requestAnimationFrame(gameLoop);
}
}
document.addEventListener('keydown',e=>{
if(!started&&e.key!=='Enter'&&e.key!==' ')return;
if(gameOver&&(e.key==='Enter'||e.key===' ')){startGame();return;}
if(e.key==='p'||e.key==='P'){togglePause();return;}
if(paused||gameOver||!started)return;
switch(e.key){
case'ArrowLeft':case'a':case'A':
if(!collides(piece,-1,0))piece.x--;
break;
case'ArrowRight':case'd':case'D':
if(!collides(piece,1,0))piece.x++;
break;
case'ArrowDown':case's':case'S':
if(!collides(piece,0,1)){piece.y++;score+=1;updateHUD();}
else lockPiece();
break;
case'ArrowUp':case'w':case'W':{
const rotated=rotate(piece.shape);
let kicks=[0,-1,1,-2,2];
if(piece.type==='I')kicks=[0,-1,1,-2,2];
for(const k of kicks){
if(!collides(piece,k,0,rotated)){piece.shape=rotated;piece.x+=k;break;}
}
break;
}
case' ':
e.preventDefault();
hardDrop();
break;
}
draw();
});
document.getElementById('start-btn').addEventListener('click',startGame);
document.getElementById('restart-btn').addEventListener('click',startGame);
document.getElementById('resume-btn').addEventListener('click',togglePause);
document.getElementById('pause-ov').addEventListener('click',e=>{if(e.target.id==='pause-ov')togglePause();});
draw();
})();
</script>
</body>
</html>