Java Projekt: Spieleserver TicTacToe
Rechnerpraktikum aus Programmierung
2004-01-13
Susanne Hafner – 0152203
Rechnerpraktikum aus Programmierung im WS 2003/04
unter der Leitung von Dr. Michael Hahsler
Das Projekt in der Lehrveranstaltung Rechnerpraktikum aus Programmierung hat den Umfang von etwa der halben Lehrveranstaltung und umfasst Analyse, Design, Implementierung und Dokumentation eines Softwaresystems. Ziel dieses Projekts ist es, einen Spieleserver für ein einfaches Spiel mit zwei Mitspielern zu implementieren. Ich habe mich für TicTacToe entschieden. Um das Projekt trotz geringer Programmiererfahrung in dieser kurzen Zeit umsetzen zu können, habe ich auf eine grafische Benutzeroberfläche vorerst verzichtet und den Schwerpunkt des Projekts auf die Lauffähigkeit des Programms in einem Netzwerk gerichtet. Es soll möglich sein einen Mitspieler zu einem Spiel einzuladen und mit diesem eine beliebige Anzahl an Runden zu spielen. Das Spiel läuft nach den üblichen Regeln eines TicTacToe – Spiels ab: Ein Spieler setzt X, der andere O, die Spieler sind abwechselnd an der Reihe. Gewonnen hat, wer als erster drei seiner Symbole waagrecht, senkrecht oder diagonal in einer Reihe hat. Das Spiel endet unentschieden, wenn alle Felder besetzt sind, aber kein Spieler drei Symbole in einer Reihe hat. Um eine Art Spielestatistik zu implementieren, soll für jeden Spieler die Anzahl der gewonnenen, verlorenen und unentschiedenen Spiele gespeichert werden, so lange dieser online ist.
Dieses Use-Case-Diagramm beschreibt die wichtigsten Anwendungsfälle die während einer Verbindung mit dem Spieleserver auftreten können. Dabei beinhaltet der Use-Case „einloggen“ die Verbindung mit dem Server und die Eingabe des eigenen Spielernamens. Ist dies geschehen und hat der Spieler einen gültigen Spielernamen eingegeben, kann er einen Mitspieler zu einem gemeinsamen Spiel einladen. Ansonsten bleibt die Verbindung zum Server aufrecht, der Spieler muss jedoch einen anderen – gültigen – Namen eingeben.
Mitspieler einladen setzt neben einem gültigen Login auch eine gültige Eingabe für einen Mitspieler voraus: Eine Einladung ist nur möglich, wenn der gewünschte Spielpartner am Server eingeloggt ist und zur Zeit nicht mit einem anderen Spieler spielt. Es soll weiters verhindert werden, dass der Spieler sich selbst einlädt.
Nach erfolgreicher Einladung startet ein neues Spiel. Der Spieler kann einen Stein setzen. Dazu muss er jedoch an der Reihe sein (ist er dies nicht, soll eine Meldung erscheinen, dass der Spieler warten muss) und in ein mögliches Feld setzen: Möglich sind alle freien Felder am Spielfeld, welches von eins bis neun durchnummeriert ist. Im Fall einer ungültigen Eingabe soll eine Fehlermeldung erscheinen.

Folgende grundlegende Struktur wurde für die Problemlösung entworfen.

Der Server horcht an einem Port und wartet bis ein Benutzer eine Verbindung herstellen will. Der Server erzeugt für jeden Benutzer einen Thread. Der Thread wiederum erzeugt für seinen Benutzer ein Protokoll, über das alle Eingaben verarbeitet werden können. Weiters soll das Protokoll eine Spielerinstanz erzeugen. Für ein Spiel werden zwei Spieler benötigt.
Im Folgenden werden die einzelnen Klassen im Detail dargestellt:

