HTML5-Tutorium: Canvas: MiniPong 04: Unterschied zwischen den Versionen

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
Kowa (Diskussion | Beiträge)
K Kowa verschob die Seite HTML5-Tutorium: Canvas: MiniPong 03 nach HTML5-Tutorium: Canvas: MiniPong 04, ohne dabei eine Weiterleitung anzulegen
Kowa (Diskussion | Beiträge)
Keine Bearbeitungszusammenfassung
Zeile 1: Zeile 1:
{{HTML5-Tutorium:Canvas:MiniPong:Menü}}
{{HTML5-Tutorium:Canvas:MiniPong:Menü}}
'''Musterlösung''': <code>[http://glossar.hs-augsburg.de/beispiel/tutorium/html5_canvas/minipong/html5_canvas_minipong_03/WebContent/index.html Minipong 3]</code>
'''Musterlösung''': <code>[https://glossar.hs-augsburg.de/beispiel/tutorium/es5/minipong/WK_MiniPong04/web/index.html index.html]</code>
([http://glossar.hs-augsburg.de/beispiel/tutorium/html5_canvas/minipong/html5_canvas_minipong_03 SVN-Repository])
([https://glossar.hs-augsburg.de/beispiel/tutorium/es5/minipong/WK_MiniPong04/ WK_MiniPong04 (SVN)])


=Ziel: Implementierung von MiniPong mit Schläger, Ball und Punktanzeige=
==Ziel: Das fertige Spiel „MiniPong“==
Im dritten Teil des Tutoriums wird beschrieben, wie man MiniPong mit Hilfe einer einzigen JavaScript-Datei <code>main.js</code> erstellt (wenn man von der Auslagerung der Konstanten in eine
[[Datei:MiniPong04 Model Overview.png|gerahmt|rechts|Klassendiagramm]]
eigene „Initialisierungsdatei“ einmal absieht).
[[Kowarschick|Wolfgang Kowarschick]] bezeichnet so eine Datei als „Moloch-Datei“ (Moloch = Monstrum, Ungeheuer, alles verschlingende Bestie).


Drei Objekte werden implementiert: Das ROOT-Objekt der Anwendung, ein Ball-Objekt und ein Schläger-Objekt.
Im vierten Teil des Tutoriums wird eine funktionsfähige Version von MiniPong erstellt.
[[Datei:MiniPong03Canvas.png|miniatur|ohne|582px|Das Objektmodell von MiniPong 03]]


Anmerkung: Darüber hinaus gibt es noch ein Info-Objekt, zur Ausgabe von Informationen über den Spielverlauf.
Das sich dieses Text-Objekt jedoch nicht auf der Bühne befindet, wurde es im obigen Modell nicht aufgeführt.
Das heißt, das obige Modell ist genaugenommen unvollständig.
=Anwendung „<code>MiniPongCanvas03</code>“=


==Neues Projekt anlegen==
==Neues Projekt anlegen==


Legen Sie ein neues „Statisches Web-Projekt“ mit dem Namen <code>MiniPongCanvas03</code> an.
Legen Sie ein neues Projekt mit dem Namen „<code>MiniPong04</code>“ an.
 
Speichern Sie dieses Projekt wie üblich in Ihrem Repository.
 
==Dateien erstellen==
 
Kopieren Sie die Dateien <code>index.html</code> und <code>css/Main.css</code> von [[HTML5-Tutorium: Canvas: MiniPong 02|Teil 2]] des Tutoriums,
passen Sie den Projekttitel in der Datei <code>index.html</code> an
und erstellen Sie die Dateien <code>js/CONSTANT.js</code> <code>js/Main.js</code> neu.
 
===<code>index.html</code>===
 
Fügen Sie in den HTML-Rumpf (<code>body</code>) folgenden Code ''vor'' das <code>canvas</code>-Element
ein:
 
<source lang="javascript">
<p id="d_info">&nbsp;</p>
</source>
 
Der Text dieses Elements wird von JavaScript aus geändert, um den jeweiligen Punktestand anzuzeigen.
<code>&amp;nbsp;</code> ist ein nicht-trennbares Leerzeichen (Non-Breaking SPace) und sorgt dafür,
dass sich Eclipse nicht über ein leeres <code>p</code>-Element beschwert.
 
Fügen Sie in den HTML-Rumpf (<code>body</code>) folgendes Formular ''hinter'' das <code>canvas</code>-Element
ein:
 
<source lang="javascript">
<form>
  <input id="d_start_stop" type="button" value=" "/>
</form>
</source>
 
Dieser Button fungiert – je nach aktuellen Status des Spiels – als Start- oder Stopp-Button.
Die Beschriftung sowie die Zuordnung der zugehörigen Aktion wird jeweils mittels JavaScript
an den aktuellen Zustand der Anwendung angepasst.
 
Das  Attribut <code>value</code> muss laut HTML5-Standard vorhanden sein.
Dessen Wert dar nicht „leer“ sein. Daher wird es hier angegeben, obwohl
es eigentlich unnötig ist, da der Wert sofort von JavaScript überschrieben wird.
 
===<code>CONSTANT.js</code>===
 
Fügen Sie alle Variablen der beiden Dateien <code>CONSTANT.js</code> von [[HTML5-Tutorium: Canvas: MiniPong 01#CONSTANT.js|Teil 1]]
und [[HTML5-Tutorium: Canvas: MiniPong 02#CONSTANT.js|Teil 2]] des Tutoriums in die Datei <code>CONSTANT.js</code> ds dritten Teils ein.
 
'''Achtung: Definieren Sie keine Variable doppelt.'''
 
Sie werde später noch weitere Konstanten in diese Datei einfügen,
wie z.B. Konstanten, die die Startposition von Ball und Schläger definieren,
oder eine Konstate <code>PADDLE_FRICTION</code>, die die Reibung des Schlägers festlegt.
(Beachten Sie, dass in den Use Cases Folgendes gefordert wurde:
''Wird der Schläger im Moment der Kollision bewegt, so wird der Ball abhängig von der Bewegungsrichtung und Geschwindigkeit des Schlägers abgelenkt.'')
 
===<code>main.js</code>===
 
Fügen Sie die Variablen <code>g_ball</code>, <code>g_paddle</code>, <code>g_context</code>, <code>g_imterval</code>
der beiden Dateien <code>main.js</code> von [[HTML5-Tutorium: Canvas: MiniPong 01#main.js|Teil 1]]
und [[HTML5-Tutorium: Canvas: MiniPong 02#main.js|Teil 2]] des Tutoriums in die Datei <code>main.js</code> des dritten Teils ein.
Definieren Sie zusätzlich eine globale Variable <code>g_score</code>, in der der aktuelle Punktestand des Spiels abgelegt wird.
 
====Das Ball-Objekt <code>g_ball</code>====
Erweitern Sie das Ball-Objekt <code>g_ball</code> um eine Resetmethode, deren Aufgabe es ist, den Ball
vor Spielbeginn zur Startposition zu verschieben. Außerdem muss diese Funktion eine Startgeschwindikeit
des Balls festlegen.
 
<source lang="javascript">
// Moves the ball to its starting position
// and computes randomly a starting velocity vector.
reset:
  function()
  { this.x  = BALL_X;
    this.y  = BALL_Y;
    this.vx =  (Math.random()<0.5?1:-1)
              * (BALL_VX_MIN+Math.random()*(BALL_VX_MAX-BALL_VX_MIN));
    this.vy =  (BALL_VY_MIN+Math.random()*(BALL_VY_MAX-BALL_VY_MIN));
  },
</source>
 
Definieren Sie die Konstanten <code>BALL_X</code> und <code>BALL_Y</code> in der Datei <code>CONSTANT.js</code> so, dass
der Ball in der Mitte des oberen Spielfeldrands zum liegen kommt. Legen Sie für die übrigen Konstanten
sinnvolle Werte fest.
 
Legen Sie im Ball-Objekt für alle [[Attribut]]e sinnvolle Initialwerte fest.
Diejenigen Werte, die später von der <code>reset</code>-Methode gesetzt werden,
können einfach mit <code>0</code> oder <code>null</code> vorbelegt werden.
 
====Das Schläger-Objekt <code>g_paddle</code>====
Erweitern Sie das Schläger-Objekt <code>g_paddle</code> um eine Resetmethode, deren Aufgabe es ist, den Schläger
auf die Startposition (die ebenfalls in der Datei <code>CONSTANT.js</code> definiert wird)
und die Startgeschwindigkeit des Schlägers (in x-Richtung) auf <code>0</code> zu setzen:
 
<source lang="javascript">
// Moves the paddle to its starting position
// and sets its velocity to zero.
reset:
  function()
  { this.x  = PADDLE_X;
    this.y  = PADDLE_Y;
    this.vx = 0;
  },
</source>
 
Legen Sie im Schläger-Objekt für alle [[Attribut]]e sinnvolle Initialwerte fest.
Diejenigen Werte, die später von der <code>reset</code>-Methode festgelegt werden,
können einfach mit <code>0</code> oder <code>null</code> vorbelegt werden.
 
Definieren Sie für das Schläger-Objekt ein weiteres Attribut <code>friction</code> und
initialisieren Sie dieses mit der Konstanten <code>PADDLE_FRICTION</code>.
Der Wert dieser Konstanten sollte ca. <code>0.3</code> betragen.
 
===Die Draw-Funktion===
 
Die Draw-Funktion <code>f_draw</code> hat nur eine Aufgabe: Das Neuzeichnen der Bühne,
d.h., das Löschen des Bühneninhalts und das Neuzeichnen aller [[Sprites]]
auf der Bühne. Dazu ruft sie der Reihe nach die <code>draw</code>-Methoden der
einezelnen Sprites auf und übergibt diesen die Bühne auf der der jeweilige
Sprite gezeichnet werden soll. (In unseren Fall gibt es nur zwei Sprites: Ball und Schläger.)
 
<source lang="javascript">
// Clears the canvas and redraws all sprites (ball and paddle).
function f_draw()
{ g_context.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
 
  g_ball.draw(g_context);
  g_paddle.draw(g_context);
}
</source>
 
Zusätzlich sollte man noch eine Funktion definieren, mit der man Informationen
in das Textfeld der HTML-Seite schreiben kann:
 
<source lang="javascript">
// Displays information on the HTML page.
function f_info(p_info)
{ document.getElementById("d_info").firstChild.nodeValue = p_info; }
</source>
 
===Die Init-Funktion===
 
Die Funktion <code>f_init</code> unterscheidet sich nicht wesentlich von der
Init-Funktion der beiden ersten Teile des Tutoriums.
 
Neu ist lediglich der Aufruf der Observer-Funktion <code>o_stop_game</code> und das Fehlen
der Timerdefinition. Der Animations-Timer wird im Spiel erst gestartet, wenn der Spieler
den Start-Knopf gedrückt hat. Die Stopp-Funktion hat die Aufgabe,
das Spiel in die Ausgangssituation zu bringen (siehe Definition dieser Funktion am Ende des Artikels).
Sie wird im weiteren Spielverlauf jedesmal dann aufgerufen, wenn ein Spiel durch einen Spielfehler
oder durch den Spieler beendet wird.
 
<source lang="javascript">
// Has to be called after the HTML page has been loaded.
function f_init()
{ var l_canvas = document.getElementById("d_canvas");
 
  // Initialize the canvas.
  l_canvas.width  = CANVAS_WIDTH;
  l_canvas.height = CANVAS_HEIGHT;
  g_context      = l_canvas.getContext("2d");
 
  // Reset the game (i.e. change its status to "waiting for game to be started").
  o_stop_game();
}
 
// Execute the init function after the HTML page has been loaded.
window.onload = f_init;
</source>
 
==Observer==
[[Observer]] sind speziele Funktionen, die in Falle von betimmten [[Ereignis (OOP)|Ereignis]]sen (events),
wie Timer-Event, Tasten-Event, Maus-Event etc. ausgeführt werden.
 
Die beiden Observer <code>o_start_paddle_moving</code> und <code>o_stop_paddle_moving</code>
können direkt aus der Datei <code>main.js</code> von Teil zwei des Tutoriums übernommen werden.
Sie haben die Aufgabe, den Schläger in eine bestimmte Richtung zu bewegen, solange der Spieler
eine bestimmte Taste drückt.
 
Der Observer <code>o_redraw</code> wächst im Teil 3 des Tutoriums zum „Moloch“ an. Er hat die Aufgabe,
eine Kollision zwischen Ball und Wand oder Ball und Schläger zu erkennen und entsprechend zu reagieren:
* Ball und Wand (links, rechts, oben): Ball prallt ab
* Ball und Wand (unten außerhalb des sichbaren Bereichs): Spielende
* Ball und Schläger: Ball prallt ab und wird evtl. abgelenkt; Erhöhung des Scores um einen Punkt
Außerdem muss diese Funktion auch noch den Canvas löschen und die neu positionierten
Sprites zeichnen, sofern die Funktion nicht aufgrund des Spielendes vorzeitig verlassen wurde.
<source lang="javascript">
//  An event observer:
//  It is called every 1000/FPS milliseconds, but only when the game has been started.
function o_redraw()
{ // Collision detection: ball <-> wall (left, right, top)
  // Collision response:  change the moving direction of the ball
  if (g_ball.x <= g_ball.r || g_ball.x >= CANVAS_WIDTH - g_ball.r)
  { g_ball.vx = -g_ball.vx; }
  if (g_ball.y <= g_ball.r)
  { g_ball.vy = -g_ball.vy; }
 
  // Collision detection: ball <-> wall (bottom outside of the canvas)
  // Collision response:  stop the game (as the ball has vanished)
  if (g_ball.y - 2*g_ball.r > CANVAS_HEIGHT)
  { o_stop_game();
    return; // leave o_redraw early
  }
  // Collision detection: ball <-> paddle
  // Collision response:  change the moving direction of the ball
  //                    + change the velocity of the ball
  //                      (due to the friction between the ball and the paddle)
  //                    + display the new score
  // Collision detection and handling has to be improved:
  // A collision with the small side of the paddle should be handled.
  if (  g_ball.y + g_ball.r    >= g_paddle.y
      && g_ball.y + g_ball.r    <= g_paddle.y + g_paddle.height
      && g_ball.x + 0.5*g_ball.r >= g_paddle.x
      && g_ball.x - 0.5*g_ball.r <= g_paddle.x + g_paddle.width
    )
  { // Resolve penetration.
    if (g_ball.vy > 0)                // The ball is moving from top to bottom.
    { g_ball.y = g_paddle.y - g_ball.r; }
    else                              // The ball is moving from bottom to top.
    { g_ball.y = g_paddle.y + g_paddle.height + g_ball.r; }
 
    g_ball.vy = -g_ball.vy;
    g_ball.vx += g_paddle.friction*g_paddle.vx;
   
    // Raise the score and display it.
    g_score++;
    f_info("Punkte: " + g_score);
  }
 
  // Move the ball.
  g_ball.move();
 
  // Move the paddle if possible.
  if (  (g_paddle.vx < 0 && g_paddle.x > 0)
      || (g_paddle.vx > 0 && g_paddle.x + g_paddle.width < CANVAS_WIDTH)
    )
  { g_paddle.move(); }
 
  // Redraw the canvas.
  f_draw();
}
</source>
 
Im Spiel gibt es noch zwei weitere Observer. Der Observer
<code>o_start_game</code> wird bei einem Klick auf den
Start/Stopp-Button aufgerufen, falls diese als Start-Button
fungiert. Seine Aufgabe ist es, den Score auf Null zurückzusetzen,
den Start/Stopp-Button als Stopp-Button zu aktivieren, die
Steuerung des Schlägers zu aktivieren, den Animations-Timer der  „physics engine“
zu starten und zu guter Letzt den eine Information über den
Spielstart in das Textfeld auf der Bühne zu schreiben. Aktuell könnte
im letzten Schritt auch gleich der aktuelle Punktestand (nnull Punkte)
angezeigt werden.
 
<source lang="javascript">
// An event observer:
// It is called whenever a "start game event" is signaled.
function o_start_game()
{ // Reset the score.
  g_score = 0;
 
  // Set the button "d_start_stop" to be a stop button (view and behaviour).
  document.getElementById("d_start_stop").value      = "Stopp";
  document.getElementById("d_start_stop").onmousedown = o_stop_game;
 
  // React to key down and key up events.
  document.onkeydown = o_start_paddle_moving;
  document.onkeyup  = o_stop_paddle_moving;
 
  // Start the timer for redrawing the canvas every 1000/FPS milliseconds.
  g_timer = window.setInterval(o_redraw, 1000/FPS);
 
  // Show the current score (zero points)
  f_info("Punkte: " + g_score);
}
</source>
 
Beachten Sie, dass der Code <code>f_info("Punkte: " + g_score);</code>
an zwei Stellen im Programm aufgerufen wird. Damit ist der Code nicht mehr
[[Don't repeat yourself|DRY]]. Wenn die Anzeige der Punkte veränder werden soll,
muss dies an zwei Stellen erfolgen. Wie kann man den Code verbessern?
 
Die Aufgabe des Stopp-Spiel-Observers ist im Wesentlichen,
die Aktionen des Start-Spiel-Servers rückgängig zu machen:
* Der Animations-Timer wird gestoppt.
* Die Schläger-Steuerungstasten werden deaktiviert.
* Der Start-Stop-Button wird als Start-Button aktiviert.
 
Außerdem werden der Ball un der Schläger an ihre jeweiligen Startpositionen gesetzt.
Anschließend muss die Bühne neu gezeichnet werden, damit die neuen Positionen dieser
biden Sprites auch sichtbar wird.
 
<source lang="javascript">
// An event observer:
// It is called whenever a "stop game event" is signaled.
function o_stop_game()
{ // Stop the canvas redrawing timer.
  window.clearInterval(g_timer);
 
  // Stop reacting to key down and key up events.
  document.onkeydown = null;
  document.onkeyup  = null;
 
  //Set the button "d_start_stop" to be a start button (view and behaviour).
  document.getElementById("d_start_stop").value      = "Start";
  document.getElementById("d_start_stop").onmousedown = o_start_game;
 
  // Put the ball and the paddle on their starting positions
  // and then redraw the canvas.
  g_ball.reset();
  g_paddle.reset();
  f_draw();
}
</source>
 
Man beachte, dass die Befehle in umgekehrter Reihenfolge wie in der Start-Funktion
ausgeführt werden. Dies wird bei komplementären Start-/Stop-Anweisungen üblicherweise so gemacht,
da es bei dieser Reihenfolge der Stop-Anweisungen i. Allg. nicht zu Abhängigkeitsfehlern kommt.
Wenn beispielsweise die Anweisung B nur dann funktioniert, wenn das Ergebnis der
Anweisung A vorliegt, dann muss A vor B gestartet aber nach B beendet werden.
 
Man beachte außerdem, dass die Stop-Funktion den Score nicht verändert, damit der Spieler
die erzielten Punkte auch nach Spielende noch lesen kann. Hier ist allerdings noch ein kleiner Fehler
enthalten.
 
==Anwendung testen und sichern==
Testen Sie Ihre Anwendung in gewohnter Weise.
 
Vergessen Sie nicht, sie im SVN-Repository zu sichern.
 
=Erweiterung=
 
Erweitern Sie die Anwendung so, dass Sie zwei Schläger unabhängig voneinander
links und rechts am Bühnenrand bewegen können, d.h., dass zwei Spieler gegeneinander spielen können.
Jeder Spieler hat seinen eigenen Score. Der erste Spieler, der 7 Punkte erreicht, gewinnt.
 
Am besten zeigen Sie die erzielten Punkte auch auf der Bühne selbst an (vgl. Bilder im [[Wikipedia:Pong|Wikipedia-Artikel zu Pong]]).
Wie man Text auf der Bühne darstellen kann, wurde im [[HTML5-Tutorium: Canvas: Hello World|Hello-World-Tutorium]] gezeigt.
 
[[Datei:MiniPong03aCanvas.png|miniatur|ohne|945px|Das Objektmodell von MiniPong 03a]]
 
Beachten Sie wieder das [[Programmierprinzip#Don.27t_repeat_yourself.2C_DRY.5B4.5D|Programmierprinzip  „Don't repeat yourself“ (Dry)]].
 
'''Musterlösung''': <code>[http://glossar.hs-augsburg.de/beispiel/tutorium/html5_canvas/minipong/html5_canvas_minipong_03a/WebContent/index.html Minipong 3a]</code>
([http://glossar.hs-augsburg.de/beispiel/tutorium/html5_canvas/minipong/html5_canvas_minipong_03a SVN-Repository])
 
'''Siehe auch''': http://www.guimp.com/pong_flash.html; Hier ist der Name MiniPong wirklich gerechtfertigt.
 
=Probleme der Kollisionserkennung=
 
Die in [[HTML5-Tutorium: Canvas: MiniPong 01#Probleme_der_Kollisionserkennung|Teil 1 des Tutoriums angesprochenen]] Probleme de  „Tunnelns“ und der „Überlappung“
bei einer [[Kollisionserkennung und -behandlung#A-posteriori-Kollisionserkennung_.28auch_.E2.80.9Ediskrete_Kollisionserkennung.E2.80.9C.29|A-posteriori-Kollissionserkennung]] kann man in dieser Minipong-Version besonders schön veranschaulichen.
 
==Tunneln==
Beispiel: [http://glossar.hs-augsburg.de/beispiel/tutorium/html5_canvas/minipong/html5_canvas_minipong_03_tunneling/WebContent/index.html modifizierte Minipong-Anwendung]
 
In diesem Beispiel wurden einfach die Framrate deutlich reduziert (auf 10 Frames pro Sekunde)
sowie eine hohe Ballgeschwindigkeit (200 Pixel pro Sekunde in y-Richtung) eingestellt.
 
(Damit Sie den Effekt auf jeden Fall beobachten können, wurde außerdem die Steuerung des
Schlägers deaktiviert.)
 
==Überlappung==
Beispiele: [http://glossar.hs-augsburg.de/beispiel/tutorium/html5_canvas/minipong/html5_canvas_minipong_03_penetration/WebContent/index.html modifizierte Minipong-Anwendung],
[http://glossar.hs-augsburg.de/beispiel/tutorium/html5_canvas/minipong/html5_canvas_minipong_03a_penetration/WebContent/index.html modifizierte Pong-Anwendung],
 
Um diesen Effekt zu erreichen wurde nur eine wesentliche Änderung am Source-Code vorgenommen.
In der Funktion <code>o_redraw</code> wurden folgende fünf Zeilen auskommentiert:
 
<source lang="javascript">
// Resolve penetration.
if (g_ball.vy > 0)                // The ball is moving from top to bottom.
  g_ball.y = g_paddle.y - g_ball.r;
else                              // The ball is moving from bottom to top.
  g_ball.y = g_paddle.y + g_paddle.height + g_ball.r;   
</source>
 
Diese Anweisungen verschieben den Ball im Falle einer Kollision soweit, dass er den Schläger nicht überlappt, sondern nur berührt.
Wenn diese Korrekturmaßnahme unterbleibt, kann der Ball in gewissen Situationen im Schläger hängen beleiben. Eine derartige Situation kann man beispielsweise erzwingen,
wenn man in der Datei <code>CONSTANT.js</code> der Minipong-Anwendung folgende Werte wählt:
 
<source lang="javascript">
BALL_VX_MIN  = 116, // pixels per second
BALL_VX_MAX  = 116, // pixels per second
BALL_VY_MIN  =  21, // pixels per second
BALL_VY_MAX  =  21, // pixels per second
</source>
 
Aber auch ohne Modifikation der Startwerte des Balls kann dieser Effekt auftreten,
wenn man den Ball während des Spiels geeignet mit dem Schläger ablenkt.
 
=Weitere Verbesserungsmöglichkeiten=
 
Der hier vorgestellte Minipong-Code realisiert alle in der Use-Case-Beschreibung geforderten Anwendungsfälle:
 
[[Datei:MiniPongUseCases.png|miniatur|ohne|924px|Use Cases der Tutoriums-Anwendung <code>Minipong</code>]]
 
Der Code ist dennoch nicht zufriedenstellend, da viele [[Programmierprinzipien]] verletzt werden.
Auf die Beachtung dieser Prinzipien könnte bei Mini-Anwendungen wie MiniPong sicherlich verzichtet werden,
da der Aufwand größer ist als der Ertrag.
Bei größeren Anwendungen sollte man dies Prinzipien allerdings tunlichst beachten, da man anderenfalls mit großer Wahrscheinlichkeit
keine robust laufende Anwendung und keinen wiederverwendbaren Code erhält.
 
{{Codequalität
| application    = MiniPongCanvas03
| readability    = 4
| writability    = 3
| continuity      = 4
| customizability = 4
| dry            = 3
| demeter        = 5
| verifiability  = 2
| interfaces      = 5
| contract        = 0
| liskov          = 5
| modularity      = 1
}}
 
==Verstöße gegen das „[[Programmierprinzipien#Verst.C3.A4ndlichkeit.2C_Comprehesibility.2C_Lesbarkeit.2C_Readability|Prinzip der Verständlichkeit/Lesbarkeit]]“==
 
Es gibt nur einen wesentlichen Verstoß gegen das Prinzip „Verständlichkeit/Lesbarkeit“:
* '''Die Kommentare wurden nicht im [[JSDoc]]-Format geschrieben.'''
Dieses Format kann verwendet werden, um eine [[API]]-Dokumentation automatisch zu generieren.
 
Ansonsten wurde versucht, den Code so lesbar und verständlich wie möglich zu gestalten:
 
* Die Breite des Codes wurde fast überall auch 80 Zeichen beschränkt, damit er auf normalem Dia-A4-Papier in normaler Schriftgröße ausgedruckt werden kann.
* Der Code wurde sauber und platzsparend eingerückt.
* Klammerpaare werden leicht als solche erkannt.
* Es gibt keine überflüssigen Leerzeilen.
* Es gibt keine Tab-Symbole, damit der Code auch mit anderen Text-Programmen (wie z.B. in einem Browser) gut gelesen werden kann.
* Die Bezeichner von Variablen, Funktionen etc. unterscheiden sich syntaktisch so, dass man ihre Aufgabe sofort erkennt. Beispielsweise handelt es sich bei <code>g_ball</code> um eine globale Variable, bei <code>p_event</code> um einen [[Parameter]] und bei <code>CANVAS_WIDTH</code> um eine Konstante (vgl. [[Multimedia-Programmierung: Style Guide|Style Guide]]).
* Die Namen der Variablen, Parameter, Funktionen, Attribute, Methoden und Konstanten sind selbsterklärend. Beispielsweise wird in der Variablen <code>g_ball</code> ein Ballobjekt gespeichert.
* Der Code wurde mit sinnvollen Kommentaren versehen (für fortgeschrittene Programmierer sollte die Zahl allerdings etwas reduziert werden).
 
==Verstöße gegen das „[[Programmierprinzipien#Schreibbarkeit.2C_Writability|Prinzip der Schreibbarkeit]]“==
 
'''Die Entwicklungsumgebung ist in mancherlei Hinsicht nicht zufriedenstellend:'''
 
* Juno ist deutlich träger als Indigo. Diese Erfahrung haben neben [[Wolfgang Kowarschick]] anscheinend mehrere Benutzer gemacht (siehe z.B. [https://bugs.eclipse.org/bugs/show_bug.cgi?id=385272], [http://www.eclipse.org/forums/index.php/m/901301/], [http://stackoverflow.com/questions/11639108/eclipse-juno-wtp-egit-dead-slow]).
* Juno ist anscheinend auch hinsichtlich der Fehlerkorrektur und der Autovervollständigung ein Rückschritt gegenüber Indigo (eigene Erfahrung von [[Wolfgang Kowarschick]]).
 
Zur Lösung dieser Probleme sollte man versuchsweise auf Indigo umsteigen (auch wenn auf [http://www.eclipse.org/downloads/ http://www.eclipse.org/] Juno defaultmäßig zum Download angeboten wird).
Eine Alternative wäre evtl. auch die Entwicklungsumgebung [http://netbeans.org/ Netbeans] mit der ich ([[Wolfgang Kowarschick]]) sehr gute Erfahrungen hinsichtlich der Entwicklung Web-Anwendungen gemacht habe.
 
Aber selbst wenn man diejenige Entwicklungsumgebung gefunden hat, mit der man persönlich am Besten zurechtkommt, gibt es
weitere Verbesserungsmöglichkeiten:
 
* Für Code-Schnipsel, die man häufig verwendet, sollte man Tastaturkürzel definieren. Ein sehr interessantes Werkzeug für diesen Zweck ist [http://code.google.com/p/zen-coding/ Zen Coding] (siehe auch [http://nooshu.com/aptana-on-steroids-using-zen-coding Aptana on Steroids using Zen Coding] und [https://github.com/sergeche/eclipse-zencoding eclipse-zencoding]).
* Für Dateien (html, js, css ...) sollte man sich Templates anlegen bzw. die Templates der verwendeten Entwicklungsumgebung an seine Vorlieben anpassen.
* Das Grundgerüst des zu implementierenden Codes (Objekt-, Klassen-, Attribut-, Methoden-Definitionen etc.) sollte automatisch generiert werden (z.B. mit Hilfe eines UML-Werkzeugs).
* Für Code und Modell gemeinsam sollte [[Refactoring]] verfügbar sein. Das heißt, es sollte jederzeit problemlos möglich sein, die Namen von Klassen, Objekten, Methoden, Attributen etc. konsistent in allen Dateien zu ändern.
 
==Verstöße gegen das „[[Programmierprinzipien#Stetigkeit.2C_Continuity|Prinzip der Stetigkeit]]“==
 
Ein Programm-Code heißt [[Programmierprinzipien#Stetigkeit.2C_Continuity|stetig]],
wenn kleine Änderungswünsche auch nur kleine Änderungen am Code zur Folge haben.
 
Dieses Prinzip wird relativ gut beachtet. Man vergleiche beispielsweise den Code
der MiniPong-Datei [http://glossar.hs-augsburg.de/beispiel/tutorium/html5_canvas/minipong/html5_canvas_minipong_03/WebContent/js/main.js <code>main.js</code>]
der Canvas-Version mit dem Code von [http://glossar.hs-augsburg.de/beispiel/tutorium/html_svg/minipong/html_svg_minipong_03/WebContent/js/main.js <code>main.js</code>]
der SVG-Version:
# Beide Musterlösungen ([http://glossar.hs-augsburg.de/beispiel/tutorium/html5_canvas/minipong/html5_canvas_minipong_03/ Canvas-Version], [http://glossar.hs-augsburg.de/beispiel/tutorium/html_svg/minipong/html_svg_minipong_03/ SVG-Version]) in Eclipse laden
# Im Projektexplorer: beide <code>main.js</code>-Dateien selektieren
# Rechtsklick auf eine der beiden Dateien → <code>Compare with</code> → <code>Einander</code>
 
Der Änderungswunsch lautete „Ersetze die Canvas-Bühne durch eine SVG-Bühne“. Der in zwei Tutorien
([[HTML5-Tutorium: Canvas: MiniPong 03]] und [[HTML-Tutorium: SVG: MiniPong 03]]) erstellte Code unterscheidet sich
nicht sonderlich stark.
 
Hinsichtlich des Prinzips „Don't repeat yourself“ sind allerdings noch einige Verbesserungen möglich (siehe unten).
 
==Verstöße gegen das „[[Programmierprinzipien#Konfigurierbarkeit.2C_Customizability|Prinzip der Konfigurierbarkeit]]“==
 
Ein wichtiges Prinzip, um Stetigkeit zu erzielen, ist, die einfache Konfigurirbarkeit der Anwendung sicherzustellen.
Konstanten sollten nie im Code stehen, sondern immer in einer eigenen Konfigurationsdatei.
 
Gegen dieses Prinzip wird nur in einer Hinsicht verstoßen:
'''Texte, mit denen der Benutzer informiert wird (<code>Start</code>, <code>Stopp</code>, <code>Punkte</code>) stehen direkt im Code.'''
 
(Anmerkung: In der Datei [http://glossar.hs-augsburg.de/beispiel/tutorium/html5_canvas/minipong/html5_canvas_minipong_03a/WebContent/js/main.js <code>main.js</code>]
der zu [http://glossar.hs-augsburg.de/beispiel/tutorium/html5_canvas/minipong/html5_canvas_minipong_03a/ Pong erweiterten Version von MiniPong] sind noch deutlich mehr
Informationstexte direkt im Code enthalten.)
 
Alle anderen Konstanten wurden in die Datei <code>CONSTANT.js</code> ausgelagert, die somit für MiniPong als Konfigurationsdatei fungiert.
 
In eine künftigen MiniPong-Version sollten die Informationstexte auch in eine Informationsdatei ausgelagert werden.
Im Rahmen dieser Auslagerung sollte MiniPong gleich „[[Internationalisierung|internationalisiert]]“ werden. Das heißt, die Informationstexte
sollten jeweils in der Sprache von Benutzer bevorzugten Sprache ausgeliefert werden.
 
==Verstöße gegen das Prinzip „[[Don't repeat yourself|Don't repeat yourself]]“==
 
Die Beactung des Prinzips „Don't repeat yourself“ (DRY) ist ein weiteres wichtiges Instrument,
um die Stetigkeit von Programmcode zu erhöhen.
 
'''Der Minipong-Code ist nicht [[Don't repeat yourself|DRY]].'''
 
Beispiel: Der Code <code>f_info("Punkte: " + g_score);</code> wird
an zwei Stellen im Programm aufgerufen. Wenn irgendwann einmal
der Text der Punktausgabe verändert werden soll, muss dies an zwei Stellen erfolgen.
Ändert man den Text nur an einer Stelle, so verhält sich das Programm
danach inkonsitstent.
 
Es gibt aber noch eine subtilere Form der Wiederholung von Code:
Die grafischen Objekte (Ball, Schläger) enthalten
gleiche Attribute (z.B. Position <code>x</code>, <code>y</code>) und
gleiche Methoden (<code>move</code>, <code>draw</code>) mit vergleichbaren Aufgaben.
Die Methode <code>move</code> könnte beispielsweise für alle grafischen
Objekte identisch definiert werden:
 
<source lang="javascript">
move:
  function()
  { this.x  += this.vx/FPS;
    this.y  += this.vy/FPS;
    this.vx += this.ax * (this.vx > 0 ? 1 : -1);
    this.vy += this.ay * (this.vy > 0 ? 1 : -1);
  },
</source>
 
Voraussetzung wäre allerdings, dass alle grafischen Objekte je einen Positionsvektor
$p = (x,y)$, einen Geschwindigkeitsvektor $v = (v_x, v_y)$ sowie einen Beschleunigungsvektor $a = (a_x, a_y)$
besitzen würden.
 
Selbst die obige Definition der Funktion <code>move</code> ist noch nicht DRY. Beispielsweise sollte
man an Stelle von
<source lang="javascript">
this.x += this.vx/FPS;
this.y += this.vy/FPS;
</source>
besser mit echten 2D-Vektor-Objekten (wie z.B. <code>this.position</code> und <code>this.velocity</code>) arbeiten,
für die es Methoden wie <code>modifyAdd</code> und <code>scalar</code> gibt:
 
* <code>modifyAdd</code>: Modifiziert den aktuellen Vektor, indem sie einen zweiten Vektor hinzuaddiert.
* <code>scalar</code>: Berechnet ein Skalarprodukt, d.h multipliziert den aktuellen Vektor mit einer reelen Zahl, modifiziert aber den aktuellen Vektor nicht.
 
Die neue Position eines grafischen Objektes würde dann folgendermaßen ermittelt werden:
 
<source lang="javascript">
this.position.modifyAdd(this.velocity.scalar(1/FPS));
</source>
 
==Verstöße gegen das „[[Programmierprinzipien#Gesetz_von_Demeter.5B5.5D.2C_Law_of_Demeter.2C_LoD|Gesetz von Demeter]]“==
 
Das [[Programmierprinzipien#Gesetz_von_Demeter.5B5.5D.2C_Law_of_Demeter.2C_LoD|Gesetz von Demeter]] verlangt, dass es keine Ketten von Methoden- und/oder
Attributaufrufen gibt, dass ein Objekt also nicht mit „Freunden von Freunden“ kommuniziert, sondern nur mit seinen direkten „Freunden“. Anweisungen der Art <code>object.attrtibut.methode()</code>, in der zwei oder mehr Punktoperationen ($\cdots$<code>.</code>$\cdots$) vorkommen,
sind verboten. (Ein führendes <code>this.</code> ist allerdings erlaubt.)
 
Die Beachtung des Gesetes von Demeter erhöht die Stetigkeit.
 
'''Gegen dieses Gesetz wird in <code>MiniPongCanvas03</code> nicht verstoßen.'''
 
In der SVG-Version [http://glossar.hs-augsburg.de/beispiel/tutorium/html_svg/minipong/html_svg_minipong_03/WebContent/js/main.js <code>MiniPongSVG03</code>] kommt
derartiger Code allerdings in der <code>init</code>-Methode des Ball-Objektes vor:
 
<source lang="javascript">
p_canvas
  .circle(this.x, this.y, this.r)
  .attr({"stroke"      : BALL_BORDER_COLOR,
        "stroke-width" : BALL_BORDER_WIDTH,
        "fill"        : BALL_COLOR,
        });
</source>
 
In diesen speziellen Fall liegt allerdings kein Verstoß gegen das Gesetz von Demeter vor.
 
Die Methode <code>circle</code> fungiert als Konstruktor. Sie erzeugt ein neues Objekt
und liefert das neu erzeugte Objekt als Ergebnis. Auf dieses Objekt wird die Methode <code>attr</code>
angewendet. Der obige Code könnte also auch – etwas umständlicher – folgendermaßen geschrieben werden:
 
<source lang="javascript">
var l_circle = p_canvas.circle(this.x, this.y, this.r);
 
l_circle.attr({"stroke"      : BALL_BORDER_COLOR,
              "stroke-width" : BALL_BORDER_WIDTH,
              "fill"        : BALL_COLOR,
              });
</source>
 
Das heißt, bei beiden der obigen Methodenaufrufen „spricht“ das Ball-Objekt nur, wie von Demeter gefordert,
mit einem ihm direkt bekannten „Freund“: <code>p_canvas</code> wurde ihm als Parameter übergeben und <code>l_circle</code>
hat es selbst erzeugt.
 
==Verstöße gegen das [[Programmierprinzipien#.C3.9Cberpr.C3.BCfbarkeit.2C_Verifiability|Prinzip der Überprüfbarkeit]]“==
 
'''Bis auf ein einfaches Use-Cases-Diagramm und ein Objektmodell gibt es derzeit keine formale Spezifikation der Anwendung.'''
 
Das heißt: Derzeit kann formal nicht beweisen werden, dass die Anwendung das tut, was von ihr erwartet wird.
Es gibt auch keine Testfälle, mit denen die Erfüllung der Spezifikation zumindest teilweise verifiziert werden könnte.
Es bleibt dem Entwickler nur, Beta-Tester zu suchen, die das Spiel ausführlich auf diversen Brwosern testen und
ihm gefundene Probleme mitteilen. (Er selbst kann auch als Beta-Tester fungieren.)
 
Es fehlen:
 
* Ein gutes ([[UML]]-)[[Datenmodell]], das möglichst viele Aspekte der guten Programmierung umsetzt.
* Ein Code-Generator, der das Datenmodell automatisch in ein Codegerüst überführt.
* Eine formale Spezifikation der Aufgaben der Funktionen und Methoden oder zumindest eine hinreichende Anzahl an Testfällen ([[unit test]]s), mit der die [[Semantik]] der Funktionen und Methoden beschrieben wird.
* Ein Testwerkzeug, wie z.B. [http://busterjs.org Buster,js], um zu verifizieren, ob der aktuelle Code tatsächlich die mit Hilfe von Testfällen spezifizierte Semantik realisiert. (Siehe auch [[WikipediaEN:List_of_unit_testing_frameworks#JavaScript|Liste von Unit-Werkzeugen auf Wikipedia]])
 
==Verstöße gegen das Prinzip „[[Programmierprinzipien#Benutze_Schnittstellen.2C_Make_Use_of_Interfaces|Benzutze Interfaces]]“==
In JavaScript gibt es keine [[Klasse]]n und damit erst recht keine [[Schnittstelle_(OOP)|Interfaces]] (= Klasse ohne Implementierung der Methoden).
Daher kann dieses Prinzip in JavaScript-Code höchstens auf Spezifikationsebene (d.h. in Objekt- und Klassendiagrammen),
aber nicht im Code selbst beachtet werden.
 
==Verstöße gegen das Prinzip „[[Programmierprinzipien#Benutze_Integrit.C3.A4tsbedingungen.2C_Make_Use_of_Integrity_Constraints.2C_Design_by_Contract.5B2.5D|Design by Contract]]“==
 
'''Da es für die MiniPong-Anwendung keinerlei formale Spezifikation gibt (zu der auch die Definition von Integritätsbedigungen zählt), verletzt diese Anwendung das Prinzip  „Design by Contract“ zu 100 Prozent.'''
 
Das Prinzip „Design by Contract“ wurde von Bertrand Meyer formuliert.<ref name="Meyer">{{Quelle|Meyer, B. (1997): Object-oriented Software Construction}}</ref>
Es besagt im Wesentlichen, dass jede [[Operation]] ([[Funktion]], [[Prozedur]], [[Methode]] ...) einen „Vertrag“ zu erfüllen hat: Wenn der Aufrufer sicherstellt, dass die
so genannten [[Vorbedingung]]en erfüllt sind, führt die Operation ihre Aufgabe ordnungsgemäß aus. Das heißt, sie garantiert, dass durch die
Ausführung des zugehörigen Codes keine [[Invariante]]n verletzt werden und dass nach Abschluss der Operation alle [[Nachbedingung]]en erfüllt sind.
 
Für jede Operation müssen also so genannte [[Integritätsbedingung]]en definiert werden:
 
* [[Vorbedingung]]en müssen erfüllt sein, bevor eine Operation ausgeführt werden kann.
* [[Nachbedingung]]en müssen erfüllt sein, nachdem eine Operation ausgeführt wurde.
* [[Invariante]]n müssten stets erfüllt sein.
 
Bei JavaScript gibt es noch nicht einmal die einfachste Form der Integritätsbedingungen, die [[Typisierung]] von Variablen und Parametern.
Jede Variable und jeder Parameter kann zu jedem Zeitpunkt einen [[Wert]] oder ein [[Objekt]] von einem anderen Typ haben.
Es ist z.B. jederzeit möglich, der Variablen <code>x</code> des Ball-Objekts eine Zeichenkette als Wert zuzuweisen und
damit einen Laufzeitfehler zu provozieren:
 
<source lang="javascript">
g_ball.x = "ganz links am Bildschirmrand";
</source>
 
Um dieses Problem abzumildern, muss man entweder
 
* geeignete Sicherheitsabfragen (z.B. mit Hilfe von Assertbefehlen wie <code>assert(isInteger(x))</code>) in den Code einfügen oder
* zahlreiche zusätzliche Testfälle generieren, die in einer typisierten Sprache überflüssig wären, oder
* ein Werkzeug verwenden, dass die Korrektheit einer JavaScript-Anwendung automatisch beweist oder widerlegt.
 
Die erste Lösung hat zwei gravierende Nachteile:
* Fehler werden erst zur Laufzeit und nicht zur Entwicklungszeit (Compilezeit) entdeckt.
* Assert-Befehle verlangsamen den Code, während Integritätsbedingungen, die von einem Compiler zur Compilezeit berücksichtigt werden, den Code sogar beschleunigen können.
 
Die zweite Lösung ist machbar, aber ziemlich aufwändig, wenn man alle Testfälle „von Hand“ generieren muss.
 
Die dritte Lösung wäre wunderbar geeignet, aber leider gibt es keine bislang keine derartigen Werkzeuge für JavaScript. Zumindest ist mir ([[Wolfgang Kowarschick]]) keines bekannt.
 
Für die erste Lösung des Problems bietet sich eine Kompromisslösung an. Man versieht den Code mit Assertbefehlen und anderen Integritätstests
und lässt diese später beim  Komprimieren des JavaScript-Codes automatisch entfernen. So bekommt man zum Entwicklungszeitpunkt zumindest bei
Laufzeittests aussagekräftige Fehlermeldungen, hat aber im Produktivbetrieb keine Leistungseinbußen.
 
==Verstöße gegen das  „[[Programmierprinzipien#Liskovsches_Substitutionsprinzip.5B3.5D.2C_LSP.2C_Ersetzbarkeitsprinzip.2C_Liskov_substitution_principle.5B4.5D|Liskovsches Substitutionsprinzip]]“==
 
Das „Liskovsche Substitutionsprinzip“ besagt, dass man eine geerbte Methode nicht so überschreiben darf, dass sich
das Objekt überraschend anders verhält, als es es täte, wenn die Methode nicht überschrieben worden wäre.
 
Da Vererbung in MiniPong gar nicht verwendet wird, wird gegen dieses Prinzip auch nicht verstoßen.
 
==Verstöße gegen das „[[Programmierprinzipien#Modularit.C3.A4t.2C_Modularity.2C_Teile_und_herrsche.2C_Divide_et_impera|Prinzip der Modularität]]==
'''<code>MiniPongCanvas03</code> wurde als  „Moloch“ realisiert'''. Von Modularisierung ist allenfalls am Rande etwas zu merken:
 
* Die Konstanten wurden in eine eigene Datei ausgelagert (aber liegen immer noch direkt im ROOT-Objekt der Anwendung).
* Die Ball- und Paddle-Methoden wurden in die das Ball- und das Schläger-Objekt integriert.
 
Hier besteht daher noch großes Verbesserungspotenzial. Es gibt immer noch viele zu viele globale Variablen (<code>g_...</code>) und zu viele globale Funktionen
(<code>f_...</code> sowie alle Observer-Funktionen). Bei sauberer Modularisierung des Codes lösen sich viele der zuvor beschriebenen
Probleme in Wohlgefallen auf.
 
'''Anmerkung:''' Verglichen mit einer früheren Version von <code>MiniPongCanvas03</code> ist der aktuelle Code allerdings schon richtig modular.
Man sehe sich mal
[http://glossar.hs-augsburg.de/beispiel/tutorium/html5_canvas/minipong/html5_canvas_minipong_03_moloch/WebContent/js/main.js <code>main.js</code>]
der [http://glossar.hs-augsburg.de/beispiel/tutorium/html5_canvas/minipong/html5_canvas_minipong_03_moloch/ Vorgänger-Version vom Wintersemester 2011] an.
Hier ist sämlicher Code in einer Datei enthalten (die Konstanten sind im Code verteilt), ein Ball-Objekt und ein Schläger-Objekt gibt es auch noch nicht.
 
=Quellen=
<references/>
<ol start = "2">
<li>{{Quelle|Braun, H. (2011): Webanimationen mit Canvas}}</li>
<li>{{Quelle|Kowarschick, W.: Multimedia-Programmierung}}</li>
</ol>
 
=Fortsetzung des Tutoriums=


Sie sollten nun [[HTML5-Tutorium: Canvas: MiniPong 04|Teil 4 des Tutoriums]] bearbeiten.
Kopieren Sie die Dateien des Projektes [https://glossar.hs-augsburg.de/beispiel/tutorium/es5/minipong/WK_MiniPong03/ MiniPong03]
in das neue Projekt.


==Quellen==
# {{Quelle|Braun, H. (2011): Webanimationen mit Canvas}}
# {{Quelle|Kowarschick, W.: Multimedia-Programmierung}}
[[Kategorie: HTML5-Tutorium: Canvas: MiniPong]][[Kategorie: HTML5-Beispiel]][[Kategorie:Kapitel:Multimedia-Programmierung:Beispiele]]
[[Kategorie: HTML5-Tutorium: Canvas: MiniPong]][[Kategorie: HTML5-Beispiel]][[Kategorie:Kapitel:Multimedia-Programmierung:Beispiele]]

Version vom 23. November 2016, 16:45 Uhr

Dieser Artikel erfüllt die GlossarWiki-Qualitätsanforderungen nur teilweise:

Korrektheit: 3
(zu größeren Teilen überprüft)
Umfang: 3
(einige wichtige Fakten fehlen)
Quellenangaben: 5
(vollständig vorhanden)
Quellenarten: 5
(ausgezeichnet)
Konformität: 5
(ausgezeichnet)

HTML-Tutorium: MiniPong

MiniPong: | Teil 1 | Teil 2 | Teil 3 | Teil 4 | Teil 5

Musterlösung: index.html (WK_MiniPong04 (SVN))

Ziel: Das fertige Spiel „MiniPong“

Datei:MiniPong04 Model Overview.png
Klassendiagramm

Im vierten Teil des Tutoriums wird eine funktionsfähige Version von MiniPong erstellt.


Neues Projekt anlegen

Legen Sie ein neues Projekt mit dem Namen „MiniPong04“ an.

Kopieren Sie die Dateien des Projektes MiniPong03 in das neue Projekt.

Quellen

  1. Braun (2011): Herbert Braun; Webanimationen mit Canvas; in: c't Webdesign; Band: 2011; Seite(n): 44–48; Verlag: Heise Zeitschriften Verlag; Adresse: Hannover; 2011; Quellengüte: 5 (Artikel)
  2. Kowarschick (MMProg): Wolfgang Kowarschick; Vorlesung „Multimedia-Programmierung“; Hochschule: Hochschule Augsburg; Adresse: Augsburg; Web-Link; 2018; Quellengüte: 3 (Vorlesung)