Spielprogrammierung mit Java
HomeAufgabenDruckenJava-Online

TCP BoxGame


Dots & Boxes oder auch Käsekästchen ist ein einfaches Strategiespiel. Zwei Spieler können abwechselnd jeweils eine Kante anfärben. Wird eine Box geschlossen, gehört sie dem Spieler, der die letzte Kante gelegt hat. Dieser färbt die Box mit seiner Farbe aus und darf dann noch einmal spielen. Ziel ist es, möglichst viele Boxes mit der eigenen Farbe auszufärben.

Unsere Implementierung 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.

Beispiel zeigen

Beispiel im Online-Editor bearbeiten

Programmcode für lokale Bearbeitung Downloaden: TcpBoxGame.zip

 

Programmcode:

// TcpBoxGame.java

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

public class TcpBoxGame extends GameGrid implements GGMouseTouchListener, TcpNodeListener, GGExitListener
{
  private static final boolean customizableGrid = false;
  Hashtable<Location, LinkedList<Stroke>> BoxMap = new Hashtable<Location, LinkedList<Stroke>>();
  Hashtable<Integer, Stroke> StrokeMap = new Hashtable<Integer, Stroke>();
  private Player[] players = new Player[2];
  private static int playerCounter = 0;
  private String sessionID = "$BoxGame:";
  private final String nickname = "plr";
  private TcpNode tcpNode = new TcpNode();
  private boolean isMyMove = false;
  private Player thisPlayer;
  private Player otherPlayer;

  interface Command
  {
    char change = 'c'// change player
    char move = 'm';   // move pearl
    char over = 'o';   // game over
    char start = 's';  // start game
    char terminate = 't'// terminate game
  }

  public TcpBoxGame(int height, int width)
  {
    super(width + 2, height + 2, 75, Color.WHITE, false);
    getBg().clear(Color.WHITE);
    players[0] = new Player(Color.BLUE, "Blue");
    players[1] = new Player(Color.RED, "Red");
    for (int x = 1; x < getNbHorzCells(); x++)
    {
      for (int y = 1; y < getNbVertCells(); y++)
      {
        Location loc = new Location(x, y);
        BoxMap.put(loc, new LinkedList<Stroke>());
        for (StrokeDirection d : StrokeDirection.values())
        {
          //prevent loop from drawing unnecessary strokes
          if (y == getNbVertCells() - && d == StrokeDirection.VERTICAL
            || x == getNbHorzCells() - && d == StrokeDirection.HORIZONTAL)
            continue;
          Stroke s = new Stroke(this, d);
          addActorNoRefresh(s, new Location(x, y));
          s.addMouseTouchListener(this, GGMouse.lClick | GGMouse.enter | GGMouse.leave);
          StrokeMap.put(s.getId(), s);
          for (Location l : s.getPossibleFillLocations())
            BoxMap.get(l).add(s);
        }
      }
    }
    addStatusBar(20);
    setTitle("The box game -- www.java-online.ch");
    tcpNode.addTcpNodeListener(this);
    addExitListener(this);
    show();
    connect();
  }

  public static void main(String[] args)
  {
    int height = 3;
    int width = 3;

    new TcpBoxGame(height, width);
  }

   public void mouseTouched(Actor actor, GGMouse mouse, Point spot)
  {
    Stroke s = (Stroke) actor;
    if (s.isDrawn() || !isMyMove)
      return;
    switch (mouse.getEvent())
    {
      case GGMouse.enter:
        s.show(thisPlayer.id + 1); //important, that not s.draw is called!
        break;
      case GGMouse.leave:
        s.show(0);
        break;
      case GGMouse.lClick:
        s.draw(thisPlayer.id);
        tcpNode.sendMessage("" + Command.move + s.getId());
        boolean nextPlayer = true;
        for (Location loc : s.getPossibleFillLocations())
        {
          if (players[thisPlayer.id].tryToFillBoxes(loc))
            nextPlayer = false;
        }
        if (nextPlayer)
        {
          tcpNode.sendMessage("" + Command.change);
          updateStatusText("Wait for your turn.");
          isMyMove = false;
        }
        else
          updateStatusText("Your turn again.");
        break;
    }
    refresh();
  }

  private void updateStatusText(String additionalMessage)
  {
    String msg = players[0].getLabelledScore() + " vs " + players[1].getLabelledScore();
    if (Stroke.allDrawn())
    {
      isMyMove = false;
      msg = "Final Score -- " + msg;
      if (thisPlayer.score < otherPlayer.score)
        msg += " -- You lose!";
      else
        msg += " -- You win!";
    }
    else
      msg = additionalMessage + " " + msg;
    setStatusText(msg);
  }

  private boolean outOfValidGrid(Location loc)
  {
    return loc.y >= getNbVertCells() - || loc.x >= getNbHorzCells() - 1
      || loc.y < || loc.x < 1;
  }

  class Player
  {
    private int id;
    private Color color;
    private int score;
    private String name;

