Academic Integrity: tutoring, explanations, and feedback — we don’t complete graded work or submit on a student’s behalf.

The programs in Figs. 27.13 and 27.15 implemented a multithreaded, client/server

ID: 3535013 • Letter: T

Question

The programs in Figs. 27.13 and 27.15 implemented a multithreaded, client/server version of the game of Tic-Tac-Toe. Our goal in developing this game was to demonstrate a multithreaded server that could process multiple connections from clients at the same time. The server in the example is really a mediator between the two client applets—it makes sure that each move is valid and that each client moves in the proper order. The server does not determine who won or lost or whether there was a draw. Also, there’s no capability to allow a new game to be played or to terminate an existing game.

Modify the TicTacToeServer class to test for a win, loss or draw after each move. Send a message to each client that indicates the result of the game when the game is over.

Modify the TicTacToeClient class to display a button that when clicked allows the client to play another game. The button should be enabled only when a game completes. Both class TicTacToeClient and class TicTacToeServermust be modified to reset the board and all state information. Also, the other TicTacToeClient should be notified that a new game is about to begin so that its board and state can be reset.

Modify the TicTacToeClient class to provide a button that allows a client to terminate the program at any time. When the user clicks the button, the server and the other client should be notified. The server should then wait for a connection from another client so that a new game can begin.

Modify the TicTacToeClient class and the TicTacToeServer class so that the winner of a game can choose game piece X or O for the next game. Remember: X always goes first.

If you’d like to be ambitious, allow a client to play against the server while the server waits for a connection from another client.

// Fig. 27.13: TicTacToeServer.java

// Server side of client/server Tic-Tac-Toe program.

import java.awt.BorderLayout;

import java.net.ServerSocket;

import java.net.Socket;

import java.io.IOException;

import java.util.Formatter;

import java.util.Scanner;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

import java.util.concurrent.locks.Condition;

import javax.swing.JFrame;

import javax.swing.JTextArea;

import javax.swing.SwingUtilities;


public class TicTacToeServer extends JFrame

