views:

48

answers:

2

Hello!

I'm currently trying to implement the game of Nim using Java, I want to be able to have one player act as the server and another as the player.

I'm fairly new to Java networking and have only had experience using basic TCP/IP where the human client connects to a computer host.

The trouble I'm having is that I need to be able to differentiate between the different players whilst implementing the protocol for the game (The protocol being the logic for the game).

As it stands I can let one player (Client) interact with the Server. All that happens is the Client can play the game but there is no oppostion (The Server merely tracks the state of the game e.g. How many sticks left, valid input etc..).

How would I go about adding a second player to take the place of the host?

Edit:

The Client and Server code has been posted, it is the code I have used and I'm quite comfortable with, the question I am asking is would it be a suitable base to implement a multi-player game or would I need to do something completely different?

My Nim Protocol: (Untested)

public class NimLogic 
{
    private static final int WAITING = 0;
    private static final int EVALUATING = 1;
    private static final int ANOTHER = 2;
    private int currentState = WAITING;
    private int theInput = 0;
    private int totalSticks = 10;

    String processInput(String input) {
        String theOutput = null;

        try 
        {
            theInput = Integer.parseInt(input);
        } 
        catch (Exception e) 
        {
            // Ignore conversion error
        }

        switch (currentState) 
        {
        case WAITING:
            theOutput = "Take how many sticks?";
            currentState = EVALUATING;
            break;

        case EVALUATING:

            if(theInput == 1 | theInput == 2 | theInput == 3)
            {
                if (theInput < totalSticks) 
                {
                    totalSticks -= theInput;
                    theOutput = "There are" + totalSticks + " left.";
                } 
                else if (theInput > totalSticks) 
                {
                    theOutput = "Error: You cannot take more sticks than that are available";
                    currentState = EVALUATING;
                } 
            }

            if(totalSticks == 1)
            {
                theOutput = "Game Over! Play again? (Yes = 1, No = 0)...";
                currentState = ANOTHER;
            }
            break;

        case ANOTHER:
            if (theInput == 1) 
            {
                totalSticks = 10;
                currentState = EVALUATING;
                theOutput = "Take how many sticks?";
            } 
            else 
            {
                theOutput = "Bye.";
            }
        }
        return theOutput;
    }
}

Thanks for all the help!

Edit:

Client

public class Client
{
   @SuppressWarnings("static-access")
public static void main(String machine[])
   {
      Socket kkSocket = null;
      PrintStream os = null;
      DataInputStream is = null;

      try
      {
         kkSocket = new Socket(machine[0], 4444);
         os = new PrintStream(kkSocket.getOutputStream());
         is = new DataInputStream(kkSocket.getInputStream());
      }
      catch(UnknownHostException e)
      {
         System.err.println("Socket Connect failed on " + machine[0]);
      }
      catch (IOException e)
      {
         System.err.println("Streams failed on " + machine[0]);
      }

      if (kkSocket != null && os != null && is != null )
      {
         try
         {
             String fromServer, fromClient;

             while((fromServer = is.readLine()) != null && !fromServer.equals("Bye."))
             {
                fromClient = JOptionPane.showInputDialog(fromServer);
                os.println(fromClient);
             }
             JOptionPane.showMessageDialog(null, "Goodbye, keep smiling.");
             os.close();
             is.close();
             kkSocket.close();
          }

         catch (UnknownHostException e)
         {
            System.err.println("Can't connect to " + machine[0] + e);
         }
         catch (IOException e)
         {
            e.printStackTrace();
            System.err.println("I/O failed on " +machine[0]);
         }
      }
   }
}

Server

