Einfaches Tetris Spiel was eigentlich fast jeder aus der Gameboy Zeit noch kennen sollte
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>