Game-Apps für Smartphones und Tablets

Pädagogische Hochschule Bern  
HomeOnline-Editor startenDruckenAndroid-TurtlegrafikJava-Online

Schiffe versenken mit TCP/IP- Kommunikation

GameApps, bei welchen zwei oder mehrere Spieler über das Internet kommunizieren, sind besonders attraktiv. Die Klassenbibliothek TcpALib, die man zusätzlich zu JDroidLib importieren muss und unser Server ermöglichen es, solche Spiele zu entwickeln, Das einführende Beispiel stellt eine einfache Version des Spiels "Schiffe versenken" dar.

Unser TcpRelay Server verwaltet die Kommunikation zwischen den beiden TcpNodes Session-orientiert und sorgt ebenfalls dafür, dass alle Daten über den Port 80 versendet und somit nicht von Firewalls angehalten werden.




 

Die beiden Spielerpartner installieren und starten das Spiel auf ihren Android-Geräten und geben in der angezeigten Dialogbox die gleiche Session ID (mindestens 3 Zeichen) ein.

Sobald die Verbindung aufgebaut ist, wird das Spiel gestartet. Bei einem Spieler wird in der Statuszeile die Aufforderung zum Spielen angezeigt, der andere muss warten. Danach wird nach jedem Zug die Spielberechtigung gewechselt.

Da die Kommunikation sessionorientiert verwaltet wird, können mehrer Spielgruppen mit verschiedenen SessionID's gleichzeitug spielen.

 

Spielregeln: Jeder Spieler erhält ein Spielfeld mit 10 zufällig verteilten Schiffen. Der aktive Spieler versucht mit einem Tap 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.

Beispiel im Online-Editor bearbeiten

App installieren auf Smartphone oder Tablet

QR-Code

Programmcode herunterladen: TcpShip.zip

 

Das folgende Spielkonzept wird implementiert:

1. Es werden entweder die zwei Koordinaten der mit Touchevent gewählten Zelle oder ein Kommando besteht aus der Zahl 9 und einer zweiten Zahl zwischen den beiden TcpNodes übertragen (Zahl 9 wählen wir deswegen, da diese in unserem 8x8x Gitternicht als Koordinate nicht vorkommen kann. 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 Tap 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.

Programmcode:

// TcpShip.java

package app.tcpship;

import ch.aplu.android.*;
import ch.aplu.tcp.*;
import ch.aplu.util.Monitor;


public class TcpShip extends GameGrid implements GGTouchListener,TcpNodeListener
{
  private String sessionID = "xy";
  private final String myNodeName = "luka";
  private TcpNode node = new TcpNode();
  private boolean isMyMove = false;
  private final int nbShips = 10;
  private Location loc;
  private int myScore = 0;
  private int enemyScore = 0;
  protected GGStatusBar status;

  private interface Command
  {
    int GAME_START = 0;
    int SHIP_MISSED = 1;
    int SHIP_HIT = 2;
  }
    
  public TcpShip()
  {
    super(6, 6, cellZoom(60), RED, false);
    setScreenOrientation(GGNavigationListener.ScreenOrientation.FIXED); 
    status = addStatusBar(30);
  }
  
  public void main()
  {
    setTitle("TCP BattleShip");
    init();
  }
  
  public void init()
  {
    for (int = 0; i < nbShips; i++)
      addActor(new Ship(), getRandomEmptyLocation());
    addTouchListener(thisGGTouch.click);    
    refresh();
    node.addTcpNodeListener(this);
    showToast("Connecting to  relay...");
    connect();
    Monitor.putSleep(7000);
    if (node.getNodeState() == TcpNodeState.CONNECTED)
    {
      status.setText("Connection established.");
    }
    else
      status.setText("Connection failed");
  }
  
  private void connect()
  {
    while (sessionID.length() < 3)
      sessionID = sessionID + requestEntry("Enter unique game room name (more than 2 characters):");   
    status.setText("Trying to connect to host");
    node.connect(sessionID, myNodeName);
  }
  
  private String requestEntry(String prompt)
  {
    return GGInputDialog.show("TcpShip" , prompt, "");
  }
  
  public void nodeStateChanged(TcpNodeState state)
  {
    if (state == TcpNodeState.DISCONNECTED)
      status.setText("Connection broken.");
  }
  
  public boolean touchEvent(GGTouch tap)
  {
    if (!isMyMove)
      return true;
    loc = toLocationInGrid(tap.getX(), tap.getY());
    node.sendMessage("" + loc.x + loc.y); // send string
    status.setText("Wait to play!");
    isMyMove = false;
    return false;
  }
  
  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 TcpShip.Command.GAME_START:
          isMyMove = false;
          status.setText("Wait! Enemy will play first.");
          break;
        case TcpShip.Command.SHIP_HIT:
          addActor(new Actor("checkgreen"), loc);
          myScore++;
          if (myScore == nbShips)
            gameOver(true);
          break;
        case TcpShip.Command.SHIP_MISSED:
          addActor(new Actor("checkred"), 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("explosion"), loc);
        node.sendMessage("9" + TcpShip.Command.SHIP_HIT);
        enemyScore++;
        if (enemyScore == nbShips)
        {
          gameOver(false);
          return;
        }
      }
      else
        node.sendMessage("9" + TcpShip.Command.SHIP_MISSED);

      status.setText("Play now! Score: " + myScore + " : " + enemyScore);
      isMyMove = true;
      refresh();
    }
  }

  private void gameOver(boolean isWinner)
  {
    isMyMove = false;
    removeAllActors();
    if (isWinner)
      status.setText("You won!");  
    else
      status.setText("You lost!");  
    refresh();
  }

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

class Ship extends Actor
{
  public Ship()
  {
    super("boat");
  }
}

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 (ASCI-Code von 0 ist 48)
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