Tutorial „Image-Maps mit einfacher PHP-Unterstützung“

Copyright

Tutorial "Image-Maps mit einfacher PHP-Unterstützung" (C) 2011-2023 Stephan Kreutzer

Tutorial "Image-Maps mit einfacher PHP-Unterstützung" is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License version 3 or
any later version, as published by the Free Software Foundation.

Tutorial "Image-Maps mit einfacher PHP-Unterstützung" is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License 3 for more details.

You should have received a copy of the GNU Affero General Public License 3
along with Tutorial "Image-Maps mit einfacher PHP-Unterstützung". If not, see <http://www.gnu.org/licenses/>.

The complete source code is available at <https://gitlab.com/homebrew-browsergames/technologietutorials>.
          

Kurzes Beschreibungsvideo. Ausführlicheres Code Review.

Einführung

Weil reine Image-Maps keinerlei wirkliche Spiel-Funktionalität bieten, besteht die konsequente Fortsetzung logischerweise darin, die Image-Maps zunächst ziemlich direkt mit Logik zu versehen. Hierfür gibt es verschiedene Möglichkeiten; üblicherweise wird JavaScript oder PHP verwendet, die beteiligten Script-/Programmier-Sprachen sind aber von der konkreten Aufgabenstellung abhängig. Mit JavaScript wäre beispielsweise die Verzweigung zu unterschiedlichen Bildern je nach Tageszeit (Tag-/Nachtwechsel) sogar schon clientseitig realisierbar, weil aber insbesondere der Spielfortschritt oder ein Inventar in einer Datenbank serverseitig abgelegt werden will, behandelt dieses Tutorial hauptsächlich eine einfache PHP-Umsetzung. Auf diese Weise ist die virtuelle Welt der Image-Maps nicht mehr länger nur begehbar, sondern es wird echte Interaktivität erzielt.

Technisch gesehen soll die Verlinkung eines Image-Map-Bereiches nicht mehr unmittelbar auf die nächste HTML-Seite führen, sondern auf entsprechenden PHP-Code. Dieser wird aufgrund des Klicks eben nicht nur wiederum seine HTML-Image-Map zur Anzeige ausgeben – es wird ferner eine entsprechende Datenbank-Operation ausgeführt, um Werte und Wertveränderungen permanent zu speichern und ggf. die HTML-Ausgabe zu variieren.

Voraussetzungen

Die Beteiligung von PHP und einer Datenbank bringt es mit sich, dass das Browsergame nur noch auf einem Server betrieben werden kann. Die Programmierung und Entwicklung sollte dabei immer auf einer lokal installierten Server-Software auf dem eigenen Rechner durchgeführt werden. Ein fertiges, getestetes Ergebnis wird dann später auf einen Webserver hochgeladen, um über das Internet potentiellen Spielern zugänglich gemacht zu werden. Kenntnisse hinsichtlich Installation und Betrieb von Server-Software lokal und online sind nicht Gegenstand dieses Tutorials und werden vorausgesetzt. Es sollte berücksichtigt werden, dass zwar das lokale Experimentieren mit Browsergame-Code für jedermann vergleichsweise gefahrlos möglich ist, gleichzeitig aber im Web-Umfeld durch fehlerhaften Code oder falsche Konfiguration erhebliche Sicherheitslücken entstehen können, die im schlimmsten Fall sogar den gesamten Server lahm legen oder etwa zu strafrechtlichen Konsequenzen führen könnten. Deshalb besteht eine weitere wichtige Voraussetzung darin, sämtliche Versuche zu unterlassen, bei welchen man nicht genau weiß, was man eigentlich tut – entsprechendes Wissen ist vorher anzueignen, bevor dieses Tutorial Anwendung finden kann. Darüber hinaus ist der Code des Tutorials keineswegs vor Sicherheitslücken gefeit, sodass er vor dem Einsatz in einer Produktiv-Umgebung überprüft werden muss. Festgestellte Fehler sollen bitte gemeldet werden.

