Einführung in die Programmierung mit HTML und PHP
Einführung in das Konzept eines Multiplayer-Spieles
Zentrale - Lehrgänge - Einführung HTML+PHP - Multiplayer-Konzept

Lehrziel

Jetzt wollen wir die Grundlagen kennenlernen, mit denen man ein Spiel auf einem Server so organisiert, daß mehrere unabhängige Besucher gleichzeitig und zusammen gegeneinander spielen können.

Im ersten Teil (in dieser Lektion) werden wir nur genau zwei Spieler gegeneinander antreten lassen.
Im zweiten Teil (nächste Lektion) erweitern wir das noch ein bißchen, so daß sich ein ganzer Haufen von Spielern an verschiedenen Spielbrettern zusammenfinden kann.

Das heutige Ziel läßt sich am besten mit bildlicher Darstellung verdeutlichen. Wie schauen uns das als Animation an... (schalten Sie bitte die Video-Auflösung auf "480" hoch, damit Sie die Schrift erkennen können!)


Die Sache mag vielleicht auf den ersten Blick etwas irritierend aussehen. Besonders, wenn man als Anfänger das Denken in Abläufen noch nicht verinnerlicht hat. Wir werden uns das Zeug aber wieder schrittweise vornehmen und in sehr einfache, elementare Teile zerlegt kennenlernen. Zum Schluß brauchen Sie sie nur noch in Kombination zu betrachten (auch daran gewöhnt man sich)...
Die meiste Arbeit wird mal wieder machen, das ganze hübsch zu bekommen...

Das Prinzip des Zusammenspielens (Smalltalk zu den Zielstellungen des Tages)
Die Speicherung des Spielzustands (PHP-Reorganisation und Systematisierung für Multiplayer)
An- und Abmeldung und Spielende (Ausbau des Steuer-Panels, Begriff der "Sitzung")
Weitere Rollen- und Rechteverteilung (Allgemeine Unterscheidung von Spielleiter, Teilnehmer, Zugucker)
Passives Aktualisieren der Anzeige (adaptives Redirect nach Timeout)
Schutz der Spielerkennung gegen Hacking (Session-Diebstahl und HTTPS)

Das Prinzip des Zusammenspielens

Gemeinsamer Spiel-Zustand
Das Wichtigste am Zusammenspielen - das sollte aus der Animation erkennbar sein - ist, daß beide Spieler einen gemeinsamen Spiel-Zustand verwenden. Der Spielzustand wird bisher bei unserem TicTacToe im Browser jedes einzelnen Spielers gespeichert. Das darf auch durchaus weiterhin der Fall sein (eine Kopie mehr schadet nicht). Aber zusätzlich muß der Spielzustand auf dem Server zwischengespeichert werden. Und es muß organisiert werden, daß jeder Spieler diesen gespeicherten Zustand serviert bekommt, falls ein solcher bereits existiert. Dieses werden wir uns gleich im nächsten Abschnitt vornehmen.

Bei der Gelegenheit ist heute dringend eine Systematisierung angesagt: Wir hatten in der letzten Lektion das Speichern von einem Teil des Spielzustands (nämlich der Spieler-Daten) vom Prinzip her kennengelernt. Und wir hatten zum Schluß schon mal in Aussicht gestellt, daß man das ja auch einfach verallgemeinern könnte, indem man einfach restlos sämtliche Daten rund um's Spiel (Spielfeld, Spieler, Dranseiender, eventuelle Spielregeln und andere Details) in eine große Datenstruktur packt und diese dann genau wie gehabt im Block speichern läßt. Das machen wir ebenfalls im nächsten Abschnitt.
Bessere Unterscheidung der Spielphasen
Wir hatten bisher im Solomodus zwar schon vorgesehen, daß das Spiel extra gestartet werden muß, aber für die Vorbereitung wie das Eintragen der Spielernamen keine extra Aktionen vorgesehen. Bislang ist es so, daß das Eintragen von Namen immer erfolgt, wenn das Spiel gestartet oder ein Stein gesetzt wird. Für ein ordentliches Zusammenspiel taugt das nicht: Da muß das Eintragen der Namen und eine eventuelle Auswahl von Spielregeln vor Beginn des Spieles abschließend erfolgen, während diese Parameter mitten im Spiel konstant bleiben sollten.

Wir werden einfach extra Schaltknöpfe einführen, die abhängig von der Spielphase benutzt werden können (oder eben auch nicht), und über diese Schalter Aktionen in der Spielvorbereitung auslösen lassen.

Außerdem wollen wir ein Spiel beenden (und damit unser zunächst noch einziges Spielbrett freigeben) können. Wir wollen ja nicht, daß nach einmaligem Aufstellen eines Spiels dieses bis zum Ende des Universums nur von genau den beiden zuerst angemeldeten Spielern gespielt werden darf. Dazu führen wir einerseits einen weiteren Schaltknopf ein, mit dem man ein Spiel gezielt wieder freigeben kann. Zum anderen ergänzen wir dieses durch ein Konzept der automatischen Freigabe nach einem gewissen "Timeout": Wenn die Spieler eine gewisse Zeit in einem laufenden Spiel keinen Zug mehr gemacht haben, darf das Spiel von nachrückenden Spielern übernommen und neu eingerichtet werden.

Auf diese Weise haben wir gleich eine Vorbereitung für die Massive-Multiplayer-Variante der nächsten Lektion geschaffen, in der es beliebig viele Spielbretter geben wird, die aber auch irgendwie einem Müllbeseitigungs-Verfahren unterworfen werden müssen.
Spielleiter, Teilnehmer und Zugucker
Sie kennen es sicherlich schon von Kindesbeinen an - aus dem Kindergarten oder spätestens der Schule - daß es äußerst abstoßend ist, wenn fremde Leute einem in eine erstmal begonnene Sache, in die Sie bereits Energie hineingesteckt und sich dafür begeistert haben, hineinpfuschen oder gar die Sache aus der Hand reißen. Im Internet ist dieses Problem in voller Blüte vor ihrer Nase - wenn Sie keine Maßnahmen ergreifen, um die Besucher Ihrer Spielwiese (oder Geschäftsanwendung - was auch immer) davor zu schützen! Der Unterschied zu Ihrer Kindheit ist lediglich, daß Sie es im Internet mit der gesamten Menschheit auf einmal zu tun haben - sobald Ihre Webanwendung ein wenig Popularität erlangt haben sollte. Und nebenbei auch noch mit Bots, Hackern, Stalkern und sonstigen Gaunern. Die Freiheit des Internets ist eben auch die Freiheit der Andersdenkenden. Und zu denen zählen Dummlinge und Böslinge nun mal immer auch mit dazu!

Wir werden organisieren, daß Plätze an unserem Spielbrett besetzt werden können und daß die dort "sitzenden" Spieler durch nachfolgende Besucher nicht mehr gestört, sondern bestenfalls noch beobachtet werden können. Außerdem werden wir eine Unterscheidung zwischen dem Spieleröffner als Spielleiter und einem später hinzutretenden Mitspieler einrichten.

Jeder sich "anmeldende" Spieler wird dazu vom Server eine eindeutige Kennung zugewiesen kriegen und Aktionen eines (beliebigen) Besuchers (Zuguckers / Hackers / Stalkers / Bots) werden nur genau dann akzeptiert werden, wenn dieser Besucher genau jene Kennung beim Server einreicht, die für einen der bereits sitzenden Spieler ausgegeben wurde. Das Prinzip sollte ebenfalls aus der eingangs gezeigten Animation hinreichend hervorgehen.

Im allgemeinen (in komplexeren Anwendungen) nennt man Konzepte dieser Art:
  • Benutzer-Identifizierung
  • Rollen-Verteilung
  • Rechte-Verteilung
  • Zugriffs-Schutz
Alle diese Aspekte spielen gewöhnlich zusammen.
Aktualisieren der Anzeige
Was ebenfalls aus der Animation erkennbar sein sollte, ist das einfache Verfahren der passiven Aktualisierung des Spielzustands und dessen Anzeige auf Seite der Teilnehmer: Jeder Teilnehmer muß von sich aus aktiv werden, um den aktuellen (und also eventuell vom Spielpartner veränderten) Spielzustand vom Server abzuholen.

Das läßt durchaus zu wünschen übrig: Wenn ein Spieler etwas auf seiner Seite verändert, wird dadurch nicht automatisch auch die Anzeige auf der Seite des anderen Spielers aktualisiert. Der muß erst selbst eine eventuelle Aktualisierung nachfragen.

Die Alternative dazu wäre ein aus dem Standpunkt des Teilnehmer-Browsers ERZWUNGENES Nachladen der Spielanzeige auf Entschluß des Servers hin. Wo also der Server derjenige ist, der festlegt, wann was im Browser eines Besuchers angezeigt zu werden hat. Sie hören eventuell heraus, daß dieses Prinzip durchaus auch eine problematische Note in sich tragen kann: Was wenn das jeder Server im Internet könnte, von dem SIE das GAR NICHT WOLLEN?! Sie bräuchten dafür also zumindest ein ausgeklügeltes System der Steuerung, mit dem Sie als Browserbesitzer und Internetsurfer sich gegen das Aufdrängeln von zum Beispiel unerwünschter Werbung und Malware zur Wehr setzen könnten. Und zwar VOR dem Aktivwerden eventueller amoklaufender Server!

Aus eben jenem Grund ist das Internet von seinen Anfängen an von vornherein als ein Netz von PASSIVEN Dienstangeboten ausgelegt worden: Normalerweise ist für jede Aktion im Internet das AKTIV-werden der Person notwendig, die AUS EIGENEM WILLEN heraus etwas aus dem Netz zu holen beabsichtigt.

Es gibt durchaus die Möglichkeit, dieses Prinzip der "passiven Dienste / aktiven Nutzer" umzukehren. Das setzt aber sogenannte "aktive Komponenten" auf Seite des Browsers voraus - also Programme, die Abfragen im Netz automatisch wiederholt tätigen, ohne daß der Mensch vorm Browser selbst aktiv werden muß.

Daß wir heute das passive Verfahren verwenden hat damit zu tun, daß wir die Materie auf Einsteigerniveau belassen wollen: Wir wollen NICHT noch extra eine der Programmiersprachen kennenlernen, die notwendige Voraussetzung für das Erstellen solcher Programme im Browser wären!

Außerdem hat diese Beschränkung noch einen tiefergehenden, guten Grund: Anfänger auf dem Gebiet der Serverprogrammierung begehen regelmäßig massiv Fehler, die von Hackern (der böswilligen Sorte) rücksichtslos und konsequent ausgenutzt werden. Wenn aufgrund solcher Fehler ein Server mit Malware verseucht ist, wird diese Malware üblicherweise auch jedem Serverbesucher "angeboten". Wenn DAS dann zusammenkommt mit Besuchern, die Javascript oder andere "aktive Komponenten" auf ihrem Browser aktiviert haben, ist der Malware-Verbreitung heutzutage in der Regel jede Tür geöffnet!

SIE sollen HIER eben genau VERHINDERN lernen, es soweit kommen zu lassen. Und dazu gehört als erstes, daß Sie lernen, wie man Multiplayer-Games einrichtet, OHNE Javascript oder irgendwelche anderen gefährlichen Sachen im Browser des Besuchers zwingend vorauszusetzen. So daß Sie Ihren Besuchern überhaupt die CHANCE lassen, sich gegen Malware zu schützen, und zwar OHNE auf die Spaß machenden Sachen Ihres Angebots verzichten zu müssen.

Nebenbei gilt das Prinzip natürlich nicht nur für Computerspiele, sondern für jede Art von Geschäftslogik im Internet. Facebook und die restlos überall anzutreffenden Werbeeinblendungen sind die prägnantesten Vorreiter in Sachen Malwareverbreitung - und zwar eben in Verbindung mit der Gängelung der Surfer zur globalen Einschaltung von Javascript. Sie müssen sich nicht an diesem traurigen Zirkus beteiligen! Sie lernen das hier besser zu machen!

Wenn Sie dereinst in Serverprogrammierung und -administration so gut geworden sind, daß Sie mit guten Gewissen davon ausgehen können, daß Ihr Server zum einen nur äußerst schwer, zum anderen bestenfalls äußerst kurz von Malware befallen sein kann, DANN wird der Zeitpunkt gekommen sein, wo Sie sich das Betreiben von Webanwendungen mit aktiven Komponenten leisten können. Bis dahin werden Sie aber auch (hoffentlich) zu einer verantwortungsvollen Einstellung gegenüber Internet-Teilnehmern im allgemeinen gefunden haben, dank derer Sie bezüglich aktiven Komponenten im allgemeinen gesunde Zurückhaltung üben!

Es gibt GENAU EINE Ausnahme, die uns das Nachladen von Inhalten ermöglicht, aber nicht gleichzeitig zum Aktivieren jener gefährlichen "aktiven Komponenten" und zum Erlernen von deren Programmierung zwingt. Und DIE werden wir in unserem Begleitprojekt anwenden: Wenn die "Aktivität" des Browsers ausschließlich darin besteht, einen Seiteninhalt automatisch nach einer gewissen Zeit aktualisieren zu lassen. Dafür gibt es ein extra nur für diesen einen Zweck vorgesehenes spezielles Element im HTTP-Protokoll und ein Gegenstück dafür in HTML. Dieses letztere lernen wir kennen und nutzen.
Schutz gegen Hacking zum Dritten
Ganz zum Schluß werden wir wieder mal eine Hacking-Runde einlegen, wo der Diebstahl von diesen Kennungen geprobt wird, die wir zur Identifizierung in unserem einfachen Rollensystem einsetzen.

Zwar stellt unsere einfache Anwendung hier noch nichts dar, was für gewöhnliche Angreifer ein lohnenswertes Ziel wäre. Aber mit dem Lernen ist es immer so, daß es genau dort am leichtesten fällt, wo die Beispiele am trivialsten sind, während man ernsthafte Anwendungen erst dann überhaupt begreifen kann, wenn man das Prinzip an trivial einfachen Beispielen beherrschen gelernt hat.

