2

I am working on a client/server Tic-Tac-Toe game that consists of one server, and a client that consists of two threads. The entire program includes a TicTacToeServer class, TicTacToeService class, and TicTacToeClientPanel (which is the GUI and the client put together).

The main problem I am facing is within the client class itself. I launch two windows of the GUI/client (for the two different players), and am able to place one marker (X) on the first player. After this, the threads seem to halt, and I am unable to continue playing the game.

If I attempt to put client 1's (player 1) thread to sleep, it sleeps for its given allotment, but client 2's (player 2) thread never begins.

Is there any way that I can alternate between these two threads and go through my program, dependent on which player's turn it is?

    import java.awt.*;  //Color and GridLayout
import java.awt.event.*;
import java.io.*;   //DataInputStream & DataOutputStream
import java.net.Socket;
import java.util.Scanner;

import javax.swing.*;   //JPanel & JPanel
import javax.swing.border.LineBorder;

/**
 * This is the Main Panel for the TicTacToe Client.
 * It uses a displayBoard of Cell objects to display the TicTacToe board
 * @author Professor Myers
 * 
 */
public class TicTacToeClientPanel extends JPanel implements Runnable {
    //instance variables and constants
    private Cell displayBoard[][] = new Cell[3][3];
    private Scanner fromServer;
    private PrintWriter out;
    private Boolean myTurn, waiting, inputReady;
    private Thread thread;
private char mySymbol;
private int rowSelected, columnSelected;
private JLabel statusLabel, playerInfo;
public static final int PLAYER1 = 1, PLAYER2 = 2;

public TicTacToeClientPanel()
{
    //give initial values to instance variables
    mySymbol = ' ';
    myTurn = false;
    JPanel sub1 = new JPanel();
    playerInfo = new JLabel("");
    statusLabel = new JLabel("");

    sub1.setLayout(new GridBagLayout());
    GridBagConstraints c = new GridBagConstraints();


    //initialize Cells in board array and add to display
    for (int y = 0; y < 3; y++)
    {
        for (int x = 0; x < 3; x++)
        {
            displayBoard[y][x] = new Cell(x+1, y+1);

            c.gridx = y;
            c.gridy = x;
            c.fill = GridBagConstraints.BOTH;
            sub1.add(displayBoard[y][x], c);
        }
    }

    setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
    add(new JPanel().add(playerInfo));
    add(sub1);
    add(new JPanel().add(statusLabel));

    connectToServer();

}

private void connectToServer()
{
    try
    {
        //create socket
        //set up Scanner and PrintWriter
        Socket s = new Socket("localhost", 8880);

        InputStream instream = s.getInputStream();
        OutputStream outstream = s.getOutputStream();
        fromServer = new Scanner(instream);
        out = new PrintWriter(outstream);

    }
    catch (Exception e)
    {
        System.err.println(e);
    }
    //start the thread
    thread = new Thread(this);
    thread.start();
}

public void run()
{
    int otherPRow, otherPColumn;

    try {

        int player = Integer.parseInt(fromServer.nextLine());   //Begin the game
        int message = 0;

        //set up symbol
        //keep track of who's turn it is
        //Display the player number and symbol (JLabel)
        //Display the status of the player (who's turn is it)
        if (player == PLAYER1) {
            mySymbol = 'X';
            playerInfo.setText("Player 1 with symbol \'X\'");
            statusLabel.setText("My turn");
            myTurn = true;  //player1 goes first

            message = Integer.parseInt(fromServer.nextLine());
        }
        else if (player == PLAYER2) {
            mySymbol = 'O';
            playerInfo.setText("Player 2 with symbol \'O\'");
            statusLabel.setText("Waiting for Player 1 to move");
            //what to do with waiting?
            myTurn = false;

            while (!myTurn) {
                if (fromServer.hasNextLine()) {
                    System.out.println("ITS HAPPENING");
                    myTurn = true;
                    thread.setPriority(thread.MAX_PRIORITY);
                }
            }
        }

        while(message != 1 && message != 2 && message != 3) //CHANGE TO GAME NOT OVER
        {   
            if(player == PLAYER1)
            {
                //wait for user to select a cell - sleep for awhile
                //"write" the row and column to server
                //"read" from the server - perform the appropriate action

                //this code is only reached if server passes 5 or 4 to the first player
                if (myTurn) {
                    waiting = true;
                    while(waiting) {
                        Thread.sleep(1000);
                    }   //thread sleeps until something is clicked
                }

                //if this cell value is not empty (WRITE)
                if (displayBoard[rowSelected-1][columnSelected-1].getSymbol() != ' ') {

                    System.out.println("Success");

                    out.println(rowSelected + '\n' + columnSelected);
                    out.flush();

                    statusLabel.setText("Waiting for Player 2 to move");
                }

                waiting = true;
                while (waiting)
                    Thread.currentThread().sleep(1000);


                //READ from server
                message = Integer.parseInt(fromServer.nextLine());  
                if (message == 1) {
                    statusLabel.setText("I Won! (X)");
                    return;
                }
                else if (message == 2) {
                    //update from player 2's turn
                    otherPRow = Integer.parseInt(fromServer.nextLine());
                    otherPColumn = Integer.parseInt(fromServer.nextLine());
                    displayBoard[otherPRow-1][otherPColumn-1].setSymbol('O');

                    statusLabel.setText("Player 2 has won (O)");
                    return;
                }
                else if (message == 3) {
                    //update from player 2's turn
                    otherPRow = Integer.parseInt(fromServer.nextLine());
                    otherPColumn = Integer.parseInt(fromServer.nextLine());
                    displayBoard[otherPRow-1][otherPColumn-1].setSymbol('O');

                    statusLabel.setText("Game is over, no winner");
                    return;
                }
                else if (message == 4) {    //traverses back to beginning of loop
                    otherPRow = Integer.parseInt(fromServer.nextLine());
                    otherPColumn = Integer.parseInt(fromServer.nextLine()); //What kind does it send? normal or +1?

                    displayBoard[otherPRow-1][otherPColumn-1].setSymbol('O');

                    statusLabel.setText("My turn");
                    myTurn = true;
                }

            }
            else if(player == PLAYER2)
            {   
                //"read" from the server - perform the appropriate action
                //wait for the user to select a cell - sleep for a while
                //"write" the row and column to server  
                myTurn = true;
                statusLabel.setText("My turn");

                message = Integer.parseInt(fromServer.nextLine());
                System.out.println(message);


                //player1 has won or game is full
                if (message == 1 || message == 3) {
                    otherPRow = Integer.parseInt(fromServer.nextLine());
                    otherPColumn = Integer.parseInt(fromServer.nextLine());

                    displayBoard[otherPRow-1][otherPColumn-1].setSymbol('X');

                    if (message == 1) {
                        statusLabel.setText("Player 1 (X) won");
                        return;
                    }
                    else {
                        statusLabel.setText("Game is over, no winner");
                        return;
                    }
                }
                else if (message == 2) {    //player2 has won
                    statusLabel.setText("I won! (O)");
                    return;
                }
                else if (message == 4) {
                    otherPRow = Integer.parseInt(fromServer.nextLine());
                    otherPColumn = Integer.parseInt(fromServer.nextLine());

                    displayBoard[otherPRow-1][otherPColumn-1].setSymbol('X');

                    //SLEEP for user input
                    statusLabel.setText("My turn");
                    myTurn = true;
                    waiting = true;

                    while (waiting) {
                        Thread.sleep(1000);
                    }

                    //WRITE to server 
                    char s = displayBoard[rowSelected-1][columnSelected-1].getSymbol();
                    if (s != ' ' && s != 'X' && waiting == false) {
                        out.println(rowSelected + '\n' + columnSelected);
                        out.flush();
                    }
                    statusLabel.setText("Waiting for Player 1 to move");

                    if (!myTurn) 
                        Thread.currentThread().sleep(10000);
                }
            }
        }
    }
    catch (Exception e)
    {
    }
}


public class Cell extends JPanel
{
    int row;
    int column;
    private char symbol;