Importiert werden:
import java.net.*;
import java.io.*;
import java.util.*;
Variablen
ServerSocket serverSocket
Die Variable serverSocket speichert ein Socket und hat Initialwert null.
boolean listening = true
listening ermöglicht das Öffnen eines neuen Threads, wenn Verbindung zum Server hergestellt wird
LinkedList liste = new LinkedList()
Die Variable liste wird als leere Liste initialisiert.
In der Liste wird jeder erzeugte Thread gespeichert. So ist es später einfach möglich Namen oder auch andere Daten der Online-Spieler zu suchen, da über den gespeicherten Thread auf all diese Daten zugegriffen werden kann.
Methoden
public void run ()
Horcht am Port 2708. Kann eine Verbindung aufgebaut werden, erzeugt diese Methode den Thread, fügt ihn der Liste hinzu und startet ihn.
public void addThread (TTTThread t)
Diese Methode fügt einen Thread der LinkedList liste hinzu. Dies ist notwendig, damit Online-Spielerlisten erzeugt werden können.
public void removeThread (TTTThread t)
Diese Methode löscht einen Thread aus liste
public String zeigeSpieler (Spieler spieler)
Diese Methode sucht in der Threadliste nach inaktiven Spielern und gibt diese als String zurück bzw. eine Meldung, dass keine inaktiven Spieler verfügbar sind.
public String zeigeStatistik ()
Sucht aus liste alle Spielerinstanzen der jeweiligen Threads und gibt diese zurück.
public boolean pruefeName (Spieler spieler, String spielername)
Auch diese Methode arbeitet mit der Threadliste. Sie prüft ob spielername ein existierender Spielername ist und nicht mit dem Namen von spieler übereinstimmt.
public boolean pruefeLogin (String spielername)
Diese Methode durchsucht die Threadliste nach spielername. Sie liefert true, wenn noch kein entsprechender Spielername existiert.
public TTTProtocol getProtocolGegner (String spielername)
Diese Methode liefert das Protokoll zu spielername aus der Liste.

Importiert wird :
import java.io.*;
Methode
public static void main (String[ ] args)
Das ist die Mainmethode um den Server zu starten. Sie erzeugt eine Instanz von TTTServer und startet von diesem die Methode run().

Die Klasse TTThread ist abgeleitet von Thread.
Importiert werden:
import java.net.*;
import java.io.*;
import java.util.*;
Variablen
private Socket socket
Wert wird von Server mitgeliefert.
private TTTProtocol protokoll
Speichert das Protokoll, das von TTTThread erzeugt wird.
private TTTServer server
Speichert den Server, durch den TTTThread erzeugt wird.
PrintWriter out
BufferedReader in
Speichern Buffer zur Input/Output-Verarbeitung.
Methoden
public TTTThread (Socket socket, TTTServer server)
Der Konstruktor weist den Variablen socket und server die mitgelieferten Werte zu und erzeugt eine neues Protokoll, das unter der Variable protokoll abgespeichert wird.
public void run()
Die Run-Methode von Thread wird überschrieben. Es werden die Buffer erzeugt und Eingaben verarbeitet. Weiters werden hier wieder Sockets geschlossen, der Thread aus Threadliste entfernt und dann geschlossen.
public void ausgeben (String s)
Ermöglicht die Ausgabe von einem anderen Protokoll aus.
public Spieler getSpieler ()
public int getOnlinestatus ()
public String getSpielername ()
public TTTProtocol getSpielerprotokoll ()
public TTTProtocol getProtocol2 ()
get-Methoden liefern jeweilige Variablen und sind unter anderem für die LinkedList am Server wichtig.