Ähnlich wie die Verschlüsselung am Ende der vorherigen Lektion ein Overkill im Rahmen unseres trivialen Begleitprojektes war, wird die Maßnahme zum Schutz unserer Spielerkennung in DIESEM KONKRETEN Fall einen Overkill darstellen. Aber sobald Sie auch nur geringfügig ernsthaftere Anwendungen betreiben wollen, brauchen Sie zwingend eine ordentliche Absicherung. Und das warum und wie sehen wir uns an...

Die Speicherung des Spielzustands

Damit mehrere Personen ein Ding gemeinsam benutzen können, muß dieses Ding für alle Personen ein und dasselbe sein. Versteht sich irgendwie von selbst, oder?
Für unser TicTacToe bedeutet das, daß die Daten, die den Spielzustand darstellen, an einer zentralen Stelle - auf dem Server, auf dem das Spiel läuft - gespeichert werden müssen. Und zwar so, daß sie für alle Spieler gleichermaßen sichtbar sind.

Es gibt verschiedene Wege, so eine Datenspeicherung zu realisieren:
  • Ablegen in einer Datei
  • Ablegen in einer Datenbank
  • Direkter Austausch zwischen den Spielern
Für unsere Zwecke wäre eine Datenbank vollkommener Overkill. Wir müßten dazu zudem erstmal das Denken in Datenbank-Philosophie kennenlernen und außerdem noch eine extra Programmiersprache (Datenbankabfragesprache). Das wollen wir nicht für so eine lächerliche Kleinigkeit!
Der direkte Datenaustausch zwischen Spielern würde aktive Komponenten in den Browsern erfordern, was ich Ihnen aus verschiedenen bereits diskutierten Gründen vorläufig gerade explizit abrate - bis Sie sich mit Problemen der Netzwerk-Rechnerei und besonders deren Mißbrauch gründlich vertraut gemacht haben.

Bleibt die Speicherung in einer Datei. DIE ist unter PHP das leichteste, was Sie sich vorstellen können: Noch leichter als das echo-en in den HTML-Formulartext, wo wir uns ja noch um das HTML-Escaping hatten kümmern müssen und es dennoch bereits auf Anfängerniveau hingekriegt hatten.
Vom Prinzip her funktioniert das in PHP so:
file_put_contents($dateiname,$daten);   // zum Speichern
$daten = file_get_contents($dateiname); // zum Laden
...wobei Sie für die hier als Beispiel angedeuteten Variablen $dateiname und $daten natürlich Ihre eigenen Konstruktionen einsetzen - was immer das auch nach Ihren Ideen sein sollen täte.

Sinnvolle Dateiablage
Wobei wir uns hier durchaus einen Kopf machen dürfen über eine sinnvolle Platzierung der Datei in den unendlichen Tiefen des Dateisystems Ihres Server-Rechners: Wenn eine solche Datei Daten enthält, die in der Öffentlichkeit sichtbar sein sollen, darf sie ohne weiteres neben den PHP-Dateien abgelegt werden, aus denen heraus sie benutzt wird. Falls nicht, müßten Sie sie außerhalb des Dateibaumes ablegen, den der Server ausliefern soll - also außerhalb des "root"-directories des Apache-Servers in Ihrem XAMPP.

Da wir keine vertraulichen Daten haben, legen wir die Datei direkt neben dem PHP-Script ab.

Der Dateiname, der hier verwendet wird, bezieht sich auf das Dateisystem des Rechners, also das, was Sie in einem Dateimanager wie dem Windows-Explorer zu Gesicht bekommen, nicht auf die vom Webserver auszuliefernden Ressourcen. Falls Sie "relative" Dateinamen verwenden, ist das egal. Falls Sie "absolute" Dateinamen verwenden, kritisch (da müssen Sie aufpassen)! Falls Sie sich mit diesen Begriffen noch nicht auseinandergesetzt hatten, finden Sie im Internet viele sehr leicht verständliche Einführungen in das Konzept von Dateisystemen und Dateinamen, zum Beispiel hier: meinbeepweb.beepworld.de: relative und absolute Dateinamen

Relativitäts-Theorie
Wobei mit den Begriffen "absolut" und "relativ" allerdings noch etliche verschiedene Varianten bzw. Aspekte gemeint sein können. Am besten sehen wir uns das mal anhand eines kleinen Beispiels an. Ich nehme dazu die Datei, die Sie gerade im Browser vor der Nase haben, wenn Sie das hier lesen... (und zur Erinnerung bezüglich Pfaden im Internet sei nochmal zurück verwiesen auf die Lektion 4 - Daten zum Server übertragen...)
// ============================================================
// Sicht auf die Ressource aus einem Web-Standpunkt heraus... *
// ============================================================

// absoluter Ressourcen-Identifikator (mit Query) im Internet...
$_SERVER["SCRIPT_URI"].'?'.$_SERVER['QUERY_STRING']:
    "http://harryboeck.dyndns.org/Bildungswerk/?thema=A-PHP-HTML_7"

// absoluter Ressourcenname (ohne Query) im Internet...
$_SERVER["SCRIPT_URI"]:
    "http://harryboeck.dyndns.org/Bildungswerk/"

// absoluter Ressourcen-Identifikator (mit Query) im Webserver,
// relativ gegenüber dem Internet...
$_SERVER["REQUEST_URI"]:
    "/Bildungswerk/?thema=A-PHP-HTML_7"

// ============================================================
// Abbildung der Web-Ressource auf eine konkrete Datei        *
// im Webserver (in diesem Fall auf ein PHP-Script)           *
// ============================================================

// absoluter Dateiname im Webserver-Dateibaum,
// relativ gegenüber dem Internet bzw. dem Webserver-Root-Directory...
$_SERVER['SCRIPT_NAME']:
    "/Bildungswerk/index.php"

// absoluter Dateiname im Operationssystem...
$_SERVER['SCRIPT_FILENAME']:
    "D:/Doc/public/Bildungswerk/index.php"

// absoluter Dateiname auf dem Datenträger,
// relativ gegenüber dem Operationssystem...
substr($_SERVER['SCRIPT_FILENAME'],2):
    "/Doc/public/Bildungswerk/index.php"

// relativerrelativ in jeder Hinsicht, mithin "absolut relativ" - Sie verstehen hoffentlich, wie relativ die Begriffe "absolut" und "relativ" sind?! Dateiname:
pathinfo($_SERVER['SCRIPT_FILENAME'],PATHINFO_BASENAME):
    "index.php"

Da wir das Prinzip "KISS"Keep It Stupid Simple anwenden, benutzen wir natürlich einen relativen Dateinamen, zum Beispiel "tictactoe.bin", was bedeutet, daß eine Datei mit eben diesem Namen unmittelbar neben Ihrem TicTacToe-Spiel-Script zu liegen kommen wird. (Sie DÜRFEN diese Datei natürlich auch woanders hin verfrachten - zum Beispiel würde sich ein extra Unterverzeichnis anbieten. In der nächsten Lektion werden wir das auch dahingehend modifizieren.)

Die "Endung" der Datei dürfen Sie natürlich frei wählen; es bietet sich aber an, sie so zu setzen, daß SIE SELBST durch IHR EIGENES Betriebssystem eine bestmögliche Unterstützung beim Öffnen und Betrachten/Bearbeiten einer Datei bekommen. Unter Windows wird dies an der "Endung" der Datei festgemacht, also passen Sie sinnvollerweise die "Endung" der Datei dem Inhalt derselben an!
Das führt uns zur nächsten Überlegung...
Sinnvoller Dateiinhalt / Dateityp
Wir haben bereits mehrere verschiedene Kodierungen von strukturierten Daten im Zusammenhang mit der Speicherung in einem Formular unter dem Begriff "Serialisierung" kennengelernt. Dieselben Freiheiten und noch viel mehr stehen uns natürlich auch beim Speichern in Dateien offen. Das Prinzip wird gleich bleiben, daß wir die ursprünglich strukturierten Daten mittels einer "Serialisierung" zum Speichern in einen linearen Datenblock umwandeln und beim Laden das ganze wieder umkehren.

Wobei wir uns aber alle Maßnahmen zum Schutz gegen Datenveränderung klemmen können, weil die Daten, die wir auf dem Server speichern, den Server nie verlassen, um durch die Hände potentieller Hacker und Netzwerkprotokolle verunstaltet werden zu können. Speziell die Kopfstände mit Verschlüsselung und base64-Enkoding können wir uns sparen.

Es bleibt also übrig, Serialisierungsfunktionen einzusetzen. Von denen schlage ich Ihnen gleich die erste der diskutierten Formen vor: serialize / unserialize! Lassen Sie sich aber nicht davon abhalten, auch mal mit den anderen Varianten zu experimentieren!

Wir werden also zunächst ein Konstrukt der folgenden Art anstreben:
$dateiname = 'tictactoe.bin';

file_put_contents($dateiname, serialize($daten));    // zum Speichern
$daten = unserialize(file_get_contents($dateiname)); // zum Laden

Das Format der Daten in dieser Form von Serialisierung (mit "serialize") ist ein sehr spezielles, für das es außerhalb von PHP keine Verwendung und daher auch keine allgemeinen Dateibetrachter oder -editoren gibt. Deshalb wäre es sinnvoll, dieses Format als "Binärformat" aufzufassen, was man allgemein für Daten verwendet, die in einem nicht allgemein verwendbaren und betrachtbaren Format vorliegen. Deshalb mein Vorschlag für Sie, der Datei die Endung ".bin" zu verpassen!
Alle Daten in einer Datenstruktur zusammenfassen
Jetzt fehlt noch für maximale Vereinfachung nicht nur hier, sondern auch für das Ablegen der Daten im Browser (was wir aus gutem Grund - der noch weiter unten diskutiert wird - beibehalten werden), daß diese in einer großen Datenstruktur zusammengefaßt werden anstatt wie momentan historisch gewachsen verstreut herumzuliegen. Nichts leichter als das: Wir wissen ja inzwischen, wie das geht (mit Arrays)...
Da die Grundlagen inzwischen vertraut sein dürften, ersparen wir uns großartige Theorie und ziehen das Ganze als Übung durch...

Strukturbildung
Unsere bisherige inkrementell gewachsene Konstruktion lautete (zudem inzwischen ein wenig verstreut):
$spielfeld = '000000000';
$erster = '1';
$dran = '';
$spieler = array
(
    '1' => neuer_spieler('Schwarz'),
    '2' => neuer_spieler('Weiß'   ),
);

Daraus machen wir jetzt den Block:
//===========================================
// Datenstrukturen                          *
//===========================================

$spiel = array
(
    'spielfeld' => '000000000',
    'erster'    => '1',
    'dran'      => '',
    'zugnr'     => 0,       // habe ich mal nebenbei zur Vereinfachung aufgenommen
    'spieler'   => array
    (
        '1' => neuer_spieler('Schwarz'),
        '2' => neuer_spieler('Weiß'   ),
    ),
);
Referenzbildung
Das wird ergänzt um eine Referenzbildung auf sämtliche in die Datenstruktur verlagerten Daten, damit der gesamte restliche Code erstmal ohne jede Änderung weiterhin lauffähig bleibt. Wir benutzen die Referenzen also wieder einmal, um eine Modifikation in einem Codeabschnitt vom Rest des Systems abzukapseln (Kapselung ist mitnichten eine Neuerfindung oder spezielle Fähigkeit von objektorientierter Ausdrucksweise, auch wenn Ihnen die Mehrheit der akademisch vorgeprägten Programmierer solches weismachen wollen werden).
// --------------------------------------
// Bildung der Referenzen               *
// --------------------------------------

$spielfeld  = &$spiel['spielfeld'];
$erster     = &$spiel['erster'];
$dran       = &$spiel['dran'];
$zugnr      = &$spiel['zugnr'];
$spieler    = &$spiel['spieler'];
$namen      = array
(
    '1' => &$spieler['1']['name'],
    '2' => &$spieler['2']['name'],
);
Vereinfachung der Datenspeicherung
Das ergänzen wir um eine Vereinfachung der Datenspeicherung im Formular und für das Anlegen von Savegames: Wir speichern jetzt nicht mehr nur die Spieler im Block, sondern das gesamte Spiel. Im Formular fallen dadurch alle hidden inputs bis auf eines für die geballten Spieldaten weg. Das heißt also:
  • $spielfeld, $erster, $dran und $spieler werden als hidden input entfernt.
  • Ein hidden input für $spiel kommt hinzu.
  • Im Codeabschnitt, wo die Parameter aus dem abgesendeten Formular entgegengenommen werden, fällt das Entgegennehmen von $spielfeld, $erster, $dran und $spieler flach.
  • Statt dessen wird der Parameter "spiel" auf die Variable $spiel übernommen.
Wenn Sie faul (also ein einigermaßen brauchbarer Programmierer) sind, löschen Sie beim "spielfeld" einfach den Wortteil "feld" und schmeißen die andere drei Elemente weg!

Wobei wir gleichzeitig eine Änderung in der Bedeutung der Spielspeicherung im Formular haben: Als Grundlage für die Spielzüge wird uns ab jetzt der auf dem Server gespeicherte Spielstand dienen. DER wird in unserer Variablen $spiel landen. EIGENTLICH - um das Spiel überhaupt spielbar zu machen - brauchen wir eine Speicherung im Formular der Client-Browser gar nicht mehr. Diese extra Daten werden uns lediglich für ein Detail dienen, zu dem wir im nächsten Abschnitt dieser Lektion kommen. Wir werden diese extra Daten daher unter einem geringfügig modifizierten Variablennamen zugänglich machen (ich habe hier als Anregung einfach mal das Suffix "_alt" angehängt, weil damit der unmittelbar vorhergehende Spielzustand bereitgestellt wird), und zwar sowohl im PHP-Code als auch konsistent dazu im HTML-Code.
Das "Savegame" dagegen behält seine Bedeutung bei. Wobei uns hier niemand verbieten würde - wenn wir schon mal beim systematischen Überarbeiten sind -, den Savegames einen extra Namen zum besseren Auseinanderhalten vom laufenden Spielstand zu verpassen. Ich habe hier einfach mal als Anregung den Namen zu "savegame" geändert.