    public Cell(int r, int c) 
    {
        row = r;
        column = c;
        symbol = ' ';
        setBorder(new LineBorder(Color.black,1));
        setPreferredSize(new Dimension(100,150));
        addMouseListener(new ClickListener());
    }

    public void setSymbol(char c)
    {
        symbol = c;
        repaint();
    }

    public char getSymbol()
    {
        return symbol;
    }

    protected void paintComponent (Graphics g)
    {
        super.paintComponent(g);

        if(symbol == 'X')
        {
            g.drawLine(10, 10, getWidth()-10, getHeight()-10);
            g.drawLine(getWidth()-10, 10, 10, getHeight()-10);
        }
        else if(symbol == 'O')
        {
            g.drawOval(10, 10, getWidth()-10, getHeight()-20);
        }
    }

    private class ClickListener extends MouseAdapter
    {
        public void mouseClicked(MouseEvent e)
        {
            System.out.println("Clicked: " + row + " " + column);

            if(symbol == ' ' && myTurn)
            {
                setSymbol(mySymbol);
                myTurn = false;
                rowSelected = row;
                columnSelected = column;
                statusLabel.setText("Waiting for the other player to move");
                waiting = false;
            }
        }
    }
}

public static void main (String[] args)
{
    JFrame frame = new JFrame();
    frame.setBounds(0, 0, 1000, 1200);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    TicTacToeClientPanel ttt = new TicTacToeClientPanel();
    frame.getContentPane().add(ttt);
    frame.pack();
    frame.setVisible(true);
}

}

  • 1
    This should happen automatically. Each thread should be given a time slice by the JDK. If you post more of your thread code we may be able to find out why the 2nd thread isn't starting or is blocked. – Gray Dec 11 '12 at 00:30
  • 2
    I think we're going to have see some code. The basic premise I would use is a "token" based approach. While a player has the "token" they can make a move. This should eliminate the need to worry about trying to pause or sleep threads – MadProgrammer Dec 11 '12 at 00:32
  • The "UI" Thread has to be different from the "Player" threads. Can you describe how the UI is created and how the player threads are expected to control the UI? – Miserable Variable Dec 11 '12 at 00:35
  • 1
    Is this a programming exercise? Because in a strictly turn based game like tic-tac-toe you only need one thread to service both player requests. – Perception Dec 11 '12 at 00:37
  • I've attached the code to the client class. The way I've coded it is really different based off other examples I've seen online. I'm pretty new to all of this as you can see. I'm attempting to use the 'myTurn' variable as a token, but I've been pretty unsuccessful with that. – user1893199 Dec 11 '12 at 00:38
  • 1
    You shouldn't be putting any Thread to sleep. Instead you should write your code so that functionality is based on state. In other words certain functionality such as adding an x or o is disabled when its not the players turn and then re-enabled when it is his turn. You must make your classes state dependent and keep your program event-driven. – Hovercraft Full Of Eels Dec 11 '12 at 00:43
  • 1
    You should never synchronize or wait on a boolean field. Critical bug there. See here: http://stackoverflow.com/questions/10324272/why-is-it-not-good-practice-to-synchronize-on-boolean/10324280#10324280 – Gray Dec 11 '12 at 00:50
  • Edit on my previous comment: as I now see @MadProgrammer already mentioned. – Hovercraft Full Of Eels Dec 11 '12 at 00:51
  • 1
    Also, if you are using _any_ field in multiple threads it must be `volatile` or `synchronized`. Seems like some reading might be in order. http://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html – Gray Dec 11 '12 at 00:52
  • I would highly recommend that you take the time to read through [Concurrency in Swing](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html) – MadProgrammer Dec 11 '12 at 01:25
  • Since `waiting` is neither volatile nor accessed in a synchronized block (or lock context), it's possible the polling thread never sees changes to its value. [This example](http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.3) in section 17.3 of the Java Language Specification explains it better than I can. – VGR Dec 11 '12 at 02:04

1 Answers1

1

I quickly looked at your code. One tip: the variable waiting is not volatile. Therefore, there is no guarantee that the following loop will ever end:

   waiting = true;
   while(waiting) {
       Thread.sleep(1000);
   }   //thread sleeps until something is clicked 

On a mouse click waiting is set to false. That happens in a different thread. Because the variable waiting is not volatile, the JVM is allowed to optimize the above loop to:

while (true) {
   Thread.sleep(1000);
}

Try making waiting volatile. That forces each write to this variable to become visible by other threads. If the variable is not volatile, then each thread may keep its own local copy of this variable.

gogognome
  • 727
  • 8
  • 24