Variablen
private TicTacToe spiel
speichert das Spiel, wenn Spieler eines startet.
private Ratespiel allein
speichert Spiel zum Zeitvertreib.
private Spieler spieler
speichert Spielerinstanz.
private Spieler spieler2
Speichert Spielpartner, wenn gespielt bzw. eingeladen wird.
private TTTProtocol protocol2
Speichert das Protokoll des Spielpartners.
private TTTServer server
Speichert den Server.
private TTTThread thread
Speichert den Thread
Methoden
public TTTProtocol (TTTServer server, TTTThread thread)
Konstruktor. Server und Thread werden gespeichert. Neue Spielerinstanz wird erzeugt und als spieler gespeichert.
public String processInput (String inputLine)
Hier werden alle Eingaben des Benutzers verarbeitet (Name eingeben, Spieler einladen, Stein setzen, etc.)
public String welcome ()
Gibt Begrüßung zurück.
public String help ()
Es werden alle möglichen Eingaben und der Spielablauf erklärt.
public String rateZahlen ()
Gibt den Begrüßungstext des Ratespiels zurück.
public Spieler getSpieler ()
public int getOnlinestatus ()
public String getSpielername ()
public TTTProtocol getSpielerprotokoll ()
public TTTProtocol getProtocol2 ()
Get-Methoden für Spieler und Spielpartner.
public void setSpiel (TicTacToe spiel2)
public void setProtocol2 (TTTProtocol pr)
public void setSpieler2 (Spieler sp)
public void setOnlinestatus (int i)
Set-Methoden für Spielpartner (notwendig wenn eingeladen wird bzw. Spiel gestartet wird).
public void ausgeben (String x)
Ermöglicht die Ausgabe von x an den Mitspieler-Thread.

Variablen
private String spielername
Speichert den Spielernamen.
private TTTProtocol spielerprotokoll
Speichert das Protokoll des Spielers.
private int onlinestatus
Speichert den Onlinestatus des Spielers:
0 online, noch kein Name eingegeben
1 Name bereits eingegeben, inaktiv
10 Spieler hat eingeladen (muss noch auf Antwort warten)
11 Spieler wurde eingeladen (muss noch antworten)
12 Spieler spielt Ratespiel
2 Spieler spielt TicTacToe
private int gewonnen
private int verloren
private int unentschieden
Speichert die Anzahl der gewonnenen, verlorenen und unentschiedenen Spiele.
private int xo
Speichert den Wert für X oder O oder hat Wert 0, wenn kein Spiel läuft.
Methoden
public String toString () ToString-Methode von Spieler. Gibt Spielername und gewonnen/ unentschieden/ verloren zurück public String getSpielername ()public TTTProtocol getSpielerprotokoll ()public int getOnlinestatus ()
public int getXo ()Get-Methoden public void setSpielername (String newSpieler)public void setSpielerprotokoll (TTTProtocol newOne)public void setOnlinestatus (int i)public void setXo (int i)Set-Methoden public void zaehleGewonnen ()public void zaehleVerloren ()public void zaehleUnentschieden ()Zählt zur entsprechenden Variable eins dazu.

Variablen
Spieler spielerX
Spieler spielerO
Speichert die Spielerinstanzen
int amZug
Wird mit xo des jeweiligen Spielers belegt, der am Zug ist
int gespielteZuege
Speichert die Anzahl der gespielten Züge.
public int[ ] spielfeld
Stellt das TicTacToe – Spielfeld als Integerarray dar.
final int FREE = 0;
final int X = 1
final int O = 5
Konstanten, die denen einzelnen spielfeld-Elemente zugewiesen werden können. Durch die Werte eins bzw. fünf für X und O können Gewinnsituationen leicht berechnet werden.
public int status
Speichert eine der weiter unten genannten Konstanten, um darzustellen, ob Spiel zu Ende/unentschieden ist oder noch weiter geht.
final int WEITER = 0
final int FERTIG = 1
final int UNENTSCHIEDEN = -1
Konstanten, die status zugewiesen werden können.
Methoden
TicTacToe()
Konstruktor.
public int getAmZug()
Gibt xo des Spielers zurück, der gerade am Zug ist.
public int getGegner(int amZug)
Gibt xo des Spielers zurück, der gerade nicht am Zug ist.
public int gibtsGewinner()
Sucht nach drei gleichen Symbolen waagrecht, senkrecht oder diagonal und gibt – wenn vorhanden – Gewinner durch X oder O zurück, UNENTSCHIEDEN, wenn alle Felder besetzt sind oder WEITER, wenn ein nächster Zug möglich ist.
public String setzeStein(int i)
Überprüft ob ein Setzen auf spielfeld [i] möglich ist, führt den Zug – wenn möglich durch – wechselt, wenn es noch keinen Gewinner gibt, den Spieler, der einen Zug machen darf (amZug wechselt von X auf O oder umgekehrt) und gibt einen String über das Ergebnis zurück.
public String printSpielfeld()
Gibt das Spielfeld als 3x3-Feld aus. Unbesetzte Felder zeigen einen Bindestrich, besetzte Felder zeigen X oder O.