Um den HTML-Code des Formulars nicht sinnlos mit PHP-Funktionen zu verunstalten, empfehle ich, die Vorbereitung der Savegame-Daten aus dem HTML-Block herauszuziehen. Da wir das Speichern auf insgesamt drei verschiedenen Kanälen ermöglichen, lohnt es sich, dafür einen extra Block mit ordentlicher Kommentierung einzurichten...
// --------------------------------------
// Übernahme des Gesamt-Spielzustands	*
// --------------------------------------

// Das Laden eines Savegames überschreibt ein eventuell laufendes Spiel...
if (isset($_REQUEST['savegame']))
    $spiel = load($_REQUEST['savegame']);

$spiel_alt = false;
if (isset($_REQUEST['spiel_alt']))
    $spiel_alt = load($_REQUEST['spiel_alt']);

//===========================================
// Speicherung des Spielzustands            *
//===========================================
$savedata = save($spiel);
$saveurl  = '?savegame='.urlencode($savedata);
...

//===========================================
// Spielfeld-Anzeige                        *
//===========================================
?>
...
<form...>
    <!-- Speicherung des Spielzustandes -->
    <input type="hidden" name="spiel_alt" value="<?=$savedata?>"/>
        ...
        <div class="panel">
            ...
            <a href="<?=$saveurl?>"...>Spielstand...</a>
Korrektur der Reihenfolge der Operationen
Die Reihenfolge der Operationen für die Schritte
  • Anlegen der Datenstrukturen
  • Anlegen der Referenzen
  • Übernehmen der Parameter
  • Verarbeiten der aktuellen Spieleraktion zum neuen Spielzustand
  • Speichern des Zustands und Erzeugen des neuen HTML-Outputs
und auch innerhalb davon deren genaue Reihenfolge ist kritisch: Wir erinnern daran, daß wir "ablauforientiert" programmieren?! Wir hatten das jetzt schon mehrfach und zuletzt in der vorigen Lektion anläßlich der Ablauf-Korrektur zum Auswerten von Lesezeichen erläutert...
  1. ERST die Gesamt-Datenstruktur anlegen!
  2. DANN die Gesamt-Datenstruktur einlesen aus dem Request!
  3. DANN Referenzen auf Details der Datenstruktur anlegen!
  4. DANN einzelne Details der Datenstruktur einlesen aus dem Request!
  5. DANN Verarbeiten der aktuellen Spieleraktion
  6. DANN Speichern des Spielzustands (bzw. Vorbereiten der Speicherung im HTML-Output)
  7. DANN Erzeugen des HTML-Outputs
Und falls wir den Fall hätten, daß verschiedene Äste des Datenbaumes mit unterschiedlicher Größe übertragen werden könnten, müßten wir den ganzen Prozeß des Einlesens und Referenzierens systematisch ebenenweise von der Baumwurzel bis zu den Blättern durchorganisieren.
Wobei wir glücklicherweise sowas komplexes nicht brauchen (es wäre nämlich mit etwas Schreibaufwand verbunden - wobei man DAS auch wieder mit einer Funktion systematisieren könnte, aber wir wollen das jetzt auch nicht übertreiben mit dem Lernen!). Bei uns bleiben im Moment lediglich die Namen der Spieler als Details übrig. DEREN Einlesen kommt also HINTER den Abschnitt, in dem die Referenzen angelegt werden.

Da es in diesen zuletzt erläuterten Aktionen nur um Vereinfachungen und Zurechtschiebungen von Programmzeilen ging, erspare ich mir das extra Aufführen von Codezeilen. Wenn Sie Ihre Programmabschnitte systematisch wie hier in den Beispielen mit ordentlichen Kommentar-Überschriften versehen haben, dürfte diese Aufgabe für Sie ein Klacks sein! Falls nicht, sollten Sie spätestens jetzt dazu übergehen, Ihr Programm ordentlich zu dokumentieren - so daß SIE SELBST den Überblick in IHREM EIGENEN Programm behalten!
Speicherung in Datei auf dem Server
Hinzu kommt jetzt für die Multiplayer-Variante das Speichern in eine Datei - mit den weiter oben bereits diskutierten und verlinkten Funktionen... Sinnvollerweise legen wir das mit dem gerade eben diskutierten Vorbereiten der $savedata und $saveurl zusammen!
Das Laden dieses Spielzustands legen wir in den Block "Übernahme des Gesamt-Spielzustands".

Neustart: Wobei eine Kleinigkeit noch beim Laden eingebaut werden sollte: Falls die Datei mit dem Spielstand gerade noch nicht existiert oder keinen Inhalt (kein einziges Byte) hat - was bedeuten würde, daß gerade kein Spiel im Gange ist - sollte die eingangs gebildete Ursprungs-Datenstruktur $spiel erhalten bleiben, womit das Spiel einen Neustart ausführt. Nur wenn ein aktueller Spielstand gespeichert ist, sollen alle Besucher erstmal diesen einen zu Gesicht bekommen.
// --------------------------------------
// Übernahme des Gesamt-Spielzustands   *
// --------------------------------------

$spieldateiname = 'tictactoe.bin';

if (file_exists($spieldateiname) && filesize($spieldateiname) > 0)
    $spiel = unserialize(file_get_contents($spieldateiname));
Diese Operation ist übrigens die ERSTE im Block "Übernahme des Gesamt-Spielzustands"! DANACH kann der Spielzustand von einem Savegame überschrieben werden.

//===========================================
// Speicherung des Spielzustands            *
//===========================================

file_put_contents($spieldateiname,serialize($spiel));

Aufgabe: Ab damit in Ihren eigenen Code!
Und anpassen und testen und Tippfehler bereinigen - Sie kennen das ja schon...
Das Ergebnis könnte in etwa SO aussehen...

Wenn Sie jetzt ein Spiel beginnen, den Browser schließen, den Rechner herunterfahren, dann neu starten, mit dem Browser wieder hierher zurückkommen und das Spiel erneut öffnen, ist der Spielzustand immer noch derselbe wie beim letzten Verlassen. Genauso könnte auch jederzeit ein anderer Spieler hereinschneien und würde dieses Spiel zu sehen kriegen.

Das Ergebnis dieser Übung, die diesmal in purem Reorganisieren des PHP-Codes bestand, ist nach außen hin so gut wie gar nicht zu sehen. Lediglich ein Blick in angelegte Lesezeichen oder in den HTML-Code verrät, daß hier etwas geändert wurde. Wir HABEN aber damit die Voraussetzung geschaffen, daß von jetzt ab zwei Spieler am selben virtuellen Brett zusammenspielen können.
Erster Multiplayer-Versuch
Sie können jetzt bereits ein Multiplayer-Spiel ausführen. Schauen Sie sich dazu falls nötig nochmal die Animation zum Multiplayer-Spiel ganz vorn in der Lektion an! Um das unmittelbar an Ihrem eigenen Rechner auszuprobieren, machen Sie einfach ein zweites Fenster mit demselben TicTacToe auf!

Sie können natürlich auch untereinander im Klassenraum antreten. Dazu benötigen Sie die Namen oder IP-Adressen Ihrer Rechner.
  • Unter Windows XP/7 erfahren Sie den Rechnernamen - falls Sie den nicht selbst festgelegt haben - aus der Systemsteuerung -> System -> Karteireiter "Allgemein" -> Block "Registriert für" -> zweite Zeile (unter dem Benutzernamen)
  • Unter Windows XP/7 erfahren Sie die IP-Adresse aus der Netzwerksteuerung - am leichtesten zu erreichen über einen Klick auf das Netzwerksymbol rechts unten in der Taskleiste und im dann aufklappenden Dialogfeld auf dem zweiten Karteireiter
Eins von beiden IHRES PARTNERS (!), mit dem Sie spielen wollen, tragen Sie als Servernamen in die URL-Zeile Ihres Browsers ein. Dahinter kommt der Name des tictactoe-Scripts, wie Sie ihn bereits auf Ihrem System gewohnt sind. Sie DÜRFEN sich natürlich auch das Inhaltsverzeichnis des Lehrgangs Ihres Partners schnappen und über dieses zum aktuellen TicTacToe navigieren (wozu dieses natürlich in das Inhaltsverzeichnis aufgenommen worden sein muß).

Wenn Sie das Spiel durchziehen, merken Sie, daß wir im Moment noch KEINERLEI Kontrolle bzw. Spielleitung und keinerlei Ablaufunterstützung eingebaut haben:
  • Sie könnten jederzeit jeden der beiden Spielernamen überschreiben - obwohl Sie einen davon Ihrem Partner überlassen sollten! Und obwohl es unhöflich wäre, dies mitten im Spiel zu tun!
  • Sie können abwechselnd jede der beiden Steinfarben setzen - obwohl Sie eine davon Ihrem Partner überlassen sollten!
  • Sie können jederzeit das Spiel neu starten - obwohl es sehr unhöflich wäre, sowas ohne Einverständnis des Partners zu tun!
  • Sie müssen die Spielansicht aktualisieren, nachdem Ihr Partner einen Aktion ausgeführt hat, aber es gibt noch weder einen Schaltknopf dafür im Spiel noch einen Automatismus! Sie müssen dazu im Moment auf die URL-Zeile gehen und dort <Enter> drücken! "Krückig" wäre für sowas geschmeichelt!
  • Das Ändern der Spielernamen ist nur möglich, indem Sie das Spiel starten oder einen Spielzug ausführen. Es gibt keinen Schaltknopf zum "Anmelden".
  • Es könnte jederzeit ein anderer Besucher kommen und Ihnen das Spiel aus der Hand reißen.
Diese Dinge gehen wir jetzt nacheinander in 3 Abschnitten an.

Jetzt erstmal eine kleine Pause!

An- und Abmeldung und Spielende

Wir organisieren jetzt,
  • daß ein Spieler sich auf einem der beiden Plätze an einem Spielfeld anmelden kann und nach dieser Anmeldung erstmal nicht mehr von seinem Platz verdrängt werden kann.
  • daß ein Spiel gelöscht (alias neu angelegt) wird, sobald sich beide Spieler abgemeldet haben.
  • daß das An- und Abmelden möglich ist, ohne daß der Spieler ein Spiel starten oder einen Spielzug ausführen muß.
Das Letztere ist das Einfachste: Wir benötigen lediglich einen extra Schaltknopf zum Anmelden. Den müssen wir nur wieder irgendwo auf dem Bedien-Panel unterbringen. Wie das geht, wissen Sie inzwischen (HTML+CSS + eine kleine Zeile PHP-Code zum drauf reagieren). Sie bekommen von mir wieder eine Anregung, können das aber auch vollkommen anders ausführen.

Die Anmeldung könnte schlicht darin bestehen, daß ein Spieler einen Namen auf dem von ihm begehrten Platz - dessen Name dazu noch frei sein müßte - einträgt. Die Abmeldung komplementär dazu im Löschen des eigenen Namens. Zum Abmelden könnten wir der Bequemlichkeit halber einen einfachen Löschknopf neben den Platz des Spielers setzen, auf dem er sich angemeldet hat. Damit haben wir auch gleich eine erste, einfache Zugangsregelung eingeführt: Man kann sich nur auf unbelegten Plätzen anmelden! Damit einem einmal angemeldeten Spieler der Platz nicht mehr entrissen werden kann, verbinden wir das mit dem Vergeben von Kennungen an die Spieler. Sehen Sie dazu bei Bedarf nochmal in die Animation am Anfang!

Das Löschen (die Freigabe) eines Spieles kann dann trivial daran festgemacht werden, daß beide Namensfelder leer sind: Sobald alle Spielteilnehmer sich abgemeldet haben, wird das Spiel in den Ausgangszustand zurückversetzt.
Wobei es im Internet natürlich für niemanden eine Verpflichtung gibt, irgendetwas tun zu müssen: Im Internet ist die Freiheit der Surfer absolut! Daher sollte zusätzlich ein Mechanismus vorgesehen werden, der die Freigabe eines Spieles herbeiführt, falls die eingetragenen Spieler eine gewisse Zeit lang keine Aktion mehr ausgeführt haben. Wobei dieses Zeitfenster dem Spiel angepaßt sinnvoll gewählt werden sollte. Bei unserem kleinen TicTacToe sollte eine Minute ausreichend sein (zum Debuggen zwischendurch könnten sich aber auch mehrere Minuten als zweckmäßig erweisen).

Schaltknopf zum Anmelden
Ein Spieler sollte im Normalfall EINEN Platz für sich in Beschlag nehmen können. Also nicht zwei gleichzeitig. Zumindest sollte es ihm MÖGLICH sein, nur einen einzigen in Beschlag zu nehmen.

Dies könnte sich mit unserer bisherigen Praxis beißen, daß wir ein einziges großes Formular haben, dessen sämtliche Felder bislang in einem Rutsch übertragen werden. Da werden nämlich auch immer BEIDE Inputs für die Spielernamen gleichzeitig übertragen. Und bisher wird dann deren BEIDER Inhalt - egal, ob der Spieler nur eins davon selbst geändert hat - in den (jetzt zentralen) Spielzustand übernommen. Und überschreiben dort ALLES, was eventuell durch einen ANDEREN Spieler zwischendurch eingetragen worden ist. Das wäre gelinde gesagt "unschön".

Zwei Wege nach Rom...
Wenn wir das mit dem einen Formular und dem gleichzeitigen Einreichen beider Namens-Inputs so stehen lassen wollten, müßten wir uns auf der Serverseite etwas einfallen lassen, um zu erkennen, WELCHES der beiden Namensfelder der Spieler verändert hat und also belegen will.