public class Server
{
   public static void main(String arg[])
   {
      ServerSocket serverSocket = null;

      try
      {
         serverSocket = new ServerSocket(4444);
      }
      catch (IOException e)
      {
         System.err.println("Can't listen on 4444 -> " + e);
         System.exit(1);
      }

      Socket clientSocket = null;
      try       // allow the client to connect
      {
         clientSocket = serverSocket.accept();
      }
      catch (IOException e)
      {
         System.err.println("Failed accept on 4444 -> " + e);
         System.exit(1);
      }

      try
      {
         DataInputStream is =
            new DataInputStream(new BufferedInputStream
                           (clientSocket.getInputStream()));
         PrintStream os =
            new PrintStream(new BufferedOutputStream
              (clientSocket.getOutputStream(), 1024), false);
         GuessState kks = new GuessState();
         String inputLine, outputLine;

         outputLine = kks.processInput(null);
         os.println(outputLine);
         os.flush();

         while((inputLine = is.readLine()) != null
                && !outputLine.equals("Bye."))
         {
            outputLine = kks.processInput(inputLine);
            os.println(outputLine);
            os.flush();
         }
         os.close();
         is.close();
         clientSocket.close();
         serverSocket.close();
      }
      catch (IOException e)
      {
         e.printStackTrace();
      }
   }
}
+1  A: 

The server/client aspect should have no bearing on the communication of the two players. You should be able to spawn two instances of the Nim game, one that listen for an incoming connection on some port(Server), and one that connects to it (Client). Once the connection is established, you can pass Objects between the two instances over your connection that represent game information. Each instance of your Nim game is responsible for parsing that game data and running the Nim logic on it.

