我正在研究Java How-to-Code的问题,问题28.20。本书设置了一个多线程客户端/服务器,允许两个用户互相玩tic tac toe。该问题要求我们修改现有代码以添加某些功能。我坚持的是添加一个“新游戏”按钮。我已经添加了按钮,并获得按下按钮的客户端通知服务器,但我无法弄清楚如何做,是让服务器向其他客户端发送消息。现在,它只会响应按下按钮的那个。如何让它向其他客户或两个客户发送消息?感谢。
以下是客户端代码:
// Fig. 27.15: TicTacToeClient.java
// Client side of client/server Tic-Tac-Toe program.
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.net.Socket;
import java.net.InetAddress;
import java.io.IOException;
import javax.swing.*;
import java.util.Formatter;
import java.util.Scanner;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
public class TicTacToeClient extends JFrame implements Runnable
{
private JTextField idField; // textfield to display player's mark
private JTextArea displayArea; // JTextArea to display output
private JPanel boardPanel; // panel for tic-tac-toe board
private JPanel panel2; // panel to hold board
private JButton playAgain;
private JButton disconnect;
private Square[][] board; // tic-tac-toe board
private Square currentSquare; // current square
private Socket connection; // connection to server
private Scanner input; // input from server
private Formatter output; // output to server
private String ticTacToeHost; // host name for server
private String myMark; // this client's mark
private boolean myTurn; // determines which client's turn it is
private final String X_MARK = "X"; // mark for first client
private final String O_MARK = "O"; // mark for second client
// set up user-interface and board
public TicTacToeClient( String host )
{
ticTacToeHost = host; // set name of server
displayArea = new JTextArea( 4, 30 ); // set up JTextArea
displayArea.setEditable( false );
add( new JScrollPane( displayArea ), BorderLayout.SOUTH );
boardPanel = new JPanel(); // set up panel for squares in board
boardPanel.setLayout( new GridLayout( 3, 3, 0, 0 ) );
board = new Square[ 3 ][ 3 ]; // create board
// loop over the rows in the board
for ( int row = 0; row < board.length; row++ )
{
// loop over the columns in the board
for ( int column = 0; column < board[ row ].length; column++ )
{
// create square
board[ row ][ column ] = new Square( " ", row * 3 + column );
boardPanel.add( board[ row ][ column ] ); // add square
} // end inner for
} // end outer for
idField = new JTextField(); // set up textfield
idField.setEditable( false );
add(idField, BorderLayout.NORTH);
panel2 = new JPanel(); // set up panel to contain boardPanel
panel2.add(boardPanel, BorderLayout.CENTER); // add board panel
add( panel2, BorderLayout.CENTER ); // add container panel
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new BorderLayout(0,5));
playAgain = new JButton("Play again?");
playAgain.setFont(new Font("NewFont",Font.PLAIN,12));
playAgain.setEnabled(false);
buttonPanel.add(playAgain, BorderLayout.NORTH);
disconnect = new JButton("Disconnect");
disconnect.setFont(new Font("NewFont2", Font.PLAIN, 12));
buttonPanel.add(disconnect, BorderLayout.SOUTH);
add(buttonPanel, BorderLayout.EAST);
setSize( 350, 250 ); // set size of window
setVisible( true ); // show window
startClient();
} // end TicTacToeClient constructor
// start the client thread
public void startClient()
{
try // connect to server and get streams
{
// make connection to server
connection = new Socket(
InetAddress.getByName( ticTacToeHost ), 12345 );
// get streams for input and output
input = new Scanner( connection.getInputStream() );
output = new Formatter( connection.getOutputStream() );
} // end try
catch ( IOException ioException )
{
ioException.printStackTrace();
} // end catch
// create and start worker thread for this client
ExecutorService worker = Executors.newFixedThreadPool( 1 );
worker.execute( this ); // execute client
} // end method startClient
// control thread that allows continuous update of displayArea
public void run()
{
myMark = input.nextLine(); // get player's mark (X or O)
SwingUtilities.invokeLater(
new Runnable()
{
public void run()
{
// display player's mark
idField.setText( "You are player \"" + myMark + "\"" );
} // end method run
} // end anonymous inner class
); // end call to SwingUtilities.invokeLater
myTurn = ( myMark.equals( X_MARK ) ); // determine if client's turn
playAgain.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
output.format("New Game\n");
output.flush();
// loop over the rows in the board
for ( int row = 0; row < board.length; row++ )
{
// loop over the columns in the board
for ( int column = 0; column < board[ row ].length;column++ )
{
// create square
setMark(board[row][column], "");
} // end inner for
} // end outer for
}
});
// receive messages sent to client and output them
while ( true )
{
if ( input.hasNextLine() )
processMessage( input.nextLine() );
} // end while
} // end method run
// process messages received by client
private void processMessage( String message )
{
// valid move occurred
if ( message.equals( "Valid move." ) )
{
displayMessage( "Valid move, please wait.\n" );
setMark( currentSquare, myMark ); // set mark in square
} // end if
else if ( message.equals( "Invalid move, try again" ) )
{
displayMessage( message + "\n" ); // display invalid move
myTurn = true; // still this client's turn
} // end else if
else if ( message.equals( "Opponent moved" ) )
{
int location = input.nextInt(); // get move location
input.nextLine(); // skip newline after int location
int row = location / 3; // calculate row
int column = location % 3; // calculate column
setMark( board[ row ][ column ],
( myMark.equals( X_MARK ) ? O_MARK : X_MARK ) ); // mark move
displayMessage( "Opponent moved. Your turn.\n" );
myTurn = true; // now this client's turn
} // end else if
else if(message.equals("Game over. Players Tied")){
int location = input.nextInt(); // get move location
input.nextLine(); // skip newline after int location
int row = location / 3; // calculate row
int column = location % 3; // calculate column
setMark( board[ row ][ column ],
( myMark.equals( X_MARK ) ? O_MARK : X_MARK ) ); // mark move
displayMessage("Game over. Players tied");
playAgain.setEnabled(true);
}//else if
else if(message.equals("Game over, you lose")){
int location = input.nextInt(); // get move location
input.nextLine(); // skip newline after int location
int row = location / 3; // calculate row
int column = location % 3; // calculate column
setMark( board[ row ][ column ],
( myMark.equals( X_MARK ) ? O_MARK : X_MARK ) ); // mark move
displayMessage("Game over, you lose.");
playAgain.setEnabled(true);
}//else if
else if(message.equals("Game over. You win!")){
displayMessage("Game over. You win!");
playAgain.setEnabled(true);
}else if(message.equals("New Game")){
displayMessage("\nNew game requested");
// loop over the rows in the board
for ( int row = 0; row < board.length; row++ )
{
// loop over the columns in the board
for ( int column = 0; column < board[ row ].length; column++ )
{
// create square
setMark(board[row][column], "");
} // end inner for
} // end outer for
playAgain.setEnabled(false);
}
else
displayMessage( message + "\n" ); // display the message
} // end method processMessage
// manipulate displayArea in event-dispatch thread
private void displayMessage( final String messageToDisplay )
{
SwingUtilities.invokeLater(
new Runnable()
{
public void run()
{
displayArea.append( messageToDisplay ); // updates output
} // end method run
} // end inner class
); // end call to SwingUtilities.invokeLater
} // end method displayMessage
// utility method to set mark on board in event-dispatch thread
private void setMark( final Square squareToMark, final String mark )
{
SwingUtilities.invokeLater(
new Runnable()
{
public void run()
{
squareToMark.setMark( mark ); // set mark in square
} // end method run
} // end anonymous inner class
); // end call to SwingUtilities.invokeLater
} // end method setMark
// send message to server indicating clicked square
public void sendClickedSquare( int location )
{
// if it is my turn
if ( myTurn )
{
output.format( "%d\n", location ); // send location to server
output.flush();
myTurn = false; // not my turn any more
} // end if
} // end method sendClickedSquare
// set current Square
public void setCurrentSquare( Square square )
{
currentSquare = square; // set current square to argument
} // end method setCurrentSquare
// private inner class for the squares on the board
private class Square extends JPanel
{
private String mark; // mark to be drawn in this square
private int location; // location of square
public Square( String squareMark, int squareLocation )
{
mark = squareMark; // set mark for this square
location = squareLocation; // set location of this square
addMouseListener(
new MouseAdapter()
{
public void mouseReleased( MouseEvent e )
{
setCurrentSquare( Square.this ); // set current
// send location of this square
sendClickedSquare( getSquareLocation() );
} // end method mouseReleased
} // end anonymous inner class
); // end call to addMouseListener
} // end Square constructor
// return preferred size of Square
public Dimension getPreferredSize()
{
return new Dimension( 30, 30 ); // return preferred size
} // end method getPreferredSize
// return minimum size of Square
public Dimension getMinimumSize()
{
return getPreferredSize(); // return preferred size
} // end method getMinimumSize
// set mark for Square
public void setMark( String newMark )
{
mark = newMark; // set mark of square
repaint(); // repaint square
} // end method setMark
// return Square location
public int getSquareLocation()
{
return location; // return location of square
} // end method getSquareLocation
// draw Square
public void paintComponent( Graphics g )
{
super.paintComponent( g );
g.drawRect( 0, 0, 29, 29 ); // draw square
g.drawString( mark, 11, 20 ); // draw mark
} // end method paintComponent
} // end inner-class Square
} // end class TicTacToeClient
服务器代码
// 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 ] = ""; // 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\n" );
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 = 1- currentPlayer; // 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
{
if(!isWin() && !isTie())
otherPlayerTurn.signal(); // signal other player to continue
} // end try
finally
{
if(!isWin() && !isTie())
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 isWin()
{
boolean isGameOver = false;
if(!board[0].equals("") && board[0].equals(board[1])
&& board[1].equals(board[2]))
isGameOver = true;
if(!board[3].equals("") && board[3].equals(board[4])
&& board[4].equals(board[5]))
isGameOver = true;
if(!board[6].equals("") && board[6].equals(board[7])
&& board[7].equals(board[8]))
isGameOver = true;
if(!board[0].equals("") && board[0].equals(board[3])
&& board[3].equals(board[6]))
isGameOver = true;
if(!board[1].equals("") && board[1].equals(board[4])
&& board[4].equals(board[7]))
isGameOver = true;
if(!board[2].equals("") && board[2].equals(board[5])
&& board[5].equals(board[8]))
isGameOver = true;
if(!board[0].equals("") && board[0].equals(board[4])
&& board[4].equals(board[8]))
isGameOver = true;
if(!board[2].equals("") && board[2].equals(board[4])
&& board[4].equals(board[6]))
isGameOver = true;
return isGameOver; // this is left as an exercise
} // end method isWin
public boolean isTie(){
boolean isTie = false;
if(!board[0].equals("") && !board[1].equals("")
&& !board[2].equals("") && !board[3].equals("") && !board[4].equals("")
&& !board[5].equals("") && !board[6].equals("")
&& !board[7].equals("") && !board[8].equals(""))
isTie = true;
return isTie;
}//end method is Tie
// 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 )
{
if (!isTie() && !isWin()) {
output.format("Opponent moved\n");
output.format("%d\n", location); // send location of move
output.flush(); // flush output
}else if(isWin()){
output.format("Game over, you lose\n");
output.format("%d\n", location);
output.flush();
}else if( isTie()){
output.format("Game over. Players Tied\n");
output.format("%d\n", location);
output.flush();
}
} // 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\n" );
output.format( "%s\n", 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\n%s", "Player X connected",
"Waiting for another player\n" );
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.\n" );
output.flush(); // flush output
} // end if
else
{
output.format( "Player O connected, please wait\n" );
output.flush(); // flush output
} // end else
// while game not over
while ( true )
{
int location = 9; // initialize move location
if ( input.hasNextInt() )
location = input.nextInt(); // get move location
if ( input.hasNextLine() )
processMessage( input.nextLine() );
// check for valid move
if ( validateAndMove( location, playerNumber ) )
{
displayMessage( "\nlocation: " + location );
output.format( "Valid move.\n" ); // notify client
output.flush(); // flush output
if (isWin()){
output.format("Game over. You win!\n");
//output.format("%d\n", location);
output.flush();
}else if (isTie()){
output.format("Game over. Players Tied\n");
output.format("%d\n", location);
output.flush();
}
} // end if
else // move was invalid
{
output.format( "Invalid move, try again\n" );
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
public void processMessage(String message){
if ( message.equals( "New Game" ) )
{
displayMessage( "\nPlayer requested new game" );
for(int i=0; i<board.length; i++){
board[i] = "";
}
output.format("New Game\n");
output.flush();
} // end if
}
} // end class Player
} // end class TicTacToeServer
答案 0 :(得分:0)
您的客户需要收听服务器。理想情况下,客户端发送的操作消息将独立于服务器发送的状态消息。这种解耦允许比你正在做的“每个客户转弯”更强大的场景。具体而言,服务器将向所有客户端发送状态消息,客户端将根据这些消息而不是自己的操作更新其状态。