Spielprogrammierung mit Java
HomeAufgabenDruckenJava-Online

Schiffe versenken mit TCP/IP- Kommunikation

Die Klassenbibliothek TcpJLib ermöglicht es, Spiele, bei den zwei oder mehrere Spieler über das Internet kommunizieren, zu erstellen. Das einführende Beispiel stellt eine einfache Version des Spiels "Schiffe versenken" dar.

Die Verwaltung der Session ID's übernimmt unser TcpRelay Server, der ebenfalls dafür sorgt, dass alle Daten über den Port 80 versendet und empfangen und somit nicht von Firewalls angehalten werden.





Um das Programm zu testen, gehen Sie wie folgt vor:


Spiel testen

Die beiden Spieler starten das Spiel mit dem oben stehenden Link. In der Dialogbox müssen beide Spielpartner die gleiche Session ID eingegeben. Diese legen sie vor dem Spielbeginn fest. Zum Testen können Sie die Applikation auch zweimal auf dem gleichen Computer starten. Die Verbindung wird auch in diesem Fall über das Internet aufgebaut.

 


Es können problemlos gleichzeitig mehrere Spiel-Paare spielen, da die Verbindung jeweils nur zum Spieler mit der gleiche Session ID aufgebaut wird. Nach der Eingabe der SessionID muss nach einer kurzen Zeit in der Titelleiste des Spielfensters die Meldung "Connected" erscheinen. Andernfalls wurde die Verbindung nicht korrekt aufgebaut. Mit Schliessen des Spielfensters wird die Verbindung abgebrochen.

Spielregeln

Jeder Spieler erhält ein Spielfeld mit 10 zufällig verteilten Schiffen. Der aktive Spieler versucht mit einem Mausklick ein Gegner-Schiff zu treffen. Bei jeden Versuch wird im eigenen Spielfeld die Zelle markiert. Wenn er trifft erscheint beim Gegner ein Explosiosbild. Dann kommt der andere Spieler zu Zug. Wer zuerst alle Gegnerschiffe getroffen hat, hat gewonnen.

 

Das folgende Spielkonzept wird implementiert:

1. Es werden entweder die beiden Location-Koordinaten oder ein Kommando besteht aus der Zahl 9 und einer zweiten Zahl zwischen den beiden Knoten (TcpNodes) übertragen. Die Kommandi sind in einem Interface definiert.

2. Beim Starten wird die Verbindung zum RelayServer hergestellt. Es wird im Callback statusReceived() festgestellt, ob es sich um den ersten oder zweiten Spieler handelt, der ins Spiel kommt. Kommt der zweite ins Spiel, so wird dies dem ersten Spieler mit dem Kommando GAME_START mitgeteilt.

3. Mit einem Mausklick wird eine Bombe abgeschossen. Der Gegner erhält die Koordinaten. Er prüft, ob sich an der Location eines seiner Schiffe befindet. Falls ja, wird das Schiff weggenommen und an der Stelle ein Explosionsactor erzeugt. Das Resultat des Schusses wird dem Schiessenden mit den Kommandi SHIP_HIT bzw. SHIP_MISSED mitgeteilt. Damit "weiss" jeder Spieler immer genau, wieviele Schiffe er versenkt hat (myScore) und wieviele Schiffe er verloren hat (enemyScore).

4. Aus den Werten von myScore und enemyScore kann jeder Spieler herausfinden, ob er oder der Gegner gewonnen hat. Hat einer der beiden keine Schiffe mehr, so ist das Spiel beendet.

Beispiel im Online-Editor bearbeiten

Programmcode TcpBattleShip:

// TcpBattleShip.java

import ch.aplu.jgamegrid.*;
import ch.aplu.tcp.*;
import java.awt.Color;
import javax.swing.JOptionPane;

