井字棋Server/Clients开发问题:如何通知败方客户端游戏结果
Hey there, let's dig into this Tic-Tac-Toe problem you've been stuck on for two weeks—getting the losing client notified when the game ends, right? Let's break down what's missing in your current code and fix it step by step.
Our team is working on a course project modifying a Tic-Tac-Toe codebase to add win/draw detection. We've got the winning client and server showing the victory message, but we can't get the server to notify the other client that they've lost. Here's the relevant code:
Server Connections Code
public void execute() { // wait for each client to connect for ( int i = 0; i < players.length; i++ ) { try { players[ i ] = new Player( server.accept(), i ); runGame.execute( players[ i ] ); // execute player runnable } catch ( IOException ioException ) { ioException.printStackTrace(); System.exit( 1 ); } } gameLock.lock(); // lock game to signal player X's thread 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 } catch ( InterruptedException exception ) { exception.printStackTrace(); } finally { gameLock.unlock(); // unlock game after waiting } } }
Server Run Code
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 } } catch ( InterruptedException exception ) { exception.printStackTrace(); } finally { gameLock.unlock(); // unlock game after second player } // send message that other player connected output.format( "Other player connected. Your move.\n" ); output.flush(); // flush output } else { output.format( "Player O connected, please wait\n" ); output.flush(); // flush output } // 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( "\nlocation: " + location ); output.format( "Valid move.\n" ); // notify client ticTacBoard[location] = markLocation(location); if(checkForXWin()) { output.format( "Player X Has Won!\n" ); displayMessage( "\nPlayer X Has Won!" ); } else if(checkForOWin()) { output.format( "Player O Has Won!\n" ); displayMessage( "\nPlayer O Has Won!" ); } else if(checkForDraw()) { output.format( "We have a Draw!\n" ); displayMessage( "We have a Draw!\n"); gameLock.lock(); } output.flush(); } else { output.format( "Invalid move, try again\n" ); output.flush(); // flush output } }
Client Connection Code
public void startClient() { try { // 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() ); } catch ( IOException ioException ) { ioException.printStackTrace(); } // create and start worker thread for this client ExecutorService worker = Executors.newFixedThreadPool( 1 ); worker.execute( this ); // execute client }
Client Read/Write Code
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 } else if ( message.equals( "Invalid move, try again" ) ) { displayMessage( message + "\n" ); // display invalid move myTurn = true; // still this client's turn } else if ( message.equals( "Opponent moved" ) ) { int location = input.nextInt(); // get move location input.nextLine(); //skip next line 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 } else { displayMessage( message + "\n" ); // display the message } }
The core issue is straightforward: when you detect a win or draw, you're only sending a message to the current player (the one who just made the move). You never notify the other client that they lost, and you don't properly terminate the game loop for both players once the game ends.
1. Modify Server Win/Draw Detection to Notify Both Players
Update the win/draw check section in your server's run() method to send tailored messages to both players, then trigger game termination:
boolean gameEnded = false; if(checkForXWin()) { // Notify X they won, O they lost players[PLAYER_X].output.format("Player X Has Won!\n"); players[PLAYER_O].output.format("Player X Has Won! You lost.\n"); displayMessage( "\nPlayer X Has Won!" ); gameEnded = true; } else if(checkForOWin()) { // Notify O they won, X they lost players[PLAYER_O].output.format("Player O Has Won!\n"); players[PLAYER_X].output.format("Player O Has Won! You lost.\n"); displayMessage( "\nPlayer O Has Won!" ); gameEnded = true; } else if(checkForDraw()) { // Notify both it's a draw players[PLAYER_X].output.format("We have a Draw!\n"); players[PLAYER_O].output.format("We have a Draw!\n"); displayMessage( "We have a Draw!\n"); gameEnded = true; } // Handle game termination if(gameEnded) { players[PLAYER_X].output.flush(); players[PLAYER_O].output.flush(); setGameOver(true); // Mark game as over // Wake up any waiting players to exit their loops gameLock.lock(); try { otherPlayerTurn.signalAll(); } finally { gameLock.unlock(); } } else { // Only send valid move message if game is still running output.format( "Valid move.\n" ); output.flush(); }
2. Add a Game Over Flag to the Server
Add a volatile boolean field and helper methods to track game state across all player threads:
private volatile boolean gameOver = false; public boolean isGameOver() { return gameOver; } public void setGameOver(boolean value) { gameOver = value; }
3. Fix the ValidateAndMove Loop
Update the validateAndMove() method to exit early if the game ends, so players don't get stuck waiting forever:
public boolean validateAndMove( int location, int player ) { // Exit immediately if game is over if(isGameOver()) { return false; } // Wait for turn only if game is still running while ( player != currentPlayer && !isGameOver()) { gameLock.lock(); try { otherPlayerTurn.await(); } catch ( InterruptedException exception ) { exception.printStackTrace(); } finally { gameLock.unlock(); } } // Rest of your existing validation logic... }
4. (Optional) Disable Client Input After Game Ends
Add a game over flag to your client to prevent users from sending moves after the game finishes:
private boolean gameOver = false;
Update processMessage() to set the flag when game ends:
else if (message.contains("You lost.") || message.contains("Has Won!") || message.contains("Draw")) { displayMessage(message + "\n"); gameOver = true; } // Keep existing logic for other messages
Then add a check in your client's input handling code (where you send moves to the server) to ignore input if gameOver is true.
- We directly access both players' output streams to send targeted messages (win for one, loss for the other) when a game-ending condition is met.
- The volatile game over flag ensures all player threads see the updated state immediately, so their run loops exit cleanly.
- We signal all waiting threads to wake up, preventing them from getting stuck in the
await()call after the game ends.
Give these changes a shot—this should resolve the issue where the losing client never gets notified. If you hit any snags with implementation, feel free to ask for clarification!
内容的提问来源于stack exchange,提问作者N Schwartz

