1  import java.io.*;
  2  import java.net.*;
  3  import java.util.Date;
  4  import javafx.application.Application;
  5  import javafx.application.Platform;
  6  import javafx.scene.Scene;
  7  import javafx.scene.control.Label;
  8  import javafx.scene.control.ScrollPane;
  9  import javafx.scene.control.TextArea;
 10  import javafx.scene.layout.BorderPane;
 11  import javafx.scene.layout.GridPane;
 12  import javafx.scene.layout.Pane;
 13  import javafx.scene.paint.Color;
 14  import javafx.scene.shape.Ellipse;
 15  import javafx.scene.shape.Line;
 16  import javafx.stage.Stage;
 17  
 18  public class TicTacToeClient extends Application 
 19      implements TicTacToeConstants {
 20    // Indicate whether the player has the turn
 21    private boolean myTurn = false;
 22  
 23    // Indicate the token for the player
 24    private char myToken = ' ';
 25  
 26    // Indicate the token for the other player
 27    private char otherToken = ' ';
 28  
 29    // Create and initialize cells
 30    private Cell[][] cell =  new Cell[3][3];
 31  
 32    // Create and initialize a title label
 33    private Label lblTitle = new Label();
 34  
 35    // Create and initialize a status label
 36    private Label lblStatus = new Label();
 37  
 38    // Indicate selected row and column by the current move
 39    private int rowSelected;
 40    private int columnSelected;
 41  
 42    // Input and output streams from/to server
 43    private DataInputStream fromServer;
 44    private DataOutputStream toServer;
 45  
 46    // Continue to play?
 47    private boolean continueToPlay = true;
 48  
 49    // Wait for the player to mark a cell
 50    private boolean waiting = true;
 51  
 52    // Host name or ip
 53    private String host = "localhost";
 54  
 55    @Override // Override the start method in the Application class
 56    public void start(Stage primaryStage) {
 57      // Pane to hold cell
 58      GridPane pane = new GridPane(); 
 59      for (int i = 0; i < 3; i++)
 60        for (int j = 0; j < 3; j++)
 61          pane.add(cell[i][j] = new Cell(i, j), j, i);
 62  
 63      BorderPane borderPane = new BorderPane();
 64      borderPane.setTop(lblTitle);
 65      borderPane.setCenter(pane);
 66      borderPane.setBottom(lblStatus);
 67      
 68      // Create a scene and place it in the stage
 69      Scene scene = new Scene(borderPane, 320, 350);
 70      primaryStage.setTitle("TicTacToeClient"); // Set the stage title
 71      primaryStage.setScene(scene); // Place the scene in the stage
 72      primaryStage.show(); // Display the stage   
 73  
 74      // Connect to the server
 75      connectToServer();
 76    }
 77  
 78    private void connectToServer() {
 79      try {
 80        // Create a socket to connect to the server
 81        Socket socket = new Socket(host, 8000);
 82  
 83        // Create an input stream to receive data from the server
 84        fromServer = new DataInputStream(socket.getInputStream());
 85  
 86        // Create an output stream to send data to the server
 87        toServer = new DataOutputStream(socket.getOutputStream());
 88      }
 89      catch (Exception ex) {
 90        ex.printStackTrace();
 91      }
 92  
 93      // Control the game on a separate thread
 94      new Thread(() -> {
 95        try {
 96          // Get notification from the server
 97          int player = fromServer.readInt();
 98    
 99          // Am I player 1 or 2?
100          if (player == PLAYER1) {
101            myToken = 'X';
102            otherToken = 'O';
103            Platform.runLater(() -> {
104              lblTitle.setText("Player 1 with token 'X'");
105              lblStatus.setText("Waiting for player 2 to join");
106            });
107    
108            // Receive startup notification from the server
109            fromServer.readInt(); // Whatever read is ignored
110    
111            // The other player has joined
112            Platform.runLater(() -> 
113              lblStatus.setText("Player 2 has joined. I start first"));
114    
115            // It is my turn
116            myTurn = true;
117          }
118          else if (player == PLAYER2) {
119            myToken = 'O';
120            otherToken = 'X';
121            Platform.runLater(() -> {
122              lblTitle.setText("Player 2 with token 'O'");
123              lblStatus.setText("Waiting for player 1 to move");
124            });
125          }
126    
127          // Continue to play
128          while (continueToPlay) {      
129            if (player == PLAYER1) {
130              waitForPlayerAction(); // Wait for player 1 to move
131              sendMove(); // Send the move to the server
132              receiveInfoFromServer(); // Receive info from the server
133            }
134            else if (player == PLAYER2) {
135              receiveInfoFromServer(); // Receive info from the server
136              waitForPlayerAction(); // Wait for player 2 to move
137              sendMove(); // Send player 2's move to the server
138            }
139          }
140        }
141        catch (Exception ex) {
142          ex.printStackTrace();
143        }
144      }).start();
145    }
146  
147    /** Wait for the player to mark a cell */
148    private void waitForPlayerAction() throws InterruptedException {
149      while (waiting) {
150        Thread.sleep(100);
151      }
152  
153      waiting = true;
154    }
155  
156    /** Send this player's move to the server */
157    private void sendMove() throws IOException {
158      toServer.writeInt(rowSelected); // Send the selected row
159      toServer.writeInt(columnSelected); // Send the selected column
160    }
161  
162    /** Receive info from the server */
163    private void receiveInfoFromServer() throws IOException {
164      // Receive game status
165      int status = fromServer.readInt();
166  
167      if (status == PLAYER1_WON) {
168        // Player 1 won, stop playing
169        continueToPlay = false;
170        if (myToken == 'X') {
171          Platform.runLater(() -> lblStatus.setText("I won! (X)"));
172        }
173        else if (myToken == 'O') {
174          Platform.runLater(() -> 
175            lblStatus.setText("Player 1 (X) has won!"));
176          receiveMove();
177        }
178      }
179      else if (status == PLAYER2_WON) {
180        // Player 2 won, stop playing
181        continueToPlay = false;
182        if (myToken == 'O') {
183          Platform.runLater(() -> lblStatus.setText("I won! (O)"));
184        }
185        else if (myToken == 'X') {
186          Platform.runLater(() -> 
187            lblStatus.setText("Player 2 (O) has won!"));
188          receiveMove();
189        }
190      }
191      else if (status == DRAW) {
192        // No winner, game is over
193        continueToPlay = false;
194        Platform.runLater(() -> 
195          lblStatus.setText("Game is over, no winner!"));
196  
197        if (myToken == 'O') {
198          receiveMove();
199        }
200      }
201      else {
202        receiveMove();
203        Platform.runLater(() -> lblStatus.setText("My turn"));
204        myTurn = true; // It is my turn
205      }
206    }
207  
208    private void receiveMove() throws IOException {
209      // Get the other player's move
210      int row = fromServer.readInt();
211      int column = fromServer.readInt();
212      Platform.runLater(() -> cell[row][column].setToken(otherToken));
213    }
214  
215    // An inner class for a cell
216    public class Cell extends Pane {
217      // Indicate the row and column of this cell in the board
218      private int row;
219      private int column;
220  
221      // Token used for this cell
222      private char token = ' ';
223  
224      public Cell(int row, int column) {
225        this.row = row;
226        this.column = column;
227        this.setPrefSize(2000, 2000); // What happens without this?
228        setStyle("-fx-border-color: black"); // Set cell's border
229        this.setOnMouseClicked(e -> handleMouseClick());  
230      }
231  
232      /** Return token */
233      public char getToken() {
234        return token;
235      }
236  
237      /** Set a new token */
238      public void setToken(char c) {
239        token = c;
240        repaint();
241      }
242  
243      protected void repaint() {
244        if (token == 'X') {
245          Line line1 = new Line(10, 10, 
246            this.getWidth() - 10, this.getHeight() - 10);
247          line1.endXProperty().bind(this.widthProperty().subtract(10));
248          line1.endYProperty().bind(this.heightProperty().subtract(10));
249          Line line2 = new Line(10, this.getHeight() - 10, 
250            this.getWidth() - 10, 10);
251          line2.startYProperty().bind(
252            this.heightProperty().subtract(10));
253          line2.endXProperty().bind(this.widthProperty().subtract(10));
254          
255          // Add the lines to the pane
256          this.getChildren().addAll(line1, line2); 
257        }
258        else if (token == 'O') {
259          Ellipse ellipse = new Ellipse(this.getWidth() / 2, 
260            this.getHeight() / 2, this.getWidth() / 2 - 10, 
261            this.getHeight() / 2 - 10);
262          ellipse.centerXProperty().bind(
263            this.widthProperty().divide(2));
264          ellipse.centerYProperty().bind(
265              this.heightProperty().divide(2));
266          ellipse.radiusXProperty().bind(
267              this.widthProperty().divide(2).subtract(10));        
268          ellipse.radiusYProperty().bind(
269              this.heightProperty().divide(2).subtract(10));   
270          ellipse.setStroke(Color.BLACK);
271          ellipse.setFill(Color.WHITE);
272          
273          getChildren().add(ellipse); // Add the ellipse to the pane
274        }
275      }
276  
277      /* Handle a mouse click event */
278      private void handleMouseClick() {
279        // If cell is not occupied and the player has the turn
280        if (token == ' ' && myTurn) {
281          setToken(myToken);  // Set the player's token in the cell
282          myTurn = false;
283          rowSelected = row;
284          columnSelected = column;
285          lblStatus.setText("Waiting for the other player to move");
286          waiting = false; // Just completed a successful move
287        }
288      }
289    }
290  
291    /**
292     * The main method is only needed for the IDE with limited
293     * JavaFX support. Not needed for running from the command line.
294     */
295    public static void main(String[] args) {
296      launch(args);
297    }
298  }