Nichts leichter als das: Genau DAZU könnte der immer noch im Formular redundanterweise abgelegte Spielzustand dienen: Der enthält nämlich den Zustand des Spieles genau so, wie es dem Spieler gerade eben vorgelegt wurde. Indem wir die mit dem Abschicken des Formulars eingereichten Inputs mit deren Zustand im ALTEN Spielzustand vergleichen, würden wir rauskriegen, was sich geändert hat.
In diesem Fall bräuchten wir nur EINEN Anmelde-Schaltknopf, weil das PHP-Programm von selbst rauskriegen könnte, welche Inputs ein Spieler eingetragen hat. Selbst zum Aktualisieren der Spielanzeige bräuchten wir keinen extra Schaltknopf: Wenn der Anmelde-Schaltknopf gedrückt wird, OHNE daß ein Name geändert wurde, bewirkt dies eben nur eine Abfrage des aktualisierten Spielzustands OHNE etwas zu verändern.
Das war der ERSTE Weg nach Rom.



Ein ZWEITER Weg nach Rom könnte darin bestehen, für jeden der beiden Spielerplätze ein extra Formular einzurichten. Was nichts großartiges wäre: Nur halt ein extra Rahmen von <form>...</form> um die beiden Input-Gruppen herum. Wodurch je Formular nur die von ihm eingeschlossenen Elemente gesendet werden würden. (Wobei diese gigantischen Input-Gruppen zunächst mal aus nur jeweils einem Element - dem Namensfeld - bestehen würden, aber gleich noch um die dazugehörigen extra Schaltknöpfe zu ergänzen wären...)

In diesem Fall bräuchten wir die Speicherung des alten Spielzustandes im Formular nicht mehr. Wir könnten dieses hidden-input-Element einfach ersatzlos streichen. DAS wäre vorher (bevor wir dazu übergegangen waren, den Spielzustand auf dem Server zu speichern) noch nicht möglich gewesen und hätte beim Einrichten von mehreren Einzelformularen dazu geführt, daß der Spielzustand mehrfach redundant in der HTML-Seite hätte eingebettet werden müssen, was wiederum die HTML-Seite aufgebläht hätte.
Wir erinnern uns? GENAU DAS war im Thema 5 "Die Installation von Trittschaltern im Spielfeld" der EIGENTLICHE GRUND dafür gewesen, daß wir das Spielfeld und später das Bedien-Panel überhaupt in ein Formular gepackt hatten, statt es weitgehend mit einfachen Links zu realisieren! Dieser Grund entfällt, seit wir die Speicherung des Spielzustands auf den Server verlegt haben.



Zur Entscheidungsfindung vielleicht noch ein paar Gedanken:
  • Falls ein Paar mal ein Spielchen unmittelbar am selben PC gegeneinander spielen wollen täte (was weder unmöglich noch ungewöhnlich wäre) und dazu die Maus untereinander teilen wollen würde (abwechselnd) oder falls Sie als Programmierer mal eben das Spiel in einem einzigen Fensterchen testen wollen täten, würden die Benutzer eventuell doch zwei Namen gleichzeitig eintragen wollen (und würden sich vielleicht wundern, wenn einer davon einfach so wieder verschwindet). Das wäre ein Grund, den ersten Weg nach Rom vorzuziehen: Da könnten nämlich beliebig viele Änderungen gleichzeitig übermittelt und erkannt und verarbeitet werden.
  • Andererseits könnte man es ohne weiteres als zumutbar oder sogar intuitiv zu erwarten ansehen, daß man, WENN schon zwei extra Buttons für zwei extra Seiten vorhanden sind, diese dann auch verwendet und als zugehörig zu ihrer jeweiligen Seite erkennt. Das wäre ein Grund, den zweiten Weg nach Rom vorzuziehen.
  • Die Menge der Daten, die zwischen Client und Server ausgetauscht werden muß, ist beim zweiten Weg deutlich geringer. Sobald Sie Webanwendungen programmieren, die von vielen Benutzern gleichzeitig verwendet werden sollen und deshalb kritisch hinsichtlich Datendurchsatz sind, sollten Sie stets Varianten vorziehen, bei denen zwischen Client und Server nur minimalst notwendige differentielle Änderungen ausgetauscht werden.
  • Andererseits sind Anwendungen mit minimiertem differentiellen Datenaustausch von einer stabilen Verbindung zwischen Client und Server und einer stabilen Funktion des Servers abhängig. Ganz pauschal gesehen ist die Sache gegen eventuelle Ausfälle sicherer, wenn der vollständige Zustand der Webanwendung im Client gespeichert ist.
  • Nochmal andererseits ist die Wahrscheinlichkeit eines Serverausfalls meistens als unbedeutend für den Betrieb einer Sitzung in der Webanwendung einzustufen. Soll heißen: Zum einen viel zu selten und zum anderen: WENN der Server ausfällt, WILL man in der Regel den aktuellen Zustand im Client gar nicht mehr weiterverwenden, weil sich dann meist eh alle Clients neu synchronisieren müssen, sobald der Server wieder online kommt. Es kommt da ganz auf die konkrete Webanwendung an!
  • Bei rein differentieller Übertragung wird unsere Anwendung die Fähigkeit verlieren, implizit über die Browserhistorie eine Undo/Redo-Funktionalität und über mehrere geöffnete Browserfenster oder -tabs eine Simultanspiel-Funktionalität "gratis eingebaut" zu haben. Das könnte der vielleicht größte praktische Nachteil sein, andererseits in einem echten Multiplayerspiel aber wiederum auch vernachlässigbar, weil dort das Zurücknehmen von Spielzügen bei mehreren beteiligten Spielern alles andere als eine triviale Angelegenheit ist: Es müßten dazu nämlich alle Teilnehmer auf denselben Spielstand zurückstellen wollen und sich untereinander synchronisieren, sonst ist böses Blut vorprogrammiert. In der Regel läßt man zur Vermeidung des letzteren in Multiplayer-Spielen das unmittelbare Undo/Redo und oft sogar das Speichern und Wiederherstellen alter Spielstände weg.
    Die Simultanspiel-Fähigkeit andererseits läßt sich mit dem erneuten Zusetzen einer Kennung für die Spiele relativ leicht wiederherstellen.

Sie merken hoffentlich, daß das jetzt hochgradig Geschmackssache wird und Rattenschwänze an Folgeüberlegungen nach sich ziehen kann.

Was machen wir?

Ich habe zum Erstellen der Eingangs-Animation die Variante von unserem ersten Weg nach Rom umgesetzt: Sie sehen dort EINEN Anmelde-Button für BEIDE Spieler, der außerdem gleichzeitig zum Aktualisieren der Spielanzeige dient. Den Code finden Sie hier...

Im weiteren werde ich mit Ihnen den zweiten Weg nach Rom verfolgen. Und falls Sie sich einen dritten Weg ausdenken sollten, werde ich Sie auf keinen Fall davon abhalten!

Übung zum Anmelde-Schaltknopf
Gut: Nach dieser philosophisch ausschweifenden Betrachtung haben wir uns also geeinigt, daß wir eine Variante...
  • OHNE Speicherung des Spielzustands im Client
  • mit MINIMALEM differentiellen Datenfluß zwischen Client und Server
  • und daher getrennten Formularen für die verschiedenen Kategorien von Input-Elementen
...umsetzen werden. Namentlich werden wir ab jetzt getrennte Formulare vorsehen für...
  • das Spielfeld,
  • die eventuell einstellbaren Spieloptionen,
  • die Spiel-Ablauf-Steuerung und
  • die beiden Spieler und deren eventuellen Optionen (was noch bis zur Lektion 9 warten wird).

Aufgabe: Legen Sie ein Backup Ihres jetzt vorhandenen Codes an, damit Sie später eventuell nochmal eine alternative Variante durchspielen können!

Aufgabe: Löschen Sie die jetzt bei Ihnen "spiel_alt" genannten Elemente aus dem Quelltext! Sowohl aus dem vorderen PHP-Bereich als auch aus dem hinteren HTML-Bereich!

Ihr Spiel wird weiter seine Funktionalität behalten, denn inzwischen waren wir auf dieses Element eh nicht mehr angewiesen. Es diente nur noch zur eventuellen Differentialbildung (beim "ersten Weg nach Rom").

Aufgabe: Jetzt teilen wir unser großes Formular in kleinere Happen auf...
  • Entfernen Sie den großen <form>-Rahmen!
  • Setzen Sie einen kleinen <form>-Rahmen um das Spielfeld!
  • Setzen Sie einen kleinen <form>-Rahmen um das Namensfeld für Schwarz!
  • Setzen Sie einen kleinen <form>-Rahmen um das Namensfeld für Weiß!
  • Setzen Sie einen kleinen <form>-Rahmen um den Start-Button!

Aufgabe: Testen Sie das Ergebnis hinsichtlich Funktion und Layout!

Das Layout wird eventuell geringfügig verbogen sein. Das hängt damit zusammen, daß mindestens eines der Elemente, um die wir soeben einen "kleinen" Formular-Rahmen gepackt haben, ein sogenanntes "inline"-Element ist war. Welches einem normalen horizontalen Textfluß unterworfen ist war. Ein Formular dagegen ist standardmäßig ein "block"-Element, welches dem normalen Textfluß eben NICHT unterworfen ist, sondern grundsätzlich in die Breite gedehnt eine komplette Zeile belegt. Der Formular-Rahmen hat dadurch aus dem ursprünglich "inline"-Startknopf einen "block"-Startknopf gemacht. Dessen Layout nicht mehr stimmt.

Aufgabe: Definieren Sie das Layout von Formularen für Ihr TicTacToe auf standardmäßig "inline" um!

Sie benutzen natürlich CSS dafür! Sie haben bereits inline-Definitionen im CSS-Abschnitt Ihres TicTacToe zu stehen. Schauen sie einmal nach und üben Sie mal wieder copy & paste!
form
{
    display: inline;
}


So, jetzt ist das Layout wieder wie ursprünglich, und die Funktion war eh noch immer korrekt.



2. Teil: Submit-Buttons einrichten: Die Namensfelder können wir noch immer nicht "anmelden"...

Aufgabe: Ergänzen Sie für die beiden Formulare der Spielernamen jeweils einen submit-button
<input type="submit" name="anmelden" value="Anmelden!"/>
, den Sie mit "anmelden" beschriften, damit Sie die Formulare auch abschicken können! (Sie denken an copy & paste?!)...
Sorgen Sie bitte gleich mal dafür, daß diese Submit-Buttons unterhalb der Text-Inputs angeordnet werden, indem diese zum Beispiel mit einem Zeilenvorschub (<br/>) von den Text-Inputs getrennt werden!

Das zerschießt allerdings erstmal wieder das Layout: Weil wir bisher zwar die Eingabefelder, nicht aber die beiden submit-buttons positionieren lassen...
Aber wir kennen ja inzwischen die Prinzipien, wie wir in CSS Dinge zurechtgerückt kriegen...

Wir bündeln jeweils das Input-Feld und den zugehörigen Submit-Button zu einem Block und lassen diesen positionieren!

Die Positionierung hatten wir bisher für die Namensfelder organisiert, indem wir denen die Attribute class="namen white" bzw. "namen black" mitgegeben hatten. Die betreffenden CSS-Definitionen stehen immer noch. Wobei dort unter dem Selektor .namen nicht nur die Positionierung, sondern auch das Aussehen der Text-Inputs definiert wurde.

Das teilen wir jetzt: Wir werden künftig die Blöcke, die jeweils BEIDE Elemente enthalten, positionieren. Das Aussehen des text-Inputs lassen wir unverändert. Dazu verschieben wir die class-Attribute vom Text-Input auf das umgebende Formular (für das weiße natürlich entsprechend):
<form class="namen black" method="POST">
    <input class="namen black" type="text" name="namen[1]".../><br/>
    <input type="submit" name="anmelden" value="Anmelden!"/>
</form>

Dazu passend teilen wir die CSS-Definitionen neu auf, indem wir die Positionierung aus den Text-Inputs herausziehen und einen Abschnitt für die Submit-Buttons ergänzen (die sind erstmal recht fett, weil die genauso aussehen wie der Start-Button; das ist allerdings doch etwas übertrieben, deshalb verkleinern wir die hier ein wenig). Die Selektoren passen wir an die soeben veränderte Position der class-Attribute an...
/* Namen */
form.namen
{
    position:       absolute;
    top:            130px;
}
.namen.black        {left:  10px; text-align: left;}
.namen.white        {right: 10px; text-align: right;}
.namen.black input[type="text"] {text-align: left;}
.namen.white input[type="text"] {text-align: right;}

.namen input[type="text"]
{
    width:          9em;
    border:         2px inset #480915;          /* Fallback für veraltete Browser */
    border:         2px inset rgba(72,9,21,0.6);
    border-radius:  5px;
    background:     none;
    color:          white;
    font-size:      15pt;
    padding:        0.1em 0.25em;
}
.namen input[type="submit"]
{
    margin-top:     10px;
    font-size:      11pt;
    padding:        2px 5px 3px;
}

Das Ergebnis könnte in etwa SO aussehen...
Spielsteuerungs-Logik für die Anmeldung
Wir haben jetzt noch dafür zu sorgen, daß Mord- und Totschlag bei der Anmeldung an einem Spielbrett vermieden wird. Dazu gehört im einzelnen:
  • einen Platz zu besetzen geht nur, solange der noch frei ist
  • alle Operationen auf einem besetzten Platz können nur durch den Platzbesitzer ausgeführt werden, dazu zählt im einzelnen:
    • den Namen ändern
    • den Platz freigeben
    • Spielzüge ausführen (das lassen wir uns für etwas später)

Netterweise sollte die Erlaubnis dieser Operationen bereits in der Darstellung des Spiels dadurch berücksichtigt werden, daß den Mitspielern bzw. Zuschauern erst gar nicht die MÖGLICHKEIT unter die Nase gerieben wird, verbotene Dinge zu tun! All die Formulare, die wir jetzt eingerichtet haben, sollen also jeweils nur für diejenigen ausführbar sein, die das Recht dazu haben.
Sinnvollerweise lassen wir die Formulare für alle anderen einfach weg und geben statt dessen einfachen statischen Text bzw. Bildchen aus.

Sie ahnen möglicherweise, daß uns diese Sache neben einiger PHP-Programmlogik auch noch einen Abstecher ins CSS kosten wird...

Spieler-Kennungen
Die Umsetzung beginnen wir zunächst mal mit dem Vergeben von Kennungen an die sich anmeldenden Spieler... (siehe Animation)

