can you make a bot that plays it
Awesome idea! I've added an AI bot that plays Tetris for you! 🤖 Hit the "🤖 Bot ON" button to watch it think and place pieces automatically. You can toggle it off anytime to play yourself. The bot looks ahead and scores each possible position to find the best move!
can you make the bot hidden and only turns on if you press b
Done! The bot button is now hidden — just press **B** on your keyboard to secretly toggle the bot on and off. A tiny message will flash on screen so you know it's active! 🤫🤖
can you also make it so the highset score is saved nomatter who plays it
Done! The high score is now saved in your browser's memory using localStorage — so it stays even if you close the tab or someone else plays! 🏆 See if you (or the bot!) can beat it!
nothing is showing
Oops, let me fix that! I'll rebuild the whole thing fresh so everything shows up correctly. 🎮
still doesent work
Let me completely rebuild it from scratch — I think something got tangled up in the code! 🛠️
can you make each tetris block a diffrent element like in minecraft and make it so that they have diffrent effects on eachother
Whoa, what a cool idea! Here's your Minecraft-elements Tetris! Each block is a different element — Fire 🔥, Water 💧, Earth 🌍, Ice ❄️, Lightning ⚡, Wood 🪵, and Stone 🪨 — and when matching elements stack next to each other they trigger special effects like clearing rows, exploding blocks, and more! Press B to toggle the bot! 🤖
can you make water flow and make the ractions more realistic
Awesome upgrade! Water now flows and spreads sideways like real water, and all the reactions are way more realistic — Fire evaporates Water, Ice freezes Water solid, Lightning supercharges through Water, Wood floats on Water, and much more! 🌊🔥❄️⚡
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Element Tetris!</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #0a0a1a; display: flex; justify-content: center; align-items: center; min-height: 100vh; font-family: 'Segoe UI', sans-serif; color: #fff; } #wrapper { display: flex; gap: 24px; align-items: flex-start; padding: 20px; } #gameCanvas { border: 3px solid #e94560; box-shadow: 0 0 24px #e9456088; background: #0f0f23; display: block; } #sidebar { display: flex; flex-direction: column; gap: 14px; min-width: 150px; } .panel { background: #16213e; border: 2px solid #e94560; border-radius: 10px; padding: 12px; text-align: center; } .panel h3 { font-size: 12px; color: #e94560; text-transform: uppercase; letter-spacing: 2px; margin-bottom: 6px; } .panel p { font-size: 24px; font-weight: bold; color: #fff; } .panel p.highlight { color: #ffd700; } #nextCanvas { display: block; margin: 0 auto; background: #0f0f23; border-radius: 6px; } #startBtn { background: #e94560; color: #fff; border: none; border-radius: 8px; padding: 12px 0; width: 100%; font-size: 15px; font-weight: bold; cursor: pointer; letter-spacing: 1px; } #startBtn:hover { background: #c73652; } .controls { font-size: 11px; color: #aaa; line-height: 1.9; text-align: left; } .controls span { color: #e94560; font-weight: bold; } .legend { font-size: 11px; line-height: 2.0; text-align: left; } #overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.92); display: flex; flex-direction: column; justify-content: center; align-items: center; z-index: 10; } #overlay h1 { font-size: 38px; color: #e94560; text-shadow: 0 0 20px #e94560; margin-bottom: 8px; } #overlay p { font-size: 15px; color: #fff; margin-bottom: 8px; text-align:center; max-width:340px; } #overlay button { background: #e94560; color: #fff; border: none; border-radius: 10px; padding: 14px 40px; font-size: 18px; font-weight: bold; cursor: pointer; margin: 8px; } #overlay button:hover { background: #c73652; } .new-record { color: #ffd700; font-size: 20px; font-weight: bold; margin-bottom: 8px; text-shadow: 0 0 12px #ffd700; } #toast { position: fixed; top: 24px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.85); border: 2px solid #00e5ff; color: #00e5ff; border-radius: 10px; padding: 10px 28px; font-size: 15px; font-weight: bold; pointer-events: none; opacity: 0; transition: opacity 0.4s; z-index: 200; text-align: center; max-width: 340px; } #toast.show { opacity: 1; } #effectLog { position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%); display: flex; flex-direction: column; align-items: center; gap: 4px; pointer-events: none; z-index: 200; } .effect-msg { background: rgba(0,0,0,0.85); border-radius: 8px; padding: 5px 18px; font-size: 13px; font-weight: bold; animation: fadeUp 2.5s forwards; } @keyframes fadeUp { 0% { opacity:1; transform: translateY(0); } 70% { opacity:1; } 100% { opacity:0; transform: translateY(-40px); } } </style> </head> <body> <div id="overlay"> <h1>⚗️ Element Tetris!</h1> <p>Stack elements to trigger realistic reactions!<br> 🔥 Fire evaporates 💧 Water<br> 💧 Water flows and spreads!<br> ❄️ Ice freezes 💧 Water solid<br> ⚡ Lightning travels through 💧 Water<br> 🪵 Wood floats on 💧 Water & burns with 🔥<br> 🪨 Stone sinks through 💧 Water<br> 🌍 Earth absorbs 💧 Water & grounds ⚡</p> <button id="playBtn">▶ Play!</button> </div> <div id="wrapper"> <canvas id="gameCanvas" width="300" height="600"></canvas> <div id="sidebar"> <div class="panel"><h3>Score</h3><p id="scoreVal">0</p></div> <div class="panel"><h3>🏆 Best</h3><p id="highScoreVal" class="highlight">0</p></div> <div class="panel"><h3>Level</h3><p id="levelVal">1</p></div> <div class="panel"><h3>Lines</h3><p id="linesVal">0</p></div> <div class="panel"> <h3>Next</h3> <canvas id="nextCanvas" width="100" height="100"></canvas> </div> <button id="startBtn">↺ Restart</button> <div class="panel controls"> <span>← →</span> Move<br> <span>↑</span> Rotate<br> <span>↓</span> Soft Drop<br> <span>Space</span> Hard Drop<br> <span>P</span> Pause </div> <div class="panel legend"> 🔥 Fire<br> 💧 Water (flows!)<br> ❄️ Ice<br> ⚡ Lightning<br> 🪵 Wood<br> 🪨 Stone<br> 🌍 Earth </div> </div> </div> <div id="toast"></div> <div id="effectLog"></div> <script> (function() { var canvas = document.getElementById('gameCanvas'); var ctx = canvas.getContext('2d'); var nCanvas = document.getElementById('nextCanvas'); var nCtx = nCanvas.getContext('2d'); var COLS = 10, ROWS = 20, SZ = 30; // Elements: 1=Fire 2=Water 3=Ice 4=Lightning 5=Wood 6=Stone 7=Earth var ELEMENTS = [ null, { name:'Fire', emoji:'🔥', color:'#ff4500', glow:'#ff7700' }, { name:'Water', emoji:'💧', color:'#1565c0', glow:'#42a5f5' }, { name:'Ice', emoji:'❄️', color:'#b3e5fc', glow:'#e1f5fe' }, { name:'Lightning', emoji:'⚡', color:'#f9a825', glow:'#fff176' }, { name:'Wood', emoji:'🪵', color:'#5d4037', glow:'#a1887f' }, { name:'Stone', emoji:'🪨', color:'#546e7a', glow:'#90a4ae' }, { name:'Earth', emoji:'🌍', color:'#2e7d32', glow:'#66bb6a' } ]; // Water animation offset var waterAnim = 0; var SHAPES = [ [], [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]], [[2,0,0],[2,2,2],[0,0,0]], [[0,0,3],[3,3,3],[0,0,0]], [[4,4],[4,4]], [[0,5,5],[5,5,0],[0,0,0]], [[0,6,0],[6,6,6],[0,0,0]], [[7,7,0],[0,7,7],[0,0,0]] ]; var board, piece, nextPiece, score, level, lines, gameOver, paused, animId; var dropCounter, dropInterval, lastTime; var waterFlowTimer = 0, waterFlowInterval = 300; var botEnabled = false, botMoveTimer = 0, botTarget = null, botMoveInterval = 80; var toastTimeout = null; var reactionCooldown = {}; // prevent reaction spam var highScore = 0; try { highScore = parseInt(localStorage.getItem('elemTetrisHigh2') || '0', 10) || 0; } catch(e) {} document.getElementById('highScoreVal').textContent = highScore; function saveHigh() { if (score > highScore) { highScore = score; try { localStorage.setItem('elemTetrisHigh2', highScore); } catch(e) {} document.getElementById('highScoreVal').textContent = highScore; } } function createBoard() { var b = []; for (var r = 0; r < ROWS; r++) { var row = []; for (var c = 0; c < COLS; c++) row.push(0); b.push(row); } return b; } function randomPiece() { var id = Math.floor(Math.random() * 7) + 1; var shape = []; for (var r = 0; r < SHAPES[id].length; r++) shape.push(SHAPES[id][r].slice()); var el = Math.floor(Math.random() * 7) + 1; for (var rr = 0; rr < shape.length; rr++) for (var cc = 0; cc < shape[rr].length; cc++) if (shape[rr][cc]) shape[rr][cc] = el; return { id:id, el:el, shape:shape, x:Math.floor(COLS/2)-Math.floor(shape[0].length/2), y:0 }; } function collide(b, p, ox, oy, shape) { ox = ox||0; oy = oy||0; var s = shape || p.shape; for (var r = 0; r < s.length; r++) for (var c = 0; c < s[r].length; c++) { if (!s[r][c]) continue; var nx = p.x+c+ox, ny = p.y+r+oy; if (nx<0||nx>=COLS||ny>=ROWS) return true; if (ny>=0&&b[ny][nx]) return true; } return false; } function rotate(shape) { var N=shape.length, M=shape[0].length, result=[]; for (var i=0;i<M;i++){var row=[];for(var j=0;j<N;j++)row.push(0);result.push(row);} for (var r=0;r<N;r++) for (var c=0;c<M;c++) result[c][N-1-r]=shape[r][c]; return result; } function placePiece() { for (var r=0;r<piece.shape.length;r++) for (var c=0;c<piece.shape[r].length;c++) if (piece.shape[r][c]&&piece.y+r>=0) board[piece.y+r][piece.x+c]=piece.shape[r][c]; } // ── WATER FLOW ─────────────────────────────────────────────────────────── // Water tries to flow down, then sideways. Runs every waterFlowInterval ms. function flowWater() { var moved = true; // Do multiple passes so water settles for (var pass = 0; pass < 3; pass++) { moved = false; // Flow downward first for (var r = ROWS-2; r >= 0; r--) { for (var c = 0; c < COLS; c++) { if (board[r][c] === 2) { if (board[r+1][c] === 0) { board[r+1][c] = 2; board[r][c] = 0; moved = true; } } } } // Flow sideways (alternate direction each pass for realism) for (var r2 = ROWS-1; r2 >= 0; r2--) { for (var c2 = 0; c2 < COLS; c2++) { if (board[r2][c2] === 2) { // Only spread sideways if blocked below var blockedBelow = (r2 === ROWS-1 || board[r2+1][c2] !== 0); if (blockedBelow) { // Try left then right var dirs = (Math.random()<0.5) ? [-1,1] : [1,-1]; for (var d = 0; d < dirs.length; d++) { var nc = c2 + dirs[d]; if (nc>=0&&nc<COLS&&board[r2][nc]===0) { // Check if neighbor below is also blocked (don't spread onto cliffs unless water level) board[r2][nc] = 2; // Don't remove source — water spreads (up to a limit) moved = true; break; } } } } } } } // Limit water count to prevent flooding (max 30 water cells) var waterCells = []; for (var r3=0;r3<ROWS;r3++) for (var c3=0;c3<COLS;c3++) if (board[r3][c3]===2) waterCells.push([r3,c3]); if (waterCells.length > 30) { // Remove topmost water cells waterCells.sort(function(a,b){return a[0]-b[0];}); for (var i=0;i<waterCells.length-30;i++) board[waterCells[i][0]][waterCells[i][1]]=0; } } // ── REACTIONS ──────────────────────────────────────────────────────────── function showEffect(msg, color) { color = color||'#00e5ff'; var log = document.getElementById('effectLog'); var div = document.createElement('div'); div.className='effect-msg'; div.style.color=color; div.style.border='1.5px solid '+color; div.textContent=msg; log.appendChild(div); setTimeout(function(){if(div.parentNode)div.parentNode.removeChild(div);},2500); } function getNeighbors(r, c) { var dirs=[[0,1],[0,-1],[1,0],[-1,0]],result=[]; for (var i=0;i<dirs.length;i++){ var nr=r+dirs[i][0],nc=c+dirs[i][1]; if (nr>=0&&nr<ROWS&&nc>=0&&nc<COLS&&board[nr][nc]) result.push({r:nr,c:nc,el:board[nr][nc]}); } return result; } function floodFill(r, c, el, visited) { // Find all connected cells of element el var stack=[[r,c]], cells=[]; while (stack.length) { var cur=stack.pop(), cr=cur[0], cc=cur[1]; var key=cr+','+cc; if (visited[key]) continue; if (cr<0||cr>=ROWS||cc<0||cc>=COLS) continue; if (board[cr][cc]!==el) continue; visited[key]=true; cells.push([cr,cc]); stack.push([cr-1,cc],[cr+1,cc],[cr,cc-1],[cr,cc+1]); } return cells; } function checkReactions() { var toRemove={}, toConvert={}, bonusScore=0; var reacted={}; for (var r=0;r<ROWS;r++) { for (var c=0;c<COLS;c++) { if (!board[r][c]) continue; var el=board[r][c]; var nbrs=getNeighbors(r,c); for (var n=0;n<nbrs.length;n++) { var nb=nbrs[n]; var nel=nb.el; var key=Math.min(r,nb.r)+','+Math.min(c,nb.c)+':'+Math.max(r,nb.r)+','+Math.max(c,nb.c); if (reacted[key]) continue; reacted[key]=true; // 🔥 + 💧 → Fire evaporates water, fire gets smaller if ((el===1&&nel===2)||(el===2&&nel===1)) { var fr=(el===1)?r:nb.r, fc=(el===1)?c:nb.c; var wr=(el===2)?r:nb.r, wc=(el===2)?c:nb.c; toRemove[wr+','+wc]=true; // water evaporates toRemove[fr+','+fc]=true; // fire also consumed showEffect('🔥💧 Fire evaporates Water! Both consumed! +100', '#ff7700'); bonusScore+=100; } // 🔥 + 🪵 → Wood catches fire! Spreads fire else if ((el===1&&nel===5)||(el===5&&nel===1)) { var woodR=(el===5)?r:nb.r, woodC=(el===5)?c:nb.c; toConvert[woodR+','+woodC]=1; // wood becomes fire showEffect('🔥🪵 Wood ignites! Spreads fire! +150', '#ff4500'); bonusScore+=150; } // 🔥 + ❄️ → Fire melts ice into water else if ((el===1&&nel===3)||(el===3&&nel===1)) { var iceR=(el===3)?r:nb.r, iceC=(el===3)?c:nb.c; var fireR2=(el===1)?r:nb.r, fireC2=(el===1)?c:nb.c; toConvert[iceR+','+iceC]=2; // ice → water toRemove[fireR2+','+fireC2]=true; // fire used up showEffect('🔥❄️ Fire melts Ice into Water! +120', '#42a5f5'); bonusScore+=120; } // 💧 + ❄️ → Water freezes into ice else if ((el===2&&nel===3)||(el===3&&nel===2)) { var watR=(el===2)?r:nb.r, watC=(el===2)?c:nb.c; toConvert[watR+','+watC]=3; // water → ice showEffect('💧❄️ Water freezes solid! +80', '#b3e5fc'); bonusScore+=80; } // ⚡ + 💧 → Lightning conducts through ALL connected water! Zaps everything touching else if ((el===4&&nel===2)||(el===2&&nel===4)) { var visited={}; var waterStart=(el===2)?[r,c]:[nb.r,nb.c]; var connected=floodFill(waterStart[0],waterStart[1],2,visited); for (var wi=0;wi<connected.length;wi++) toRemove[connected[wi][0]+','+connected[wi][1]]=true; // Also zap things touching water for (var wi2=0;wi2<connected.length;wi2++){ var wnbrs=getNeighbors(connected[wi2][0],connected[wi2][1]); for (var wn=0;wn<wnbrs.length;wn++) if (wnbrs[wn].el!==2) toRemove[wnbrs[wn].r+','+wnbrs[wn].c]=true; } showEffect('⚡💧 Lightning conducts through Water! Chain zap! +300', '#fff176'); bonusScore+=300; } // ⚡ + 🌍 → Grounded! Lightning disappears safely, big bonus else if ((el===4&&nel===7)||(el===7&&nel===4)) { var lightR=(el===4)?r:nb.r, lightC=(el===4)?c:nb.c; toRemove[lightR+','+lightC]=true; showEffect('⚡🌍 Lightning grounded by Earth! Safe! +400', '#66bb6a'); bonusScore+=400; } // ⚡ + 🪵 → Lightning sets wood on fire else if ((el===4&&nel===5)||(el===5&&nel===4)) { var wdR=(el===5)?r:nb.r, wdC=(el===5)?c:nb.c; toConvert[wdR+','+wdC]=1; showEffect('⚡🪵 Lightning ignites Wood! +200', '#ff4500'); bonusScore+=200; } // 🪵 + 💧 → Wood floats! Wood moves up one row if below water else if ((el===5&&nel===2)||(el===2&&nel===5)) { var wR=(el===5)?r:nb.r, wC=(el===5)?c:nb.c; // Wood floats: if water is below wood, push wood up if (el===5&&nb.r===r+1) { // water below wood if (wR-1>=0&&board[wR-1][wC]===0) { toConvert[wR+','+wC]=-1; // mark to float } } else if (el===2&&r===nb.r+1) { // wood is above water // water under wood, already floating } showEffect('🪵💧 Wood floats on Water! +50', '#a1887f'); bonusScore+=50; } // 🪨 + 💧 → Stone sinks through water (stone goes down, water goes up) else if ((el===6&&nel===2)||(el===2&&nel===6)) { var stR=(el===6)?r:nb.r, stC=(el===6)?c:nb.c; var waR=(el===2)?r:nb.r, waC=(el===2)?c:nb.c; // Stone sinks: if stone above water, swap if (el===6&&nb.r===r+1&&board[r+1][c]===2) { board[r+1][c]=6; board[r][c]=2; showEffect('🪨💧 Stone sinks through Water! +30', '#90a4ae'); bonusScore+=30; } } // 🌍 + 💧 → Earth absorbs water (mud! earth stays, water removed) else if ((el===7&&nel===2)||(el===2&&nel===7)) { var absWR=(el===2)?r:nb.r, absWC=(el===2)?c:nb.c; toRemove[absWR+','+absWC]=true; showEffect('🌍💧 Earth absorbs Water! Mud formed! +60', '#66bb6a'); bonusScore+=60; } // 🪨 + 🪵 → Stone crushes wood else if ((el===6&&nel===5)||(el===5&&nel===6)) { var cWR=(el===5)?r:nb.r, cWC=(el===5)?c:nb.c; toRemove[cWR+','+cWC]=true; showEffect('🪨🪵 Stone crushes Wood! +80', '#90a4ae'); bonusScore+=80; } // 🌍 + 🌍 → Earthquake! Clears fullest row else if (el===7&&nel===7) { var rowFills=[]; for (var rq=0;rq<ROWS;rq++){var cnt=0;for(var cq=0;cq<COLS;cq++)if(board[rq][cq])cnt++;rowFills.push({r:rq,cnt:cnt});} rowFills.sort(function(a,b){return b.cnt-a.cnt;}); for (var qi=0;qi<Math.min(1,rowFills.length);qi++) for(var qc=0;qc<COLS;qc++) toRemove[rowFills[qi].r+','+qc]=true; showEffect('🌍🌍 Earthquake! Row destroyed! +250', '#228B22'); bonusScore+=250; } } } } // Apply conversions var convKeys=Object.keys(toConvert); for (var ci=0;ci<convKeys.length;ci++){ var cp=convKeys[ci].split(','); var cr2=parseInt(cp[0]),cc2=parseInt(cp[1]); if (toConvert[convKeys[ci]]===-1) { // float wood up if (cr2-1>=0&&board[cr2-1][cc2]===0){board[cr2-1][cc2]=5;board[cr2][cc2]=0;} } else { board[cr2][cc2]=toConvert[convKeys[ci]]; } } // Apply removals var remKeys=Object.keys(toRemove); for (var ri=0;ri<remKeys.length;ri++){ var rp=remKeys[ri].split(','); board[parseInt(rp[0])][parseInt(rp[1])]=0; } score+=bonusScore; updateUI(); } function clearLines() { var cleared=0; for (var r=ROWS-1;r>=0;r--){ var full=true; for (var c=0;c<COLS;c++) if (!board[r][c]){full=false;break;} if (full){ board.splice(r,1); var empty=[];for(var cc=0;cc<COLS;cc++)empty.push(0); board.unshift(empty); cleared++;r++; } } if (cleared){ var pts=[0,100,300,500,800]; score+=(pts[cleared]||800)*level; lines+=cleared; level=Math.floor(lines/10)+1; dropInterval=Math.max(80,1000-(level-1)*90); updateUI(); } } function updateUI() { document.getElementById('scoreVal').textContent=score; document.getElementById('levelVal').textContent=level; document.getElementById('linesVal').textContent=lines; saveHigh(); document.getElementById('highScoreVal').textContent=highScore; } // ── DRAWING ────────────────────────────────────────────────────────────── function drawBlock(el, x, y, sz, context, isWater) { if (!el) return; var info=ELEMENTS[el]; if (el===2) { // Animated water var waveOffset = Math.sin(waterAnim + x*0.8 + y*0.3) * 3; var grd=context.createLinearGradient(x*sz,y*sz+waveOffset,x*sz+sz,y*sz+sz); grd.addColorStop(0,'#42a5f5'); grd.addColorStop(0.5,'#1565c0'); grd.addColorStop(1,'#0d47a1'); context.fillStyle=grd; context.fillRect(x*sz+1,y*sz+1,sz-2,sz-2); // Wave highlight context.fillStyle='rgba(255,255,255,0.3)'; context.beginPath(); var wx=x*sz+2, wy=y*sz+4+waveOffset; context.moveTo(wx,wy); for (var i=0;i<sz-4;i++) context.lineTo(wx+i, wy+Math.sin((i/4)+waterAnim)*2); context.lineTo(wx+sz-4,wy+5); context.lineTo(wx,wy+5); context.closePath(); context.fill(); } else { var grd2=context.createLinearGradient(x*sz,y*sz,x*sz+sz,y*sz+sz); grd2.addColorStop(0,info.glow); grd2.addColorStop(1,info.color); context.fillStyle=grd2; context.fillRect(x*sz+1,y*sz+1,sz-2,sz-2); context.fillStyle='rgba(255,255,255,0.2)'; context.fillRect(x*sz+2,y*sz+2,sz-4,6); } // Emoji context.font=sz<24?'10px serif':'14px serif'; context.textAlign='center'; context.textBaseline='middle'; context.fillText(info.emoji,x*sz+sz/2,y*sz+sz/2+1); } function getGhostY() { var gy=0; while (!collide(board,piece,0,gy+1)) gy++; return gy; } function drawBoard() { ctx.clearRect(0,0,canvas.width,canvas.height); // Grid ctx.strokeStyle='#1e1e3a'; ctx.lineWidth=0.5; for (var r=0;r<ROWS;r++) for (var c=0;c<COLS;c++) ctx.strokeRect(c*SZ,r*SZ,SZ,SZ); // Board blocks for (var rr=0;rr<ROWS;rr++) for (var cc=0;cc<COLS;cc++) if (board[rr][cc]) drawBlock(board[rr][cc],cc,rr,SZ,ctx); // Ghost var ghostY=getGhostY(); for (var r2=0;r2<piece.shape.length;r2++) for (var c2=0;c2<piece.shape[r2].length;c2++) if (piece.shape[r2][c2]){ ctx.fillStyle='rgba(255,255,255,0.1)'; ctx.fillRect((piece.x+c2)*SZ+1,(piece.y+r2+ghostY)*SZ+1,SZ-2,SZ-2); } // Active piece for (var r3=0;r3<piece.shape.length;r3++) for (var c3=0;c3<piece.shape[r3].length;c3++) if (piece.shape[r3][c3]) drawBlock(piece.shape[r3][c3],piece.x+c3,piece.y+r3,SZ,ctx); // Pause if (paused){ ctx.fillStyle='rgba(0,0,0,0.6)';ctx.fillRect(0,0,canvas.width,canvas.height); ctx.fillStyle='#fff';ctx.font='bold 32px Segoe UI';ctx.textAlign='center';ctx.textBaseline='middle'; ctx.fillText('⏸ PAUSED',canvas.width/2,canvas.height/2); } } function drawNext() { nCtx.clearRect(0,0,nCanvas.width,nCanvas.height); var s=nextPiece.shape,bsz=20; var offX=Math.floor((5-s[0].length)/2),offY=Math.floor((5-s.length)/2); for (var r=0;r<s.length;r++) for (var c=0;c<s[r].length;c++) if (s[r][c]) drawBlock(s[r][c],offX+c,offY+r,bsz,nCtx); } function spawnPiece() { piece=nextPiece; nextPiece=randomPiece(); drawNext(); botTarget=null; if (collide(board,piece)){endGame();return;} if (botEnabled) botTarget=computeBotTarget(); } function endGame() { gameOver=true; cancelAnimationFrame(animId); saveHigh(); setTimeout(function(){ var overlay=document.getElementById('overlay'); var isNew=score>0&&score>=highScore; overlay.innerHTML= '<h1>💀 Game Over</h1>'+ (isNew?'<div class="new-record">🏆 New High Score!</div>':'')+ '<p>Score: <b>'+score+'</b> | Level: <b>'+level+'</b></p>'+ '<p style="color:#ffd700;margin-bottom:20px;">Best: <b>'+highScore+'</b></p>'+ '<button id="againBtn">▶ Play Again!</button>'; overlay.style.display='flex'; document.getElementById('againBtn').addEventListener('click',startGame); },300); } function startGame() { document.getElementById('overlay').style.display='none'; board=createBoard();score=0;level=1;lines=0; dropCounter=0;dropInterval=1000;lastTime=0; paused=false;gameOver=false;botMoveTimer=0;botTarget=null;waterFlowTimer=0; nextPiece=randomPiece();spawnPiece();updateUI(); cancelAnimationFrame(animId); animId=requestAnimationFrame(loop); } function loop(time) { if (!time) time=0; if (gameOver) return; var delta=time-lastTime; lastTime=time; waterAnim+=0.08; // water wave animation if (!paused){ dropCounter+=delta; waterFlowTimer+=delta; if (botEnabled){botMoveTimer+=delta;if(botMoveTimer>=botMoveInterval){botMoveTimer=0;doBotStep();}} // Water flows periodically if (waterFlowTimer>=waterFlowInterval){ waterFlowTimer=0; flowWater(); checkReactions(); // re-check after water moves } if (dropCounter>=dropInterval){moveDown();dropCounter=0;} drawBoard(); } animId=requestAnimationFrame(loop); } function moveDown() { if (!collide(board,piece,0,1)){piece.y++;} else { placePiece(); checkReactions(); clearLines(); spawnPiece(); dropCounter=0; } } function hardDrop() { var gy=getGhostY();piece.y+=gy;score+=gy*2;updateUI(); placePiece();checkReactions();clearLines();spawnPiece();dropCounter=0; } // ── BOT ────────────────────────────────────────────────────────────────── function showToast(msg,color) { var t=document.getElementById('toast'); t.textContent=msg;t.style.borderColor=color||'#00e5ff';t.style.color=color||'#00e5ff'; t.classList.add('show'); if (toastTimeout) clearTimeout(toastTimeout); toastTimeout=setTimeout(function(){t.classList.remove('show');},1800); } function toggleBot() { botEnabled=!botEnabled; showToast(botEnabled?'🤖 BOT ON — watch and learn!':'🤖 BOT OFF — your turn!',botEnabled?'#00e5ff':'#ff9100'); if (botEnabled&&piece) botTarget=computeBotTarget(); else botTarget=null; } function dropY(b,shape,px) { var tempP={shape:shape,x:px,y:piece.y}; while (!collide(b,tempP,0,1)) tempP.y++; return tempP.y; } function simulatePlace(b,shape,px,py) { var nb=[];for(var r=0;r<ROWS;r++) nb.push(b[r].slice()); for(var rr=0;rr<shape.length;rr++) for(var cc=0;cc<shape[rr].length;cc++) if(shape[rr][cc]&&py+rr>=0&&py+rr<ROWS&&px+cc>=0&&px+cc<COLS) nb[py+rr][px+cc]=shape[rr][cc]; return nb; } function scoreBoard(b) { var heights=[],holes=0,bumpiness=0,completedLines=0; for(var c=0;c<COLS;c++){ var h=0,found=false; for(var r=0;r<ROWS;r++){if(b[r][c]&&!found){h=ROWS-r;found=true;}if(found&&!b[r][c])holes++;} heights.push(h); } var aggH=0;for(var i=0;i<heights.length;i++) aggH+=heights[i]; for(var j=0;j<COLS-1;j++) bumpiness+=Math.abs(heights[j]-heights[j+1]); for(var rr=0;rr<ROWS;rr++){var full=true;for(var cc=0;cc<COLS;cc++)if(!b[rr][cc]){full=false;break;}if(full)completedLines++;} return -0.51*aggH+0.76*completedLines-0.36*holes-0.18*bumpiness; } function getRotations(shape) { var rotations=[],seen=[],cur=[]; for(var r=0;r<shape.length;r++) cur.push(shape[r].slice()); for(var i=0;i<4;i++){ var key=JSON.stringify(cur),dup=false; for(var k=0;k<seen.length;k++) if(seen[k]===key){dup=true;break;} if(dup) break; seen.push(key); var copy=[];for(var rr=0;rr<cur.length;rr++) copy.push(cur[rr].slice()); rotations.push(copy);cur=rotate(cur); } return rotations; } function computeBotTarget() { if (!piece) return null; var rotations=getRotations(piece.shape); var bestScore=-Infinity,bestX=piece.x,bestShape=piece.shape; for(var ri=0;ri<rotations.length;ri++){ var shape=rotations[ri],w=shape[0].length; for(var px=-1;px<=COLS-w+1;px++){ var tempP={shape:shape,x:px,y:piece.y}; if(collide(board,tempP,0,0)) continue; var py=dropY(board,shape,px); var sim=simulatePlace(board,shape,px,py); var s=scoreBoard(sim); if(s>bestScore){bestScore=s;bestX=px;bestShape=shape;} } } return {x:bestX,shape:bestShape}; } function doBotStep() { if(!botTarget){botTarget=computeBotTarget();return;} var tKey=JSON.stringify(botTarget.shape),cKey=JSON.stringify(piece.shape); if(cKey!==tKey){ var rot=rotate(piece.shape),kick=0; if(collide(board,piece,0,0,rot)) kick=piece.x>COLS/2?-1:1; if(!collide(board,piece,kick,0,rot)){piece.shape=rot;piece.x+=kick;} return; } if(piece.x<botTarget.x){if(!collide(board,piece,1,0))piece.x++;return;} if(piece.x>botTarget.x){if(!collide(board,piece,-1,0))piece.x--;return;} hardDrop();botTarget=null; } // ── KEYBOARD ───────────────────────────────────────────────────────────── document.addEventListener('keydown',function(e){ if(gameOver) return; if(e.key==='b'||e.key==='B'){toggleBot();return;} if(e.key==='p'||e.key==='P'){ paused=!paused; if(!paused){lastTime=performance.now();animId=requestAnimationFrame(loop);} return; } if(paused||botEnabled) return; if(e.key==='ArrowLeft'){if(!collide(board,piece,-1,0))piece.x--;} else if(e.key==='ArrowRight'){if(!collide(board,piece,1,0))piece.x++;} else if(e.key==='ArrowDown'){moveDown();score+=1;updateUI();dropCounter=0;} else if(e.key==='ArrowUp'){ var rot=rotate(piece.shape),kick=0; if(collide(board,piece,0,0,rot)) kick=piece.x>COLS/2?-1:1; if(!collide(board,piece,kick,0,rot)){piece.shape=rot;piece.x+=kick;} } else if(e.key===' '){e.preventDefault();hardDrop();} }); document.getElementById('playBtn').addEventListener('click',startGame); document.getElementById('startBtn').addEventListener('click',startGame); })(); </script> </body> </html>