Diese Klasse war im ursprünglichen Design nicht vorgesehen. Sie wurde jedoch implementiert, um die Wartezeit auf einen Mitspieler zu verkürzen. Es wird dadurch ein Spiel für einen Spieler implementiert. Der Spieler muss versuchen eine Zahl zwischen null und 100 mit möglichst wenigen Versuchen zu erraten.
Variablen
int zahl
Speichert die Zufallszahl, die erraten werden muss.
int anzahlZuege
Speichert die Anzahl der abgegebenen Tipps.
Spieler spieler
Speichert die Spielerinstanz, die dieses Spiel startet.
TTTProtocol protocol
Speichert das Protokoll der Spielerinstanz
Methoden
Ratespiel(Spieler spieler)
Konstruktor. Spieler wird der Variable spieler zugewiesen, anzahlZuege wird auf 0 gesetzt, Zufallszahl wird erzeugt.
public String raten(int i)
i wird mit zahl verglichen. Es wird ein String mit einem Lösungshinweis zurückgegeben oder einer Gratulation, wenn zahl erraten wurde.
Das Protokoll

Sequenzdiagramm
Dieses Sequenzdiagramm zeigt die Vorgänge, die beim Einloggen am Server passieren.

Hier werden die Abläufe während eines Spiels dargestellt

Die Implementierung erfolgte mit Hilfe des Editors JOE (http://www.javaeditor.de/) sowie des JDK 1.4.1 auf Rechnern mit den Betriebssystemen Windows 2000 bzw. Windows XP. Getestet wurde das Programm auf mehreren Rechnern (Windows 98, Windows ME, Windows 2000, Windows XP) isoliert sowie über ein Netzwerk.
Der Anwender muss sich über telnet (entweder über die Eingabeaufforderung oder einen telnet-Client) am Server einloggen. Dazu sind IP und Host des Servers anzugeben. Da der Server von einem Benutzer in der derzeitigen Form selbst gestartet werden muss, kann eine allgemeingültige IP an dieser Stelle nicht angegeben werden. Läuft der Server am eigenen Rechner, kann localhost als IP eingegeben werden. Als Port ist 2708 anzugeben.
Hat die Verbindung mit dem Host geklappt, so muss als erstes der Spielername mit dem Kommando „#name“ plus dem gewünschten Spielernamen eingegeben werden.
Mögliche Kommandos sind mit „#help“ abrufbar. Hier eine Zusammenfassung:
#help Anzeige der Hilfe
#ww Anzeige aller Spieler, die eingeladen werden können
#who Anzeige aller Online-Spieler in der Form
Spieler (gewonnen/unentschieden/verloren)
#allein startet das Ratespiel zum Zeitvertreib (der Spieler kann während eines
Spiels zu einem TicTacToe eingeladen werden!)
#ask +name Einladung des Spielers mit dem Namen name zu einem TicTacToe
#show Abruf des aktuellen Spielfelds während einer TicTacToe-Runde
#reset Abbruch eines Spiels bzw. Zurücksetzen des Status
#quit vom Server ausloggen
TicTacToe wird auf einem 3x3-Spielfeld gespielt. Ein Spieler setzt X, der andere O, die Spieler sind abwechselnd an der Reihe. Das TicTacToe-Spielfeld ist von links oben nach rechts unten durchnummeriert (siehe unten). Um auf ein Feld zu setzen, muss # gefolgt vom gewünschten Feld eingegeben werden. Gewonnen hat, wer als erster drei seiner Symbole waagrecht, senkrecht oder diagonal in einer Reihe hat. Das Spiel endet unentschieden, wenn alle Felder besetzt sind, aber kein Spieler drei Symbole in einer Reihe hat.
1 2 3
4 5 6
7 8 9