Die Kennungen verdienen etwas Betrachtung:
  • Sie sollen die Spieler und Zugucker gegeneinander vor unerwünschten Einmischungen "schützen"!
  • Sie dürfen dazu nicht von anderen Spielern erratbar/errechenbar sein!
  • Sie müssen dazu kryptographische Qualitäten aufweisen!
  • Jeder Rechner bzw. der auf diesem laufende Browser soll eine eigene Kennung bekommen!
  • Falls sich zwei Personen einvernehmlich nebeneinander einen Rechner, eine Maus und einen Browser zu teilen vermögen, vertrauen wir deren sozialer Kompetenz, sich auch eine Spieloberfläche einvernehmlich teilen zu können. Mehrere Personen an einem Browser unterscheiden wir also nicht!
Wir können die Erzeugung dieser Kennungen dem PHP-Laufzeitsystem überlassen - in der Hoffnung, daß dieses korrekt implementiert ist und uns die Arbeit abnimmt. In unserem Fall werden wir das auch der Einfachheit halber genau so tun. Legen Sie sich aber ruhig ein gesundes Maß an Paranoidität zu, weil es immer wieder mal vorkommen kann, daß es aus wer weiß was für abwegigen Gründen zu Schwachstellen kommen kann (zum Beispiel wenn man den PHP-Interpreter oder Bibliotheken davon versehentlich mit so einer Nebensächlichkeit wie einem ungünstig gewählten Optimierungs-Flag compiliert).
Siehe zum Beispiel: Weak RNG in PHP session ID generation

Das PHP-Laufzeitsystem bietet sogenannte "Sitzungen" (englisch "Sessions"), was nichts weiter bedeutet als daß...
  • jeder Browser, der den Server besucht, vom Server eine Zufallszahl als Sitzungs-Kennung zugewiesen bekommt.
  • diese Kennung als sogenanntes "Session-Cookie" (-> Wikipedia) implizit in der Antwort vom Server an den Browser mitgeschickt wird.
  • der Browser sämtliche Cookies, die er kennt, die zu dem Server gehören, von dem er gerade eine Web-Ressource abfragt, (und dort gegebenenfalls zu dem Bereich der Webressource,) bei jeder Anfrage implizit wieder an den Server mitschickt (wozu er nach den Standards, die im Wikipedia-Artikel beschrieben sind, verpflichtet ist).
  • anhand dieses implizit mitgeschickten Cookies alias der Sitzungs-Kennung der Browser gegenüber dem Server bei allen Folgeoperationen identifiziert (wiedererkannt) wird.
Übung zur Sitzungs-Kennung und dem Sitzungs-Cookie
Das Zeug ist für Sie unmittelbar greifbar:
  • Klicken Sie mit Rechts hier in die Seite hinein, die Sie gerade lesen!
  • wählen Sie im Kontextmenü "page info" (deutsch: "Seiteninformationen anzeigen")!
  • wählen Sie im Dialogfeld den Karteireiter "Sicherheit"!
  • Klicken Sie in der Mitte rechts auf "Cookies anzeigen"!
  • Dort sehen Sie für diesen Webserver ein Cookie "general_session_id" - das ist bei mir festgelegte Standard-Session-Cookie von PHP.
  • Klicken Sie auf dieses Cookie, um es auszuwählen!
  • Links unter der Cookie-Liste wird der Inhalt des gerade ausgewählten Cookies dargestellt.
Das Verhalten des Cookies können Sie zum besseren Verstehen unmittelbar selbst ausprobieren:
  1. Sein Inhalt ist im Moment eine Zufallszahl.
  2. Wenn Sie die Webseite neu laden lassen ("neu laden" Button im Menü anklicken!), bleibt das Cookie mit seinem Wert unverändert erhalten (es wandert bei jeder Abfrage vom Browser zum Server und wieder zurück).
  3. Wenn Sie das Cookie löschen und die Webseite neu anfordern, kann der Browser logischerweise kein Cookie zum Server mitschicken; der Server "denkt" daraufhin, daß der Browser ein neuer Besucher wäre, behandelt die Anfrage auch in jeder Hinsicht als ob sie von einem noch unbekannten Besucher stammen würde, würfelt deshalb ein neues Cookie aus und schickt dieses wiederum nebenbei mit der Antwort zum Browser mit; der speichert das neue Cookie dann erneut.
  4. -> Goto Punkt 1!

An einer Sitzung hängt noch einiges mehr, aber für uns soll erstmal nur die Identifizierung des Browsers des Besuchers interessant sein. Dazu benötigen wir lediglich die Eröffnung einer Sitzung (also die Zuweisung oder Wiedererkennung dieser ominösen Zufallszahl) und die Abfrage der Sitzungskennung (weil WIR ja ebenfalls die Kennung benutzen und kontrollieren möchten):
session_start();        // erzeugt die Session-ID
$sid = session_id();    // fragt dieselbe aus der Systemumgebung ab oder setzt sie neu
Wenn wir das so umsetzen, brauchen wir uns um nichts weiter kümmern. Die eingebaute Session-Verwaltung macht alles automatisch.

Session-Start-Zeitpunkt
Eine Kleinigkeit gibt es allerdings zu beachten: Wenn Sie eine Session eröffnen oder eine vorhandene in Benutzung nehmen wollen, müssen Sie den Aufruf von session_start() vor der ersten Ausgabe von HTML-Text ausführen! Das hängt damit zusammen, daß gewisse Details des Antwort des Servers, die in den Kopfdaten der Server-Nachricht enthalten sind, vom PHP-System zwingend eingestellt werden wollen, und daß das PHP-System sich selbst den Strick nimmt, wenn diese Details nicht bis auf den letzten Tropfen VOR der ersten Daten-Ausgabe eingestellt worden sind.

Was heißt das?

Nun, Sie müssen den session_start() eben weit genug vorn in Ihrem Script unterbringen. Das ist ein weiterer Grund dafür, ein Script systematisch in Abschnitte zu teilen:
  1. Initialisierung (mit eventuellem Session-Setup)
  2. Parameterverarbeitung
  3. Ausgabe-Vorbereitung
  4. Ausgabe-Erzeugung
...so, wie wir das in unserem TicTacToe ja auch schon machen.

Wer sich tiefergehend mit den Sessions und Cookies befassen will, die hier im Hintergrund werkeln, darf die gesetzten Links verfolgen...

Einbau der Sitzungs-Kennung
Die Spielerkennung soll nun noch in den Spielerdaten eingetragen werden, wenn ein Spieler einen Platz mit seinem Namen besetzt (ich werde dazu im folgenden immer "registrieren" sagen). Das Definieren neuer Datenfelder in Arrays haben Sie jetzt schon zig mal hinter sich, das sollte keine Hürde mehr sein...
  • Wo notieren Sie die Kennung am sinnvollsten, damit sie dem PLATZ zugeordnet ist?
  • Wie könnten Sie das Datenelement an dieser Stelle sinnvoll nennen?
  • Und wo muß diese Ergänzung nun in Ihrem Code untergebracht werden?
  • Denken Sie daran, daß Sie, um eine session_id() nutzen zu können, einen session_start() ausgeführt haben müssen! Und daß der weit genug vorn in Ihrem Programm erfolgen muß! (Sie dürfen den ruhig GANZ nach vorn legen!)

Aufgabe: Ab in Ihren Code damit! Testen!
(Es hat sich noch nichts funktionell geändert, aber das Programm sollte keine Fehlermeldung schmeißen!)
Zugriffsrechte-Logik
Wir wollen jetzt einrichten, daß ein Besucher des Spiels einen Platz nur dann belegen kann, wenn der noch frei ist. Bzw. daß er seinen Namen höchstens dann ändern kann, wenn der betreffende Platz ihm "gehört" (also: seine session_id trägt).

Dabei hatten wir zwei Dinge vor:
  1. Unerlaubte Aktionen unausführbar zu machen.
  2. Unerlaubte Aktionen erst gar nicht offensiv in der Bedienoberfläche anzubieten.
Eins nach dem anderen: Zuerst machen wir die Aktions-Möglichkeiten dicht - womit wir uns auch gleich gegen Hacking schützen. Hacker sind bekanntlich nicht auf Bedienoberflächen angewiesen (Sie kennen das inzwischen aus eigener Erfahrung). Dann erst nehmen wir uns die Modifizierung der Bedienoberfläche vor.

Namen nur auf leeren Platz neu eintragen lassen
Momentan lassen wir bei Anforderung einer Namenseintragung dieses einfach ausführen - zusammen mit dem Eintragen der Spieler-Kennung auf dem begehrten Platz...
foreach($_REQUEST['namen'] as $i => $name)
{
    $spiel['spieler'][$i]['id'  ] = $sid;   // die Sitzungs-Kennung
    $spiel['spieler'][$i]['name'] = $name;  // der oder die Namen
}
DAS wollen wir jetzt nur noch unter der BEDINGUNG machen daß auf dem Platz noch NIEMAND "sitzt" - also noch KEINE Kennung drauf liegt. Das mit den Bedingungen hatten Sie bereits kennengelernt und mehrfach angewendet. Versuchen Sie sich mal alleine daran!
Aufgabe: Fügen Sie die beschriebene Bedingung für den Schleifeninhalt ein!

...Wobei der Vergleich mit einem Dingens voraussetzt, daß dieses Dingens auch bereits existiert! Momentan ist es so, daß die "id" eines Spielers ursprünglich noch gar nicht eingetragen ist! Der PHP-Interpreter bemeckert das dementsprechend. Schauen Sie mal ganz oben an den Anfang Ihres Programms, wie in der Datenstruktur $spiel die Spieler vorbereitet werden! Da wird eine Funktion "neuer_spieler()" aufgerufen, in der bisher nur der Name und der Punktestand gesetzt wird! Der PHP-Interpreter hat das nicht existierende Element stillschweigend so behandelt, als ob es "leer" definiert worden wäre. Er kommt damit dem Programmierer beim schnellen Entwickeln eines Programms maximal entgegen und verhält sich dabei so intuitiv wie möglich. Allerdings sollten Sie sich nicht auf solche uninitialisierten Zustände verlassen und grundsätzlich jede Variable initialisieren - zumindest nachdem Sie Ihre Idee zur Variablenverwendung ausprobiert und sich für eine Variable entschlossen haben!

Aufgabe: Ändern Sie die Initialisierung neuer Spieler, indem Sie NEBEN dem Namen und Punktestand AUCH eine "id" eintragen lassen! Lassen Sie dazu die "id" als Parameter an die betreffende Funktion übergeben! Und sorgen Sie dafür, daß bei der Initialisierung des Spiels leere "id"s beim Aufruf der "neuen Spieler" als Argumente eingetragen werden!

Eine Ergänzung noch: Wenn sich ein Spieler NEU anmeldet, sollte er eventuell einen genullten Punktestand zugewiesen bekommen anstatt die Punkte seines Vorgängers zu übernehmen! Oder?
Aufgabe: Ändern Sie das!

Wenn Sie schon mal beim Systematisieren sind, können Sie alle drei Zeilen Programmcode beim Anmelden auch ersetzen durch einen einzigen kleinen Funktionsaufruf für einen "neuen Spieler"! (Fies, ja? Daß ich Sie ERST die Arbeit machen lasse, bevor ich Ihnen den Tipp zum Vereinfachen gebe! Das hat ebenfalls Methode: Es soll Ihnen schließlich KLAR werden, daß Systematisierung keine Schikane ist, sondern Vereinfachung! Für SIE SELBST!)
Das könnte dann zum Schluß etwa so aussehen...
    $spiel['spieler'][$i] = neuer_spieler($sid,$name); // ein komplett neuer Spieler

Namen auf besetztem Platz nur durch Besitzer ändern lassen
Zweiter Teil: Etwas andere Bedingung (nahezu entgegengesetzt) und etwas andere Aktion (nur der Name geändert, Rest bleibt wie er ist)...
Der ANLASS (die Stelle im Code, wo es passiert) bleibt derselbe wie eben!

Aufgabe: Formulieren Sie einen zweiten Teil der Platzbesetzung! So daß die "id" des bereits besetzten Platzes auf Gleichheit zur "session-id" ("sid") des aktuell anfragenden Spielers geprüft wird! Weisen Sie nur bei Gleichheit den geänderten Namen zu!

Spätestens jetzt sollten Ihnen die Finger wund geworden sein vom Tippen der elend ewig gleich wiederholten Array-Index-Zugriffe!
Aufgabe: Vereinfachen Sie das, indem Sie sich eine Referenz auf den durch den Schleifenindex ausgewählten Spieler anlegen! (Schon wieder echt fies, nicht wahr?! In Zukunft schaffen Sie solche Vereinfachungen aus eigener Kraft, im Vorbeigehen und ohne dazu explizit aufgefordert werden zu müssen! Oder Sie müssen sich halt abschuften! "Wer's nicht im Kopf hat, hat's in den Fingern" - heißt es unter Programmierern...)

Aufgabe: Als Komplettierung könnten Sie noch Rückmeldungen in die Statuszeile ausgeben lassen über die erfolgte Anmeldung oder Umbenennung!
Wie Sie die Statuszeile beschreiben, hatten wir in Lektion 6 bei der Sieg- und Remisauswertung. Sie finden das Zeug in Ihrem Code! Benutzen Sie copy & paste!

Erfinden Sie eine neue CSS-"Klasse" für die Anmelde-Meldung! Das hat einen guten Grund: Die Anmelde-Meldung wird wahrscheinlich etwas länger ausfallen als eine Sieges- oder Remis-Meldung. Damit die in die Statusleiste paßt, muß die Schrift verkleinert werden! Außerdem sieht die in Fettschrift viel zu ... fett eben aus!
.status .anmeldung
{
    font-weight:    500;
    font-size:      15px;
}

