I had never done a full interactive app before so this is a really big step for me. It's very simple and fits into a single file. One thing that will be odd is the fact that I didn't use arrays. I don't know why but there were a ton of runtime errors occuring whenever I tried to do something that involved an object in an array.import javafx.application.Application;import javafx.event.EventHandler;import javafx.event.ActionEvent;import javafx.scene.shape.Rectangle;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.control.Button;import javafx.scene.text.Text;import javafx.scene.text.Font;import javafx.stage.Stage;public class TicTacToe extends Application implements EventHandler < ActionEvent > { Tile t11, t12, t13, t21, t22, t23, t31, t32, t33; boolean currTurn; boolean gameCont; Text winNote; Text credits; Button reset; Button close; public static void main(String[] args) {  launch(args); } public void start(Stage primaryStage) {  gameCont = true;  currTurn = false;  credits = new Text();  credits.setLayoutX(310);  credits.setLayoutY(695);  winNote = new Text();  winNote.setLayoutX(110);  winNote.setLayoutY(637);  reset = new Button(New Game);  reset.setLayoutX(25);  reset.setLayoutY(620);  reset.setOnAction(new EventHandler < ActionEvent > () {   public void handle(ActionEvent event) {    winNote.setText(New Game Started);    gameCont = true;    currTurn = false;    t11.text.setText();    t12.text.setText();    t13.text.setText();    t21.text.setText();    t22.text.setText();    t23.text.setText();    t31.text.setText();    t32.text.setText();    t33.text.setText();   }  });  close = new Button(Close);  close.setLayoutX(25);  close.setLayoutY(655);  close.setOnAction(new EventHandler < ActionEvent > () {   public void handle(ActionEvent event) {    primaryStage.close();   }  });  t11 = new Tile(Color.LIGHTBLUE);  t11.setLayoutY(0);  t11.setLayoutX(0);  t12 = new Tile(Color.LIGHTYELLOW);  t12.setLayoutY(0);  t12.setLayoutX(200);  t13 = new Tile(Color.LIGHTBLUE);  t13.setLayoutY(0);  t13.setLayoutX(400);  t21 = new Tile(Color.LIGHTYELLOW);  t21.setLayoutY(200);  t21.setLayoutX(0);  t22 = new Tile(Color.LIGHTBLUE);  t22.setLayoutY(200);  t22.setLayoutX(200);  t23 = new Tile(Color.LIGHTYELLOW);  t23.setLayoutY(200);  t23.setLayoutX(400);  t31 = new Tile(Color.LIGHTBLUE);  t31.setLayoutY(400);  t31.setLayoutX(0);  t32 = new Tile(Color.LIGHTYELLOW);  t32.setLayoutY(400);  t32.setLayoutX(200);  t33 = new Tile(Color.LIGHTBLUE);  t33.setLayoutY(400);  t33.setLayoutX(400);  primaryStage.setTitle(Tic Tac Toe);  Pane scene = new Pane();  primaryStage.setScene(new Scene(scene, 600, 700));  scene.getChildren().addAll(t11, t12, t13, t21, t22, t23, t31, t32, t33, reset, close, winNote, credits);  primaryStage.show(); } private class Tile extends StackPane {  private Text text = new Text();  public Tile(Color color) {   text.setFont(Font.font(Calibri, 80));   Rectangle border = new Rectangle(200, 200);   border.setFill(color);   border.setStroke(Color.BLACK);   getChildren().addAll(border, text);   setOnMouseClicked(event -> {    if (event.getButton() == MouseButton.PRIMARY) {     if (gameCont == true) {      if (text.getText().isEmpty()) {       if (currTurn == false) {        text.setText(X);        currTurn = true;       } else {        text.setText(O);        currTurn = false;       }       // BELOW ARE CHECKS FOR ROWS       if (t11.text.getText().equals(X) && t12.text.getText().equals(X) && t13.text.getText().equals(X)) {        winNote.setText(X Wins!);       } else if (t11.text.getText().equals(O) && t12.text.getText().equals(O) && t13.text.getText().equals(O)) {        winNote.setText(O Wins!);       } else if (t21.text.getText().equals(X) && t22.text.getText().equals(X) && t23.text.getText().equals(X)) {        winNote.setText(X Wins!);       } else if (t21.text.getText().equals(O) && t22.text.getText().equals(O) && t23.text.getText().equals(O)) {        winNote.setText(O Wins!);       } else if (t31.text.getText().equals(X) && t32.text.getText().equals(X) && t33.text.getText().equals(X)) {        winNote.setText(X Wins!);       } else if (t31.text.getText().equals(O) && t32.text.getText().equals(O) && t33.text.getText().equals(O)) {        winNote.setText(O Wins!);       }       // BELOW ARE CHECKS FOR COLUMNS       else if (t11.text.getText().equals(X) && t21.text.getText().equals(X) && t31.text.getText().equals(X)) {        winNote.setText(X Wins!);       } else if (t11.text.getText().equals(O) && t21.text.getText().equals(O) && t31.text.getText().equals(O)) {        winNote.setText(O Wins!);       } else if (t12.text.getText().equals(X) && t22.text.getText().equals(X) && t32.text.getText().equals(X)) {        winNote.setText(X Wins!);       } else if (t12.text.getText().equals(O) && t22.text.getText().equals(O) && t32.text.getText().equals(O)) {        winNote.setText(O Wins!);       } else if (t13.text.getText().equals(X) && t23.text.getText().equals(X) && t33.text.getText().equals(X)) {        winNote.setText(X Wins!);       } else if (t13.text.getText().equals(O) && t23.text.getText().equals(O) && t33.text.getText().equals(O)) {        winNote.setText(O Wins!);       }       // BELOW ARE CHECKS FOR DIAGONALS       else if (t11.text.getText().equals(X) && t22.text.getText().equals(X) && t33.text.getText().equals(X)) {        winNote.setText(X Wins!);       } else if (t11.text.getText().equals(O) && t22.text.getText().equals(O) && t33.text.getText().equals(O)) {        winNote.setText(O Wins!);       } else if (t13.text.getText().equals(X) && t22.text.getText().equals(X) && t31.text.getText().equals(X)) {        winNote.setText(X Wins!);       } else if (t13.text.getText().equals(O) && t22.text.getText().equals(O) && t31.text.getText().equals(O)) {        winNote.setText(O Wins!);       }       // ENDS THE GAME IF SOMEONE WINS       if (winNote.getText().equals(X Wins!) || winNote.getText().equals(O Wins!))        gameCont = false;       // BELOW IS THE DRAW CHECK, ENDS THE GAME IF PASSED       else if (!(t11.text.getText().equals()) && !(t12.text.getText().equals()) && !(t13.text.getText().equals()) && !(t21.text.getText().equals()) && !(t22.text.getText().equals()) && !(t23.text.getText().equals()) && !(t31.text.getText().equals()) && !(t32.text.getText().equals()) && !(t33.text.getText().equals())) {        winNote.setText(Draw!);        gameCont = false;       }      }     }    }   });  } } public void handle(ActionEvent event) {  System.out.println(What happened?); }}
Create Tic Tac Toe with a full GUI
java;beginner;game;javafx
There are 2 big problems with your code. The first, as you pointed out yourself, is that you don't use a Tile[][] for the tiles. This means you constantly have to repeat yourself instead of just looping over rows/columns.The second is the fact that you don't use any encapsulation. And to make things worse, your Tile class is doing all the work, mostly by accessing the variables of your TicTacToe class.Having everything in one file is not something to be proud of. At best, it's something that we can tolerate if the program is truly small enough to just be 2 methods or so. For your tictactoe game it really should be split up into more manageable classes, each into their own file.Let's first look at what we expect to find in a Tile class. A Tile should represent a single square. So it should have a text that can be either empty, X or O. It can also have a border and a color.What it should not have, is logic about entire rows/columns/diagonals of Tiles. It shouldn't even have access to other Tiles at all. The only useful method that it could have that uses another Tile, is a helper method to check if it has the same text as that other tile. The resulting Tile class looks something like this:public class Tile extends StackPane {    private Text text = new Text();    public Tile(Color color) {        text.setFont(Font.font(Calibri, 80));        Rectangle border = new Rectangle(200, 200);        border.setFill(color);        border.setStroke(Color.BLACK);        getChildren().addAll(border, text);    }    public String getText(){        return text.getText();    }    public void setText(String text) {        this.text.setText(text);    }    public boolean isSamePlayer(Tile other){        return getText().equals(other.getText());    }}And there is no reason not to put this into it's own file.Notice how I made the text field private? No other class should even know that the tile stores this as a text field. We might have stored it into a String for all they care. As long as we have a method getText() that returns if it's an X, O or neither.Now that we fixed the Tile class let's look at all the things that should be improved in the TicTacToe class.First of all, let's encapsulate all the variables. This is as easy as adding the private keyword to all of them:private boolean currTurn;private boolean gameCont;private Text winNote;...Next a method that overrides a method of a superclass should be anotated with @Override.@Overridepublic void start(Stage primaryStage) {    ...}@Overridepublic void handle(ActionEvent event) {    ...}Now that my IDE stops complaining we can address the biggest issue of this class. Putting all the Tiles into an array.private Tile[][] tiles = new Tile[3][3];The variable declaration was the easy part. Let's look at the other things that need to happen.First the reset action:reset.setOnAction(new EventHandler<ActionEvent>() {        @Override        public void handle(ActionEvent event) {            winNote.setText(New Game Started);            gameCont = true;            currTurn = false;            for(Tile[] row: tiles){                for(Tile tile : row){                    tile.setText();                       }            }        }    });That didn't look too hard, did it? We just loop over the rows, and then over each tile in a row so handle all the tiles.We can use this exact same loops a bit later on to add all the tiles to the scene:    for(Tile[] row : tiles){        for(Tile tile : row) {            scene.getChildren().add(tile);        }    }    scene.getChildren().addAll(reset, close, winNote, credits);For the creation of the tiles we have to put in a bit more effort. Not only do we need to alternate the colors, we also need to add a clickListener because we removed that from the constructor of the tile. I would write the construction of all the tiles like this:    for(int y = 0; y < 3; y++){        for(int x = 0; x < 3; x++){            final Tile tile;            if((x+y)%2==0) {                tile = new Tile(Color.LIGHTBLUE);            } else {                tile = new Tile(Color.LIGHTYELLOW);            }            tile.setLayoutY(200 * y);            tile.setLayoutX(200 * x);            tile.setOnMouseClicked(event -> {                if (event.getButton() == MouseButton.PRIMARY) {                    handleTileClicked(tile);                }            });            tiles[y][x] = tile;        }    }Notice how I put the checking what needs to happen into it's own method. We do need to pass the tile to that method so that it knows which tile was clicked.Now let's look at what that method has to do. In your eyes, it should start with some sanity checks. This I fully agree with. But we can slightly improve this by quitting early so we don't end up with such a nested if(){ if(){ ... structure. A second thing to note is that if you have a boolean, you can just do if(gameCont){.Our method now starts like this:private void handleTileClicked(Tile tile) {    if (!gameCont) {        return;    }    if (!tile.getText().isEmpty()) {        return;    }    //TODO, rest of the method}Here I noticed that we it might be useful to add a helper method to the Tile class to ask if it's empty. Then the check can be simplified to:if (!tile.isEmpty()) {    return;}Next we have a currTurn == false which again can be simplified to !currTurn. We can also flip the if/else so we don't need the !.if (currTurn) {    tile.setText(O);    currTurn = false;} else {    tile.setText(X);    currTurn = true;}Now there's something else here that I'm not happy with. The fact that your variable currTurn doesn't immediatly tells if it's currently X's turn or O's turn. Let's rename this to turnX and negate all the booleans.public void start(Stage primaryStage) {    ...    turnX = true;    ...    reset.setOnAction(new EventHandler<ActionEvent>() {        @Override        public void handle(ActionEvent event) {            ...            turnX = true;            ...        }    }    ... } private void handleTileClicked(Tile tile) {    if (!gameCont) {        return;    }    if (!tile.isEmpty()) {        return;    }    if (turnX) {        tile.setText(X);        turnX = false;    } else {        tile.setText(O);        turnX = true;    }Next is checking if the game ended. If we want to do this the smart way, we only need to check the player that just clicked the tile. Sadly enough we just flipped our turnX boolean around. So let's delay that to the end of this method instead. private void handleTileClicked(Tile tile) {    if (!gameCont) {        return;    }    if (!tile.isEmpty()) {        return;    }    if (turnX) {        tile.setText(X);    } else {        tile.setText(O);    }    if(isVictorious()){        if(turnX){            winNote.setText(X wins!);        } else {            winNote.setText(Y wins!);        }        gameCont = false;    } else if(boardIsFull()) {        winNote.setText(Draw!);        gameCont = false;    }    turnX != turnX; //just flip value to change }As you can see I also simplified the victory and draw code inside this method by calling other helper functions. We still need to implement those ofcourse. Let's start with the easiest one, checking if all tiles are set:public boolean boardIsFull(){    for(Tile[] row : tiles){        for(Tile tile : row){            if(tile.isEmpty()){                return false;            }        }    }    return true;}Checking for victory is slightly harder. But still possible. The first thing I noticed though is that we will always check 3 tiles. So let's first add another helper method that does just that:private boolean allSame(Tile tile1, Tile tile2, Tile tile3){    return tile1.isSamePlayer(tile2) && tile1.isSamePlayer(tile3);}This was made extra easy with that helper method we provided in Tile. Now for the actual isVictorious() method:private boolean isVictorious() {    //check rows    for(Tile[] row : tiles){        if (allSame(row[0], row[1], row[2])) {            return true;        }    }    //check columns    for(int column = 0; column < 3; column ++){        if(allSame(tiles[0][column],tiles[1][column],tiles[2][column])){            return true;        }    }    //checkdiag    if (allSame(tiles[0][0], tiles[1][1], tiles[2][2])) {        return true;    }    if (allSame(tiles[0][2], tiles[1][1], tiles[2][0])) {        return true;    }    return false;}And we're done. Let's test our game to see if we made any mistakes.Clicking once puts an X and then shows X is the winner. It seems our checks are wrong. What happened?Well, if all 3 tiles are empty, they are still the same. But we don't want that to mean the current player won. Let's fix this by changing our helper function in Tile:    public boolean isSamePlayer(Tile other) {        return !isEmpty() && getText().equals(other.getText());    }This slightly changed the meaning of this method, so let's add a method to clarify the exact meaning:    /**     * Returns false if the tile is empty.      * Otherwise returns whether this tile has the same mark as the other tile.     */    public boolean isSamePlayer(Tile other) {        return !isEmpty() && getText().equals(other.getText());    }Now everything works as intended again. And we're done with the major refactoring.The last thing I would do to increase the readability of this code is use some helper functions to split up the start method. That one is still rather long.Methods like:private void initCloseButton(final Stage primaryStage) {    close = new Button(Close);    close.setLayoutX(25);    close.setLayoutY(655);    close.setOnAction(new EventHandler<ActionEvent>() {        @Override        public void handle(ActionEvent event) {            primaryStage.close();        }    });}are a possibility. But I'll leave that up to you.