![]() |
|
Analog zum Aufbau des Servers wird zunächst die Klassenhierarchie des Clients wiederholt (siehe Abb. 7-2). Auch diese Hierarchie muss um die Netzwerk- und um die Protokollfunktionalität erweitert werden. Wie bereits beim Server sind Änderungen beim Client nur in der Hauptklassendatei notwendig. Diese betreffen nur einen kleinen Ausschnitt des gesamten Programms. Um den Überblick nicht zu verlieren, soll dennoch die Klasse GUISVUser vollständig angegeben werden. Nach der Definition der Klasse erfolgen zuerst Variablendefinitionen. Hierunter findet der Leser die Variable netz, die zur Netzwerkanbindung verwendet wird.
Abb. 7.2: Klassenhierarchie des Clients
import java.applet.*; SpielCanvasPos1 sc1; Panel buttonPanel; // Panel fuer Buttons (shoot und setship) Panel sc1Panel; // Spielfeld des Computers Die nun folgende Methode init zum Aufbau des GUIs sowie die Methode ausgabeFenster wurden bereits detailliert vorgestellt. public void init() { computer = new Spielfeld(); // Setzen der Buttons setship = new Button(); shoot = new Button("Schuss abgeben"); //Spielfeld des Computers sc1Panel = new Panel(); label1 = new Label("Spielfeld des Computers", Label.LEFT); label1.setBounds(0,133,142,133); //Spielfeld des Benutzers sc2Panel = new Panel(); sc2 = new SpielCanvas(spieler); label2 = new Label("Spielfeld des Benutzers", Label.LEFT); } Object anker = getParent(); anker = ((Component) anker).getParent(); FehlerFenster dialog = new FehlerFenster((Frame)anker, s); dialog.setVisible(true); } Die eigentliche Logik des Spiels ist in der nun folgenden inneren Klasse ButtonListener verborgen. Zunächst wird das Aufstellen neuer Schiffe und damit der Beginn eines neuen Spiels implementiert.
class ButtonListener implements ActionListener { private int val; this.val = val; } Daten daten; spieler.initialisieren(); netz = new SVClient((GUISVUser) c); } catch (IOException ioe){ ausgabeFenster("Probleme beim Netzaufbau"); } return; SchiffFenster fenster = new SchiffFenster(spieler, c); fenster.setTitle("Schiffe setzen"); } Wird der Button Neues Spiel betätigt, so werden zunächst die Spielfelder zurückgesetzt. Anschließend wird die Verbindung mit dem Server hergestellt. Tritt hierbei ein Fehler auf, so wird dieser gemeldet und die Methode bricht ab. Nach einem erfolgreichen Verbindungsaufbau wird das Fenster erstellt, mit dessen Hilfe der Benutzer seine Schiffe setzen kann. Ist der Wert des angegebenen Parameters val ungleich null, so wurde der Button Schuss betätigt. In diesem Fall muss die Spiellogik des Clients durchlaufen werden. Im Gegensatz zur komplizierten Logik des Servers ist die Logik des Clients allerdings wesentlich einfacher. Zuerst werden die Koordinaten, die der Spieler mit der Maus markiert hat, an den Server geschickt.
else{ //Schiessen return; daten.status=daten.SCHUSS; daten = netz.getData(); }catch (IOException ioe) {} Zunächst wird hierbei geprüft, ob der Benutzer korrekte Koordinaten eingegeben hat. Anschließend werden diese Daten zusammen mit der Information, dass es sich um einen Schuss handelt, an den Server geschickt und der Button shoot auf inaktiv gesetzt. Der Client wartet dann auf die Antwort des Servers. Meldet der Server, dass der Client getroffen hat, so wird die folgende Schleife durchlaufen:
if (daten.status == daten.TREFFER) { //TREFFER //Einfaerben computer.schiffZahl--; //Gewinnmeldung try { netz.beenden(); } catch (IOException ioe) {} //Neues Spiel vorbereiten } sc1.sa1.tmp.x = sc1.sa1.tmp.y = -1; } Zuerst wird hierbei im Spielfeld der Treffer vermerkt und die Anzahl der verbleibenden Schiffe des Servers neu berechnet. Hat der Gegner keine Schiffe mehr, so wird die Verbindung abgebaut. Zum Ende der Schleife ist eine Besonderheit zu beachten: Da im Spielfeld die Position, die der Spieler zum Schuss markiert hat, grün eingezeichnet wird, muss das Koordinatenpaar auf den Wert -1 zurückgesetzt werden. Ansonsten wäre es unmöglich, das entsprechende Feld rot (für Treffer) einzufärben. Hat der Spieler keinen Treffer erzielt, so wird die folgende Schleife durchlaufen.
else { System.out.println("Wasser"); sc1.sa1.tmp.x = sc1.sa1.tmp.y = -1; shoot.setEnabled(true); sc2.repaint(); } } } Auch hierbei wird das Spielfeld auf den entsprechenden Wert (Wasser) gesetzt. Anschließend wird wiederum das Koordinatenpaar zurückgesetzt. Die dann folgende Methode rechnerTrifft verarbeitet Schüsse des Servers. Nach Ende dieser Methode ist der Spieler wieder an der Reihe, der Button shoot wird also wieder aktiviert.
public void rechnerTrifft(Daten d) { while (true) { if (spieler.spiel[d.p.x][d.p.y]!=spieler.SCHIFF) if (spieler.spiel[d.p.x][d.p.y] !=spieler.VERSENKT) { spieler.spiel[d.p.x][d.p.y]=spieler.WASSER; } Die Phase, in der der Server aktiv ist, wird in einer unendlichen while-Schleife durchlaufen. Hierbei wird zuerst geprüft, ob der Server getroffen hat. Ist dies nicht der Fall, so wird das Koordinatenfeld korrigiert und die Methode abgebrochen. Im Anschluss daran werden Rechnertreffer verarbeitet.
//Rechner hat uns wirklich getroffen Hat der Rechner getroffen, so wird ihm dies mitgeteilt und das Spielfeld aktualisiert. Anschließend wird geprüft, ob noch Schiffe des Clients vorhanden sind.
if (spieler.schiffZahl==0){ //Meldung dass Spieler verloren hat try { netz.beenden(); } catch (IOException ioe) {} //Neues Spiel anbieten } Hierzu wird ein Fenster mit der dementsprechenden Meldung angezeigt. Anschließend wird die Verbindung abgebaut. Der Button shoot muss dann auf false gesetzt werden, damit der Spieler in der Folge neue Schiffe platziert. Ist das Spiel noch nicht vorbei, so darf der Server erneut schießen. Hierzu wird auf die Antwort des Servers gewartet.
try { d = netz.getData(); }catch (IOException ioe) {} shoot.setEnabled(false); } } } } Die Bildschirmausgabe eines Spiels ist in Abb. 7-3 dargestellt.
Abb. 7.3: Schiffe versenken |
|
|