Das Resultat könnte dann etwa so aussehen:
foreach($_REQUEST['namen'] as $i => $name)
{
    $spieler_i = &$spiel['spieler'][$i];

    if ($spieler_i['id'] == '')
    {
        $spieler_i = neuer_spieler($sid,$name);
        $status = '&nbsp;<span class="anmeldung">'.$name
                .' auf Platz '.$i.' angemeldet</span>&nbsp;';
    }
    elseif($spieler_i['id'] == $sid)
    {
        $spieler_i['name'] = $name;
        $status = '&nbsp;<span class="anmeldung">'.$name
                .' auf Platz '.$i.' umbenannt</span>&nbsp;';
    }
}

Die Datei könnte insgesamt inzwischen in etwa SO aussehen...

Wenn Sie das jetzt ausprobieren wollen und dazu ein noch nicht benutzes Spiel haben wollen, müssen Sie zunächst das gespeicherte Spiel auf Ihrem Server von Hand löschen. Das ändern wir jetzt...

Platz freigeben
Zum Abmelden können (und werden) wir einen hübschen Ausschaltknopf hinsetzen, aber vorher können wir schon mal die Aktion funktionsfähig einrichten, indem wir das Abmelden daran festmachen, daß man den Namen auf seinem Platz leer macht. Den Ausschaltknopf richten wir dann so ein, daß der einfach nur genau dieselbe Aktion auslöst...

Dies ist ein Spezielfall der gerade eingerichteten Umbenennung, den Sie natürlich eben als Fallunterscheidung dort einhängen... Also dann...

Aufgabe: Falls beim Umbenennen ein leerer Name vorgegeben wird, machen Sie den Platz wieder frei! Und zwar, indem Sie genau dasselbe auf den Platz legen, was auch bei der Spielinitialisierung dort drauf gelegt wird!

Sie merken, daß die Aufgabenstellungen allmählich ein wenig freier werden und von Ihnen Nachgucken im eigenen Code und allmählich erweiterte Schlußfolgerungsketten verlangen?! Das soll so sein. Und es gibt jetzt auch keine versteckten Hinweise mehr, da Sie wirklich nur copy & paste machen brauchen...

Aufgabe: Testen Sie die korrekte Funktion der Besitzrechte!

Dazu benötigen Sie einen zweiten Browser, der NICHT dieselbe Sitzungskennung verwendet wie Ihr "normaler" Browser! Das werden Sie bei Multiplayer-Spielen immer wieder mal benötigen.

Wir hatten gerade erläutert, daß die Sitzungskennung in einem "Cookie" des Browsers abgelegt wird. Und daß die Browser verpflichtet sind - wegen Standards, die wir weiter oben unter dem Abschnitt "Spieler-Kennungen" erwähnt hatten -, solche Cookies jedesmal bei einer Anfrage an einen Server mitzuschicken - und zwar jedesmal sämtliche Cookies, die zu diesem Server gehören (wobei man da noch weitere Einschränkungen setzen kann, die für uns aber erstmal irrelevant sind, und die Sie sich bei Interesse selbst nachlesen können...).

Alle üblichen Browser sind nun so ausgelegt, daß sie die Cookies GLOBAL interpretieren, also keine Unterscheidung nach Fenstern oder Tabulator-Karteireitern machen. Wenn man sich in EINEM Fenster mit einem Server verbindet, so gilt man automatisch in SÄMTLICHEN Fenstern als mit diesem Server verbunden. Das ist eingeführt worden, um DAU's nicht intellektuell zu überfordern.

Der Firefox hatte (in der Vergangenheit) einen "private Browsing" Modus, den man PARALLEL zu einer normalen Sitzung benutzen konnte. Wo ich das Ding gerade eben für den Lehrgang ausprobiere, muß ich feststellen, daß in der aktuellen Version dieser Modus zwingend zum Schließen aller anderen Browserfenster mit der vorherigen nicht-privaten Sitzung führt. Wer hat diesen Unsinn schon wieder verbrochen?!

Nun gut... Viele Wege führen nach Rom, nicht wahr?! Nehmen Sie halt einfach einen ANDEREN Browser als Zweitgerät! Um einfach nur eine Funktion zu testen, kann man auch den Internet Explorer hinnehmen. Oder Sie laden sich einen der vier an der Unterkante der Lehrgangsübersicht verlinkten Browser herunter. Sie könnten somit insgesamt bis zu 6 verschiedene Sitzungen nebeneinander betreiben (unter einem derzeit üblichen Windows-System; unter Linux sieht die Auswahl noch etwas üppiger aus).
Freigabe aufgegebener Spiele
Aus der Freigabe machen wir jetzt auch kein Drama mehr...
Nochmal zur Erinnerung: Es geht darum, eine Spiel in den Ausgangszustand zu versetzen (also mit den Spielerplätzen das zu machen, was wir eben als letztes eingerichtet hatten), wenn die Spieler eine gewisse Zeit lang keinen Spielzug mehr gemacht haben.

  • Wie kriegen wir heraus, wann der letzte Spielzug stattgefunden hat?
  • Wie kriegen wir raus, ob der abfragende Browser einer der am Brett sitzenden Spieler ist?
  • Wie bauen wir das so einfach wie möglich in den vorhandenen Code ein?

Jetzt benötigen Sie noch einen Hinweis, wie Sie von einer Datei den Zeitpunkt des letzten Schreibens abfragen können und wie Sie den Zeitpunkt des aktuellen Zugriffs ermitteln können...
$letzte_dateiänderung = filemtime($spieldateiname); // file modification time
$jetzt = time();
Beides sind "Unix Timestamps", die einen linear wachsenden Wert in Sekunden darstellen. (Der allerdings als Ganzzahl mit begrenztem Wertebereich von 2^31 mit Vorsicht bezüglich regelmäßigen Überläufen zu genießen ist. Darum brauchen SIE sich JETZT aber erstmal noch keinen Kopf machen (auch aus so einer Kleinigkeit wie Zeit- und Datumsrechnung kann man komplette eigene Wissenschaften machen...).)

Verlassenes Spiel automatisch freigeben
Aufgabe: Kombinieren Sie die eben erhaltenen Hinweise zu einer automatischen Löschung (alias Ignorierung) eines veralteten Spielstands! Denken Sie daran, daß Ihre Aufgabe mehrere Teile umfaßt:
  1. Dafür sorgen, daß der Spielzustand NUR DANN GESPEICHERT wird, wenn einer der BETEILIGTEN Spieler diesen abruft!
  2. Dafür sorgen, daß der Spielstand AUCH DANN NICHT GELADEN wird, wenn er zu alt (> 60 Sekunden) geworden ist!

Testen Sie es, indem Sie nach der Lösung dieser Aufgabe eine kleine (bis zu 5 Minuten) Pause einlegen, wo sie vorher noch bei einem Spiel die Plätze besetzen lassen!
Wenn Sie das Spiel nach der Pause erneut aufrufen, sollte es wieder leere Plätze präsentieren!

Falls es Probleme mit dieser zeitgesteuerten Funktion geben sollte, sind Sie natürlich nicht auf das Pause machen angewiesen... Viele Wege führen mal wieder nach Rom: Sie können zum Debuggen das Zeitintervall von 60 auf eine Sekunde herabsetzen (und sollten dann praktisch immer ein "frisches" Spiel präsentiert bekommen). Falls sich ein versteckter Fehler mal nur bei größeren Zeitdifferenzen zeigen sollte, können Sie auch die Zeitstempel einer Datei manipulieren. (Allerdings nicht mit dem Standard-Dateimanager von Windows, sondern "richtigen" solchen, üblicherweise einem "Norton Commander"-Klon...)

Das Ergebnis Ihrer Arbeit bis hierhin könnte in etwa SO aussehen...

Angebot der Bedienoberfläche einschränken
Jetzt kommt Feinschliff! Damit wir die eventuellen Zugucker und die beiden Spieler gegeneinander von vornherein gar nicht erst in Versuchung bringen, bieten wir jetzt Aktionsmöglichkeiten auf der Bedienoberfläche von vornherein gar nicht erst an, wenn sie eh nicht ausführbar wären!

Die Anmeldung auf bereits besetzten Plätzen schalten wir ab, indem wir die Inputs (Textfelder und Submit-Buttons) "disabled" schalten und diese Eigenschaft optisch durch entsprechende CSS-Auszeichnung untermalen. Das machen wir einzeln für die linke und rechte Seite des Bedien-Panels! Wir prüfen jeweils, ob der aktuell anfragende Browser die Kennung trägt, die der auf dem jeweiligen Platz registrierte Spieler trägt ODER ob der jeweilige Platz noch leer ist. Wenn das zutrifft, gibt's die Bedienelemente aktiv geschaltet, sonst passiv oder gar nicht!

Für das CSS gibt's noch eine Anregung, weil das für Sie ja zunächst noch Nebensache bleiben soll:
input[type="text"][disabled="true"],
input[type="text"][disabled="true"]:hover,
input[type="text"][disabled="true"]:focus
{
    border:     2px groove #480915; /* Fallback für veraltete Browser */
    border:     2px groove rgba(72,9,21,0.6);
    background: none;
}

Aufgabe: Ausführen! Und testen!

Das Ergebnis Ihrer Arbeit könnte in etwa SO aussehen...

Wenn Sie damit experimentieren, merken Sie, daß Sie hinsichtlich der Anmeldung schon mal keinen Unsinn mehr anstellen können. Aber auf dem Spielfeld dürfen Sie immer noch toben, was das Zeug hält, und ein Neustart des Spieles ist auch noch jederzeit jedermann möglich.
Das ändern wir gleich...
Erstmal zwischendurch eine Pause!

Weitere Rollen- und Rechteverteilung

Dasselbe, was wir gerade mit der Anmeldung gemacht haben, bauen wir jetzt auch noch für's Spielfeld und den Rest des Bedienpanels ein.
  • Auf dem Spielfeld sollte jeweils nur der gerade dran seiende Spieler einen Stein setzen können!
  • Der Start-Button sollte nur von den beteiligten Spielern gedrückt werden dürfen. Eventuell ziehen Sie es aber auch vor, einen der beiden Spieler (da wäre der zuerst angemeldete sinnvoll) zum Spielleiter zu erklären und den Neustart des Spieles (also den Start der jeweils nächsten Runde) nur diesem zu erlauben.

Das "dran sein" ist eine gegenüber den Anmelderechten stärkere Einschränkung. Die müssen wir extra abhandeln, was aber trivial ist, weil unsere Variable "dran" ja noch immmer existiert und schon die Nummer des dran seienden Spielers enthält. Wir brauchen nur mal wieder für unterschiedliche HTML-Ausgaben oder eine CSS-Modifikation zu sorgen. Und natürlich dafür, daß die Spielzugannahme auch tatsächlich nur von dem Spieler erfolgt, der gerade dran ist.

Wenn Sie das Konzept eies Spielleiters umsetzen möchten, wäre es vielleicht noch gut, diesen optisch anzuzeigen. Zum Beispiel, indem dessen Namensanzeige eine farbige Umrandung bekommt. Oder so ähnlich. Es sind Ihnen wieder mal alle Wege offen. Sie dürften aber auch drauf verzichten, falls Sie eher demokratischen Philosophien anhängen.

Da es hier nicht mehr viel zum Prinzip zu sagen gibt, das ganze also eher eine Fleißarbeit zur Wiederholung von Bekanntem darstellt, ziehen wir das als Übung durch...

Stein setzen nur für dran seienden Spieler
Nochmal zur Aktivierung der richtigen Gedanken, um den "dran seienden Spieler" korrekt zu identifizieren:
  • Listen Sie alle Daten auf, die etwas mit der Auswahl des dran seienden Spielers zu tun haben!
  • Listen Sie alle Daten auf, die etwas mit der Identifizierung des dran seienden Spielers zu tun haben!
  • Formulieren Sie damit eine Bedingung (einen boolschen Wert)$ist_dran = ($sid == $spieler[$dran]['id']);, die nur dann zutrifft, wenn der anfragende Browser zu der Person gehört, die jetzt dran ist!
  • Ergänzen Sie diese Bedingung zusätzlich noch um einen Ausschluß des Falls, daß das Spiel noch gar nicht gestartet oder bereits beendet ist!

Aufgabe: Benutzen Sie diese Bedingung, um nur unter dieser Bedingung aktivierbare Felder auf dem Spielfeld zu erzeugen!

Spielstart nur für Teilnehmer (oder Spielleiter)
Ob Sie den Start der nächsten Runde nur dem Spielleiter zugestehen möchten oder beiden Spielern gleichberechtigt, ist völlig Geschmackssache.

Falls wir die Spielleiter-Rolle benutzbar machen wollen, müssen wir aber erstmal einen Spielleiter als solchen definieren...
  • Was würden Sie davon halten, den zuerst kommenden Spieler zum Spielleiter zu erklären?
  • Wo könnten wir diese Eigenschaft am EINFACHSTEN für die weitere Verarbeitung benutzbar notieren?
    • Überlegen Sie sich, warum und wie Sie auf die Eigenschaft "Spielleiter" zugreifen wollen werden!
      • Ausgehend von einem Spieler ermitteln, ob DER der Spielleiter ist?
      • Oder ausgehend vom Spiel insgesamt, welches die Kennung des Spielleiters ist, um die mit der aktuellen Browserkennung zu vergleichen?
    • Denken Sie sich in Abhängigkeit von Ihrer Beantwortung dieser Frage eine Eigenschaft in Ihrer Spiel-Datenstruktur aus!

Aufgaben zum Einbau der Spielleiter-Rolle:
  • Organisieren Sie, daß diese Eigenschaft zu Anfang eines neuen Spiels so initialisiert wird, daß zunächst KEIN Spielleiter festgelegt ist!
  • Organisieren Sie, daß der erste sich anmeldende Spieler zum Spielleiter erklärt wird!...
  • ...Und daß der Spielleiter beim Abmelden eines Spielers mit dessen Kennung auf den anderen Spieler
    wechselt bzw. wieder freigegeben wird!

Aufgaben zur Einschränkung des Startrechts:
  • Bauen Sie für den Spielstart die entsprechende (von Ihnen bevorzugte) Bedingung an die korrekte Stelle in Ihren Programmcode ein!
  • Blenden Sie den Startknopf aus (oder machen Sie ihn passiv alias disabled), wenn der Spieler den Start nicht auslösen darf!

