JavaScript/Tutorials/Spiele/Sum-up
Am Beispiel eines kleinen Zahlenspiels können Sie die Möglichkeiten der DOM-Manipulation nun Schritt für Schritt nachvollziehen.
Inhaltsverzeichnis
Vorüberlegungen
Spielidee
- Eine Zufallszahl wird als Additionsziel definiert und angezeigt.
- In einem Feld aus Zahlen sollen solange Zahlen angeklickt (und damit addiert) werden, bis diese Zielzahl erreicht wird.
- Auf jeden Klick hin wird eine (nicht angezeigte) Zwischensumme gebildet.
- Wenn das Ziel erreicht oder überschritten wird, wird eine Erfolgs- oder Misserfolgsmeldung ausgegeben
- Mit dem Erreichen oder Über-/Unterschreiten der Zielzahl wird die geheime Zwischensumme auf 0 gesetzt und eine neue Zufallszahl definiert und angezeigt.
- Das Spiel endet, wenn
- alle Zahlen angeklickt worden sind
- die restlichen Klick-Zahlen die geforderte Zufallszahl nicht mehr erreichen können.
Programmlogik
Das Spiel wird einzelne Funktionen aufgeteilt:
- eine Funktion
createNumbers()
, die die Arrays mit den (Zufalls)-Zahlen erzeugt - eine Funktion
createGameBoard()
, die die Buttons mit den enthaltenen Zahlen erzeugt undnext()
aufruft - eine Funktion
next()
, die- die Zwischensumme resettet
- eine neue Zufallszahl festlegt.
- Dann muss sie prüfen, ob die restlichen Klick-Zahlen die neue Zufallszahl überhaupt erreichen können, ansonsten ruft sie
end()
auf.
- eine Funktion
click()
, die dafür sorgt, dass …- die Klick-Zahl verrechnet wird
- das geklickte Element visuell verschwindet.
- Im Anschluss bewertet sie, ob die Zufallszahl erreicht/überschritten wurde und aktualisiert eventuell die Anzeige. Nach einer kurzen Verzögerung ruft sie nötigenfalls
next()
auf.
- Eine Funktion
end()
, die bei Spielende das Resultat ausgibt und evtl. einen Neustart anbietet
JavaScript
Zufallszahlen erzeugen und zerlegen
Im Spiel sollen Sie solange Zahlen addieren, bis die gegebene Summe erreicht ist. Dafür benötigen wir eine Reihe von Summen (numbers
) und dementsprechend viele Teiler (factors
). Mit einigen Funktionen können Sie sie schnell erzeugen:
'use strict';
document.addEventListener('DOMContentLoaded', function () {
var size = 1, //Anzahl der Zahlen des Arrays kann beliebig verändert werden
numbers = [], //Array der Additionsziele
factors = []; //Array der Teiler der Additionsziele
document.getElementById('button').addEventListener('click', createGame);
function createGame(){
for (var i = 0; i < size; i++) {
numbers[i] = (rand(10 , 19));
var zahl = (numbers[i]), // Zerlegt die Zufallszahl in drei Teiler (Faktoren)
factor1 = rand(3,(zahl/2)),
factor2 = rand(1,4);
factors[3 * i + 0] = factor1;
factors[3 * i + 1] = factor2;
factors[3 * i + 2] = zahl - (factor1 + factor2);
}
//Ausgabe
createElement('#textblock', 'p', numbers);
createElement('#textblock', 'p', factors);
}
// erzeugt Zufallszahlen
function rand (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function createElement(parent, elem, content){
...
}
});
Spielfeld
Nachdem die Überprüfung erfolgreich war, können Sie nun das Spielfeld anlegen:
function createGame(){
var container = document.querySelector('#game');
if (container) { //löscht evtl. vorhandene Elemente
while (container.firstChild) {
container.removeChild(container.firstChild);
}
}
for (var i = 0; i < size; i++) {
numbers[i] = (rand(10 , 19));
var zahl = (numbers[i]), // zerlegt die Zufallszahl in drei Teiler (Faktoren)
factor1 = rand(3,(zahl/2)),
factor2 = rand(1,4);
factors[3 * i + 0] = factor1;
factors[3 * i + 1] = factor2;
factors[3 * i + 2] = zahl - (factor1 + factor2);
}
shuffle(factors);
//Ausgabe
for (var i = 0; i < factors.length; i++) { //erzeugt in einer Schleife neue Elemente
createElement('#game', 'button', factors[i]);
}
var x = document.querySelectorAll('button'), //Buttons erhalten eine Klasse für eine zufällige Färbung
i;
for (i = 0; i < x.length; i++) {
x[i].className = 'type' + rand(1,9);
}
createElement('#game', 'output', 'Zahl');
document.querySelector('output').id = 'display';
}
createGame()
löscht zunächst alle evtl. noch vorhandenen Elemente. Hiermit kann verhindert werden, dass noch bestehende Inhalte bzw. Event-Handler auf das aktuelle Spielfeld wirken.In der for-Schleife werden , wie oben besprochen, die Zufallszahlen erzeugt und in Teilsummen (factors
) zerlegt.
Danach wird in einer Schleife für jeden Wert des Arrays factors
ein Button erzeugt. Der oben besprochenen HelferFunktion createElement('#game', 'button', factors[i]);
werden die id des Elternelements, der gewünschte Elementname und der Inhalt des zu überzeugenden Elements übergeben. Mit dem dritten Parameter factors[i]
werden die entsprechenden Werte des Arrays zugewiesen.
<div id="game">
<p>Für dieses Spiel benötigen Sie JavaScript.</p>
<button id="button">Start!</button>
</div>
game
, das einen Hinweis enthält, und einem Button.Durch einen Klick auf diesen erhalten Sie:
<div id="game">
<button class="type9">2</button>
<button class="type7">5</button>
<button class="type1">4</button>
<button class="type5">4</button>
<button class="type3">1</button>
<button class="type4">12</button>
<button class="type5">5</button>
...
<output id="display">Zahl</output>
</div>
game
, das jetzt die Buttons mit den Teilsummen und ein output-Element für die Berechnungen und Ergebnisse enthält.Spielfeld mit CSS gestalten
Gut geht anders – die Buttons sehen aus wie Buttons. Deshalb sollte unser Spiel mit CSS gestylt werden. Das output-Element und das Spielbrett können über ihre Selektoren angesprochen werden. Die Buttons haben jedoch (noch) keine Unterscheidungsmerkmale.
function createOutput(parent,elem, content){
...
var x = document.querySelectorAll('button'),
i;
for (i = 0; i < x.length; i++) {
x[i].className = 'type' + rand(1,12);
}
...
}
createGame()
-Funktion wird leicht verändert. Über querySelectorAll werden alle neu erzeugten Buttons ausgewählt und erhalten dann mittels className
eine nach dem Zufallsprinizp ausgewählte Klasse zugewiesen.
class : 'type' + rand(1,12);
als zusätzlicher Parameter übergeben werden müsste.Da Sie den Buttons Klassen zugewiesen haben, können Sie sie jetzt mit CSS unterschiedlich gestalten:
output {
display: block;
float: right;
width: 8rem;
height:8rem;
background: #333;
font-size: 4em;
text-align: center;
border: 1px solid #000066;
border-radius:10%;
}
button[class^="type"] {
background-color: #FFFFFF;
border: 1px solid #000066;
border-radius: 40%;
font-size: 120%;
font-weight: bold;
margin: 0 5px 5px 0;
padding: 0.25em 0;
text-align: center;
width: 3rem;
height: 3rem;
}
button:focus,button:hover {
border: 2px solid yellow;
color: yellow;
}
button.type1 {background: white; }
button.type2 {background: yellow;}
button.type3 {background: beige;}
button.type4 {background: orange;}
type
im Klassennamen enthalten, erhalten ein quadratisches Aussehen und werden größer als die Standardschriftgröße dargestellt. Per Zufallsprinzip wurden den Buttons Klassen zugewiesen, die unterschiedliche Hintergrundfarben haben.
output
mit einer Schriftgröße von 4em angezeigt.Event-Delegation
Das Spielfeld ist fertig – jetzt können wir uns um das Spiel selbst kümmern: die einzelnen Buttons sollen anklickbar gemacht werden. Was liegt näher als den Buttons beim Erzeugen ein click-Event zuzuweisen?
for (i = 0; i < factors.length; i++) {
newElm = document.createElement('button');
container.appendChild(newElm);
newElm.appendChild(document.createTextNode(factors[i]));
newElm.onclick = clickHandler;
}
Die Überwachung zahlreicher Elemente im Dokument ist sehr aufwändig umzusetzen und langsam in der Ausführung, da jedes Element herausgesucht, durchlaufen und bei jedem denselben Event-Handler registriert werden muss.
Bei solchen Aufgabenstellungen können Sie vom Bubbling-Effekt profitieren, das ist das Aufsteigen der Ereignisse im DOM-Baum. Machen Sie sich die Verschachtelung der Elemente im DOM-Baum zunutze und überwachen Sie die Ereignisse von verschiedenen Elementen bei einem gemeinsamen, höherliegenden Element, zu dem die Ereignisse aufsteigen. Diese Technik nennt sich Event-Delegation (englisch delegation für Übertragung von Aufgaben). Dabei wird einem zentralen Element die Aufgabe übertragen, die Ereignisse zu verarbeiten, die bei seinen Nachfahrenelementen passieren.
function createGame(){
...
container.addEventListener('click', click);
...
}
}
function click (e) {
var target = e.target;
var value = target.innerText;
alert (value + ' wurde geklickt!');
removeElement(e);
}
Die aufgerufene Funktion click(e)
ermittelt mit der Eigenschaft target von welchem Element das Ereignis ausgeht.
removeElement()
entfernt.Das Spiel läuft
Mit jedem Klick auf einen Button wird nun überprüft, ob das Additionsziel erreicht wurde; Falls ja wird die Funktion next()
aufgerufen, die ein weiteres Additionsziel ausgibt. Gibt es kein Additionsziel oder verfügbare Buttons mehr, wird die Funktion end()
aufgerufen;
function createGame(){
...
container.addEventListener('click', click);
...
}
}
function click (e) {
var target = e.target;
var value = target.innerText;
alert (value + ' wurde geklickt!');
}
DOM-Manipulation und CSS
Mit JavaScript können Sie das DOM so manipulieren, dass das Aussehen der Seite völlig verändert wird. Dabei sollten aber vorzugsweise die Elemente nicht mit style formatiert werden, sondern bereits im Stylesheet vorhandene Klassenattribute gesetzt, geändert und entfernt werden. Existiert für diese Klasse eine Festlegung im Stylesheet, wird die Darstellung vom Browser sofort geändert.
Klassenattribute hinzufügen und entfernen
Beim Erreichen oder Überschreiten des Additionsziels soll eine Erfolgs- bzw. Misserfolgsmeldung angezeigt werden. Dabei machen wir uns zunutze, dass ein Setzen von Klassen nicht nur das Aussehen des selektierten Elements verändern kann, sondern auch Pseudoklassen wie ::after und ::before.
.exact {
font-weight: bold;
color: lime;
}
.exact:after {
content: "✓";
display: block;
margin: -0.5em 0 0 1.5em;
}
.inexact {
font-weight: bold;
color:red;
}
.inexact:after {
content: "✗";
display: block;
margin: -0.5em 0 0 1.5em;
}
if (current <= 0) {
diff.push(current);
display.className = (
current === 0
? "exact"
: "inexact"
);
setTimeout(next, 1500);
}
current
kleiner gleich 0 ist. Falls dies zutrifft, wird überprüft, ob der Wert von current
0 ist und mit einem ternären Operator für den true
-Fall dem output-Element mit className die Klasse exact
, für den false-Fall die Klasse inexact
zugewiesen.
Die geänderte Klasse und die Erfolgs- bzw. Fehlermeldungen bleiben für 1500ms sichtbar, da der Aufruf eines neuen Durchlaufs mit setTimeout
verzögert wurde.
next()
werden evtl. vorhandene Klassenattribute durch className = '';
wieder gelöscht.Die hier vorgestellte Vorgehensweise ermöglicht es, nicht nur die Textfarbe, sondern eine Vielzahl von CSS-Eigenschaften gleichzeitig zu ändern.
Nachteilig an der Verwendung der className
-Eigenschaft ist die Überschreibung eines eventuell schon vorher vorhandenen Klassenattributs. Mit der classList-Eigenschaft können Sie komfortabel eine (auch von mehreren) Klassen ändern, hinzufügen oder löschen.
das fertige Spiel
In der vorherigen Variante verändert sich die Position der Buttons mit jedem Klick, da der Platz der durch Klicks entfernten Buttons von seinem Nachbarn eingenommen wird.
Schöner wäre es, wenn die Buttons nur in einer Richtung (Luftblasen vergleichbar) nach oben steigen würden.
function createGameBoard(arr) {
var i,
parent = document.querySelector('#gameboard');
parent.addEventListener('click', clickHandler);
if (parent) { //löscht evtl. vorhandene Elemente
while (parent.firstChild) {
parent.removeChild(container.firstChild);
}
}
for (i = 0; i < arr.length; i++) { //erzeugt in einer Schleife neue Elemente
if ((i+6) % 6 == 0){ //Abfrage, ob Zahl durch 6 teilbar ist, erzeugt dann Spalte
var div = document.createElement('div');
parent.appendChild(div);
}
createOutput('#gameboard', 'button', arr[i]);
}
}
createGameBoard()
werden nun 2 Zeilen eingefügt, sodass immer je sechs Buttons innerhalb eines div angelegt werden.
numbers
mit unseren zufällig erzeugten Additionszielen; die Größe des Arrays wird durchsize
festgelegt und ein weiterer Arrayfactors
mit den Teilern.Die Funktion
Mithilfe der im DOM-Tutorial besprochenen HelferfunktioncreateGame
füllt den Arraynumbers
. In einer Schleife wird das Array mit Zufallszahlen gefüllt, die mit der Helferfunktionrand (min, max)
erzeugt werden.Anschließend werden die einzelnen Werte des Arrays in 3 Teiler zerlegt und im Array
factors
hinterlegt.createElement()
werden die beiden Arrays in je einem Textabsatz ausgegeben.