Der Tutorial-Code geht davon aus, dass der Inhalt der Datei $/src/database.sql (oder ein Äquivalent) auf dem Datenbank-System des Servers ausgeführt worden ist. Datenbank-Name, Datenbank-Benutzer, Host und Passwort müssen dabei angepasst und in $/ftp/database.inc.php vermerkt werden.

Spielkonzept

Ganz zu Anfang steht immer die Konzeptions-Phase, so auch im Tutorial. Die beschriebene Technologie eröffnet ein breites Spektrum an Umsetzungsmöglichkeiten diverser Spiele-Genres, weshalb von vornherein festgelegt werden muss, worin das Spielziel besteht, wie es gesteuert wird und welche Philosophien bei der Gestaltung verfolgt werden sollen. Das Tutorial-Spiel ist im Mittelalter angesiedelt. Es muss ein Schatz gefunden werden, was in Form einer Art Adventure realisiert werden soll. Um das Ziel zu erreichen, müssen Gegenstände gesammelt werden. Während jeder Spieler seine Gegenstände selbst verwaltet, teilen sich alle Spieler die Spielwelt, es handelt sich also in eingeschränktem Umfang um ein Multiplayer-Spiel. Es ist nicht möglich, mit anderen Spielern in Kontakt zu treten, ihre jeweiligen Handlungen beeinflussen das Spielgeschehen nur mittelbar. Es gibt keine grafischen Steuerelemente, vielmehr wird jede Aktion durch einen Klick direkt auf die Ansichtsoberfläche ausgelöst.
Das Detail-Konzept (könnte Aufgabe eines Spiel-Designers sein) sieht vor, dass von einem Baum pro Tag ein Apfel gesammelt werden kann. Hat er erste Spieler den Apfel genommen, müssen die anderen Spieler einen Tag warten (nicht 24h, sondern mit Wechsel um 0:00). Jeder Spieler kann beliebig viele Äpfel sammeln. Es gibt im Spiel einen NPC, der Hunger hat. Wenn man ihm 3 Äpfel gibt, bietet er zum Dank einen Schlüssel an. Mit diesem kann man die Burg aufschließen, hat damit den Schatz gefunden und das Spiel gewonnen (individuell, nicht insgesamt).

Grundlagen

Da die Interaktion im Spiel permanenter Natur sein soll, ist die Art der Datenbank-Nutzung von Interesse, wobei das Spielkonzept deren Aufbau und die Art ihrer Nutzung vorgibt. Um die Verbindungs-Informationen für den Datenbank-Zugriff nicht über sämtlichen PHP-Code zu verteilen, wird zur Vereinfachung jeweils die $/src/database.inc.php eingebunden, was in der Variable namens $mysql_connection das Connection-Handle zur MySQL-Datenbank bereitstellt.
Laut Konzept werden Gegenstände getrennt nach Spielern verwaltet, also wird ein Login benötigt. In den Projektdateien wird diese Aufgabe von $/ftp/index.php übernommen, einem primitiven Beispiel-Login-Script. Der Login ermittelt zunächst, ob ein Benutzer mit dem eingegebenen Namen schon existiert. Wenn nicht, wird er erstmalig angelegt. Ebenso werden seine Spieldaten initialisiert. Bei Erfolg (der wiederholten Anmeldung oder der Erst-„Registrierung“) wird die Session gesetzt, was alle weiteren Spiel-Seiten abprüfen werden, ob selbige beim Besucher vorliegt und er spielen darf. $/ftp/index.php leitet nach weiter, der Start-Ansicht des Spiels. Da weder Passwort noch E-Mail-Adresse von der Registrierung/Anmeldung verlangt werden, genügt es, den Namen eines anderen Spielers zu erraten, um mit einem fremden Account spielen zu können. Der Login ist daher ungeeignet für reale Bedingungen, aber für das Tutorial soll dieses simple Verfahren vorerst ausreichen. Dementsprechend einfach ist die Datenbank-Tabelle users gehalten, die lediglich eine Benutzer-ID und den Spielernamen entgegennimmt.

Programmierung

