Member Avatar

I made this game equivalent to tictactoe but with a slight twist called find the X between two players using socket programming with Java. Don't know where I went wrong in trying to find the X but for some odd reason my count variable never updates allowing the victory message to never pop up.

Basically the game is as follows, the dealer starts by placing the X then the spotter has to guess where it is by placing the O. Still quite new to Java and socket programming, I followed a few Java socket programming examples which is what led me to the code as shown below:

The client and server uses the .startsWith() function to fetch the necessary inputs for the corresponding areas. Thus, by using keywords such as "MOVE" alongside with Integer.parseInt(variable.substring()) it is able to get the location on the board.

Server code

package com.findthex;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.Executors;

public class FindTheXServer {

    public static void main(String[] args) throws Exception {
        try (var listener = new ServerSocket(4444)) {
            System.out.println("Find the X Server is Running...");
            var pool = Executors.newFixedThreadPool(200);
            while (true) {
                Game game = new Game();
                pool.execute(game.new Player(listener.accept(), "Dealer"));
                pool.execute(game.new Player(listener.accept(), "Spotter"));
            }
        }
    }
}

class Game {

    private Player[] board = new Player[3];
    Player currentPlayer;

    public boolean hasWinner() {
        return (board[0] != null || board[1] != null && board[2] != null);
    }

    public synchronized void move(int location, Player player) {
        if (player != currentPlayer) {
            throw new IllegalStateException("Not your turn");
        } else if (player.opponent == null) {
            throw new IllegalStateException("You don't have an opponent yet");
        }
        board[location] = currentPlayer;
        currentPlayer = currentPlayer.opponent;
    }

    class Player implements Runnable {
        String choice;
        int rounds, dealerScore, spotterScore, count;
        Player opponent;
        Socket socket;
        Scanner input;
        PrintWriter output;

        public Player(Socket socket, String choice) {
            this.socket = socket;
            this.choice = choice;
            this.rounds = 1;
            this.dealerScore = 0;
            this.spotterScore = 0;
            this.count = 0;
        }

