Spielprogrammierung mit Java
HomeAufgabenDruckenJava-Online

Mehr Personen Nim-Spiel mit TCP/IP- Kommunikation

 

Die Tcp Nim-Spiel zeigt wie ein Mehrpersonenspiel über das Internet konzipiert werden kann. Jede Spielgruppe, bestehend aus einem NimServer und zwei oder mehreren NimPlayers muss eine eindeutige Session ID haben. Diese wird beim Starten des NimServers festgelegt. Die Verwaltung der Session ID's übernimmt unser TcpRelay. Der NimServer regelt die Spielreihenfolge und beendet das Spiel, wenn sich einer der beteiligten Spieler abgemeldet hat.

Um das Spiel zu testen, muss einer der Spieler den NimServer starten, eine SessionID und die Anzahl der beteiligten Spieler angeben. Achtung: das Consolenfenster des NimServers muss während dem ganzen Spiel offen bleiben!
Danach starten die NimPlayer das Spiel und geben die gleiche SessionID ein.

NimServer starten

NimPlayer starten

Spielregeln:


   

Der NimServer muss gestartet sein, bevor die NimPlayer das Spiel starten.
Der spielberechtigte Spieler nimmt mit Mausklick 1, 2, oder 3 Zündhölzer weg. Dabei wird via TCP/IP jeweils ein Integer (x-Koordinate des entfernten Zündholzes) gesendet und das entprechende Zündholz auch aus seiner Ansicht entfernt. Mit Klick auf OK wird der Zug beendet und als Information der Integer -1 gesendet. Der Spieler wird in einen wartenden Zustand versetzt, während die anderen Spieler spielberechtigt sind.
Wer das letzte Hölzchen entfernt, hat verloren

NimServer im Online-Editor bearbeiten

NimPlayer im Online-Editor bearbeiten

Programmcode für lokale Bearbeitung downloaden: TcpNim.zip

Programmcode Server:

// TcpNimServer.java

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

public class TcpNimServer extends TcpBridge implements TcpBridgeListener
{
  private final int nbMatchesStart = 15;
  private final static String serverName = "NimServer";
  private final static String sessionID = "";
  private Console c;
  private Vector<String> players = new Vector<String>();
  private int groupSize;
  private int nbMatches;

  // Protocol tags
  private interface Command
  {
    int GAME_RUNNING = 0;
    int NUMBER_OF_PLAYERS = 1;
    int INIT_PLAYGROUND = 2;
    int GAME_STARTING = 3;
    int REMOTE_MOVE = 4;
    int LOCAL_MOVE = 5;
    int REPORT_OK = 6;
    int REPORT_POSITION = 7;
    int WINNER = 8;
    int LOOSER = 9;
    int PLAYER_DISCONNECT = 10;
  }

  public TcpNimServer()
  {
    super(sessionID, serverName);

    // Request group size
    boolean ok = false;
    while (!ok)
    {
      String groupSizeStr =
        JOptionPane.showInputDialog(null,
        "Number of players in group (1..10)""");
      if (groupSizeStr == null)
        System.exit(0);
      try
      {
        groupSize = Integer.parseInt(groupSizeStr.trim());
      }
      catch (NumberFormatException ex)
      {
      }
      if (groupSize > && groupSize <= 10)
        ok = true;
    }
    c = new Console();
    c.println("Server '" + serverName + "' connecting to relay " +
      getRelay() + "...");
    addTcpBridgeListener(this);
    ArrayList<String> connectList = connectToRelay(6000);
    if (connectList.isEmpty())
      errorQuit("Connection failed (timeout reached)");
    if (!connectList.get(0).equals(serverName))
      errorQuit("An instance of '" + serverName + "' already running.");
    c.println("Connection established");
  }

  private void errorQuit(String msg)
  {
    c.println(msg);
    c.println("Shutdown now...");
    TcpTools.delay(3000);
    System.exit(1);
  }

  public void notifyRelayConnection(boolean connected)
  {
    if (!connected)
      c.println("Connection to relay broken.");
  }

  public void notifyAgentConnection(String agentName, boolean connected)
  {
    String str = "";
    if (!connected)
    {
      c.println("Player: " + agentName + " disconnected");
      if (players.contains(agentName))
      {
        players.remove(agentName);
        sendCommandToGroup(Command.PLAYER_DISCONNECT,
          TcpTools.stringToIntAry(agentName));
        sendCommandToGroup(Command.NUMBER_OF_PLAYERS, players.size());
        sendCommandToGroup(Command.INIT_PLAYGROUND, nbMatchesStart);
      }
      return;
    }

    c.println("Player: " + agentName + " connected");
    if (players.size() == groupSize)
    {
      c.println("Game already running");
      sendCommand(serverName, agentName, Command.GAME_RUNNING);
      return;
    }

    // Send starting number of matches
    sendCommand(serverName, agentName, Command.INIT_PLAYGROUND, nbMatchesStart);

    // Add player to group
    players.add(agentName);
    sendCommandToGroup(Command.NUMBER_OF_PLAYERS, players.size());

    if (players.size() == groupSize)
    {
      // Group completed. Game started.
      for (String nickname : players)
        str += nickname + "&&";
      sendCommandToGroup(Command.GAME_STARTING, TcpTools.stringToIntAry(str));
      nbMatches = nbMatchesStart;
      TcpTools.delay(5000);
      // A random player starts to play
      String startPlayer = players.get((int)(groupSize * Math.random()));
      giveMoveTo(startPlayer);
    }
  }