Ihr Programmcode könnte damit in etwa SO aussehen...

Mit diesem Zustand haben wir das Ziel erreicht, die eingangs gezeigte Animation nachzubauen. Wobei wir mit den getrennten Anmelde-Knöpfen eine geringfügige Abweichung eingebaut haben. Der Rest ist aber identisch.

Sie können mit diesem Zustand bereits recht ordentlich in großen Gruppen zusammenspielen, wo sich gleichzeitig zwei Spieler zusammenfinden und beliebig viele Zuschauer zugucken können. Wobei das jetzt bei einem TicTacToe-Spiel sicherlich nicht DER abhebende Hammer von spannendem Gameplay ist. Aber das Prinzip, wie es technisch zu organisieren ist, sollte rübergekommen sein.

Was beim Testspiel spätestens jetzt als mangelhaft auffällt, ist der Umstand, daß man beim Zusammenspiel wie auch beim Beobachten eines Spieles die Spielanzeige am laufenden Band aktualisieren muß, damit man zu sehen bekommt, was der Partner in der Zwischenzeit gemacht hat! Momentan geschieht das durch Drücken des Anmelde-Schaltknopfes. Aber das Wahre ist diese Regelung nicht!

Wir hatten eingangs schon mal ausführlich auf mögliche Techniken der Aktualisierung orientiert. Die nehmen wir uns gleich im Anschluß an die Pause im nächsten Abschnitt vor...

Jetzt erstmal eine größere Pause zum Entspannen der Gedanken!

Passives Aktualisieren der Anzeige

Ich hatte Ihnen angekündigt, daß wir einen Auto-Nachlade-Mechanismus einbauen können, der uns nicht das Programmieren von Javascript oder dergleichen und den Besuchern unserer Web-Anwendungen nicht das Aktivieren "aktiver Komponenten" in deren Browser abverlangt.
Und das geht so:
<html>
<head>
    ...
    <meta http-equiv="refresh" content="15;url=?"/>
    ...
Das ist ein HTML-"Meta-Tag", welches im Kopfbereich eines HTML-Dokuments eingesetzt werden kann.
  • Das Attribut "http-equiv" zeigt an, daß es sich um eine Sache handelt, die außerdem auch in den Kopfdaten der Serverantwort im HTTP-Protokoll vorkommen kann. Dieses Thema ist für uns nebensächlich und schon mal weiter vorn im Zusammenhang der Sessions als Nebenbemerkung behandelt worden. Bei Interesse schlagen Sie dort nach!
  • Das "content"-Attribut definiert in diesem Beispiel eine Zeitverzögerung von 15 Sekunden sowie die URL, die nach dieser Verzögerung vom Server angefordert wird. Da wir einfach dieselbe Datei anfordern lassen, können wir die URL in einer "relativen" Form angeben, bei der vom Browser automatisch implizit vorn der Ressourcenname der aktuell angezeigten Datei bzw. alles, was zu diesem fehlt, ergänzt wird. In diesem Beispiel ist von der URL nur der Query-String angegeben worden.
  • Die "Query" kann bei uns völlig beliebig aussehen oder auch wie im Beispiel leer (nur das Fragezeichen als Query-Trennzeichen muß bleiben) sein. Wir wollen ja nur eine Aktualisierung der Anzeige haben, wozu es reicht, KEINE Aktion bei der Anfrage auszulösen. Statt der leeren Query könnten wir natürlich auch den Dateinamen (bis hin zur kompletten absoluten URL) dort eintragen - Hauptsache daß hinter dem "url=" IRGENDWAS an gültiger URL-Variante steht -, hätten aber mehr zu schreiben und keinen Vorteil.

Für interessierte Hinterfrager: Aber von solcherart Weiterleitungen wird doch in der Wikipedia abgeraten?

Die Zeitverzögerung kann man wieder ausgeklügelt steuern oder auch grob ansetzen, je nach Lust des Programmierers und gewünschtem Kompromiß hinsichtlich Benutzbarkeit:
  • Wenn mitten im Spiel die Wartezeiten zu groß sind, wird es langweilig.
  • Wenn in der Anmeldephase die Wartezeiten zu kurz sind, kann man nicht vernünftig seinen Namen schreiben, ehe einem das Formular unter den Fingern weggerissen und durch ein neues ersetzt wird.
  • Mit kürzeren Wartezeiten steigt die Serverlast. Wenn Sie also mal eine Seite bauen, auf der Sie VIELE Spiele hosten, kann ein sinnvoller Kompromiß relevant werden.
  • Wenn über einen Browser beide Seiten gespielt werden, ist für diesen ein Refresh komplett überflüssig.

Wenn Sie mal daran Gefallen finden, einen Spiele-Host aufzubauen, werden Sie wahrscheinlich doch beim aktiven Nachladen mittels Javascript landen. Bis dahin haben Sie hoffentlich genügend Einblick in Hack-Techniken gefunden, so daß Sie zu einem VERANTWORTLICH handelnden Spielehoster werden: Einer, der dort, wo er Werbung und Foren anbietet, keine aktiven Komponenten erzwingt. Der also seine Spiele (mit aktiven Komponenten) von einem extra Server anbietet, der weder Werbung noch sonstwelche relativ Hack-anfälligen Webanwendungen entält! Leider ist solch ein Verhalten heutzutage nur homöopatisch dosiert zu finden. Facebook ist ein Paradebeispiel, wie man seine Kundschaft aus paranoider Geschäftemacherei heraus geradezu vorsätzlich dazu verleiten kann, sich Malware reinzuziehen.

Zurück zu unserer Kompromißlösung: Wir richten also für die unterschiedlichen Spielphasen unterschiedliche Refresh-Zeiten ein. Außerdem wäre es höflich, dem Nutzer eine Möglichkeit zu bieten, den Auto-Refresh zeitweilig abzuschalten, damit er beim Anmelden in aller Ruhe seinen Namen schreiben kann. Wir könnten dafür wieder mal einen extra Button vorsehen. Außerdem könnten wir das Auto-Refresh standardmäßig deaktivieren, solange ein Spieler noch gar keinen Namen angemeldet hat. Dann kann er zu Anfang in aller Seelenruhe sich anmelden. Erst, wenn er wirklich aktiv werden will - also sich angemeldet hat - oder wenn er sich explizit zum Beobachten entschließen will - woraufhin er das Autorefresh über den einzurichtenden extra Button explizit starten kann -, wird er eine Aktualisierung erhalten...

Über die genauen Zusammenhänge dieser Abhängigkeiten könnten wir uns durchaus einen Kopf machen, denn die intuitive Benutzbarkeit der Anwendung hängt davon ab, ob wir uns hier logische Schludrigkeiten leisten oder es ordentlich machen...
  • Die Entscheidung des einzelnen Besuchers, Auto-Refresh zu benutzen oder nicht, sollte immer die Oberhand haben: Wenn der Spieler sich entscheidet, das Auto-Refresh abzustellen, dann hat es gefälligst auszubleiben. Bzw. andersrum!
    Das bedeutet, daß diese Benutzer-Entscheidung alle anderen automatisch getroffenen BEI BEDARF überschreiben können muß! Dennoch sollte Umschaltungen zu Auto-Refresh-Modi standardmäßig nach den Regeln erfolgen, die für die Anwendung in den MEISTEN Fällen am sinnvollsten sind. Die Automatik-Entscheidungen sollten also bei Änderungen des Zustands, die für den Besucher WICHTIG sind, immer erstmal greifen, aber wenn der Benutzer sich dann dagegen entscheidet, soll er sie außer Kraft setzen (also mit anderen Worten: umschalten) können.

    Zur Implementierung würde ich empfehlen, einfach eine manuelle Steuerung vorzusehen und diese, nachdem die fertig ist, um eine Automatik zur "ereignisgesteuerten" Vor-Einstellung zu ergänzen!

  • Die Entscheidung zum Auto-Refresh muß gespeichert werden, weil ein Auto-Refresh ja eben nicht nur bei einer Nutzer-Aktion erfolgen soll, sondern gegebenenfalls fortlaufend hintereinander und auch ohne weitere Nutzeraktionen.

  • Die Entscheidung des einzelnen Besuchers ist eine Besucher-spezifische (sprich: individuelle) und gehört daher nicht im Spielzustand gespeichert (denn dann wäre sie ja eine Spiel-spezifische und somit einheitlich für sämtliche Spielteilnehmer und -zugucker gültig, gelle?)! Wie man sowas speichert, hatten wir bereits mit dem Speichern eines Spielzustands im Browser des Besuchers durchexerziert. Allerdings bringt in der derzeitigen Organisationsform (mit den vielen kleinen Formularen bzw. einfachen Links) eine Speicherung im Browser relativ viel Aufwand mit sich.
    Für diesen Zweck können wir eine Speicherung von Daten auf dem Server nutzen, die abhängig von der Session-Kennung - sprich: abhängig vom Browser - und bereits fix und fertig eingebaut ist. Es gibt dort - analog zu den Ihnen bereits bekannten Superglobals $GLOBALS, $_SERVER und $_REQUEST (alias $_GET und $_POST) noch die ebenfalls superglobale vordefinierte Variable $_SESSION, die aber nur genau dann bereitgestellt wird, wenn Sie eine Session mit session_start() in Benutzung genommen haben (was bei uns ja inzwischen der Fall ist).
    Alles, was Sie in dieser Variablen speichern, wird auf dem Server für eine gewisse Zeit bleibend ("persistent") gespeichert und beim nächsten session_start genau wie vorher in dieser Variablen wiederhergestellt. Es ist dasselbe wie unsere Speicherung im Browser, nur daß diese auf dem Server verbleibt und Sie sich erstmal keinen Kopf um das Speichern und Laden machen müssen, weil alles bereits fertig eingebaut ist.

    Zur Implementation würde ich empfehlen, das Auto-Refresh unmittelbar von dem in der Session gespeicherten Zustand steuern zu lassen, und diesen anschließend von dem im Punkt vorher skizzierten Mechanismus bei entsprechenden Aktionen umschalten zu lassen.

Auf geht's!
Auto-Refresh einrichten
Aufgaben:
  • Legen Sie ein Element in der $_SESSION-Variablen an, um den boolschen Wert zu speichern, ob ein Auto-Refresh erfolgen soll! (Sie könnten dieses Element zum Beispiel 'autorefresh' nennen)
    Denken Sie daran, vorher die Session zu starten, sonst gibt's $_SESSION noch nicht!
    Falls Sie sich Hornhaut auf den Fingern ersparen wollen, können Sie auch wieder eine Referenzvariable für den Zugriff auf dieses Array-Element anlegen! (Sie könnten diese $autorefresh nennen...)
  • Initialisieren Sie diese Variable gleich beim Anlegen zunächst zu false!
    Tun Sie das aber nur, wenn diese noch nicht existiert! Denn sobald Elemente in der $_SESSION einmal angelegt sind, werden sie am Ende eines PHP-Scripts immer automatisch gespeichert und beim nächsten Abruf eines Scripts mit session_start() durch dieses wiederhergestellt. Was ja auch der tiefere Sinn der ganzen Sache ist...

    Um dies sinnvoll mit dem Anlegen einer Referenz auf das Session-Element zu verbinden, würde ich Ihnen raten,
    1. ERST die Referenz auf das gewünschte Element anzulegen
    2. und DANN zu testen, ob das Element bereits "gesetzt" wurde,
    3. und DARAUFHIN bei Bedarf den Initialwert einzutragen
  • Lassen Sie in dem Fall, daß diese Variable true wäre, unter den <meta>-Tags des Spiel-HTML-Kopfes ein meta-refresh wie weiter oben abgebildet einsetzen!
    Testen Sie zwischendurch, ob das funktioniert, indem Sie zum Testen einen festen true-Wert in der Bedingung einsetzen!
  • Lassen Sie die Refresh-Zeit in Abhängigkeit vom $dran-sein eines Spielers verkürzen (zum Beispiel von 15 auf 3 Sekunden)!
  • Lassen Sie den boolschen Wert Ihrer $autorefresh-Variablen (oder wie immer Sie die genannt haben) in Abhängigkeit der von Ihnen gewünschten Regeln automatisch ein- und ausschalten.
  • Bauen Sie einen sogenannten "Toggle-Button" zum Umschalten des Auto-Refresh in Ihr Bedien-Panel!

Im Beispielcode sehen Sie, was aus der Anregung nach Zusammenfassung der logischen Zusammenhänge entstehen könnte...

Zu guter Letzt noch eine Schönheitskorrektur: Wenn Sie das Spiel jetzt mal ausprobieren, fällt Ihnen zum Schluß auf, daß die Statusmeldung über den Spielausgang bisher nur bei demjenigen Spieler kommt, der den letzten Zug in einer Runde macht. Im Singleplayer-Modus war das OK, weil ja dort eh alle eventuell am selben Bildschirm zusammen spielenden Spieler die Meldung an eben diesem einen Bildschirm zu Gesicht bekamen. In einem Multiplayer-Spiel gehört es sich aber, daß diese Meldungen an alle Mitspieler und auch an die Zugucker ausgegeben werden. Also an alle Browser, die den Spielzustand reihum regelmäßig abfragen. Bei einer Fehlermeldung im Falle eines ungültigen Zuges wäre es tolerierbar, wenn nur der verursachende Spieler diese zu Gesicht bekommt. Bei Meldungen zum Zustand des Spieles insgesamt dagegen müssen alle Zuschauer Bescheid kriegen!

Dazu muß die Statusmeldung ebenfalls zu einem Element des Spielzustands gemacht werden, das auch über die auslösenden Aktion hinaus erhalten bleibt. Sie kennen die Prozedur bereits...
Aufgaben:
  • Führen Sie ein neues Element für den Status im Spielzustand ein! (Nein, korrekter: Verlagern Sie die ja bereits vorhandene Variable $status in den Spielzustand hinein!)
  • Passen Sie den Rest des Programmcodes an oder führen Sie eine weitere Referenzvariable (an der richtigen Stelle) ein, die genauso heißt wie die frühere alleinstehende Variable und Ihnen diese Anpassung erspart!
  • Sorgen Sie dafür, daß die Statusmeldung beim Start der nächsten Runde geleert wird (bisher war das implizit sichergestellt, weil die Statusmeldung bei jeder Aktion nur für die eine Aktions-Abfrage aus dem Leerzustand aufgebaut wurde; mit dem persistenten Speichern müssen wir sie explizit Bei Bedarf leeren).
  • Gleichermaßen müssen Sie in allen Aktionszweigen, die zu keiner (also einer leeren) Statusmeldung führen sollen, dieselbe aktiv leeren lassen!

