Spielprogrammierung mit Java
HomeAufgabenDruckenJava-Online

Tic Tac Toe


Tic Tac Toe (auch: Drei gewinnt) ist ein klassisches, einfaches Strategiespiel, dessen Geschichte sich bis ins 12. Jahrhundert v. Chr. zurückverfolgen lässt. Auf einem 3×3 Felder grossen Spielfeld machen die beiden Spieler abwechselnd ihre Zeichen (ein Spieler Kreuze, der andere Kreise). Der Spieler, der als erstes drei seiner Zeichen in eine Reihe, Spalte oder eine der beiden Diagonalen setzen kann, gewinnt. Wenn allerdings beide Spieler optimal spielen, kann keiner gewinnen, und es kommt zu einem Unentschieden.

Unser TicTacToe-Spiel ermöglicht zwei Spielern, die sich mit der gleichen SessionID anmelden, über das Internet zu spielen. 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. Es können gleichzeitig beliebig viele Spielpaare spielen.

 

Spiel starten

Die beiden Spieler starten das Spiel mit dem oben stehenden Link und geben die gleiche Session ID ein. Der beginnende Spieler setzt die Kreuze, der andere die Kreise. Im Mitteilungsfenster unten dem Brett wird jeweils angezeigt, wer spielberechtigt ist.

Zum Testen können Sie die Applikation auch zweimal auf dem gleichen Computer starten.

Beispiel im Online-Editor bearbeiten

Programmcode für lokale Bearbeitung downloaden: TcpTicTacToe.zip

Eine Weiterentwicklung dieses Spieles mit einem ausführlichen Tutorial finden Sie unter http://www.aplu.ch/TcpJLib


Erklärungen zum unten stehenden Programmcode:
TcpNode tcpNode = new TcpNode()
tcpNode.connect(id, nickname)
Die Kommunikation erfolgt mit Hilfe von zwei TcpNodes (analog Bsp. Schiffe versenken)
statusReceived()

In der Methode statusReceived(), die aufgerufen wird, wenn sich die Spieler an- und abmelden, wird die Reihenfolge der Spieler festgelegt. Der Spieler, der sich zuletzt anmeldet, beginnt das Spiel und erhält das Zeichen "X"

tcpNode.sendMessage("" + loc.x + loc.y) Nach jedem Spielzug wird ein String gesendet, welcher die Koordinaten des Mausklicks enthält (analog Bsp. Schiffe versenken)
messageReceived()
int x = text.charAt(0) - 48
int y = text.charAt(1) - 48

Empfangen wird ein String mit zwei Zeichen. Die Zeichen werden in einen Integer umgewaldelt. Das Zeichen '0' hat den ASCII-Code 48

addMark()
Actor mark = new Actor("sprites/mark.gif", 2)
mark.show(sign == 'X' ? 1 : 0)

Mit der Methode addMark() werden die Zeichen Kreuz bzw. Kreis gesetzt.
Dem Actor mark sind zwei Sprite-Bilder zugeordnet ( mark_0.gif, mark_1.gif).
Wenn die Bedingung sign == 'X' stimmt, wird das Sprite 1 (Kreuz), sonst das Sprite 0 (Kreis) angezeigt

getMark()
Actor actor = getOneActorAt(new Location(i, k))
actor.getIdVisible() == 1 ? 'X' : 'O'

Umgekehrt gibt die Methode getMark() für alle nicht leeren Zellen, die Zeichen X bzw. O zurück.
( Falls SpriteId = 1 ist, Kreuz, sonst Kreis)

isOver()


if (pattern.toString().contains("XXX"))

Mit der Methode isOver() wird die aktuelle Spielsituation wie folgt überprüft:
Die horizontale, vertikale und diagonale Belegung des Spielbretts wird in einem kommagetrennten Stringmuster der Form XOX,XX ,O O dargestellt (leere Zellen durch Leerzeichen).
Mit der Methode contains() getestet werden kann, ob drei aneinander grenzende X oder O vorkommen

isBoardFull()
return getActors().size() == 9

Die Methode getActors().size() gibt die Anzahl der Actors auf dem Brett zurück. Mit 9 Actors ist das Brett voll und damit "Game over".

