Search code examples
javajavafxnetwork-programmingjavafx-8

Updating UI of TicTacToe in javafx


Here, I have a code below which I collected from the Internet for understanding TicTacToe game's code in java. I understand how the code is written along with its logics. It is played in a single window where two users are differentiated based on the button of the mouse clicked. Now I want to make a similar game which could be played over a network built with a server and a client.

TicTacToe.java

package sample;

import java.util.ArrayList;
import java.util.List;

import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Main extends Application {

    private boolean playable = true;
    private boolean turnX = true;
    private Tile[][] board = new Tile[3][3];
    private List<Combo> combos = new ArrayList<>();

    private Pane root = new Pane();

    private Parent createContent() {
        root.setPrefSize(600, 600);

        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                Tile tile = new Tile(); //each tile is a rectangular block
                tile.setTranslateX(j * 200);
                tile.setTranslateY(i * 200);

                root.getChildren().add(tile);

                board[j][i] = tile;
            }
        }

        // horizontal
        for (int y = 0; y < 3; y++) {
            combos.add(new Combo(board[0][y], board[1][y], board[2][y]));
        }

        // vertical
        for (int x = 0; x < 3; x++) {
            combos.add(new Combo(board[x][0], board[x][1], board[x][2]));
        }

        // diagonals
        combos.add(new Combo(board[0][0], board[1][1], board[2][2]));
        combos.add(new Combo(board[2][0], board[1][1], board[0][2]));

        return root;
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setScene(new Scene(createContent()));
        primaryStage.show();
    }

    private void checkState() {
        for (Combo combo : combos) {
            if (combo.isComplete()) {
                playable = false;
                playWinAnimation(combo);
                break;
            }
        }
    }

    private void playWinAnimation(Combo combo) {
        Line line = new Line();
        line.setStartX(combo.tiles[0].getCenterX());
        line.setStartY(combo.tiles[0].getCenterY());
        line.setEndX(combo.tiles[0].getCenterX());
        line.setEndY(combo.tiles[0].getCenterY());

        root.getChildren().add(line);

        Timeline timeline = new Timeline();
        timeline.getKeyFrames().add(new KeyFrame(Duration.seconds(1),
                new KeyValue(line.endXProperty(), combo.tiles[2].getCenterX()),
                new KeyValue(line.endYProperty(), combo.tiles[2].getCenterY())));
        timeline.play();
    }

    private class Combo {
        private Tile[] tiles;
        public Combo(Tile... tiles) {
            this.tiles = tiles;
        }

        public boolean isComplete() {
            if (tiles[0].getValue().isEmpty())
                return false;

            return tiles[0].getValue().equals(tiles[1].getValue())
                    && tiles[0].getValue().equals(tiles[2].getValue());
        }
    }

    private class Tile extends StackPane {
        private Text text = new Text();
        private boolean clicked = false;;

        public Tile() {
            Rectangle border = new Rectangle(200, 200);
            border.setFill(null);
            border.setStroke(Color.BLACK);

            text.setFont(Font.font(72));

            setAlignment(Pos.CENTER);
            getChildren().addAll(border, text);

            setOnMouseClicked(event -> {
                if (!playable || clicked)
                    return;

                if (event.getButton() == MouseButton.PRIMARY) {
                    if (!turnX)
                        return;

                    if(!clicked){
                        drawX();
                        turnX = false;
                        clicked = true;
                        checkState();
                    }
                }
                else if (event.getButton() == MouseButton.SECONDARY) {
                    if (turnX)
                        return;

                    if(!clicked){
                        drawO();
                        turnX = true;
                        clicked = true;
                        checkState();
                    }
                }
            });
        }

        public double getCenterX() {
            return getTranslateX() + 100;
        }

        public double getCenterY() {
            return getTranslateY() + 100;
        }

        public String getValue() {
            return text.getText();
        }

        private void drawX() {
            text.setText("X");
        }

        private void drawO() {
            text.setText("O");
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

I want to build the server and client in different files. The server.java will contain server sockets, input-output streams, and accept connections from a socket. I have no problem with that. Similarly, the client.java will also have sockets and necessary streams. Both the server file and the client file will also have the TicTacToe.java. I can set up the code up to this point.

My problem:

I cannot update the UI over the socket network. How can I do it? I've watched few codes on Platform.runLater() but do not understand them well. I wanted insight.

It would be very helpful for me if someone could give me a direction on how to write a code for this and implementing what I want so that I can solve my problem.

Thanks ...


Solution

  • What I have done to use the Platform.RunLater function is create a background thread to receive packets from client/server. Then update the UI using the platform.runLater function...

    private ScheduledExecutorService scheduledPINGExecutorService;
    private ScheduledFuture<?>       scheduledPINGCheck;
    
    public static void main(String[] args){
            launch(args);
    }
    @Override
    public void start(Stage primaryStage){
            primaryStage.setTitle("Title");
    
            primaryStage.setScene(new Scene(getMainPage(primaryStage), 800, 1000));
            primaryStage.show();
    
    scheduledPINGExecutorService = Executors.newScheduledThreadPool(1);
    
    Runnable checkPING = () -> {
                //check for new packet from client
                getData() <---- define this function depending on your networking setup
                Platform.runLater(() -> { //populate UI with new data  });
            };
    scheduledPINGCheck = scheduledPINGExecutorService.scheduleAtFixedRate(checkPING, 1, 500, TimeUnit.MILLISECONDS);
    
        }
    
    '''