        @Override
        public void run() {
            try {
                while (true) {
                    setup();
                    processCommands();
                    count = 1;
                    rounds = rounds + 1;
                    if (rounds == 6 ) break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (opponent != null && opponent.output != null) {
                    opponent.output.println("OTHER_PLAYER_LEFT");
                }
                try {socket.close();} catch (IOException e) {}
            }
        }

        private void setup() throws IOException {
            input = new Scanner(socket.getInputStream());
            output = new PrintWriter(socket.getOutputStream(), true);
            output.println("Welcome " + choice);
            if (choice.equals("Dealer")) {
                currentPlayer = this;
                output.println("Waiting for opponent to connect.");
            } else {
                opponent = currentPlayer;
                opponent.opponent = this;
                opponent.output.println("Game has started! Your move.");
            }
        }

        private void processCommands() {
            while (input.hasNextLine()) {
                var command = input.nextLine();
                if (command.startsWith("Quit")) {
                    return;
                } else if (command.startsWith("Move")) {
                    processMoveCommand(Integer.parseInt(command.substring(5)));
                }
            }
        }

        private void processMoveCommand(int location) {
            try {
                move(location, this);
                output.println("Valid_move");
                opponent.output.println("Opponent_picked " + location);
                if (count == 1 && hasWinner()) {
                    output.println("Victory");
                    opponent.output.println("Defeat");
                    count = 0;
                }
            } catch (IllegalStateException e) {
                output.println("MESSAGE " + e.getMessage());
            }
        }
    }
}

Client side

package com.findthex;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class FindTheXClient {

    private JFrame frame = new JFrame("Find the X");
    private JLabel messageLabel = new JLabel("...");

    private Square[] board = new Square[3];
    private Square currentSquare;

    private Socket socket;
    private Scanner in;
    private PrintWriter out;

    public FindTheQueenClient(String serverAddress) throws Exception {
        socket = new Socket(serverAddress, 4444);
        in = new Scanner(socket.getInputStream());
        out = new PrintWriter(socket.getOutputStream(), true);

        messageLabel.setBackground(Color.lightGray);
        frame.getContentPane().add(messageLabel, BorderLayout.SOUTH);

        var boardPanel = new JPanel();
        boardPanel.setBackground(Color.black);
        boardPanel.setLayout(new GridLayout(1, 3, 3, 3));
        for (var i = 0; i < board.length; i++) {
            final int j = i;
            board[i] = new Square();
            board[i].addMouseListener(new MouseAdapter() {
                public void mousePressed(MouseEvent e) {
                    currentSquare = board[j];
                    out.println("Move " + j);
                }
            });
            boardPanel.add(board[i]);
        }
        frame.getContentPane().add(boardPanel, BorderLayout.CENTER);
    }

    public void play() throws Exception {
        try {
            var response = in.nextLine();
            var choice = response.substring(8);
            var opponentChoice = choice.equals("Dealer") ? 'O' : 'X';
            frame.setTitle("Find the X: Player " + choice);
            while (in.hasNextLine()) {
                response = in.nextLine();
                if (response.startsWith("Valid_move")) {
                    messageLabel.setText("Valid move, please wait");
                    currentSquare.setText('X');
                    currentSquare.repaint();
                } else if (response.startsWith("Opponent_picked")) {
                    var loc = Integer.parseInt(response.substring(16));
                    board[loc].setText(opponentChoice);
                    board[loc].repaint();
                    messageLabel.setText("Opponent moved, your turn");
                } else if (response.startsWith("MESSAGE")) {
                    messageLabel.setText((response.substring(8)));
                } else if (response.startsWith("Victory")) {
                    JOptionPane.showMessageDialog(frame, "Winner Winner Chicken Dinner!");
                    break;
                } else if (response.startsWith("Defeat")) {
                    JOptionPane.showMessageDialog(frame, "Sorry you lost");
                    break;
                } else if (response.startsWith("OTHER_PLAYER_LEFT")) {
                    JOptionPane.showMessageDialog(frame, "Other player left");
                    break;
                }
            }
            out.println("Quit");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            socket.close();
            frame.dispose();
        }
    }

    static class Square extends JPanel {
        JLabel label = new JLabel();

        public Square() {
            setBackground(Color.white);
            setLayout(new GridLayout());
            label.setFont(new Font("Arial", Font.BOLD, 40));
            add(label);
        }

        public void setText(char text) {
            label.setForeground(text == 'X' ? Color.BLUE : Color.RED);
            label.setText(text + "");
        }
    }

    public static void main(String[] args) throws Exception {
        if (args.length != 1) {
            System.err.println("Pass the server IP as the sole command line argument.");
            return;

    package com.findthex;

    import java.io.IOException;
    import java.io.PrintWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.Scanner;
    import java.util.concurrent.Executors;

    public class FindTheXServer {

        public static void main(String[] args) throws Exception {
            try (var listener = new ServerSocket(4444)) {
                System.out.println("Find the X Server is Running...");
                var pool = Executors.newFixedThreadPool(200);
                while (true) {
                    Game game = new Game();
                    pool.execute(game.new Player(listener.accept(), "Dealer"));
                    pool.execute(game.new Player(listener.accept(), "Spotter"));
                }
            }
        }
    }

    class Game {

        private Player[] board = new Player[3];
        Player currentPlayer;

        public boolean hasWinner() {
            return (board[0] != null || board[1] != null && board[2] != null);
        }

        public synchronized void move(int location, Player player) {
            if (player != currentPlayer) {
                throw new IllegalStateException("Not your turn");
            } else if (player.opponent == null) {
                throw new IllegalStateException("You don't have an opponent yet");
            }
            board[location] = currentPlayer;
            currentPlayer = currentPlayer.opponent;
        }

        class Player implements Runnable {
            String choice;
            int rounds, dealerScore, spotterScore, count;
            Player opponent;
            Socket socket;
            Scanner input;
            PrintWriter output;

            public Player(Socket socket, String choice) {
                this.socket = socket;
                this.choice = choice;
                this.rounds = 1;
                this.dealerScore = 0;
                this.spotterScore = 0;
                this.count = 0;
            }

            @Override
            public void run() {
                try {
                    while (true) {
                        setup();
                        processCommands();
                        count = 1;
                        rounds = rounds + 1;
                        if (rounds == 6 ) break;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (opponent != null && opponent.output != null) {
                        opponent.output.println("OTHER_PLAYER_LEFT");
                    }
                    try {socket.close();} catch (IOException e) {}
                }
            }

            private void setup() throws IOException {
                input = new Scanner(socket.getInputStream());
                output = new PrintWriter(socket.getOutputStream(), true);
                output.println("Welcome " + choice);
                if (choice.equals("Dealer")) {
                    currentPlayer = this;
                    output.println("Waiting for opponent to connect.");
                } else {
                    opponent = currentPlayer;
                    opponent.opponent = this;
                    opponent.output.println("Game has started! Your move.");
                }
            }

            private void processCommands() {
                while (input.hasNextLine()) {
                    var command = input.nextLine();
                    if (command.startsWith("Quit")) {
                        return;
                    } else if (command.startsWith("Move")) {
                        processMoveCommand(Integer.parseInt(command.substring(5)));
                    }
                }
            }

            private void processMoveCommand(int location) {
                try {
                    move(location, this);
                    output.println("Valid_move");
                    opponent.output.println("Opponent_picked " + location);
                    if (count == 1 && hasWinner()) {
                        output.println("Victory");
                        opponent.output.println("Defeat");
                        count = 0;
                    }
                } catch (IllegalStateException e) {
                    output.println("MESSAGE " + e.getMessage());
                }
            }
        }
    }

        }
        FindTheXClient client = new FindTheXClient(args[0]);
        client.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        client.frame.setSize(320, 320);
        client.frame.setVisible(true);
        client.frame.setResizable(false);
        client.play();
    }
}

I was also trying to adjust the GridLayout but ended up with | | layout instead of three boxes in the middle so it can be a bit more presentable to the players. Also trying to implement 6 rounds and
so the game is best out of 5, however, count and rounds never seem to update the my run() function in the server code.

To run the code I used "127.0.0.1" as my serverAddress in IntelliJ.

A major possibility could also be due to the my win condition in the hasWinner() function.

Main problems:

  1. count and rounds doesn't seem as though they update during therun() function on server side.

  2. I can't seem to understand why for some strange reason, it only updates the Dealer's window and not the Spotter's window in Java Swing.

  3. Win condition logic isn't quite there for me, after trying to pull all nighters, can't seem to figure out what exactly would be ideal.

  4. Layout of the grid doesn't seem to work as expected with | | instead of three boxes in the middle.

In the server code I can't see where you uodate count. I was expecting a count++ somewhere.

the client code listing seems to be corrupted - main method is missing code and server code replaces it!
please repost the client.

commented: Great +0
Member Avatar

Sorry about that, first time using daniweb so I had trouble getting use to the UI. Here is the code, didn't realize it was missing a piece.

package com.findthex;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class FindTheXClient {

    private JFrame frame = new JFrame("Find the X");
    private JLabel messageLabel = new JLabel("...");

    private Square[] board = new Square[9];
    private Square currentSquare;

    private Socket socket;
    private Scanner in;
    private PrintWriter out;

    public FindTheXClient(String serverAddress) throws Exception {
        socket = new Socket(serverAddress, 4444);
        in = new Scanner(socket.getInputStream());
        out = new PrintWriter(socket.getOutputStream(), true);

        messageLabel.setBackground(Color.lightGray);
        frame.getContentPane().add(messageLabel, BorderLayout.SOUTH);

        var boardPanel = new JPanel();
        boardPanel.setBackground(Color.black);
        boardPanel.setLayout(new GridLayout(3, 3, 2, 2));
        for (var i = 0; i < board.length; i++) {
            final int j = i;
            board[i] = new Square();
            board[i].addMouseListener(new MouseAdapter() {
                public void mousePressed(MouseEvent e) {
                    currentSquare = board[j];
                    out.println("Move " + j);
                }
            });
            boardPanel.add(board[i]);
        }
        frame.getContentPane().add(boardPanel, BorderLayout.CENTER);
    }

    public void play() throws Exception {
        try {
            var response = in.nextLine();
            var choice = response.substring(8);
            var opponentChoice = choice.equals("Dealer") ? 'O' : 'X';
            frame.setTitle("Find the X: Player " + choice);
            while (in.hasNextLine()) {
                response = in.nextLine();
                if (response.startsWith("Valid_move")) {
                    messageLabel.setText("Valid move, please wait");
                    currentSquare.setText('X');
                    currentSquare.repaint();
                } else if (response.startsWith("Opponent_picked")) {
                    var loc = Integer.parseInt(response.substring(16));
                    board[loc].setText(opponentChoice);
                    board[loc].repaint();
                    messageLabel.setText("Opponent moved, your turn");
                } else if (response.startsWith("MESSAGE")) {
                    messageLabel.setText((response.substring(8)));
                } else if (response.startsWith("Victory")) {
                    JOptionPane.showMessageDialog(frame, "Winner Winner Chicken Dinner!");
                    break;
                } else if (response.startsWith("Defeat")) {
                    JOptionPane.showMessageDialog(frame, "Sorry you lost");
                    break;
                } else if (response.startsWith("OTHER_PLAYER_LEFT")) {
                    JOptionPane.showMessageDialog(frame, "Other player left");
                    break;
                }
            }
            out.println("Quit");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            socket.close();
            frame.dispose();
        }
    }

    static class Square extends JPanel {
        JLabel label = new JLabel();

        public Square() {
            setBackground(Color.white);
            setLayout(new GridLayout());
            label.setFont(new Font("Arial", Font.BOLD, 40));
            add(label);
        }

        public void setText(char text) {
            label.setForeground(text == 'X' ? Color.BLUE : Color.RED);
            label.setText(text + "");
        }
    }

    public static void main(String[] args) throws Exception {
        if (args.length != 1) {
            System.err.println("Pass the server IP as the sole command line argument.");
            return;
        }
        FindTheXClient client = new FindTheXClient(args[0]);
        client.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        client.frame.setSize(320, 320);
        client.frame.setVisible(true);
        client.frame.setResizable(false);
        client.play();
    }
}

Oh yes, i didn't update it. I just tried to give it a 0 instead I should have done count++; during therun() and count--; in the processMoveCommand()function. I am currently thinking of instead trying to update count on the client side, I don't know which will be good though.

P.S. sorry about the comment, was testing.

I am currently thinking of instead trying to update count on the client side, I don't know which will be good though.

In general the more you share the logic between the server and the client, the harder it will be be. Tracking distributed state info across asynchronous components on different machines is a nightmare.
The simplest architecture is to have the server do all the work and the clients just get user input to send to the server, and display whatever the server tells them to.

Member Avatar

The simplest architecture is to have the server do all the work and the clients just get user input to send to the server, and display whatever the server tells them to.

Ahh, so that's how it is. Honestly, it is a disaster.. been trying to debug this for a few days now. I was basically trying to make a turn based game where dealer puts the X on the GUI then the spotter "guesses" where it is. I have been having trouble thinking of a good win condition so I thought I'd just use count == 1 & hasWinner() to basically force the game like where the spotter picks a square and wins/loses.

Do you have any other suggestions I can try?

You've got a lot of good code in there, but I think you have made it hard on yourself by trying to do it all in one big hit.
It's easier in the end to break it down into smaller steps and code AND TEST each one before moving on. Yes you will write some test code that is superflous to the end product, but you wil still get there faster. Right now you have to fire up a server and two clients just to test a simple counter in the game logic. That's slow.
Eg
I would start with the game itself as a stand alone unit. Public methods to start, make moves, declare winner etc. I would test that with a some hard-coded calls to the public methods and make sure its working. Shouldn't take long.
Then I would code the client with its GUI as a simple program and test it by having it call the game's public methods directly. Make sure it looks and works correctly.
Finally I would code, integrate, and test the server and client socket implementations.

It's an old but true cliché: "good programmers test early and test often".