    public Player(Color color, String name)
    {
      this.name = name;
      this.id = playerCounter++;
      this.color = color;
      this.score = 0;
    }

    public String toString()
    {
      return name;
    }

    public Player nextPlayer()
    {
      return players[(id + 1) % playerCounter];
    }

    public String getLabelledScore()
    {
      return name + ": " + score;
    }

    private boolean tryToFillBoxes(Location loc)
    {
      if (outOfValidGrid(loc))
        return false;
      for (Stroke s : BoxMap.get(loc))
        if (!s.isDrawn())
          return false;
      getPanel().fillCell(loc, color);
      score++;
      return true;
    }
  }

  private void connect()
  {
    setStatusText("Connecting...");
    String id = requestEntry("Enter unique game room ID (more than 3 characters)");
    sessionID = sessionID + id;
    tcpNode.connect(sessionID, nickname);
  }

  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 void messageReceived(String sender, String text)
  {
    if (isMyMove)
      return;
    char command = text.charAt(0);
    switch (command)
    {
      case Command.start:
        setStatusText("Game started. Wait for the partner's move.");
        break;
      case Command.terminate:
        setStatusText("Partner exited game room. Terminating now...");
        TcpTools.delay(4000);
        System.exit(0);
        break;
      case Command.move:
        int strokeId = Integer.parseInt(text.substring(1, text.length()));
        Stroke s = StrokeMap.get(strokeId);
        s.draw(otherPlayer.id);
        for (Location loc : s.getPossibleFillLocations())
        {
          players[otherPlayer.id].tryToFillBoxes(loc);
        }
        updateStatusText("Wait for your turn.");
        break;
      case Command.change:
        isMyMove = true;
        updateStatusText("Your move.");
        break;
    }
    refresh();
  }

  public void nodeStateChanged(TcpNodeState arg0)
  {
    // Not used
  }

  public void statusReceived(String text)
  {
    if (text.contains("In session:--- (0)"))  // we are first player
    {
      setStatusText("Connected to room " + sessionID + ". Wait for partner.");
      thisPlayer = players[0];
      otherPlayer = players[1];
    }
    else if (text.contains("In session:--- (1)")// we are second player
    {
      setStatusText("Connected. Make first move.");
      isMyMove = true;  // Second player starts
      thisPlayer = players[1];
      otherPlayer = players[0];
      tcpNode.sendMessage("" + Command.start);
    }
    else if (text.contains("In session:--- ")// we are next player
    {
      setStatusText("Game in progress. Terminating now...");
      TcpTools.delay(4000);
      System.exit(0);
    }
  }

  public boolean notifyExit()
  {
    tcpNode.sendMessage("" + Command.terminate);
    return true;
  }
}

// --------class Stroke--------------
class Stroke extends Actor
{
  private GameGrid gg;
  private StrokeDirection direction;
  private boolean drawn;
  private int id;
  private static int drawnStrokes;
  private static int strokeCounter = 0;

  public Stroke(GameGrid gg, StrokeDirection d)
  {
    super(true"sprites/strokeBoarder.png"3);
    this.id = strokeCounter++;
    this.gg = gg;
    this.direction = d;
    this.drawn = false;
  }

  public void reset()
  {
    this.turn(direction.ordinal() * 90);
    this.setLocationOffset(scaleOffset(direction.getOffset()));
    this.setMouseTouchCircle(new Point(0, 0), gg.getCellSize() / 3);
  }

  private Point scaleOffset(GGVector offset)
  {
    int scaleFactor = gg.getCellSize() / 2;
    return new Point((int) (offset.x * scaleFactor), (int) (offset.y * scaleFactor));
  }

  public LinkedList<Location> getPossibleFillLocations()
  {
    LinkedList<Location> fillLocs = new LinkedList<Location>();
    Location loc = getLocation();
    fillLocs.add(loc);
    if (loc.y != && direction == StrokeDirection.HORIZONTAL)
      fillLocs.add(new Location(loc.x, loc.y - 1));
    if (loc.x != && direction == StrokeDirection.VERTICAL)
      fillLocs.add(new Location(loc.x - 1, loc.y));
    return fillLocs;
  }

  public StrokeDirection getStrokeDirection()
  {
    return direction;
  }

  public void draw(int playerId)
  {
    drawnStrokes++;
    drawn = true;
    show(1 + playerId);
  }

  public boolean isDrawn()
  {
    return drawn;
  }

  public static boolean allDrawn()
  {
    return strokeCounter == drawnStrokes;
  }

  public int getId()
  {
    return id;
  }

  public String toString()
  {
    return getLocation() + " " + direction;
  }
}

// -----------------------------------
enum StrokeDirection
{
  HORIZONTAL(new GGVector(0, -1)), VERTICAL(new GGVector(-1, 0));
  private GGVector offset;

  StrokeDirection(GGVector offset)
  {
    this.offset = offset;
  }

  public GGVector getOffset()
  {
    return offset;
  }
}