{

private String[] board = new String[ 9 ]; // tic-tac-toe board

private JTextArea outputArea; // for outputting moves

private Player[] players; // array of Players

private ServerSocket server; // server socket to connect with clients

private int currentPlayer; // keeps track of player with current move

private final static int PLAYER_X = 0; // constant for first player

private final static int PLAYER_O = 1; // constant for second player

private final static String[] MARKS = { "X", "O" }; // array of marks

private ExecutorService runGame; // will run players

private Lock gameLock; // to lock game for synchronization

private Condition otherPlayerConnected; // to wait for other player

private Condition otherPlayerTurn; // to wait for other player's turn


// set up tic-tac-toe server and GUI that displays messages

public TicTacToeServer()

{

super( "Tic-Tac-Toe Server" ); // set title of window


// create ExecutorService with a thread for each player

runGame = Executors.newFixedThreadPool( 2 );

gameLock = new ReentrantLock(); // create lock for game


// condition variable for both players being connected

otherPlayerConnected = gameLock.newCondition();


// condition variable for the other player's turn

otherPlayerTurn = gameLock.newCondition();


for ( int i = 0; i < 9; i++ )

board[ i ] = new String( "" ); // create tic-tac-toe board

players = new Player[ 2 ]; // create array of players

currentPlayer = PLAYER_X; // set current player to first player

try

{

server = new ServerSocket( 12345, 2 ); // set up ServerSocket

} // end try

catch ( IOException ioException )

{

ioException.printStackTrace();

System.exit( 1 );

} // end catch


outputArea = new JTextArea(); // create JTextArea for output

add( outputArea, BorderLayout.CENTER );

outputArea.setText( "Server awaiting connections " );


setSize( 300, 300 ); // set size of window

setVisible( true ); // show window

} // end TicTacToeServer constructor


// wait for two connections so game can be played

public void execute()

{

// wait for each client to connect

for ( int i = 0; i < players.length; i++ )

{

try // wait for connection, create Player, start runnable

{

players[ i ] = new Player( server.accept(), i );

runGame.execute( players[ i ] ); // execute player runnable

} // end try

catch ( IOException ioException )

{

ioException.printStackTrace();

System.exit( 1 );

} // end catch

} // end for


gameLock.lock(); // lock game to signal player X's thread


try

{

players[ PLAYER_X ].setSuspended( false ); // resume player X

otherPlayerConnected.signal(); // wake up player X's thread

} // end try

finally

{

gameLock.unlock(); // unlock game after signalling player X

} // end finally

} // end method execute

// display message in outputArea

private void displayMessage( final String messageToDisplay )

{

// display message from event-dispatch thread of execution

SwingUtilities.invokeLater(

new Runnable()

{

public void run() // updates outputArea

{

outputArea.append( messageToDisplay ); // add message

} // end method run

} // end inner class

); // end call to SwingUtilities.invokeLater

} // end method displayMessage


// determine if move is valid

public boolean validateAndMove( int location, int player )

{

// while not current player, must wait for turn

while ( player != currentPlayer )

{

gameLock.lock(); // lock game to wait for other player to go


try

{

otherPlayerTurn.await(); // wait for player's turn

} // end try

catch ( InterruptedException exception )

{

exception.printStackTrace();

} // end catch

finally

{

gameLock.unlock(); // unlock game after waiting

} // end finally

} // end while


// if location not occupied, make move

if ( !isOccupied( location ) )

{

board[ location ] = MARKS[ currentPlayer ]; // set move on board

currentPlayer = ( currentPlayer + 1 ) % 2; // change player


// let new current player know that move occurred

players[ currentPlayer ].otherPlayerMoved( location );


gameLock.lock(); // lock game to signal other player to go


try

{

otherPlayerTurn.signal(); // signal other player to continue

} // end try

finally

{

gameLock.unlock(); // unlock game after signaling

} // end finally


return true; // notify player that move was valid

} // end if

else // move was not valid

return false; // notify player that move was invalid

} // end method validateAndMove


// determine whether location is occupied

public boolean isOccupied( int location )

{

if ( board[ location ].equals( MARKS[ PLAYER_X ] ) ||

board [ location ].equals( MARKS[ PLAYER_O ] ) )

return true; // location is occupied

else

return false; // location is not occupied

} // end method isOccupied


// place code in this method to determine whether game over

public boolean isGameOver()

{

return false; // this is left as an exercise

} // end method isGameOver


// private inner class Player manages each Player as a runnable

private class Player implements Runnable

{

private Socket connection; // connection to client

private Scanner input; // input from client

private Formatter output; // output to client

private int playerNumber; // tracks which player this is

private String mark; // mark for this player

private boolean suspended = true; // whether thread is suspended


// set up Player thread

public Player( Socket socket, int number )

{

playerNumber = number; // store this player's number

mark = MARKS[ playerNumber ]; // specify player's mark

connection = socket; // store socket for client

try // obtain streams from Socket

{

input = new Scanner( connection.getInputStream() );

output = new Formatter( connection.getOutputStream() );

} // end try

catch ( IOException ioException )

{

ioException.printStackTrace();

System.exit( 1 );

} // end catch

} // end Player constructor


// send message that other player moved

public void otherPlayerMoved( int location )

{

output.format( "Opponent moved " );

output.format( "%d ", location ); // send location of move

output.flush(); // flush output

} // end method otherPlayerMoved


// control thread's execution

public void run()

{

// send client its mark (X or O), process messages from client

try

{

displayMessage( "Player " + mark + " connected " );

output.format( "%s ", mark ); // send player's mark

output.flush(); // flush output


// if player X, wait for another player to arrive

if ( playerNumber == PLAYER_X )

{

output.format( "%s %s", "Player X connected",

"Waiting for another player " );

output.flush(); // flush output


gameLock.lock(); // lock game to wait for second player


try

{

while( suspended )

{

otherPlayerConnected.await(); // wait for player O

} // end while

} // end try

catch ( InterruptedException exception )

{

exception.printStackTrace();

} // end catch

finally

{

gameLock.unlock(); // unlock game after second player

} // end finally


// send message that other player connected

output.format( "Other player connected. Your move. " );

output.flush(); // flush output

} // end if

else

{

output.format( "Player O connected, please wait " );

output.flush(); // flush output

} // end else


// while game not over

while ( !isGameOver() )

{

int location = 0; // initialize move location


if ( input.hasNext() )

location = input.nextInt(); // get move location


// check for valid move

if ( validateAndMove( location, playerNumber ) )

{

displayMessage( " location: " + location );

output.format( "Valid move. " ); // notify client

output.flush(); // flush output

} // end if

else // move was invalid

{

output.format( "Invalid move, try again " );

output.flush(); // flush output

} // end else

} // end while

} // end try

finally

{

try

{

connection.close(); // close connection to client

} // end try

catch ( IOException ioException )

{

ioException.printStackTrace();

System.exit( 1 );

} // end catch

} // end finally

} // end method run


// set whether or not thread is suspended

public void setSuspended( boolean status )

{

suspended = status; // set value of suspended

} // end method setSuspended

} // end class Player

} // end class TicTacToeServer

// Fig. 27.14: TicTacToeServerTest.java

// Class that tests Tic-Tac-Toe server.

import javax.swing.JFrame;


public class TicTacToeServerTest

{

public static void main( String[] args )

{

TicTacToeServer application = new TicTacToeServer();

application.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

application.execute();

} // end main

} // end class TicTacToeServerTest

Explanation / Answer

import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.*; public class TicTacToe implements ActionListener { final String VERSION = "1.0"; //Setting up ALL the variables JFrame window = new JFrame("Tic-Tac-Toe " + VERSION); JMenuBar mnuMain = new JMenuBar(); JMenuItem mnuNewGame = new JMenuItem("New Game"), mnuInstruction = new JMenuItem("Instructions"), mnuExit = new JMenuItem("Exit"), mnuAbout = new JMenuItem("About"); JButton btn1v1 = new JButton("Player vs Player"), btn1vCPU = new JButton("Player vs CPU"), btnBack = new JButton("