Symple Tetris Game

Einfaches Tetris Spiel was eigentlich fast jeder aus der Gameboy Zeit noch kennen sollte

Der hier verwendete Code

<!DOCTYPE html> <!-- saved from url=(0035)https://pranx.com/tetris/index.html --> <html lang="en" data-lt-installed="true"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Online Tetris Game</title> <!-- <link rel="canonical" href="https://pranx.com/tetris/"> --> <style> body, html { font-family: Helvetica, sans-serif; background-color: transparent;cursor: default !important; } #tetris { margin: 0 auto; padding: 0; color: #444; } #stats { display: none; vertical-align: top; } #canvas { display: inline-block; vertical-align: top; border: 2px solid #333; background-color: #ffffff;} #menu { display: inline-block; vertical-align: top; position: relative; } #menu p { margin: 0.5em 0; text-align: center; } #start { Color: #F00;} #menu p a { text-decoration: none; color: #000; font-weight: bold; } #focusLost { position: fixed; z-index: 1000; top: 0; display: none; bottom: 0; left: 0; right: 0; background: rgba(0,0,0,0.8); font-size: 19px; font-weight: bold; color: #FFF; text-align: center; padding-top: 22%; } #upcoming { display: block; margin: 0 auto; background-color: #E0E0E0; } #score { color: #444; font-weight: bold; vertical-align: middle; } #rows { color: #444; font-weight: bold; vertical-align: middle; } #stats { position: absolute; bottom: 0em; right: 1em; } @media screen and (min-width: 0px) and (min-height: 0px) { #tetris { font-size: 0.75em; width: 250px; } #menu { width: 100px; height: 200px; } #upcoming { width: 50px; height: 50px; } #canvas { width: 100px; height: 200px; } } /* 10px chunks */ @media screen and (min-width: 400px) and (min-height: 400px) { #tetris { font-size: 1.00em; width: 350px; } #menu { width: 150px; height: 300px; } #upcoming { width: 75px; height: 75px; } #canvas { width: 150px; height: 300px; } } /* 15px chunks */ @media screen and (min-width: 500px) and (min-height: 500px) { #tetris { font-size: 1.25em; width: 450px; } #menu { width: 200px; height: 400px; } #upcoming { width: 100px; height: 100px; } #canvas { width: 200px; height: 400px; } } /* 20px chunks */ @media screen and (min-width: 600px) and (min-height: 600px) { #tetris { font-size: 1.50em; width: 550px; } #menu { width: 250px; height: 500px; } #upcoming { width: 125px; height: 125px; } #canvas { width: 250px; height: 500px; } } /* 25px chunks */ @media screen and (min-width: 700px) and (min-height: 700px) { #tetris { font-size: 1.75em; width: 650px; } #menu { width: 300px; height: 600px; } #upcoming { width: 150px; height: 150px; } #canvas { width: 300px; height: 600px; } } /* 30px chunks */ @media screen and (min-width: 800px) and (min-height: 800px) { #tetris { font-size: 2.00em; width: 750px; } #menu { width: 350px; height: 700px; } #upcoming { width: 175px; height: 175px; } #canvas { width: 350px; height: 700px; } } /* 35px chunks */ @media screen and (min-width: 900px) and (min-height: 900px) { #tetris { font-size: 2.25em; width: 850px; } #menu { width: 400px; height: 800px; } #upcoming { width: 200px; height: 200px; } #canvas { width: 400px; height: 800px; } } /* 40px chunks */ </style> <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','GTM-5RJ4ST6');</script> </head> <body onblur="showAlert()"> <div id="tetris"> <div id="menu"> <p id="start"> <a href="javascript:play();"><strong>Click Here To START</strong></a> </p> <p><canvas id="upcoming" width="50" height="50"></canvas></p> <p>score <span id="score">00000</span></p> <p>rows <span id="rows">0</span></p> <div id="stats" style="cursor: pointer; width: 80px; opacity: 0.9; z-index: 10001;"> <div style="background-color: rgb(8, 8, 24); padding: 2px 0px 3px;"> <div style="font-family: Helvetica, Arial, sans-serif; text-align: left; font-size: 9px; color: rgb(0, 255, 255); margin: 0px 0px 1px 3px;"> <span style="font-weight:bold">60 FPS</span> (17-60)</div> <canvas width="74" height="30" style="display: block; margin-left: 3px;"></canvas> </div> </div> </div> <canvas id="canvas" width="100" height="200"> Canvas not supported! </canvas> </div> <script> var loc = window.location.href + ''; if (loc.indexOf('http://') == 0) { window.location.href = loc.replace('http://', 'https://'); // https redirect } var Stats = function() { function s(a, g, d) { var f, c, e; for (c = 0; c < 30; c++) for (f = 0; f < 73; f++) e = (f + c * 74) * 4, a[e] = a[e + 4], a[e + 1] = a[e + 5], a[e + 2] = a[e + 6]; for (c = 0; c < 30; c++) e = (73 + c * 74) * 4, c < g ? (a[e] = b[d].bg.r, a[e + 1] = b[d].bg.g, a[e + 2] = b[d].bg.b) : (a[e] = b[d].fg.r, a[e + 1] = b[d].fg.g, a[e + 2] = b[d].fg.b) } var r = 0, t = 2, g, u = 0, j = (new Date).getTime(), F = j, v = j, l = 0, w = 1E3, x = 0, k, d, a, m, y, n = 0, z = 1E3, A = 0, f, c, o, B, p = 0, C = 1E3, D = 0, h, i, q, E, b = { fps: { bg: { r: 16, g: 16, b: 48 }, fg: { r: 0, g: 255, b: 255 } }, ms: { bg: { r: 16, g: 48, b: 16 }, fg: { r: 0, g: 255, b: 0 } }, mb: { bg: { r: 48, g: 16, b: 26 }, fg: { r: 255, g: 0, b: 128 } } }; g = document.createElement("div"); g.style.cursor = "pointer"; g.style.width = "80px"; g.style.opacity = "0.9"; g.style.zIndex = "10001"; g.addEventListener("click", function() { r++; r == t && (r = 0); k.style.display = "none"; f.style.display = "none"; h.style.display = "none"; switch (r) { case 0: k.style.display = "block"; break; case 1: f.style.display = "block"; break; case 2: h.style.display = "block" } }, !1); k = document.createElement("div"); k.style.backgroundColor = "rgb(" + Math.floor(b.fps.bg.r / 2) + "," + Math.floor(b.fps.bg.g / 2) + "," + Math.floor(b.fps.bg.b / 2) + ")"; k.style.padding = "2px 0px 3px 0px"; g.appendChild(k); d = document.createElement("div"); d.style.fontFamily = "Helvetica, Arial, sans-serif"; d.style.textAlign = "left"; d.style.fontSize = "9px"; d.style.color = "rgb(" + b.fps.fg.r + "," + b.fps.fg.g + "," + b.fps.fg.b + ")"; d.style.margin = "0px 0px 1px 3px"; d.innerHTML = '<span style="font-weight:bold">FPS</span>'; k.appendChild(d); a = document.createElement("canvas"); a.width = 74; a.height = 30; a.style.display = "block"; a.style.marginLeft = "3px"; k.appendChild(a); m = a.getContext("2d"); m.fillStyle = "rgb(" + b.fps.bg.r + "," + b.fps.bg.g + "," + b.fps.bg.b + ")"; m.fillRect(0, 0, a.width, a.height); y = m.getImageData(0, 0, a.width, a.height); f = document.createElement("div"); f.style.backgroundColor = "rgb(" + Math.floor(b.ms.bg.r / 2) + "," + Math.floor(b.ms.bg.g / 2) + "," + Math.floor(b.ms.bg.b / 2) + ")"; f.style.padding = "2px 0px 3px 0px"; f.style.display = "none"; g.appendChild(f); c = document.createElement("div"); c.style.fontFamily = "Helvetica, Arial, sans-serif"; c.style.textAlign = "left"; c.style.fontSize = "9px"; c.style.color = "rgb(" + b.ms.fg.r + "," + b.ms.fg.g + "," + b.ms.fg.b + ")"; c.style.margin = "0px 0px 1px 3px"; c.innerHTML = '<span style="font-weight:bold">MS</span>'; f.appendChild(c); a = document.createElement("canvas"); a.width = 74; a.height = 30; a.style.display = "block"; a.style.marginLeft = "3px"; f.appendChild(a); o = a.getContext("2d"); o.fillStyle = "rgb(" + b.ms.bg.r + "," + b.ms.bg.g + "," + b.ms.bg.b + ")"; o.fillRect(0, 0, a.width, a.height); B = o.getImageData(0, 0, a.width, a.height); try { performance && performance.memory && performance.memory.totalJSHeapSize && (t = 3) } catch (G) {} h = document.createElement("div"); h.style.backgroundColor = "rgb(" + Math.floor(b.mb.bg.r / 2) + "," + Math.floor(b.mb.bg.g / 2) + "," + Math.floor(b.mb.bg.b / 2) + ")"; h.style.padding = "2px 0px 3px 0px"; h.style.display = "none"; g.appendChild(h); i = document.createElement("div"); i.style.fontFamily = "Helvetica, Arial, sans-serif"; i.style.textAlign = "left"; i.style.fontSize = "9px"; i.style.color = "rgb(" + b.mb.fg.r + "," + b.mb.fg.g + "," + b.mb.fg.b + ")"; i.style.margin = "0px 0px 1px 3px"; i.innerHTML = '<span style="font-weight:bold">MB</span>'; h.appendChild(i); a = document.createElement("canvas"); a.width = 74; a.height = 30; a.style.display = "block"; a.style.marginLeft = "3px"; h.appendChild(a); q = a.getContext("2d"); q.fillStyle = "#301010"; q.fillRect(0, 0, a.width, a.height); E = q.getImageData(0, 0, a.width, a.height); return { domElement: g, update: function() { u++; j = (new Date).getTime(); n = j - F; z = Math.min(z, n); A = Math.max(A, n); s(B.data, Math.min(30, 30 - n / 200 * 30), "ms"); c.innerHTML = '<span style="font-weight:bold">' + n + " MS</span> (" + z + "-" + A + ")"; o.putImageData(B, 0, 0); F = j; if (j > v + 1E3) { l = Math.round(u * 1E3 / (j - v)); w = Math.min(w, l); x = Math.max(x, l); s(y.data, Math.min(30, 30 - l / 100 * 30), "fps"); d.innerHTML = '<span style="font-weight:bold">' + l + " FPS</span> (" + w + "-" + x + ")"; m.putImageData(y, 0, 0); if (t == 3) p = performance.memory.usedJSHeapSize * 9.54E-7, C = Math.min(C, p), D = Math.max(D, p), s(E.data, Math.min(30, 30 - p / 2), "mb"), i.innerHTML = '<span style="font-weight:bold">' + Math.round(p) + " MB</span> (" + Math.round(C) + "-" + Math.round(D) + ")", q.putImageData(E, 0, 0); v = j; u = 0 } } } }; //------------------------------------------------------------------------- // base helper methods //------------------------------------------------------------------------- function get(id) { return document.getElementById(id); } function hide(id) { get(id).style.visibility = 'hidden'; } function show(id) { get(id).style.visibility = null; } function html(id, html) { get(id).innerHTML = html; } function timestamp() { return new Date().getTime(); } function random(min, max) { return (min + (Math.random() * (max - min))); } function randomChoice(choices) { return choices[Math.round(random(0, choices.length - 1))]; } if (!window.requestAnimationFrame) { // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ window.requestAnimationFrame = window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback, element) { window.setTimeout(callback, 1000 / 60); } } //------------------------------------------------------------------------- // game constants //------------------------------------------------------------------------- var KEY = { ESC: 27, SPACE: 32, LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40 } , DIR = { UP: 0, RIGHT: 1, DOWN: 2, LEFT: 3, MIN: 0, MAX: 3 } , stats = new Stats() , canvas = get('canvas') , ctx = canvas.getContext('2d') , ucanvas = get('upcoming') , uctx = ucanvas.getContext('2d') , speed = { start: 0.6, decrement: 0.005, min: 0.1 } , // how long before piece drops by 1 row (seconds) nx = 10 , // width of tetris court (in blocks) ny = 20 , // height of tetris court (in blocks) nu = 5; // width/height of upcoming preview (in blocks) //------------------------------------------------------------------------- // game variables (initialized during reset) //------------------------------------------------------------------------- var dx, dy, // pixel size of a single tetris block blocks, // 2 dimensional array (nx*ny) representing tetris court - either empty block or occupied by a 'piece' actions, // queue of user actions (inputs) playing, // true|false - game is in progress dt, // time since starting this game current, // the current piece next, // the next piece score, // the current score vscore, // the currently displayed score (it catches up to score in small chunks - like a spinning slot machine) rows, // number of completed rows in the current game step; // how long before current piece drops by 1 row //------------------------------------------------------------------------- // tetris pieces // // blocks: each element represents a rotation of the piece (0, 90, 180, 270) // each element is a 16 bit integer where the 16 bits represent // a 4x4 set of blocks, e.g. j.blocks[0] = 0x44C0 // // 0100 = 0x4 << 3 = 0x4000 // 0100 = 0x4 << 2 = 0x0400 // 1100 = 0xC << 1 = 0x00C0 // 0000 = 0x0 << 0 = 0x0000 // ------ // 0x44C0 // //------------------------------------------------------------------------- var i = { size: 4, blocks: [0x0F00, 0x2222, 0x00F0, 0x4444], color: 'cyan' }; var j = { size: 3, blocks: [0x44C0, 0x8E00, 0x6440, 0x0E20], color: 'blue' }; var l = { size: 3, blocks: [0x4460, 0x0E80, 0xC440, 0x2E00], color: 'orange' }; var o = { size: 2, blocks: [0xCC00, 0xCC00, 0xCC00, 0xCC00], color: 'yellow' }; var s = { size: 3, blocks: [0x06C0, 0x8C40, 0x6C00, 0x4620], color: 'green' }; var t = { size: 3, blocks: [0x0E40, 0x4C40, 0x4E00, 0x4640], color: 'purple' }; var z = { size: 3, blocks: [0x0C60, 0x4C80, 0xC600, 0x2640], color: 'red' }; //------------------------------------------------ // do the bit manipulation and iterate through each // occupied block (x,y) for a given piece //------------------------------------------------ function eachblock(type, x, y, dir, fn) { var bit, result, row = 0, col = 0, blocks = type.blocks[dir]; for (bit = 0x8000; bit > 0; bit = bit >> 1) { if (blocks & bit) { fn(x + col, y + row); } if (++col === 4) { col = 0; ++row; } } } //----------------------------------------------------- // check if a piece can fit into a position in the grid //----------------------------------------------------- function occupied(type, x, y, dir) { var result = false eachblock(type, x, y, dir, function(x, y) { if ((x < 0) || (x >= nx) || (y < 0) || (y >= ny) || getBlock(x, y)) result = true; }); return result; } function unoccupied(type, x, y, dir) { return !occupied(type, x, y, dir); } //----------------------------------------- // start with 4 instances of each piece and // pick randomly until the 'bag is empty' //----------------------------------------- var pieces = []; function randomPiece() { if (pieces.length == 0) pieces = [i, i, i, i, j, j, j, j, l, l, l, l, o, o, o, o, s, s, s, s, t, t, t, t, z, z, z, z]; var type = pieces.splice(random(0, pieces.length - 1), 1)[0]; return { type: type, dir: DIR.UP, x: Math.round(random(0, nx - type.size)), y: 0 }; } //------------------------------------------------------------------------- // GAME LOOP //------------------------------------------------------------------------- function run() { showStats(); // initialize FPS counter addEvents(); // attach keydown and resize events var last = now = timestamp(); function frame() { now = timestamp(); update(Math.min(1, (now - last) / 1000.0)); // using requestAnimationFrame have to be able to handle large delta's caused when it 'hibernates' in a background or non-visible tab draw(); stats.update(); last = now; requestAnimationFrame(frame, canvas); } resize(); // setup all our sizing information reset(); // reset the per-game variables frame(); // start the first frame } function showStats() { stats.domElement.id = 'stats'; get('menu').appendChild(stats.domElement); } function addEvents() { document.addEventListener('keydown', keydown, false); window.addEventListener('resize', resize, false); } function resize(event) { canvas.width = canvas.clientWidth; // set canvas logical size equal to its physical size canvas.height = canvas.clientHeight; // (ditto) ucanvas.width = ucanvas.clientWidth; ucanvas.height = ucanvas.clientHeight; dx = canvas.width / nx; // pixel size of a single tetris block dy = canvas.height / ny; // (ditto) invalidate(); invalidateNext(); } function keydown(ev) { var handled = false; if (playing) { switch (ev.keyCode) { case KEY.LEFT: actions.push(DIR.LEFT); handled = true; break; case KEY.RIGHT: actions.push(DIR.RIGHT); handled = true; break; case KEY.UP: actions.push(DIR.UP); handled = true; break; case KEY.DOWN: actions.push(DIR.DOWN); handled = true; break; case KEY.ESC: lose(); handled = true; break; } } else if (ev.keyCode == KEY.SPACE) { play(); handled = true; } if (handled) ev.preventDefault(); // prevent arrow keys from scrolling the page (supported in IE9+ and all other browsers) } //------------------------------------------------------------------------- // GAME LOGIC //------------------------------------------------------------------------- function play() { hide('start'); reset(); playing = true; } function lose() { show('start'); setVisualScore(); playing = false; } function setVisualScore(n) { vscore = n || score; invalidateScore(); } function setScore(n) { score = n; setVisualScore(n); } function addScore(n) { score = score + n; } function clearScore() { setScore(0); } function clearRows() { setRows(0); } function setRows(n) { rows = n; step = Math.max(speed.min, speed.start - (speed.decrement * rows)); invalidateRows(); } function addRows(n) { setRows(rows + n); } function getBlock(x, y) { return (blocks && blocks[x] ? blocks[x][y] : null); } function setBlock(x, y, type) { blocks[x] = blocks[x] || []; blocks[x][y] = type; invalidate(); } function clearBlocks() { blocks = []; invalidate(); } function clearActions() { actions = []; } function setCurrentPiece(piece) { current = piece || randomPiece(); invalidate(); } function setNextPiece(piece) { next = piece || randomPiece(); invalidateNext(); } function reset() { dt = 0; clearActions(); clearBlocks(); clearRows(); clearScore(); setCurrentPiece(next); setNextPiece(); } function update(idt) { if (playing) { if (vscore < score) setVisualScore(vscore + 1); handle(actions.shift()); dt = dt + idt; if (dt > step) { dt = dt - step; drop(); } } } function handle(action) { switch (action) { case DIR.LEFT: move(DIR.LEFT); break; case DIR.RIGHT: move(DIR.RIGHT); break; case DIR.UP: rotate(); break; case DIR.DOWN: drop(); break; } } function move(dir) { var x = current.x , y = current.y; switch (dir) { case DIR.RIGHT: x = x + 1; break; case DIR.LEFT: x = x - 1; break; case DIR.DOWN: y = y + 1; break; } if (unoccupied(current.type, x, y, current.dir)) { current.x = x; current.y = y; invalidate(); return true; } else { return false; } } function rotate() { var newdir = (current.dir == DIR.MAX ? DIR.MIN : current.dir + 1); if (unoccupied(current.type, current.x, current.y, newdir)) { current.dir = newdir; invalidate(); } } function drop() { if (!move(DIR.DOWN)) { addScore(10); dropPiece(); removeLines(); setCurrentPiece(next); setNextPiece(randomPiece()); clearActions(); if (occupied(current.type, current.x, current.y, current.dir)) { lose(); } } } function dropPiece() { eachblock(current.type, current.x, current.y, current.dir, function(x, y) { setBlock(x, y, current.type); }); } function removeLines() { var x, y, complete, n = 0; for (y = ny; y > 0; --y) { complete = true; for (x = 0; x < nx; ++x) { if (!getBlock(x, y)) complete = false; } if (complete) { removeLine(y); y = y + 1; // recheck same line n++; } } if (n > 0) { addRows(n); addScore(100 * Math.pow(2, n - 1)); // 1: 100, 2: 200, 3: 400, 4: 800 } } function removeLine(n) { var x, y; for (y = n; y >= 0; --y) { for (x = 0; x < nx; ++x) setBlock(x, y, (y == 0) ? null : getBlock(x, y - 1)); } } //------------------------------------------------------------------------- // RENDERING //------------------------------------------------------------------------- var invalid = {}; function invalidate() { invalid.court = true; } function invalidateNext() { invalid.next = true; } function invalidateScore() { invalid.score = true; } function invalidateRows() { invalid.rows = true; } function draw() { ctx.save(); ctx.lineWidth = 1; ctx.translate(0.5, 0.5); // for crisp 1px black lines drawCourt(); drawNext(); drawScore(); drawRows(); ctx.restore(); } function drawCourt() { if (invalid.court) { ctx.clearRect(0, 0, canvas.width, canvas.height); if (playing) drawPiece(ctx, current.type, current.x, current.y, current.dir); var x, y, block; for (y = 0; y < ny; y++) { for (x = 0; x < nx; x++) { if (block = getBlock(x, y)) drawBlock(ctx, x, y, block.color); } } ctx.strokeRect(0, 0, nx * dx - 1, ny * dy - 1); // court boundary invalid.court = false; } } function drawNext() { if (invalid.next) { var padding = (nu - next.type.size) / 2; // half-arsed attempt at centering next piece display uctx.save(); uctx.translate(0.5, 0.5); uctx.clearRect(0, 0, nu * dx, nu * dy); drawPiece(uctx, next.type, padding, padding, next.dir); uctx.strokeStyle = 'black'; uctx.strokeRect(0, 0, nu * dx - 1, nu * dy - 1); uctx.restore(); invalid.next = false; } } function drawScore() { if (invalid.score) { html('score', ("00000" + Math.floor(vscore)).slice(-5)); invalid.score = false; } } function drawRows() { if (invalid.rows) { html('rows', rows); invalid.rows = false; } } function drawPiece(ctx, type, x, y, dir) { eachblock(type, x, y, dir, function(x, y) { drawBlock(ctx, x, y, type.color); }); } function drawBlock(ctx, x, y, color) { ctx.fillStyle = color; ctx.fillRect(x * dx, y * dy, dx, dy); ctx.strokeRect(x * dx, y * dy, dx, dy) } //------------------------------------------------------------------------- // FINALLY, lets run the game //------------------------------------------------------------------------- run(); function showAlert() { document.getElementById("focusLost").style.display = "table-cell"; } function hideAlert() { document.getElementById("focusLost").style.display = "none"; } </script> <div id="focusLost" onclick="hideAlert()"> Click Here to Continue </div> </body></html>