Alle Code-Dateien zur Darstellung der Spieloberfläche sind ähnlich aufgebaut. $/ftp/ansicht_1.php prüft erst die Session, gefolgt von einer Datenbank-Abfrage, die über interne Logik und Ansicht entscheidet. Bei $/ftp/ansicht_1.php besteht die Logik einzig und allein darin, das Burgtor „aufzuschließen“, wenn der Spieler den Burgschlüssel besitzt (d.h. ihm eine vorher unbekannte Navigationsmöglichkeit zu eröffnen – da die Navigation im Zuständigkeitsbereich der Oberfläche liegt, ist kein eigener Logik-Bereich nötig). Um herauszufinden, ob der Spieler den Schlüssel hat, wird sein Inventar abgefragt:

Auszug aus $/ftp/ansicht_1.php

              $inventar = mysqli_query($mysql_connection,
                                       "SELECT `burgschluessel`\n".
                                       "FROM `inventory`\n".
                                       "WHERE `user_id`=".$_SESSION['user_id']."\n");
            

Bei der ersten Anmeldung wurde die Tabelle inventory wie folgt initialisiert:

Auszug aus $/ftp/index.php

              mysqli_query($mysql_connection,
                           "INSERT INTO `inventory` (`user_id`,\n".
                           "    `aepfel`,\n".
                           "    `burgschluessel`)\n".
                           "VALUES (".$id.",\n".
                           "    0,\n".
                           "    0)\n");
            

$id ist hierbei identisch mit $_SESSION['user_id']. Damit wird gleich nach der ersten Anmeldung der Wert in $inventar['burgschluessel'] == 0 sein, die Anweisung

Auszug aus $/ftp/ansicht_1.php

              if ($inventar['burgschluessel'] == 2)
              {
                  echo "          <area shape=\"poly\" coords=\"635,325,635,288,653,281,664,281,681,290,681,331,635,325\" href=\"ansicht_4.php\" alt=\"Burgtor\" title=\"Schlüssel benutzen\" />\n";
              }
            

übersprungen werden. Weil PHP-Dateien auf dem Server liegen und stets eine generierte Ausgabe zurückliefern, kann der Benutzer weder Position noch Verweis des „Burgtors“ aus dem empfangenen HTML-Quellcode auslesen. Er könnte höchstens ansicht_4.php erraten, jedoch ist dort eine entsprechende Sicherung eingebaut. Die Abprüfung auf == 2 hat die besondere Bewandtnis, dass das Inventar (inventory) das Attribut burgschluessel nicht als Menge, sondern als Zustand aufnehmen soll. 0 bedeutet „Schlüssel nicht bekommen“, 1 steht für „Schlüssel nicht genommen“ und 2 für „Schlüssel befindet sich in Besitz des Spielers“.

$/ftp/ansicht_2.php ist ganz ähnlich aufgebaut: es wird mittels

Auszug aus $/ftp/ansicht_2.php

              $baum = mysqli_query($mysql_connection,
                                   "SELECT `zuletzt`\n".
                                   "FROM `timer`\n".
                                   "WHERE `name` LIKE \"baum\"");
            

die Datenbank abgefragt, um daraus Logik und Ansicht zu entwickeln. Die Logik findet nur dann Anwendung, wenn laut $_GET eine „Navigation“/Interaktion vorgenommen wurde. Wenn das nicht der Fall ist, muss eine Standard-Ansicht angeboten werden. Über die Variable $variante wird gesteuert, welche Ansicht jeweils passt. Der Wert 1 für „kein Apfel“ wird gleich beim beim ersten Aufruf durch den ersten Spieler zu einem zufälligen Wert rand(2, 4) korrigiert, da $baum['zuletzt'] === NULL zutrifft. Der Hintergrund ist, dass der Baum, welchen sich alle Spieler teilen, in der Datenbank nicht im Inventar oder einer vergleichbaren Tabelle verwaltet werden kann, sondern nur einmal für alle Spieler angelegt wird. Gemäß $/src/database.sql ist hierfür die Sonder-Tabelle timer vorgesehen, welche nicht bei jeder Spieler-Erstanmeldung neu initialisiert werden darf, sondern einmalig und übergreifend eingerichtet werden muss:

