×
SVG - Javascript:
<svg id="stetris" onload="init();" width="100%" height="100%">
<script>
<![CDATA[
//----------------------------------------------------------------------
// Statische Daten
var SVG_NS ="http://www.w3.org/2000/svg";
var tickTimeReducer = 0.98;
var ROWS = 20;
var COLS = 10;
var HPAD = 2; // horizontales Padding, welches beim Auslegen der Gitter verwendet wird
var VPAD = 2; // das Gleiche für vertikales Padding
// mögliche Formen:
var SHAPE_DESCRIPTORS = [
{ color: "grey", orientations: [ [[0,0],[1,0],[0,1],[1,1]] ] },
{ color: "blue", orientations: [ [[0,0],[1,0],[2,0],[2,1]],
[[1,0],[1,1],[1,2],[0,2]],
[[0,0],[0,1],[1,1],[2,1]],
[[0,0],[1,0],[0,1],[0,2]] ] },
{ color: "purple", orientations: [ [[0,0],[0,1],[1,0],[2,0]],
[[0,0],[1,0],[1,1],[1,2]],
[[0,1],[1,1],[2,1],[2,0]],
[[0,0],[0,1],[0,2],[1,2]] ] },
{ color: "cyan", orientations: [ [[0,0],[1,0],[2,0],[3,0]],
[[0,0],[0,1],[0,2],[0,3]] ] },
{ color: "green", orientations: [ [[1,0],[2,0],[0,1],[1,1]],
[[0,0],[0,1],[1,1],[1,2]] ] },
{ color: "red", orientations: [ [[1,0],[1,1],[0,1],[0,2]],
[[0,0],[1,0],[1,1],[2,1]] ] },
{ color: "yellow", orientations: [ [[0,1],[1,1],[2,1],[1,0]],
[[0,0],[0,1],[0,2],[1,1]],
[[0,0],[1,0],[2,0],[1,1]],
[[1,0],[1,1],[1,2],[0,1]] ] }
];
//----------------------------------------------------------------------
// Hilfsfunktionen
// Ordnet eine Funktion (f) einem Array (a) zu, und speichert alle Funktions-Ausgaben in ein neues Array (res):
function mapcar(f, a) {
var res = new Array(a.length);
for (var i=0;i<a.length;++i) {
res[i] = f(a[i]);
}
return res;
}
// Ordnet eine Funktion (f) einem Array (a) zu:
function mapc(f, a) {
for (var i=0;i<a.length;++i) {
f(a[i]);
}
}
// Gibt "true" zurück, wenn das Prädikat p für jedes Element des Arrays (a) wahr ist:
function every(p, a) {
for (var i=0;i<a.length;++i) {
if (!p(a[i])) return false;
}
return true;
}
// Neuzeichnen unterbrechen (native SVG-Methode)
function suspendRedraw() {
document.getElementById("stetris").suspendRedraw(0);
catch(e) {}
}
// Neuzeichnen wieder aufnehmen
function unsuspendRedraw() {
document.getElementById("stetris").unsuspendRedraw(0);
catch(e) {}
}
//----------------------------------------------------------------------
// Formen(Shape)-Klasse
function Shape(position) {
// Erstellt eine neue Form, indem zufällig ein Form-Beschreibung und eine Ausrichtung ausgewählt wird:
this._descriptor = SHAPE_DESCRIPTORS[Math.round(Math.random()*(SHAPE_DESCRIPTORS.length-1))];
this._orientation = Math.round(Math.random()*(this._descriptor.orientations.length-1));
this._pos = position;
}
Shape.prototype = {
getCellArray : function() {
var s = this;
return mapcar(function(coord) { return [coord[0]+s._pos[0],coord[1]+s._pos[1]]; },
this._descriptor.orientations[this._orientation]);
},
getColor : function() {
return this._descriptor.color;
},
move : function(dx,dy) {
this._pos[0] += dx; this._pos[1] += dy;
},
rotate : function(dOrient) {
this._orientation = (this._orientation+dOrient) % this._descriptor.orientations.length;
if (this._orientation<0) this._orientation += this._descriptor.orientations.length;
}
};
//----------------------------------------------------------------------
// Gitter(Grid)-Klasse
function Grid(cols, rows, color, bordercolor, x, y, width, height, node) {
// Erstellt ein cols*rows-Gitter mit einem 1/2-Zellen-Rand.
// Auf Breite*Höhe der Benutzerpixel (einschliesslich Rahmen) skalieren.
// Auf x,y Benutzerpixelkoordinaten platzieren.
this._cols = cols;
this._rows = rows;
this._color = color;
node.setAttribute("transform", "translate("+x+","+y+") scale("+
(width/(cols+1))+","+(height/(rows+1))+") translate(0.5,0.5)");
this._border = document.createElementNS(SVG_NS, "rect");
this._border.setAttribute("fill", bordercolor);
this._border.setAttribute("width", cols+1);
this._border.setAttribute("height", rows+1);
this._border.setAttribute("x", "-0.5");
this._border.setAttribute("y", "-0.5");
this._background = document.createElementNS(SVG_NS, "rect");
this._background.setAttribute("fill", color);
this._background.setAttribute("width", cols);
this._background.setAttribute("height", rows);
this._rowArray = document.createElementNS(SVG_NS, "g");
for (var r=0;r<rows;++r) {
var row_group = document.createElementNS(SVG_NS, "g");
row_group.setAttribute("transform", "translate(0,"+r+")");
for (var c=0;c<cols;++c) {
var cell = document.createElementNS(SVG_NS, "rect");
cell.setAttribute("x", c);
cell.setAttribute("width", "1");
cell.setAttribute("height", "1");
cell.setAttribute("stroke", "grey");
cell.setAttribute("fill", "none");
cell.occupied = false;
row_group.appendChild(cell);
}
this._rowArray.appendChild(row_group);
}
node.appendChild(this._border);
node.appendChild(this._background);
node.appendChild(this._rowArray);
}
Grid.prototype = {
colorCell : function(coord, color) { try {
this._rowArray.childNodes.item(coord[1]).childNodes.item(coord[0]).setAttribute("fill", color);
}catch(e) {
alert("error: coord="+coord);
}
},
clearCell : function(coord) {
this.colorCell(coord, this._color);
},
occupyCell : function(coord) {
this._rowArray.childNodes.item(coord[1]).childNodes.item(coord[0]).setAttribute("occupied", "true");
},
unoccupyCell : function(coord) {
this._rowArray.childNodes.item(coord[1]).childNodes.item(coord[0]).setAttribute("occupied", "false");
},
cellInBounds : function(coord) {
return (coord[0]>=0 && coord[1]>=0 && coord[0]<this._cols && coord[1]<this._rows);
},
cellOccupied : function(coord) {
return this._rowArray.childNodes.item(coord[1]).childNodes.item(coord[0]).getAttribute("occupied")=="true";
},
eliminateFullRows : function() {
var g = this;
var bo = false;
function rowFull(r) {
for (var c=0;c<g._cols;++c) {
if (!g.cellOccupied([c,r])) return false;
}
return true;
}
function moveCellDown(c,r) {
var src = g._rowArray.childNodes.item(r).childNodes.item(c);
var dest = g._rowArray.childNodes.item(r+1).childNodes.item(c);
dest.setAttribute("fill", src.getAttribute("fill"));
dest.setAttribute("occupied", src.getAttribute("occupied"));
src.setAttribute("occupied", "false");
src.setAttribute("fill", g._color);
}
function eliminateRow(row) {
suspendRedraw();
for (var c=0;c<g._cols;++c) {
g.clearCell([c,row]);
g.unoccupyCell([c,row]);
}
for (var r=row-1;r>=0;--r) {
for (c=0;c<g._cols;++c) {
if (g.cellOccupied([c,r]))
moveCellDown(c,r);
}
}
unsuspendRedraw();
}
for (var r=0;r<this._rows;++r) {
if (rowFull(r)) {
bo = true;
eliminateRow(r);
++lines;
}
}
if (bo) {
tickTime=time(tickTime);
}
}
};
// Nach Spielschluss alle Shapes entfernen wenn ein neues Spiel begonnen wird:
function eliminateRow2(row) {
for (var c=0;c<board._cols;++c) {
board.clearCell([c,row]);
board.unoccupyCell([c,row]);
}
}
//----------------------------------------------------------------------
// Gitter <---> Formen Operationen
function canPlace(shape, grid) {
// kann nur platziert werden, wenn alle Felder der Form innerhalb des Gitters und nicht besetzt sind:
return every(function(coord){ return grid.cellInBounds(coord) &&
!grid.cellOccupied(coord); },
shape.getCellArray());
}
function show(shape, grid) {
suspendRedraw();
mapc(function(coord){ grid.colorCell(coord, shape.getColor()); },
shape.getCellArray());
unsuspendRedraw();
}
function hide(shape, grid) {
suspendRedraw();
mapc(function(coord){ grid.clearCell(coord); },
shape.getCellArray());
unsuspendRedraw();
}
function occupy(shape, grid) {
mapc(function(coord){ grid.occupyCell(coord); },
shape.getCellArray());
}
function move(shape, grid, dx, dy) {
shape.move(dx,dy);
if (!canPlace(shape, grid)) {
shape.move(-dx,-dy);
return false;
}
suspendRedraw();
shape.move(-dx, -dy);
hide(shape, grid);
shape.move(dx,dy);
show(shape, grid);
unsuspendRedraw();
return true;
}
function rotate(shape, grid, dOrient) {
shape.rotate(dOrient);
if (!canPlace(shape, grid)) {
shape.rotate(-dOrient);
return false;
}
suspendRedraw();
shape.rotate(-dOrient);
hide(shape, grid);
shape.rotate(dOrient);
show(shape, grid);
unsuspendRedraw();
return true;
}
function drop(shape, grid) {
suspendRedraw();
while (move(shape, grid, 0, 1))
unsuspendRedraw();
}
//----------------------------------------------------------------------
// Das Spiel:
var board; // Gitter auf dem gespielt wird
var preview; // Gitter in dem die nächste Form angezeigt wird
var currentShape;
var lines=0;
var score=0;
var nextShape;
var gameState ="start"; // Mögliche andere Spielstände: "stopped", "running", "finished"
var tickTime;
function startNewGame() {
// Falls schon gespielt wurde, müssen zuerst alle Formen aus dem Gitter und der "Game Over"-Text entfernt werden, und der Punkte- und Linienstand wieder auf Null gesetzt werden:
if (gameState != "start") {
var textweg = document.getElementById("spielschluss");
textweg.setAttribute("visibility","hidden");
var textweg2 = document.getElementById("schluss2");
textweg2.setAttribute("visibility","hidden");
for (var r=0;r<20;++r) {
eliminateRow2(r);
}
score = 0;
lines = 0;
}
suspendRedraw();
currentShape = new Shape([3,0]);
show(currentShape, board);
nextShape = new Shape([0,0]);
show(nextShape, preview);
unsuspendRedraw();
gameState = "running";
tickTime = 300;
tick();
}
function runNextShape() {
occupy(currentShape, board);
board.eliminateFullRows();
score = score+1;
suspendRedraw();
currentShape = nextShape;
hide(nextShape, preview);
currentShape.move(3,0);
if (!canPlace(currentShape, board)) {
unsuspendRedraw();
return false; // Spielschluss!
}
show(currentShape, board);
nextShape = new Shape([0,0]);
show(nextShape, preview);
unsuspendRedraw();
return true;
}
function tick() {
if (gameState != "running") return;
if (!move(currentShape, board, 0, 1)) {
if(!runNextShape()) {
// Bei Spielschluss wird der Text mit dem "Game Over" und den erreichten Punkten und Linien eingeblendet
document.getElementById("spielschluss").innerHTML="Game Over! Punkte:"+score;
document.getElementById("schluss2").innerHTML="Linien:"+lines;
document.getElementById("spielschluss").setAttribute("visibility","visible");
document.getElementById("schluss2").setAttribute("visibility","visible");
gameState = "finished";
return; // Spielschluss
}
}
setTimeout("tick()", tickTime);
}
function time(tickTime) {
tickTime = tickTime*tickTimeReducer;
return tickTime;
}
function pause() {
if (gameState == "running")
gameState = "stopped";
}
function resume() {
if (gameState == "stopped") {
gameState = "running";
tick();
}
}
//----------------------------------------------------------------------
// Benutzer-Eingabe-Event-Handler
function keyHandler(event) {
event.preventDefault();
switch (event.keyCode) {
case 32: /* bei Klick auf die Leertaste */
if (gameState == "running")
drop(currentShape, board);
break;
case 72: /* h */
pause();
alert("Hilfe:\n"+
"-------------------------------------\n"+
"Punkte : "+score+"\n"+
"Linien : "+lines+"\n"+
"h : Diese Hilfe anzeigen\n"+
"p : Spiel pausieren\n"+
"up : Element gegen Uhrzeigersinn rotieren\n"+
"down : Element im Uhrzeigersinn rotieren\n"+
"left : Element nach links verschieben\n"+
"right : Element nach rechts verschieben\n"+
"space : Element abwerfen\n");
resume();
break;
case 80: /* p */
if (gameState == "running")
pause();
else
resume();
break;
case 38: /* up */
if (gameState == "running")
rotate(currentShape, board, -1);
break;
case 40: /* down */
if (gameState == "running")
rotate(currentShape, board, 1);
break;
case 37: /* left */
if (gameState == "running")
move(currentShape, board, -1, 0);
break;
case 39: /* right */
if (gameState == "running")
move(currentShape, board, 1, 0);
break;
}
}
function clickHandler(event,aktion) {
switch (aktion) {
case 72: /* Hilfe-Button */
pause();
alert("Hilfe:\n"+
"-------------------------------------\n"+
"Punkte : "+score+"\n"+
"Linien : "+lines+"\n"+
"h : Diese Hilfe anzeigen\n"+
"p : Spiel pausieren\n"+
"up : Element gegen Uhrzeigersinn rotieren\n"+
"down : Element im Uhrzeigersinn rotieren\n"+
"left : Element nach links verschieben\n"+
"right : Element nach rechts verschieben\n"+
"space : Element abwerfen\n");
resume();
break;
case 80: /* Pause-Button */
if (gameState == "running")
pause();
else
resume();
break;
case 38: /* "nach links drehen"-Button */
if (gameState == "running")
rotate(currentShape, board, -1);
break;
case 40: /* "nach rechts drehen"-Button */
if (gameState == "running")
rotate(currentShape, board, 1);
break;
case 37: /* "nach links"-Button */
if (gameState == "running")
move(currentShape, board, -1, 0);
break;
case 39: /* "nach rechts"-Button */
if (gameState == "running")
move(currentShape, board, 1, 0);
break;
}
}
// Button beim Maus-Darüberfahren wird aufgehellt
function aufhellen(e) {
e.target.parentNode.setAttribute("fill-opacity",0.5);
}
// Button nach Maus-Darüberfahren und verlassen wird wieder abgedunkelt
function abdunkeln(e) {
e.target.parentNode.setAttribute("fill-opacity",1);
}
//----------------------------------------------------------------------
// Funktion welche beim Laden des Spiels ausgeführt wird:
function init() {
// Seiten-Layout wird gesetzt:
document.getElementById("stetris").setAttribute("viewBox", "0 0 "+(3*HPAD+(COLS+1)+5)+" "+(2*VPAD+(ROWS+1)));
board = new Grid(COLS, ROWS, "black", "grey", HPAD, VPAD, COLS+1, ROWS+1, document.getElementById("board"));
preview = new Grid(4,4, "black", "grey", 2*HPAD+COLS+1, VPAD, 5, 5, document.getElementById("preview"));
// Key-Event-Listener wird gestartet:
document.addEventListener("keydown", keyHandler, false);
}
]]>
</script>
<!-- Restliche SVG-Elemente definieren welche nicht erst dynamisch durch Javascript erstellt werden: -->
<g id="preview" stroke-width="0.02"/>
<g id="board" stroke-width="0.02"/>
<!-- Alle Buttons mit Event-Handler definieren: -->
<g onclick="startNewGame()" onmousemove="aufhellen(evt)" onmouseout="abdunkeln(evt)">
<rect x="2.5" y="0.1" rx="0.3" ry="0.3" width="10" height="1.5" fill="#BFE6F3" stroke="#1B005F" stroke-width="0.1"/>
<text x="3.1" y="1.2" font-size="1px">Neues Spiel starten</text>
</g>
<g onclick="clickHandler(evt,37)" onmousemove="aufhellen(evt)" onmouseout="abdunkeln(evt)">
<rect x="15" y="11" rx="0.3" ry="0.3" width="2" height="2" fill="#BFE6F3" stroke="#1B005F" stroke-width="0.1"/>
<line x1="15.5" y1="12" x2="16.5" y2="12.5" stroke="#1B005F" stroke-width="0.1"/>
<line x1="15.5" y1="12" x2="16.5" y2="11.5" stroke="#1B005F" stroke-width="0.1"/>
</g>
<g onclick="clickHandler(evt,39)" onmousemove="aufhellen(evt)" onmouseout="abdunkeln(evt)">
<rect x="18" y="11" rx="0.3" ry="0.3" width="2" height="2" fill="#BFE6F3" stroke="#1B005F" stroke-width="0.1"/>
<line x1="19.5" y1="12" x2="18.5" y2="12.5" stroke="#1B005F" stroke-width="0.1"/>
<line x1="19.5" y1="12" x2="18.5" y2="11.5" stroke="#1B005F" stroke-width="0.1"/>
</g>
<g onclick="clickHandler(evt,38)" onmousemove="aufhellen(evt)" onmouseout="abdunkeln(evt)">
<rect x="15" y="14" rx="0.3" ry="0.3" width="2" height="2" fill="#BFE6F3" stroke="#1B005F" stroke-width="0.1"/>
<path d="M 16.5 15.5 L 16.5 14.8 A 0.4 0.4 0 0 0 15.7 14.8 L 15.7 15.5" fill="none" stroke="#1B005F" stroke-width="0.1" />
<path d="M 15.4 15 L 15.7 15.5 L 16 15" fill="none" stroke="#1B005F" stroke-width="0.1" />
</g>
<g onclick="clickHandler(evt,40)" onmousemove="aufhellen(evt)" onmouseout="abdunkeln(evt)">
<rect x="18" y="14" rx="0.3" ry="0.3" width="2" height="2" fill="#BFE6F3" stroke="#1B005F" stroke-width="0.1"/>
<path d="M 18.5 15.5 L 18.5 14.8 A 0.4 0.4 0 0 1 19.3 14.8 L 19.3 15.5" fill="none" stroke="#1B005F" stroke-width="0.1" />
<path d="M 19 15 L 19.3 15.5 L 19.6 15" fill="none" stroke="#1B005F" stroke-width="0.1" />
</g>
<g onclick="clickHandler(evt,80)" onmousemove="aufhellen(evt)" onmouseout="abdunkeln(evt)">
<rect x="15.7" y="17" rx="0.3" ry="0.3" width="3.6" height="1.5" fill="#BFE6F3" stroke="#1B005F" stroke-width="0.1"/>
<text x="16.1" y="18.1" font-size="1px">Pause</text>
</g>
<g onclick="clickHandler(evt,72)" onmousemove="aufhellen(evt)" onmouseout="abdunkeln(evt)">
<rect x="15.7" y="20" rx="0.3" ry="0.3" width="3.6" height="1.5" fill="#BFE6F3" stroke="#1B005F" stroke-width="0.1"/>
<text x="16.5" y="21.1" font-size="1px">Hilfe</text>
</g>
<!-- Text der bei Spielschluss eingeblendet wird definieren: -->
<text id="spielschluss" x="1" y="10" style="stroke:none;font-size:2px;fill:red;stroke:black;stroke-width:0.04px;fill-opacity:0.8;" visibility="hidden">dyn. Text</text>
<text id="schluss2" x="1" y="12" style="stroke:none;font-size:2px;fill:red;stroke:black;stroke-width:0.04px;fill-opacity:0.8;" visibility="hidden">dyn. Text</text>
</svg>