Ihr Code könnte danach in etwa SO aussehen...

Kleine Pause vor dem Endspurt!

Schutz der Spielerkennung gegen Hacking

Letztes Thema für heute: Sicherung von Sessions gegen Hacking...

Wir hatten heute die "Sitzungen" alias "Sessions" eingeführt - und zwar zunächst für die eindeutige Kennzeichnung von Spielern (bzw. deren Browsern), zum Schluß auch noch zum Speichern einer individuellen Einstellung einer Spieloption für die Spieler. Anschließend haben wir auf Basis der Spielerkennungen die Vergabe von Rechten für Spielaktionen organisiert.

Es sollte leicht einsehbar sein, daß diese Kennungen bei ernsthaften Anwendungen kritische Angriffspunkte für Hacker darstellen: Wenn ein Hacker die Sitzungskennung eines Internet-Surfers auf einem Server herauskriegt, kann er anschließend mit seinem Browser gegenüber diesem Server so tun, als ob er die Person wäre, deren Sitzungskennung er gerade gestohlen kopiert hat.
Die Sitzungskennung hat also eine gleich hohe Wertigkeit wie Anmeldedaten: Sie berechtigt den Besitzer zu all den Handlungen, die der Server aufgrund seiner eingebauten Logik und bereits erfolgter Handlungen des legalen Besitzers dieser Sitzungskennung zugesteht.

Bei unserem Spiel ist da zwar noch nicht viel, aber nichtsdestotrotz durchaus etwas erkennbares zu holen: Wir haben ja nun Rechte eingerichtet, und zwar um im Laufe eines Spieles böses Blut zu vermeiden, falls Gedränge entstehen sollte. Nach dem Motto "wer zuerst kommt, malt zuerst". Wer einen Platz am Brett besetzt hat, behält ihn, bis er ihn freigibt oder eine Weile nichts mehr macht.
Wenn wir nun die Sitzungskennung eines am Brett sitzenden Spielers stehlen kopieren und im Browser als Cookie eintragen könnten, könnten wir diesen Spieler gewissermaßen von seinem Platz schubsen. Nun ja: Nicht "richtig" wegschubsen, denn er säße nach wie vor immer noch auf seinem Platz. Denn er BEHÄLT seine Kennung ja immer noch. Deshalb hier das wiederholte Durchstreichen der Bezeichnung "stehlen". Aber wir könnten ihn doch kräftig ärgern! Wobei er natürlich immer noch mit gleicher Münze zurückzahlen könnte. Aber auf jeden Fall könnte er nicht mehr ruhig spielen. Wir hätten gewissermaßen ein Instrument zum Mobben und Pisacken in der Hand!

Was wir uns jetzt ansehen ist, wie man eine Sitzungskennung stielt kopiert, und wie man sich, nachdem man weiß wie es geht, davor schützt...
Raubkopie einer Sitzungskennung
1. Sitzungskennungen erschnüffeln
Sitzungskennungen gehen genauso wie Anmeldedaten im Klartext über die Leitung, wo sie jeder sehen (und kopieren) kann. Wir werden uns jetzt diese Daten zwischen dem Browser und dem Webserver ansehen. Dazu machen wir ein Experiment mit einem sogenannten "Schnüffler"-Programm (englisch "sniffer") namens Wireshark.

Aufgabe: Wireshark downloaden und installieren!
Danach starten!

Das Programm kann eine Masse von Analysen ausführen, die wir aber jetzt alle ignorieren. Wir schmeißen es nur kurz für dieses Experiment an.

Auf dem Startbildschirm wählen Sie den Netzwerkadapter aus, der Ihren Rechner ans Internet bringt! Wie der konkret heißt bzw. welcher das im konkreten ist, hängt vom konkreten Rechner ab. Mit etwas gesundem Menschenverstand kommen Sie aber ans Ziel:
  • Es geht in aller Regel in einem Klassenraum um einen "Ethernet"-Adapter!
  • Der ist in den "Eigenschaften" der "Netzwerkverbindung" zu finden!
Folgen Sie dazu einfach den 5 Klicks im Bild!



Daraufhin startet die Protokollierung. Es kann passieren, daß alle möglichen von uns nicht benötigten Programme im Hintergrund wie wild im Internet herumwüten. Das ignorieren wir erstmal eisern.

Anschließend starten Sie hier auf dieser Lehrgangs-Seite hier über diesen Link ein TicTacToe auf meinem Server! Spätestens dann füllt sich das Logfenster vom Wireshark mit Einträgen.

Sofort danach schalten Sie durch Klick auf den Stop-Schalter die weitere Protokollierung wieder ab!



Daraufhin suchen Sie sich in der entstandenen giftgrün leuchtenden Protokoll-Liste eine Zeile, die das TicTacToe-Spiel (oder ein anderes Dokument von meinem Server) in der "Info"-Spalte erahnen läßt! Dort klicken Sie mit Rechts irgendwo in die Zeile und wählen im Kontextmenü "Follow TCP Stream" aus!



Das bringt Sie zur Detailansicht der an diesem Netzwerkanschluß vorbeigelaufenen Daten für das betreffende Dokument...












Und dort erkennen Sie im unteren Bereich der rosa hinterlegten Anfrage die Session-Kennung (Cookie: general_session_id... usw.)



Vereinfachung für das Experiment / Ein Wort zu realen Angriffen: Was Sie jetzt gemacht haben, war Ihren eigenen Netzwerkverkehr zu beobachten. Sie könnten das mit etwas Anstrengung auch so hinbiegen, daß Sie innerhalb des lokalen Netzes des Unterrichtsraumes den Netzwerkverkehr der anderen Lehrgangsteilnehmer mitgeschnitten kriegen. Dazu gehört allerdings in heutzutage üblichen LAN-Verkabelungs-Topologien meist eine "höhere" Netzwerktechnik: ...die wir jetzt nicht einsetzen werden, weil ich dazu in der konkreten Umgebung des Unterrichtsraumes ein bißchen was vorbereiten müßte. Eventuell wird das in zukünftigen Durchgängen aufgenommen.

Im Moment erlauben Sie sich bitte gegenseitig einen Blick auf die Bildschirme, um sich davon zu überzeugen, daß Sie mit dem Wireshark den Verkehr zu sehen bekommen können!
Sitzungskennungen kopieren
Als nächstes geht es darum, eine solcherart erschnüffelte Sitzungskennung zu kopieren und zum Beispiel in einem Browser des Angreifers weiterzuverwenden. Dazu benutzen Sie das Firefox-AddOn "Advanced Cookie Manager".

Aufgabe: Installieren Sie dieses AddOn!

In Ihrem eigenen Browser sollten Sie wieder das TicTacToe vom Lehrmaterial ansteuern! Wenn Sie auf der Seite sind (oder das Fenster oder den Tab wieder in den Vordergrund geholt haben), klicken Sie unten rechts auf das Symbol für den Cookie-Manager!



Daraufhin öffnet sich ein Dialogfenster, in dem Sie zuerst noch ganz links den richtigen Server auswählen müssen und unter Umständen in der Mitte das richtige Cookie, woraufhin Sie dieses rechts verändern können.

Vereinfachung: Damit Sie jetzt nicht in die Verlegenheit kommen, so eine elendig lange Zufallszahl abtippen zu müssen, habe ich Ihnen die Sache etwas erleichtert. Hier im nächsten Kästchen finden Sie die Session-ID's aller Benutzer der letzten halben Stunde, die bei einem TicTacToe im Lehrgang vorbeigeschaut haben. Damit man mit dem Zeug kein Schindluder treiben kann, habe ich den Geltungsbereich dieser Session-Cookies auf die hier eben verlinkte konkrete Version des TicTacToe-Spiels beschränkt. Sie brauchen jetzt bloß noch vergleichen, welches Cookie nicht zu Ihnen selbst gehört und haben schon eines Ihrer Kollegen in der Hand. Wenn Sie Lust haben, probieren Sie mal jetzt noch das tatsächliche Übernehmen einer Sitzung von einem Kollegen aus! Sie müssen dazu natürlich ein klein wenig kooperieren, also dem Sparring-Partner Zeit lassen, in das kleine Spiel einzusteigen, während es noch im Gange ist!
Tipp: Falls Sie das Spielchen hier mal allein durchziehen wollen oder müssen, nehmen Sie einfach zwei unterschiedliche Browser!

vTOVNz7mxmWiVvBh1aPfT3Mvt4z9fu31uyQJ43kK9pd

Nachdem Sie die Sitzungskennung Ihres Kollegen kopiert haben und mit diesem Browser auf das hier verlinkte TicTacToe zugreifen, können Sie anstelle Ihres Kollegen ziehen oder auch ihn abmelden oder umbenennen. Testen Sie das einmal!

Schutz dagegen
Der Schutz gegen den Session-Diebstahl ist vom Grundsatz her recht einfach: Wir verschlüsseln - wieder mal (wir kennen das schon, gelle?).
Wobei man aus Randbedingungen der Sache auch schon wieder eine Wissenschaft für sich machen kann (zum Beispiel aus der Behandlung der Übergänge zwischen unverschlüsselten und verschlüsselten Bereichen mit Session-Wechsel und selektiver Übernahme von Session-Inhalten). Das wollen wir aber im Moment noch nicht ausloten.

Wir werden einfach nur die Verschlüsselung von Webserver-Abfragen mittels "HTTPS" einrichten. Damit - werden wir sehen - entfällt die Möglichkeit, den Datenstrom einzusehen.

Die HTTPS-Übertragung muß in der Webserver-Konfiguration eingestellt werden. Um das für die Übung abzukürzen, greifen Sie mal wieder auf das Lehrmaterial auf meinem Privatserver zurück. Klicken Sie einfach hier, um das TicTacToe per HTTPS einzusehen!

Schmeißen Sie jetzt wieder Ihren Wireshark an und starten bei dem das Protokollieren wie vorhin schon mal!
Lassen Sie dann die Seite mit dem TicTacToe über HTTPS nochmal neu laden!
Stoppen Sie dann die Protokollierung wieder!

Jetzt sehen Sie im Protokoll ein paar Zeilen, wo von einem "Key Exchange" oder von "Encrypted Alerts" die Rede ist. Darunter folgen dann Zeilen mit "Application Data". Auf eine solche "Application Data"-Zeile klicken Sie mit Rechts und fordern eine Verfolgung des "TCP-Streams" an! (Genau wie vorhin, nur daß jetzt keine "GET"- und "POST"-"Anfragen" mehr zu erkennen sind...)

Das Ergebnis sollte in etwa so aussehen:



...Von Anfrage und Antwort bleibt nach außen hin nichts als Schrott übrig.



Dasselbe können Sie bei Ihrem Apache-Webserver im XAMPP auch machen: Der XAMPP ist vorkonfiguriert für HTTPS: Sie brauchen bloß in der Adreßleiste vor den Rechnernamen (das "localhost") "https://" setzen, und schon sind Sie per Verschlüsselung mit dem Webserver verbunden.

Die "ordentliche" Einrichtung einer Verschlüsselung erfordert einiges an Aufwand und Verständnis für Verschlüsselungssysteme, was den Rahmen dieses Lehrgangs um mehr als einen Tag sprengen würde. Falls Sie interessiert sind, finden Sie auf der "Raven Homepage" von Kai Billen (Nickname Kai Raven) die zugleich älteste, umfassendste und meiner Meinung nach bestverständliche Einleitung ins Thema, wo Sie mit dem Einarbeiten in GnuPG anfangen sollten! Nach Einlesen in die Funktionsweise von Kryptographie im allgemeinen, PKI im speziellen und nach Einspielen in PGP alias GnuPG werden Sie das Grundverständnis für die Zusammenhänge erreicht haben. Dann geht es nochmal in eine zweite Runde mit OpenSSL, mit dem diese PKI nochmal auf eine ganz eigene spezielle Art und Weise praktisch für das Verschlüsselungssystem im Wilden Weiten Web umgesetzt wird.

Neben einer ordentlichen Verschlüsselung sind dann für einen gegen Hacking sicheren Serverbetrieb nochmal ein paar Kleinigkeiten rund um die Verwaltung der Sitzungskennung zu beachten, deren Verständnis nach Einarbeitung in die gerade genannten Themen möglich wird. Wenn Sie Blut geleckt haben, könenn Sie dafür einen extra Lehrgang besuchen...

Endzustand heute
Wir haben jetzt einen Zustand erreicht, in dem zwei Spieler an EINEM Brett spielen und beliebig viele Besucher zuschauen können. Wir haben das Ganze mit einem ordentlich organisierten Rechtesystem ausgestattet, so daß die Spieler nicht durch Fieslinge oder dumme Versehen belästigt werden können.

Was fehlt, ist eine Möglichkeit, viele Spieler gleichzeitig an vielen Bretter spielen zu lassen: Wir haben nur ein einziges, globales Brett, an dem zwei Spieler Platz finden können (oder einer, der beide Seiten gleichzeitig spielt). Wir benötigen die Möglichkeit, beliebig viele Bretter einrichten zu können! Und eine Möglichkeit, sich schnell mal einen Partner zu suchen oder aber mit einem gewünschten Partner an einem freien Brett zusammenzufinden!

Auch die Möglichkeit des Simultanspiels, die wir in der Einzelspielerversion vom Ende der letzten Lektion noch hatten, wäre eine nette Ergänzung für Multiplayer-Umgebungen.

Dies werden wir durch einen Ausbau der Sitzungsverwaltung in der nächsten Lektion nachrüsten...



Impressum
Email
aktualisiert: 2015-05-14 23:24