Simon says

jQuery und das Canvas-Element
Kommentare

Man braucht nicht Steve Jobs zu sein (der ja Adobes Flash Player bekannterweise aus der „i“-Welt verbannt hat), um zu sehen, dass der Webbrowser selbst immer spezialisiertere Aufgaben übernimmt. Das Canvas-Element aus HTML5 spielt hier eine tragende Rolle – wir wollen es im Folgenden zur Umsetzung eines einfachen Spieleklassikers nutzen. Stets mit dabei: die jQuery Library, die uns wie gewohnt hilft, den nötigen JavaScript-Code auf ein Minimum zu reduzieren. Neben jQuerys Möglichkeiten zu Event Handling und Animation werden wir uns auch die elegante Plug-in-Architektur zunutze machen.

Simon (in Deutschland auch unter dem Namen Senso verkauft) war quasi der Gameboy der 80er: Eine portable schwarze Scheibe mit vier großen verschiedenfarbigen Tasten, die im Laufe des Spiels hintereinander aufleuchten und in der vorgespielten Reihenfolgen dann vom Spieler gedrückt werden müssen. Spielprinzip und „Benutzeroberfläche“ sind denkbar simpel, womit das Spiel als Anschauungsobjekt für eine kleine Einführung zu den neuen Grafikmöglichkeiten in HTML5 durch das Canvas-Element geradezu prädestiniert ist. Zwar gibt es für die Canvas-Zeichenfunktionen keine eigenen Wrapper-Methoden in jQuery, allerdings werden wir sehen, dass diese ohnehin relativ einfach aufgebaut sind und browserübergreifend funktionieren sollten. Um jedoch mit einzelnen Canvas-Elementen zu hantieren, diese zu animieren und weitere JavaScript-Logik zu implementieren, ist jQuery das Werkzeug der Wahl. Konsequenterweise verpacken wir gleich den kompletten Code in ein jQuery-Plug-in. Vorab noch ein Wort zu Browserkompatibilität: Das Canvas-Element wird von beinahe allen Browsern unterstützt. Wie so oft macht uns aber der Internet Explorer hier inklusive seiner aktuellen Version 8 einen Strich durch die Rechnung. Ich werde mich damit hier nicht länger aufhalten – wer nicht auf den IE verzichten kann, möchte sich das Canvas-Emulationsskript für den Internet Explorer unter http://code.google.com/p/explorercanvas/ ansehen, den Quellcode dieses Spiels jedoch bitte mit einem vernünftigeren Browser ausführen.

jQuerys Plug-in-Architektur