  private void giveMoveTo(String player)
  {
    int indexOfPlayer = players.indexOf(player);
    sendCommand(serverName, player, Command.LOCAL_MOVE);
    for (int = 0; i < groupSize; i++)
    {
      if (i != indexOfPlayer)
        sendCommand(serverName, players.get(i), Command.REMOTE_MOVE,
          TcpTools.stringToIntAry(player));
    }
  }

  private void sendCommandToGroup(int command, int... data)
  {
    for (String nickname : players)
      sendCommand(serverName, nickname, command, data);
  }

  public void pipeRequest(String source, String destination, int[] indata)
  {
    // Check if group is still complete
    if (groupSize != players.size())
      return;
    switch (indata[0])
    {
      case Command.REPORT_POSITION:  // Pass command to other players
        c.println("Player " + source + " reports position " + indata[1]);
        nbMatches--;
        c.println("Number of remaining matches: " + nbMatches);
        for (String player : players)
        {
          if (!player.equals(source))
            sendCommand(serverName, player, Command.REPORT_POSITION, indata[1]);
        }
        break;

      case Command.REPORT_OK:
        c.println("Player " + source + " reports OK click");
        int nextPlayerIndex = (players.indexOf(source) + 1) % groupSize;
        String nextPlayer = players.get(nextPlayerIndex);
        if (nbMatches <= 0) // Game over
        {
          sendCommand(serverName, source, Command.LOOSER);
          for (String player : players)
          {
            if (!player.equals(source))
              sendCommand(serverName, player, Command.WINNER,
                TcpTools.stringToIntAry(source));
          }
          TcpTools.delay(4000);
          sendCommandToGroup(Command.INIT_PLAYGROUND, nbMatchesStart);
          nbMatches = nbMatchesStart;
          giveMoveTo(source);
        }
        else
        {
          c.println("Give move to next player: " + nextPlayer);
          giveMoveTo(nextPlayer);
        }
        break;
    }
  }

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

Programmcode Player:

// TcpNimPlayer.java

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

public class TcpNimPlayer extends TcpAgent implements
  GGMouseListenerGGButtonListener
{
  // ------------------- Inner class MyTcpAgentAdapter -------------
  private class MyTcpAgentAdapter implements TcpAgentListener
  {
    private boolean isReady = true;

    public void notifyRelayConnection(boolean connected)
    {
    }

    public void notifyAgentConnection(String agentName, boolean connected)
    {
    }

    public void notifyBridgeConnection(boolean connect)
    {
      if (!connect)
      {
        gg.setTitle("Game server disconnected. Game terminated.");
        gg.setMouseEnabled(false);
        okBtn.setEnabled(false);
      }
    }

    public void dataReceived(String source, int[] data)
    {
      if (!isReady)
        return;

      String str = "";
      switch (data[0])
      {
        case Command.GAME_RUNNING:
          gg.setTitle("Game in process.\nPlease wait for next game.\n"
            + "Terminating now...");
          TcpTools.delay(5000);
          System.exit(0);
          break;

        case Command.PLAYER_DISCONNECT:
          disconnectedPlayer = TcpTools.intAryToString(data, 1);
          break;

        case Command.NUMBER_OF_PLAYERS:
          initPlayground();
          if (!disconnectedPlayer.equals(""))
            disconnectedPlayer = "Disconnected: " + disconnectedPlayer + ". ";
          gg.setTitle(disconnectedPlayer
            + "Current number in group: " + data[1]
            + ". Waiting for more players to join...");
          disconnectedPlayer = "";
          break;

        case Command.GAME_STARTING:
          str = "Group complete. In group: ";
          String[] names =
            TcpTools.split(TcpTools.intAryToString(data, 1), "&&");
          for (int i = 0; i < names.length; i++)
          {
            if (i < names.length - 1)
              str += names[i] + "; ";
            else
              str += names[i];
          }
          gg.setTitle(str + ". Wait start selection...");
          break;

        case Command.INIT_PLAYGROUND:
          nbMatches = data[1];
          initPlayground();
          break;

        case Command.REPORT_POSITION:
          Location loc = new Location(data[1], 4);
          gg.getOneActorAt(loc).removeSelf();
          nbMatches--;
          gg.setTitle(nbMatches + " left. " + remotePlayer + " is playing.");
          gg.refresh();
          break;

        case Command.LOCAL_MOVE:
          gg.activate();
          gg.setTitle(nbMatches + " left. It's you to play.");
          nbTaken = 0;
          gg.setMouseEnabled(true);
          okBtn.setEnabled(true);
          break;

        case Command.REMOTE_MOVE:
          remotePlayer = TcpTools.intAryToString(data, 1);
          gg.setTitle(nbMatches + " left. " + remotePlayer + " will play.");
          break;

        case Command.WINNER:
          String loser = TcpTools.intAryToString(data, 1);
          gg.setTitle("Game over. Player " + loser + " lost the game.");
          break;

        case Command.LOSER:
          gg.setTitle("Game over. You lost the game.");
          break;
      }
    }
  }

  // --------------- Inner class Match --------------
  private class Match extends Actor
  {
    public Match()
    {
      super("sprites/match.gif");
    }
  }
  // ------------------- End of inner class ------------------------
  //
  private final static String serverName = "NimServer";
  private final static String sessionID = "apx%3z";
  private String agentName;
  private String remotePlayer = "";
  private String disconnectedPlayer = "";
  private int nbMatches;
  private GameGrid gg;
  private GGBackground bg;
  private GGButton okBtn = new GGButton("sprites/ok.gif"true);
  private int nbTaken;

  // Protocol tags
  private interface Command
  {
    int GAME_RUNNING = 0;
    int NUMBER_OF_PLAYERS = 1;
    int INIT_PLAYGROUND = 2;
    int GAME_STARTING = 3;
    int REMOTE_MOVE = 4;
    int LOCAL_MOVE = 5;
    int REPORT_OK = 6;
    int REPORT_POSITION = 7;
    int WINNER = 8;
    int LOSER = 9;
    int PLAYER_DISCONNECT = 10;
  }

  public TcpNimPlayer()
  {
    super(sessionID, serverName);
    agentName = requestEntry("Enter your name (ASCII >3 chars):");
    initGameWindow();
    gg.addMouseListener(this, GGMouse.lPress);
    gg.setTitle("Connecting to relay " + getRelay() + "...");
    addTcpAgentListener(new MyTcpAgentAdapter());
    ArrayList<String> connectList =
      connectToRelay(agentName, 6000);
    if (connectList.isEmpty())
    {
      gg.setTitle("Connection to relay failed. Terminating now...");
      TcpTools.delay(3000);
      System.exit(1);
    }
    gg.setTitle("Connection established. Personal name: " + connectList.get(0));
    // Game server must be up and running
    if (!isBridgeConnected())
    {
      gg.setTitle("Game server not found. Terminating now...");
      TcpTools.delay(3000);
      System.exit(1);
    }
  }

  private void initGameWindow()
  {
    gg = new GameGrid(56, 9, 12, false);
    bg = gg.getBg();
    bg.clear(Color.blue);
    gg.addActor(okBtn, new Location(50, 4));
    okBtn.addButtonListener(this);
    gg.addMouseListener(this, GGMouse.lPress);
    gg.show();
  }

  private void initPlayground()
  {
    okBtn.setEnabled(false);
    gg.setMouseEnabled(false);
    gg.removeActors(Match.class);
    for (int i = 0; i < nbMatches; i++)
      gg.addActor(new Match(), new Location(2 + * i, 4));
  }

  public boolean mouseEvent(GGMouse mouse)
  {
    Location loc = gg.toLocationInGrid(mouse.getX(), mouse.getY());
    Actor actor = null;
    for (int y = 2; y < 7; y++)
    {
      actor = gg.getOneActorAt(new Location(loc.x, y), Match.class);
      if (actor != null)
        break;
    }
    if (actor != null)
    {
      if (nbTaken == 3)
        gg.setTitle("Take a maximum of 3. Click 'OK' to continue");
      else
      {
        actor.removeSelf();
        sendCommand(agentName, Command.REPORT_POSITION, actor.getLocation().x);
        nbMatches--;
        gg.setTitle(nbMatches + " matches remaining. Click 'OK' to continue");
        nbTaken++;
        gg.refresh();
      }
    }
    return false;
  }

  public void buttonClicked(GGButton button)
  {
    if (nbTaken == 0)
      gg.setTitle("You have to remove at least 1 match");
    else
    {
      sendCommand(agentName, Command.REPORT_OK);
      okBtn.setEnabled(false);
      gg.setMouseEnabled(false);
    }
  }

  public void buttonPressed(GGButton button)
  {
  }

  public void buttonReleased(GGButton button)
  {
  }

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

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