Programmcode:

// TcpTicTac.java

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

public class TcpTicTac extends GameGrid
  implements GGMouseListenerTcpNodeListener
{

  private String sessionID = "ama&19td&ap";
  private final String nickname = "tic";
  private TcpNode tcpNode = new TcpNode();
  private boolean isMyMove = false;
  private char mySign;
  private char opponentSign;

  public TcpTicTac()
  {
    super(3, 3, 80, Color.blue, nullfalse);
    setBgColor(Color.lightGray);
    setTitle("TicTacToe");
    addStatusBar(30);
    addMouseListener(thisGGMouse.lClick);
    tcpNode.addTcpNodeListener(this);
    show();
    connect();
  }

  public boolean mouseEvent(GGMouse mouse)
  {
    if (!isMyMove)
      return true;

    Location loc = toLocationInGrid(mouse.getX(), mouse.getY());
    if (getOneActorAt(loc) != null)
    {
      setStatusText("Cell occupied!");
      return true;
    }

    addMark(mySign, loc);
    tcpNode.sendMessage("" + loc.x + loc.y);  // Send move
    if (isOver())
      return true;
    setStatusText("Wait for opponent's move.");
    isMyMove = false;
    return true;
  }

  private void connect()
  {
    String id = requestEntry("Enter unique session ID (more than 3 characters)");
    sessionID = sessionID + id;
    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;

    Location loc = new Location(x, y);
    addMark(opponentSign, loc);
    if (isOver())
      return;
    isMyMove = true;
    setStatusText("It's your move.");
  }

  private void addMark(char sign, Location loc)
  {
    Actor mark = new Actor("sprites/mark.gif"2);
    mark.show(sign == 'X' ? 1 : 0);
    addActor(mark, loc);
  }

  private boolean isBoardFull()
  {
    return getActors().size() == 9;
  }

  private void gameOver(String reason)
  {
    setStatusText(reason);
    isMyMove = false;
    addActor(new Actor("sprites/gameover.gif")new Location(1, 1));
  }

  private char getMark(int i, int k)
  {
    Actor actor = getOneActorAt(new Location(i, k));
    if (actor == null)
      return ' ';
    else
      return actor.getIdVisible() == 1 ? 'X' : 'O';
  }

  private boolean isOver()
  {
    // Convert gameState into string pattern
    StringBuffer pattern = new StringBuffer();
    // Horizontal
    for (int = 0; i < 3; i++)
    {
      for (int = 0; k < 3; k++)
      {
        pattern.append(getMark(i, k));
      }
      pattern.append(',');  // Separator
    }
    // Vertical
    for (int = 0; k < 3; k++)
    {
      for (int = 0; i < 3; i++)
      {
        pattern.append(getMark(i, k));
      }
      pattern.append(',');
    }
    // Diagonal
    for (int = 0; i < 3; i++)
      pattern.append(getMark(i, i));
    pattern.append(',');
    for (int = 0; i < 3; i++)
      pattern.append(getMark(i, 2 - i));

    if (pattern.toString().contains("XXX"))
    {
      gameOver("Game over. " + (mySign == 'X' ? "You won!" : "You lost!"));
      return true;
    }
    if (pattern.toString().contains("OOO"))
    {
      gameOver("Game over. " + (mySign == 'O' ? "You won!" : "You lost!"));
      return true;
    }
    if (isBoardFull())
    {
      gameOver("Game over. It's a draw.");
      return true;
    }
    return false;
  }

  public void nodeStateChanged(TcpNodeState state)
  {
  }

  public void statusReceived(String text)
  {
    if (text.contains("In session:--- (0)"))  // we are first player
    {
      setStatusText("Connected. Wait for partner.");
      mySign = 'O';
      opponentSign = 'X';
    }
    else if (text.contains("In session:--- (1)")// we are second player
    {
      setStatusText("It's your move.");
      mySign = 'X';
      opponentSign = 'O';
      isMyMove = true;  // Second player starts
    }
    else if (text.contains("In session:--- ")// we are next player
    {
      setStatusText("Game in progress. Terminating now...");
      TcpTools.delay(4000);
      System.exit(0);
    }
  }

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

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