Einer der Gründe für die Beliebtheit von jQuery ist sicherlich die Plug-in-Architektur. Um seine Programmlogik in ein jQuery-Plug-in zu verpacken, sind nur wenige Codezeilen nötig. Sind diese geschrieben, kann eines der Key-Features von jQuery umgehend eingesetzt werden: das so genannte Chaining, wobei mehrere Funktionsaufrufe direkt hintereinander geschrieben werden können. Auf den ersten Blick sieht der Code für die Plug-in-Implementierung etwas verwirrend aus, es wird jedoch schnell verständlich. Gehen wir von außen nach innen vor:
(function($){
// …
})(jQuery);
Hier passieren mehrere Dinge. Es wird eine anonyme Funktion definiert, die in der Folge durch das Anhängen der geschwungenen Klammer umgehend ausgeführt wird. Zudem wird das jQuery- Objekt übergeben, das nach erfolgreichem Laden der jQuery Library im globalen JavaScript-Namespace liegen sollte, und in der Variablen $ gespeichert. Sinn der Sache ist es, in unserem Plug-in das berühmte $-Zeichen gefahrlos verwenden zu können – die Gefahr besteht darin, dass auch andere JavaScript Libraries dieses mitunter für sich beanspruchen, was zu Konflikten führen kann. Nun folgt die eigentliche Plug-in-Definition:
(function($){
$.fn.simon = function() {
// …
}
})(jQuery);
Alle Plug-ins werden in jQuery als Eigenschaft des bestehenden jQuery.fn-Objekts definiert. An dieser Stelle können wir unser Plug-in nun bereits verwenden; hierzu wird es wie üblich als Methode auf ein jQuery-Objekt angewandt, also beispielsweise $ (‚#simon‘).simon(). Jetzt fehlt uns aber noch eines: die bereits angesprochene Chainability. Wollten wir beispielsweise für den Container, in dem Simon gerendert wird, noch eine Hintergrundfarbe festlegen, so könnten wir dies direkt im Anschluss auf den Plug-in-Aufruf tun: $ (‚#simon‘).simon().css(‚background‘,’red‘). Damit dies aber auch funktioniert, müssen wir in unserer Plug-in-Definition noch folgende Zeilen ergänzen:
(function($){
$.fn.simon = function() {
return this.each(function() {
var $simon = $(this);
// …
}
}
})(jQuery);
Aber was ist dieses this.each, das wir da zurückgeben? Zunächst einmal bezieht sich this zu Beginn eines Plug-ins immer auf das jQuery-Objekt, auf das die Plug-in-Methode angewendet wurde; in unserem Fall also auf $ (‚#simon‘). Es würde also an sich genügen, ein einfaches return this ans Ende des Plug-ins zu stellen, um ein Chaining zu ermöglichen. Nun kann ein jQuery-Objekt jedoch nicht nur ein einzelnes HTML-Element repräsentieren, sondern auch mehrere, z. B. „alle DIV Tags“ in $ (‚div‘). Um das Plug-in also auf sämtliche Elemente eines jQuery-Objektes anzuwenden, wird die hauseigene Methode .each() verwendet, die uns im Sinne des Chaining wiederum ein Objekt mit sämtlichen bearbeiteten Objekten zurückliefert – und das wir direkt weiterreichen können. Wichtig ist noch zu erwähnen, dass das this-Keyword sich innerhalb von .each() auf ein reines DOM-Element bezieht und noch nicht auf ein jQuery-Objekt. Um auf dieses Element nun weitere jQuery-Methoden anwenden zu können, sollten wir es gleich zu Beginn in ein jQuery-Objekt umwandeln und einer Variablen (hier $simon) zuweisen. Wir können damit die Erstellung weiterer redundanter Objekte durch die mehrfache Verwendung von $ (this) umgehen und die Browserressourcen etwas schonen. Das Voranstellen eines „$“ im Variablennamen soll verdeutlichen, dass es sich um ein jQuery-Objekt handelt – eine Schreibweise, die in vielen Plug-ins verwendet wird.

Das Canvas-Element – und SVG

Nachdem die Formalitäten für unser Plug-in erledigt sind, kommen wir nun zum interessanteren Teil: dem Zeichnen der einzelnen Spielbestandteile. Genau genommen sieht unser „Spielbrett“ eher rudimentär aus und ließe sich genauso gut aus fertigen Grafiken zusammensetzen (Abb. 1) – doch das wäre ja nur der halbe Spaß.
Abb. 1: So sollte das Spiel im Browser gerendert werden

Abb. 1: So sollte das Spiel im Browser gerendert werden

Um direkt im Browser zu zeichnen, bietet uns das Canvas-Element (im Folgenden der Einfachheit halber nur Canvas) alles, was wir benötigen. Genau genommen ist der Canvas alleine völlig nutzlos – im HTML-Quelltext erscheint es stets leer und sollte keine weiteren Tags enthalten. An Attributen sind lediglich width und height vorgesehen. Um dem Canvas Leben einzuhauchen, steht jedoch eine leistungsstarke JavaScript API zur Verfügung, mit der nicht nur vielfältige Zeichenoperationen, sondern auch das Einfügen und Manipulieren von Text- und Bildelementen möglich ist. All dies mag manchem bekannt vorkommen – bereits 2001 wurde vom W3C eine Empfehlung für einen Standard zum browserbasierten Zeichnen abgegeben, namentlich SVG (Scalable Vector Graphics). Auf den ersten Blick wurde mit Canvas also das Rad ein zweites Mal erfunden – bei genauerer Betrachtung werden die Unterschiede jedoch schnell klar. Wie der Name bereits verrät, hantiert SVG mit Vektorgrafiken, während wir bei Canvas mit Bitmaps arbeiten. Des Weiteren werden bei SVG einzelne Formen in einem XML-basierten Format „beschrieben“, statt direkt über eine Befehlsfolge „gezeichnet“ zu werden. Am besten lässt sich dies anhand eines Beispiels verdeutlichen: . Hiermit erstellen wir in SVG ein einfaches rotes Rechteck. Dasselbe erreichen wir im Canvas mit folgenden Befehlen:
(function($){
$.fn.simon = function() {
// …
}
})(jQuery);
Das SVG-Format ist bereits relativ verbreitet und lässt sich auch mit gängigen Vektorzeichentools wie Adobe Illustrator bearbeiten. Zudem lassen sich einzelne Elemente einer SVG-Grafik separat manipulieren, animieren und sogar mit Event Handlern versehen. Im Canvas hingegen liegt alles in einem einzigen Bitmap, das zum Ändern eines Teils der Grafik komplett neu gezeichnet werden muss. Der große Vorteil des Canvas ist jedoch die wesentlich bessere Performance, die in der direkten Bearbeitung der Pixel begründet ist. Dies macht das Rendering per Canvas für ressourcenintensive Bitmap-Manipulationen, wie sie bei der Bildbearbeitung und -analyse oder grafikintensiven Spielen gebraucht werden, besonders interessant. Das Spiel, das wir hier auf die Beine stellen wollen, fällt zwar nicht unbedingt in diese Kategorie – wir begnügen uns mit wenigen, simplen, geometrischen Formen – jedoch soll es hier ja auch in erster Linie um die Vermittlung der Grundlagen gehen. Es steht natürlich jedem frei, das Ganze im Anschluss mit komplexen Grafikeffekten aufzupolieren.

Zeichnen im Canvas

Unser Spiel besteht aus einer runden Hintergrundfläche und vier Tasten, die kreisförmig darauf angeordnet werden. Da wir uns das Neuzeichnen dieser Tasten im Spielverlauf ersparen wollen (wir erledigen das Animieren später lieber komfortabel mit jQuery), verwenden wir für jedes Einzelteil einen eigenen Canvas, den wir per CSS positionieren können. Als Erstes zeichnen wir die runde Hintergrundfläche:
var $bg = $('').appendTo($simon);
var canvas = $bg.get(0);
var context = canvas.getContext('2d');
context.fillStyle = '#333';
context.arc(230,230,230,0,Math.PI*2,true);
context.fill();
Nicht allen ist vielleicht bekannt, dass mit dem einfachen $ ()-Aufruf nicht nur bestehende Elemente im DOM (Document Object Model) ausgewählt, sondern auch neue erstellt werden können. Wir übergeben dazu anstelle eines Selektors einfach ein komplettes HTML-Element, das wir in der Folge noch per appendTo irgendwo im DOM ablegen müssen. jQuery selbst bietet wie gesagt keine Wrapper-Methoden für die Canvas-API; um in unserem neu erstellten Canvas nun zeichnen zu können, müssen wir uns von jQuery das DOM-Element selbst holen (also jene Umwandlung, die durch den Aufruf von $ () geschieht, rückgängig machen), wofür die Methode get() zur Verfügung steht. Übergeben wird der Index des DOM-Elements, da ein jQuery-Objekt ja mehrere Elemente enthalten kann. Nun haben wir zwar unseren Canvas – zeichnen können wir darin jedoch immer noch nicht. Zuerst müssen wir uns noch ein Kontextobjekt abholen, in den meisten Fällen ist dies der 2d-Kontext (für zweidimensionales Zeichnen). Es gibt übrigens auch einen 3d-Kontext, dieser ist jedoch (noch) nicht standardisiert und wird von verschiedenen Browserherstellern unterschiedlich, wenn überhaupt, implementiert. Erst dieses Kontextobjekt bietet uns nun die Eigenschaften und Methoden, um darauf los zu pinseln: Für den Hintergrund legen wir erst mit fillStyle eine Füllfarbe fest, zeichnen dann mit der arc()-Methode einen Kreis, und füllen diesen schließlich per fill(). Der gesamte Zeichenvorgang läuft durch ein sequenzielles Setzen von Eigenschaften sowie Aufrufen von Methoden des Kontextobjekts ab. Wie bereits im Vergleich mit SVG erwähnt, können wir nach Ablauf der Zeichenoperationen auf keine einzelnen Bestandteile der Grafik zugreifen, alles wird in ein Bitmap gerendert. Ebenso sind keine einzelnen „Ebenen“ verfügbar; analog zum Zeichnen auf Papier liegen nachfolgende Formen über den vorhergehenden (dieses Verhalten kann allerdings über das so genannte Compositing gesteuert werden). In unserem Beispiel verwenden wir einfach mehrere Canvas-Elemente für den Hintergrund und die Tasten, um diese getrennt positionieren und später animieren zu können. Wer bereits mit der Drawing API in ActionScript zu tun hatte, wird hier eine starke Ähnlichkeit entdecken, einige Methoden sind dort sogar identisch benannt. Allerdings stehen in ActionScript etwas mehr Komfortmethoden wiedrawCircle() oder drawEllipse() zur Verfügung, während im Canvas die einzige direkt erzeugbare Grundform ein Rechteck ist und alles weitere durch Kombination anderer Basismethoden und deren Kombination gezeichnet wird.

Bleistift und Tusche – die Zeichenmethoden

Mark Pilgrim unterteilt in seiner sehr guten HTML5-Einführung „Dive into HTML5“ einige der verfügbaren Methoden bildlich in so genannte Bleistift- und Tuschemethoden. Wird beispielsweise über die simple Bleistiftmethode lineTo() ein Pfad gezeichnet, so ist dieser zunächst nicht sichtbar, bis er mit der zugehörigen Tuschemethode stroke() nachgezeichnet wurde. Die wichtigsten Bleistiftmethoden sehen wir in Tabelle 1 aufgelistet. Tabelle 1: Die so genannte Bleistiftmethoden
beginPath() Startet einen neuen Pfad (bildlich gesprochen „den Bleistift in die Hand nehmen“). Muss nur explizit aufgerufen werden, wenn der neue Pfad auch mit einer neuen Kontur oder Füllung versehen werden soll.
moveTo(float x,float y) Bewegt den „Bleistift“ zu den angegebenen Koordinaten x, y. Achtung: Die erste Anweisung nach einem beginPath()-Aufruf (oder in einem leeren Canvas) wird stets als moveTo() interpretiert, dies sollte also stets explizit aufgerufen werden!
lineTo(float x, float y) Zeichnet eine Linie vom aktuellen Punkt zu x, y.
quadraticCurveTo(float cpx, float cpy, float x, float y) Zeichnet eine quadratische Beziérkurve vom aktuellen Punkt zu x, y, deren Kontrollpunkt auf cpx, cpy liegt.
bezierCurveTo(float cpx1, float cpy1, float cpx2, float cpy2, float x, float y) Zeichnet eine kubische Bézierkurve (mit zwei Kontrollpunkten).
arc(float x, float y, float radius, float startAngle, float endAngle, boolean anticlockwise) Zeichnet einen Kreisbogen mit dem Mittelpunkt x, y und einem Radius ‚radius‘. Über ’startAngle‘ und ‚endAngle‘ werden Start- und Endpunkt des Bogens in der Einheit Radiant festgelegt.
closePath() Zeichnet eine Linie vom aktuellen Punkt zum Startpunkt des aktuellen Pfades. Ohne Auswirkungen, wenn der Pfad bereits geschlossen wurde oder lediglich einen Punkt enthält.Diese Methode wird automatisch aufgerufen, wenn der aktuelle Pfad mit fill() gefüllt oder mit openPath() ein neuer Pfad begonnen wird.
Grundlegende Tuschemethoden und dazugehörige Eigenschaften sehen wir in Tabelle 2. Tabelle 2: Die Tuschemethoden
strokeStylefillStyle Diese beiden Eigenschaften definieren die „Tusche“, wobei nicht nur Farbangaben erlaubt sind (die wie in CSS als ‚red‘ oder ‚#ff0000‘ geschrieben werden können), sondern auch Muster (Bilder) und Verläufe (wir bleiben in diesem Artikel aber bei den Grundlagen und verzichten erst einmal hierauf).
stroke() Zeichnet die Kontur des aktuellen Pfades mit dem definierten strokeStyle.
fill() Füllt den aktuellen Pfad mit dem definierten fillStyle.
Die arc()-Methode, die wir für die Hintergrundfläche verwendet haben, bedarf vielleicht noch ein wenig der Erläuterung. Wie bereits erwähnt, gibt es für das Zeichnen anderer Grundformen als des Rechtecks keine eigenen Methoden, wir zeichnen den Kreis also mit der arc()-Methode für Kreissegmente. Die Angabe von Mittelpunkt und Radius ist verständlich, doch wie funktioniert das mit den Winkeln? Die Angabe von Start- und Endwinkel wird in Radiant vorgenommen, was für Nicht-Mathematiker nicht gerade intuitiv erscheint. In Abbildung 2 sehen wir die Werte für die vier Himmelsrichtungen: 0 im Osten, PI/2 im Süden, PI im Westen und PI*1.5 im Norden. Wird der letzte Parameter von arc() auf true gesetzt, verläuft die Richtung gegen den Uhrzeigersinn und die Werte für Norden und Süden werden getauscht. Wer doch lieber mit den „herkömmlichen“ Grad, anstelle von Radiant arbeiten will, kann diese mit einer einfachen Formel umrechnen: Radiant = Grad * PI/180.
Abb. 2

Abb. 2

Einfacher machen wir es uns beim Zeichnen der Spielfeldtasten über die Funktion buildButton(), wo wir mit Bézierkurven arbeiten; allerdings brauchen wir hier noch ein paar andere Dinge:
context.translate(translation.x, translation.y);
context.rotate(rotation * Math.PI / 180);
context.fillStyle = color;
context.beginPath();
context.moveTo(0, 200);
context.quadraticCurveTo(22, 22, 200, 0);
context.lineTo(200, 110);
context.quadraticCurveTo(122, 122, 110, 200);
context.lineTo(0, 200);
context.shadowOffsetX = 5;
context.shadowOffsetY = 5;
context.shadowBlur = 10;
context.shadowColor = 'black';
context.fill();
context.closePath();
Da wir diese Funktion zum Zeichnen aller vier Teile verwenden wollen, müssen wir die gezeichnete Form jeweils um das Mehrfache von 90 Grad drehen. Um dies zu erreichen, rotieren wir pertranslate() den gesamten Koordinatenraum (wie gesagt, einzelne Pfade sind nicht auswählbar). Um dennoch alle vier Teile an derselben Position zeichnen zu können, muss jedoch zuerst noch der Mittelpunkt (also die Werte 0,0 für x,y) verschoben werden. Standardmäßig ist dieser in der linken oberen Ecke angesiedelt; wer hier noch die Zeichnungen aus dem Matheunterricht im Kopf hat, sei gewarnt: Die positive y-Achse verläuft im Canvas abwärts statt aufwärts. Um nun die Konturen der Tasten zu zeichnen, verwenden wir neben lineTo() die Methode quadraticCurveTo(). Ihr übergeben wir nicht nur einen Start- und Endpunkt, sondern auch einen „Kontrollpunkt“. Man könnte sagen, die gezeichnete Linie „krümmt sich dem Kontrollpunkt entgegen“ und über den Abstand des Kontrollpunktes von der Linie steuern wir die Stärke der Krümmung. Abbildung 3 macht dies anschaulich.
Abb. 3: Zeichnen einer Spieltaste über quadraticCurveTo()

Abb. 3: Zeichnen einer Spieltaste über quadraticCurveTo()

Wie wir in der Tabelle weiter oben gesehen haben, gibt es für die Zeichnung gekrümmter Linien noch eine weitere Methode: bezierCurveTo(). Die Bezeichnungen sind hier etwas irreführend, da beide Funktionen so genannte Bézierkurven zeichnen, wobei mit bzierCurveTo() eine „kubische“ Bézierkuve erstellt wird, die im Gegensatz zur quadratischen mit zwei Kontrollpunkten arbeitet, wodurch die Krümmung weiter beeinflusst werden kann. Für beide Funktionen gilt jedoch, dass es relativ schwierig ist, einen Pfad rein aus dem Kopf heraus zu erstellen, es empfiehlt sich daher, diese erst in einem Vektorzeichenprogramm vorzuzeichnen. Wer genau hinsieht, wird bei der Verwendung der Bézierkurve für die Tastenkontur noch eine kleine Unschönheit bemerken: die Krümmung der Kanten stimmt nicht exakt mit der Kreiskontur der umgebenden Hintergrundfläche überein – mit Bézierkurven lassen sich nur schwer Kreisformen zeichnen, und um uns der Idealform eines Kreises anzunähern wären wesentlich mehr Kurven erforderlich. Das soll uns jedoch nicht weiter stören – es gibt nichts, dass sich nicht noch besser machen ließe.

Der Spielablauf – Interaktion und Animation

Unser Spielfeld ist vorbereitet – kommen wir nun zum Spielablauf. Für Interaktion und Animation können wir nun wieder auf jQuery zurückgreifen. Das Erste, was wir brauchen werden, ist eine Möglichkeit, mit dem Spielfeld zu interagieren – für das simple Spielprinzip von Simon müssen wir lediglich auf die vier Tasten drücken können, benötigen also weder Tastatureingaben noch Informationen zu Mausposition oder Ähnlichem. Wir könnten nun also hergehen und auf jede der Tasten einen Click-Listener setzen (also eine Funktion, die bei Mausklick ausgeführt wird) – wir wollen es uns aber einfach machen, und wenden die so genannte Event-Delegation-Technik an. Anstelle von mehreren Listenern setzen wir nur einen einzigen, und zwar auf den Container unseres Spiels, $simon. Da in JavaScript-Events von einem Element stets an sein Elternelement weitergereicht werden (bis sie schlussendlich beim document-Objekt ankommen), brauchen wir also nur zu warten, bis ein Klick auf eine der Tasten unseren Container $simon erreicht und die als Handler definierte Funktion ausgeführt wird:
$simon.click(function(e){
if(!disableUI) {
if(status == 'idle') initSequence();
if(status == 'user') clickedButton($(e.target));
}
});
Ich kann mir an dieser Stelle noch einen kleinen Seitenhieb auf Microsofts Internet Explorer nicht verkneifen: Laut W3C-Definition feuern Events nicht erst auf dem innersten Element, von wo sie dann schrittweise hochgereicht werden, sondern durchlaufen zuerst noch eine capturing-Phase, in der der Event erst beim äußersten Element feuert und dann schrittweise nach innen wandert. Dies wurde von allen namhaften Browsern (und übrigens auch im Eventmodell von ActionScript) implementiert – mit der üblichen Ausnahme, inklusive deren aktueller Version 8. Natürlich müssen wir nun noch wissen, welche der vier Tasten eigentlich gedrückt wurde. Wie wir im Codebeispiel sehen, erwartet die anonyme Funktion, die als Klick-Handler fungiert, einen Parameter e. Dieser enthält bei Aufruf ein Objekt vom Typ event, das uns weitere Informationen liefert; wir brauchen uns übrigens keine Gedanken darüber zu machen, dass dieses Objekt in verschiedenen Browsern unterschiedlich implementiert ist, da jQuery dessen Eigenschaften für uns normalisiert. Über die Eigenschaft target kommen wir an das Element, das den Event ursprünglich auslöste – die gedrückte Taste (andere Elemente besitzt unser Spiel nicht). Bevor wir den Event aber verarbeiten, überprüfen wir mit der Variablen disableUI noch, ob derzeit überhaupt eine Interaktion erlaubt ist. Wir vermeiden damit, dass der User auf die Tasten drücken kann, während der Computer die Farbabfolge vorspielt. Das Deaktivieren der Interaktionsmöglichkeit während dem Animationsablauf wird gerne vergessen; wenn man es unterlässt, muss man sich genau überlegen, wie man mit den Benutzereingaben zwischenzeitlich umgehen will (z. B. eine Animation an einem neuen Startpunkt ausrichten). Da wir auch einen Weg brauchen, das Spiel zu starten, interpretieren wir einfach den ersten Klick in der Spielfläche als Startsignal – durch unseren zentralisierten Event Handler einfach umzusetzen. Um den aktuellen Spielzustand festzustellen, arbeiten wir mit der Variablen status, die zu Beginn wie nach Ende einer Spielrunde den Wert idle sowie während des Nachspielens durch den Benutzer den Wert user zugewiesen bekommt. Wir brauchen keinen eigenen Status für den Zeitraum, während der Computer am Zug ist – in dieser Zeit steht ohnehin disableUI auftrue. Zu Beginn des Spiels wird nun die Funktion initSequence aufgerufen, die einem Array für die Farbabfolge einen neuen, durch Zufall ermittelten Eintrag hinzufügt, und sodann eine weitere Methode aufruft, playSequence (Listing 1).
function playSequence() {
if(seqPos == sequence.length) {
seqPos = 0;
status = 'user';
disableUI = false;
return;
}
$('#button_'+sequence[seqPos], $simon)
.animate({opacity:1}, 200)
.delay(200)
.animate({opacity:0.5}, {duration:200, complete:playSequence});
seqPos++;
}
Zu Beginn dieser Funktion steht lediglich eine Abbruchbedingung für den Fall, dass die Abfolge komplett ist, doch die darauffolgenden Zeilen wollen wir uns genauer ansehen. Hier wird erst über die gewohnte $ ()-Syntax ein jQuery-Objekt für die gerade aktuelle Taste in der Abfolge ermittelt. Des Weiteren wenden wir unter Einsatz des Chaining mehrer Methoden auf unser Objekt an – die Schreibweise, jede Methode in eine einzelne Zeile zu setzen, bietet sich der Übersichtlichkeit halber an. Wir simulieren hier ein Aufleuchten der Tasten durch das Verändern des Transparenzwertes, wobei uns die animate()-Methode behilflich ist. Diese kann nicht nur für Positionsveränderungen eingesetzt werden, sondern erlaubt es uns, sämtliche verfügbaren CSS-Eigenschaften schrittweise zu verändern; diese werden in einem Objekt gesammelt als erster Parameter übergeben. Wenn man die beiden aufeinanderfolgenden Aufrufe von animate() im Skript vergleicht, sieht man, dass für den zweiten Parameter zwei verschiedene Typen möglich sind: Eine Ganzzahl, die die Animationsdauer bestimmt, oder auch ein Objekt, das neben der Dauer noch die Angabe weiterer Optionen wie einer Callback-Funktion erlaubt, die nach beendeter Animation aufgerufen wird. Wir verwenden diese Möglichkeit, um quasi rekursiv stets wieder dieplaySequence– Methode aufzurufen, bis eine komplette Tastenabfolge vorgespielt ist. Ähnliches geschieht in der clickedButton-Methode, die beim Nachspielen durch den Benutzer aufgerufen wird. Neben der Prüflogik, obwohl die der Reihenfolge entsprechend richtige Taste gedrückt wurde, enthält der Code wieder einen Abschnitt zu Tastenanimationen, wobei diesmal direkt eine anonyme Methode als Callback übergeben wird (Listing 2).
$button
.animate({opacity:1}, 200)
.delay(200)
.animate({opacity:0.5}, {duration:200, complete:function(){
seqPos++;
if(seqPos == sequence.length) {
seqPos = 0;
setTimeout(function(){initSequence();}, 1000);
} else {
disableUI = false;
}
}});
Sollen nach beendeter Animation noch weitere Methoden auf demselben Objekt ausgeführt werden, müssen diese übrigens nicht in eine Callback-Funktion verpackt werden. Alle Aufrufe vonanimate innerhalb einer Chain warten stets das Ende der vorhergehenden Animation ab – wäre dem nicht so, würden in unserem Code ja Ein- und Ausblenden der Tasten parallel passieren. Eines habe ich noch nicht angesprochen: Die delay-Methode, die zwischen den Animationen platziert ist, wurde in jQuery 1.4 eingeführt und erlaubt es, Pausen zwischen den Animationen zu setzen. Manch einer mag in Versuchung geraten, diese für Verzögerungen zwischen Codeblöcken zu verwenden, davon ist jedoch abzuraten – in der Regel ist hier die gewohnte setTimeout-Methode besser geeignet, die in dem Codeausschnitt auch für das Erzwingen einer Pause zwischen dem letzten Spielzug des Benutzers und dem neuerlichen Abspielen der erweiterten Folge verwendet wird.

Geschafft!

Damit wäre unser Spiel komplett – dank jQuery mit nur wenigen Zeilen JavaScript-Code, und mithilfe des Canvas-Elements ohne ein einziges Byte an Bitmap-Grafiken zu verschwenden. Ob die Umsetzung dem Spieleklassiker der 80er gerecht wird, sei dahingestellt – auf jeden Fall hoffe ich, der Code lädt zum experimentieren und weiterbasteln ein. Viel Spaß!
Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -