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.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.net.*; public class GUISVUser extends Applet { SpielCanvasPos1 sc1; SpielCanvas sc2; Spielfeld spieler, computer; SVClient netz; Button shoot, setship; Panel buttonPanel; // Panel fuer Buttons (shoot und setship) Panel sc1Panel; // Spielfeld des Computers Panel sc2Panel; // Spielfeld des Spielers Label label1; // Spielfeldbezeichner des Computers Label label2; // Spielfeldbezeichner des Spielers Container c; Die nun folgende Methode init zum Aufbau des GUIs sowie die Methode ausgabeFenster wurden bereits detailliert vorgestellt. public void init() { computer = new Spielfeld(); spieler = new Spielfeld(); c = this; setLayout(new GridLayout(1,3,5,0)); setSize(650,266); setBackground(new Color(16777215)); // Setzen der Buttons buttonPanel = new Panel(); buttonPanel.setLayout(new FlowLayout(FlowLayout.CENTER,5,5)); buttonPanel.setBounds(0,0,142,266); buttonPanel.setBackground(new Color(16777215)); add(buttonPanel); setship = new Button(); setship.setLabel("Neues Spiel"); setship.addActionListener(new ButtonListener(0)); setship.setBounds(31,5,80,23); setship.setBackground(new Color(12632256)); buttonPanel.add(setship); shoot = new Button("Schuss abgeben"); shoot.setEnabled(false); shoot.addActionListener(new ButtonListener(1)); shoot.setBounds(17,33,108,23); shoot.setBackground(new Color(12632256)); buttonPanel.add(shoot); //Spielfeld des Computers sc1Panel = new Panel(); sc1Panel.setLayout(new BorderLayout(0,0)); sc1Panel.setBounds(142,0,142,266); sc1Panel.setBackground(new Color(16777215)); sc1 = new SpielCanvasPos1(computer); sc1Panel.add("Center",sc1); label1 = new Label("Spielfeld des Computers", Label.LEFT); label1.setBounds(0,133,142,133); sc1Panel.add("South",label1); add(sc1Panel); //Spielfeld des Benutzers sc2Panel = new Panel(); sc2Panel.setLayout(new BorderLayout(0,0)); sc2Panel.setBounds(284,0,142,266); sc2Panel.setBackground(new Color(16777215)); add(sc2Panel); sc2 = new SpielCanvas(spieler); sc2Panel.add("Center",sc2); label2 = new Label("Spielfeld des Benutzers", Label.LEFT); label2.setBounds(0,133,142,133); sc2Panel.add("South",label2); } public void ausgabeFenster (String s) { Object anker = getParent(); while (! (anker instanceof Frame)) anker = ((Component) anker).getParent(); FehlerFenster dialog = new FehlerFenster((Frame)anker, s); dialog.setVisible(true); dialog.setSize(400,100); return; } 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; String [] s; ButtonListener (int val) { this.val = val; } public void actionPerformed(ActionEvent e) { Daten daten; Point p = null; daten = new Daten(); if (val == 0){ //Schiffe setzen spieler.initialisieren(); computer.initialisieren(); try { netz = new SVClient((GUISVUser) c); } catch (IOException ioe){ ausgabeFenster("Probleme beim Netzaufbau"); return; } if (netz.SVSocket == null) return; SchiffFenster fenster = new SchiffFenster(spieler, c); fenster.setTitle("Schiffe setzen"); fenster.pack(); fenster.setVisible(true); fenster.setSize(600,350); shoot.setEnabled(true); repaint(); } 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 //Koordinaten bereits markiert? if (sc1.sa1.tmp.x == -1) return; daten.status=daten.SCHUSS; daten.p.x=sc1.sa1.tmp.x; daten.p.y=sc1.sa1.tmp.y; netz.sendData(daten); shoot.setEnabled(false); try { 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 System.out.println("Wir haben getroffen"); //Einfaerben computer.spiel[sc1.sa1.tmp.x][sc1.sa1.tmp.y]=com puter.VERSENKT; computer.schiffZahl--; if (computer.schiffZahl==0){ //Gewinnmeldung ausgabeFenster("Herzlichen Glückwunsch, Sie haben gewonnen!"); try { netz.beenden(); } catch (IOException ioe) {} //Neues Spiel vorbereiten shoot.setEnabled(false); return; } sc1.sa1.tmp.x = sc1.sa1.tmp.y = -1; sc1.sa1.repaint(); repaint(); shoot.setEnabled(true); } 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"); computer.spiel[sc1.sa1.tmp.x][sc1.sa1.tmp.y]=com puter.WASSER; sc1.sa1.tmp.x = sc1.sa1.tmp.y = -1; sc1.sa1.repaint(); rechnerTrifft(daten); if (spieler.schiffZahl!=0) shoot.setEnabled(true); sc2.repaint(); repaint(); } } validate(); } 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; //Rechner schiesst ins Wasser return; } 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 d.status = d.TREFFER; netz.sendData(d); spieler.spiel[d.p.x][d.p.y]=spieler.VERSENKT; spieler.schiffZahl--; 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 ausgabeFenster("Leider verloren!"); try { netz.beenden(); } catch (IOException ioe) {} //Neues Spiel anbieten shoot.setEnabled(false); return; } 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); sc2.repaint(); repaint(); } } } } Die Bildschirmausgabe eines Spiels ist in Abb. 7-3 dargestellt. Abb. 7.3: Schiffe versenken