In essence, each instance of the game can run as a server or a client. Here's some code I wrote for Chess that should be applicable. Read through it. Elsewhere I instance a Server or Client and store it in a reference of type NetworkIdenitity.

   private abstract class NetworkEntity
        extends Thread {

        ObjectOutputStream outputStream;

        ObjectInputStream inputStream;

        Socket connectionHandle;

        Object messageToSend;

        Object receivedMessage;

        public NetworkEntity(final String name) {

            super(name);

        }

        @Override
        public abstract void run();

        public void getStreams()
                throws IOException {
            this.outputStream = new ObjectOutputStream(this.connectionHandle.getOutputStream());
            this.outputStream.flush();
            this.inputStream = new ObjectInputStream(this.connectionHandle.getInputStream());
        }

        public void closeConnection() {

            try {
                if (this.outputStream != null) {
                    this.outputStream.close();
                }
                if (this.inputStream != null) {
                    this.inputStream.close();
                }
                if (this.connectionHandle != null) {
                    this.connectionHandle.close();
                    chatPanel.writeToDisplay("Connection closed with "
                            + this.connectionHandle.getInetAddress().getHostName());
                }
            }
            catch (final IOException e) {
                JOptionPane.showMessageDialog(thisFrame, "Problems experienced when closing connection",
                        "Notification", JOptionPane.ERROR_MESSAGE);
            }

        }

        public void processIncomingData()
                throws IOException {

            do {
                try {
                    this.receivedMessage = this.inputStream.readObject();
                }
                catch (final ClassNotFoundException e) {
                    JOptionPane.showMessageDialog(thisFrame, "read() error: message from "
                            + this.connectionHandle.getInetAddress().getHostName() + " not received", "Notification",
                            JOptionPane.ERROR_MESSAGE);
                }
                if (this.receivedMessage instanceof Move) {
                    final Move m = (Move) this.receivedMessage;
                    System.out.println(getName() + " got move" + m);
                    requestMove(Table.this.chessBoard, Table.this.currentPlayer, Table.this.currentOpponent, m, false);
                    repaint();
                }
                else if (this.receivedMessage instanceof Board) {
                    final Board b = (Board) this.receivedMessage;
                    System.out.println(getName() + " received this board:");
                    b.printCurrentBoardState();
                    // System.out.println("local copy looked like this: " );
                    // chessBoard.printCurrentBoardState();
                    // chessBoard.setGameBoard(b.getGameBoard());
                    // switchCurrentPlayer();
                    // chessBoard.updateBoardState(currentPlayer,
                    // currentOpponent);
                    repaint();
                }
                else if (this.receivedMessage instanceof String) {
                    chatPanel.writeToDisplay((String) this.receivedMessage);
                }
            } while (/* !message.equals("SERVER>>> TERMINATE") */true);

        }

        public void sendData(final Object obj_to_send) {
            try {
                this.outputStream.writeObject(obj_to_send);
                this.outputStream.flush();
            }
            catch (final IOException e) {

            }
        }

    }

    private final class Client
        extends NetworkEntity {

        private final String hostName;

        private final int serverPort;

        public Client(final String host, final int port) {
            super("CLIENT");
            this.hostName = host;
            this.serverPort = port;
        }

        @Override
        public void run() {
            try {
                connectToServer();
                getStreams();
                processIncomingData();
            }
            catch (final EOFException eof) {
            }
            catch (final IOException ioe) {
            }
            catch (final NullPointerException npe) {
            }
            finally {
                closeConnection();
            }
        }

        private void connectToServer()
                throws IOException {
            try {
                this.connectionHandle = new Socket(InetAddress.getByName(this.hostName), this.serverPort);
                connectionEstablished = true;
                chatPanel.writeToDisplay("Successfully connected to "
                        + this.connectionHandle.getInetAddress().getHostName());
            }
            catch (final IOException e) {
                chatPanel.writeToDisplay("Failed to connect to: " + this.hostName);
            }
        }

    }

    private final class Server
        extends NetworkEntity {

        private ServerSocket server;

        private final int listenPort;

        public Server(final int listen_port) {
            super("SERVER");
            this.listenPort = listen_port;
        }

        @Override
        public void run() {

            try {
                this.server = new ServerSocket(this.listenPort, 1);
                chatPanel.writeToDisplay("Listening on port " + this.listenPort);
                try {
                    waitForConnection();
                    getStreams();
                    processIncomingData();
                }
                catch (final EOFException eof) {
                    // System.out.println(getName() + "exception: " +eof);
                    // eof.printStackTrace();
                }
                catch (final IOException ioe) {
                    // System.out.println(getName() + "exception: " +ioe);
                    // ioe.printStackTrace();
                }
                finally {
                    closeConnection();
                }
            }
            catch (final IOException e) {
                JOptionPane.showMessageDialog(thisFrame, "Network Error: " + e, "Notification",
                        JOptionPane.ERROR_MESSAGE);
            }

        }

        private void waitForConnection()
                throws IOException {

            this.connectionHandle = this.server.accept();
            connectionEstablished = true;
            chatPanel.writeToDisplay("Connection received from:" + this.connectionHandle.getInetAddress().getHostName());

        }

        @Override
        public void closeConnection() {

            super.closeConnection();

            try {
                this.server.close();
            }
            catch (final IOException e) {
                chatPanel.writeToDisplay(getName() + "failed to disconnect from the network");
            }

        }
Amir Afghani
So if I understand correctly, the server can run on it's own and by running multiple client's they can each connect to the server and pass the correct data (such as player number and value) to the server to process?
Jamie Keeling
Jamie, you can do it that way, yes. One player hosts, the rest connect to the host.
Amir Afghani
+1  A: 

I'm not quite sure if I'm answering your question here, so apologies if I'm not. Also, it's been a little while since I did any Java networking code, so there might be a few wrinkles here which hopefully others can sort out.

The following is a bit of a brain dump of the changes I'd probably make, for better or worse...

  • Rework the networking code to accept multiple connections. Normally you'd do this by handing off the socket returned by ServerSocket.accept to a thread to process. If you were dealing with a lot of connections, you could do it using NIO instead, but that's probably too far to fast for now.
  • Separate the game state from the client conversation code. To keep things simple, embed the client conversation code in the thread object. The game state needs to be in an object that's shared between all the threads servicing the socket.
  • I'd recommend making the game state a proper 'domain object' rather than having it parsing strings etc. It should have operations like 'take(clientID, int)' rather than 'processInput'.
  • Consider using the observer pattern to distribute events from the domain object to the socket threads. Examples of events might be 'turnTaken' or 'gameComplete'.
  • Embed the notion of 'turns' into the game object, and have the server broadcast an event to the socket threads announcing whose turn it is.

Hope that gives you a starter for ten?

Paul Russell
Great! Thanks for the tips.
Jamie Keeling