public class TcpBattleShip extends GameGrid
  implements GGMouseListener, TcpNodeListener
{
  private String sessionID = "ama&19td";
  private final String nickname = "captain";
  private TcpNode tcpNode = new TcpNode();
  private boolean isMyMove = false;
  private final int nbShips = 10;
  private Location loc;
  private int myScore = 0;
  private int enemyScore = 0;

  private interface Command
  {
    int GAME_START = 0;
    int SHIP_MISSED = 1;
    int SHIP_HIT = 2;
  }

  public TcpBattleShip()
  {
    super(6, 6, 50, Color.red, false);
    setTitle("TcpBattleShip");
    for (int = 0; i < nbShips; i++)
      addActor(new Ship(), getRandomEmptyLocation());
    addMouseListener(thisGGMouse.lClick);
    tcpNode.addTcpNodeListener(this);
    show();
    connect();
  }

  public boolean mouseEvent(GGMouse mouse)
  {
    if (!isMyMove)
      return true;
    loc = toLocationInGrid(mouse.getX(), mouse.getY());
    tcpNode.sendMessage("" + loc.x + loc.y); // send string
    setTitle("Wait enemy bomb!");
    isMyMove = false;
    return false;
  }

  private void connect()
  {
    String id = requestEntry("Enter unique Server ID (more than 3 characters)");
    sessionID = sessionID + id;
    setTitle("Trying to connect to host");
    tcpNode.connect(id, nickname);
  }

  public void messageReceived(String sender, String text)
  {
    int = text.charAt(0) - 48; // We get ASCII code of number
    int = text.charAt(1) - 48;
    
    if (x == 9)  // Got command
    {
      switch (y)
      {
        case Command.GAME_START:
          isMyMove = false;
          setTitle("Wait! Enemy will shoot first.");
          break;
        case Command.SHIP_HIT:
          addActor(new Actor("sprites/checkgreen.gif"), loc);
          myScore++;
          if (myScore == nbShips)
            gameOver(true);
          break;
        case Command.SHIP_MISSED:
          addActor(new Actor("sprites/checkred.gif"), loc);
          break;
      }
    }
    else // Got coordinates
    {
      Location loc = new Location(x, y);
      Actor actor = getOneActorAt(loc, Ship.class);
      if (actor != null)
      {
        actor.removeSelf();
        addActor(new Actor("sprites/explosion.gif"), loc);
        tcpNode.sendMessage("9" + Command.SHIP_HIT);
        enemyScore++;
        if (enemyScore == nbShips)
        {
          gameOver(false);
          return;
        }
      }
      else
        tcpNode.sendMessage("9" + Command.SHIP_MISSED);

      setTitle("Shoot now! Score: " + myScore + " (" + enemyScore + ")");
      isMyMove = true;
    }
  }

  private void gameOver(boolean isWinner)
  {
    isMyMove = false;
    removeAllActors();
    if (isWinner)
    {
      addActor(new Actor("sprites/you_win.gif")new Location(3, 3));
      setTitle("You won!");
    }
    else
    {
      addActor(new Actor("sprites/gameover.gif")new Location(3, 3));
      setTitle("You lost!");
    }
  }

  public void nodeStateChanged(TcpNodeState state)
  {
  }

  public void statusReceived(String text)
  {
    if (text.contains("In session:--- (0)"))  // we are first player
    {
      setTitle("Connected. Wait for partner.");
    }
    if (text.contains("In session:--- (1)"))  // we are second player
    {
      tcpNode.sendMessage("9" + Command.GAME_START);
      setTitle("It is you to play");
      isMyMove = true;  // Second player starts
    }
  }

  private String requestEntry(String prompt)
  {
    String entry = "";
    while (entry.length() < 3)
    {
      entry = JOptionPane.showInputDialog(null, prompt, "");
      if (entry == null)
        System.exit(0);
    }
    return entry.trim();
  }

  public static void main(String[] args)
  {
    new TcpBattleShip();
  }
}

// ---------- class Ship --------------------
class Ship extends Actor
{
  public Ship()
  {
    super("sprites/boat.gif");
  }
}

Erklärungen zum Programmcode:

TcpNode tcpNode = new TcpNode()
tcpNode.connect(id, nickname)

Erzeugt einen TcpNode (noch keine Verbindung)
Erstellt Verbindung mit der eingegebener SessionID und Name
Location loc = toLocationInGrid(mouse.getX(), mouse.getY())
tcpNode.sendMessage("" + loc.x + loc.y)
Koordinaten x, y des Mausklicks werden als Text gesendet

public void messageReceived(String sender, String text)
{
    int x = text.charAt(0) - 48;
    int y = text.charAt(1) - 48;
}

Empfangen wird ein String mit zwei Zeichen

Wandelt das erste Zeichern in ein Integer um
Wandelt das zweite Zeichen in ein Integer
tcpNode.sendMessage("9" + Command.SHP_HIT) Da wir nur ein 6x6 Grid haben, kann 9 und eine Zahl nie durch ein Mausklick ins Spielfeld erzeugt werden. Wir verwenden die Vorzahl 9 und anzuzeigen, dass es sich bei der nächsten Zahl um ein Kommand handelt

Anmerkung:

Eine "Vollversion" des BattleShip-Spieles, in der Sie verschiedene Schiftyppen verwenden und selbst positionieren können, können Sie mit folgendem Link starten:

BattleShip-Spiel starten

Auch bei dieser Variante müssen sich die beiden Spieler zuerst mit der gleichen SessionID anmelden. Danach erscheint bei jedem Spieler ein Spielfenster, in welchem er seine Schiffe mit Mausklick auf die roten Punkte + ziehen mit gedrücken Maustaste positionieren und gleichzeitigem Drücken der UP- bzw. DOWN-Taste drehen kann.

Mit Klick auf Continue wird das Spiel gestartet.

Weitere Informationen finden Sie unter www.aplu.ch/tcpjlib.