Auszug aus $/src/database.sql

              CREATE TABLE IF NOT EXISTS `timer` (
                `name` varchar(255) COLLATE utf8_bin NOT NULL,
                `zuletzt` date DEFAULT NULL
              ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

              INSERT INTO `timer` (`name`, `zuletzt`)
              VALUES ("baum", NULL);
            

2, 3 und 4 für $variante sorgen dafür, dass der Apfel zufällig an unterschiedlichen Positionen erscheint. Dementsprechend variieren auch die <area />-Angaben der Image-Map. Im href-Attribut zeigt sich dann, wie die interne Logik bei einem Klick als Folge des HTTP-GET-Parameters action=get_apple ausgelöst wird.
Die Logik beginnt mit einer Abprüfung auf Betrugsversuche. Es kann unmöglich das $baum['zuletzt']-Datum aus der Datenbank gleich dem aktuellen Datum zusammen mit action=get_apple zutreffen, denn wenn der Apfel im else-Zweig erfolgreich geholt wird, wird das Datum bei $baum['zuletzt'] = date("Y-m-d") gesetzt und hat somit später 1 für $variante zur Folge, action=get_apple wird fortan nicht mehr eingefügt. Dass die Datums-Vergleiche so einfach funktionieren, liegt an der geschickten Ausnutzung des ISO-8601-Datumsformats und der PHP-String-Eigenschaften. Hier der Überblick über den Ablauf:

Ablaufdiagramm zum Holen des Apfels.

$/ftp/ansicht_3.php ist nach ähnlichem Prinzip aufgebaut. Es stehen je nach Situation die unterschiedlichen Interaktions-Optionen give_apples und get_key zur Verfügung. Eine Besonderheit besteht darin, dass der Schlüssel bei give_apples nicht automatisch genommen (d.h. nicht automatisch in das Inventar eingetragen) wird, sondern ein Zwischenzustand 1 für inventory.burgschluessel vermerkt wird. Dieser hat zwar zunächst keine unmittelbare Auswirkung, denn wenn der Schlüssel nicht genommen wird, muss man erneut 3 Äpfel geben. Bei get_key lassen sich so aber Betrugsversuche identifizieren, falls der Spieler nicht mindestens einmal 3 Äpfel gegeben hat.

Wie $inventar['burgschluessel'] == 2 in $/ftp/ansicht_1.php das „Burgtor aufgeschlossen“ (also die Navigation zu $/ftp/ansicht_4.php ermöglicht) hat, muss $/ftp/ansicht_4.php mit derselben Prüfung sicherstellen, dass die Ansicht nicht unerlaubt zur Anzeige gelangt. Während die Namen der PHP-Dateien ruhig geraten und manuell angebrowsed werden dürfen (weil dort jeweils Sicherungen eingebaut sind), ist eine namens-analoge PNG-Benennung nicht sehr ratsam.

Fazit

Obwohl das Tutorial per Definition auf eine konkrete Anwendung beschränkt ist, wird doch anhand der Beispiele deutlich, welches Potential mithilfe dieser einfachen Technik realisiert werden kann. Natürlich steht es jedem Programmierer frei, Funktionalität in Bibliotheken zu kapseln oder ein Framework für die Generierung der einzelnen Seiten zu implementieren. Abhängig vom Design kann die Datenbank komplexe Benutzer-Interaktionen abbilden, welche sich in kreativ gestalteten Ansichten wiederspiegeln. Denkbar wären Kaufläden, Farmen, Auktionshäuser, Post, NPCs mit unterschiedlichem Verhalten, Berufe, das Sammeln von Items/Rohstoffen, usw. Evtl. können die Werte in der Datenbank bei jeder Anmeldung automatisch manipuliert werden, um z.B. pro Tag seit der letzten Anmeldung den Kontostand des Benutzers zu verringern.
Solche Überlegungen sind andererseits aber auch mit Vorsicht zu genießen, denn jede „Ansichts-Variante“ und jede Interaktions-Möglichkeit muss mühevoll einzeln erstellt werden. Ab einem gewissen Punkt lohnt es sich vielleicht eher, auf eine generische Technologie zu setzen, anstatt große Spiel-Teile mit der hier vorgestellten Methode umzusetzen.