Skip to content

Instantly share code, notes, and snippets.

@hkamran80
Created March 25, 2022 19:23
Show Gist options
  • Save hkamran80/a92c8c455aa8472597d1da633c37cd99 to your computer and use it in GitHub Desktop.
Save hkamran80/a92c8c455aa8472597d1da633c37cd99 to your computer and use it in GitHub Desktop.
Tic-Tac-Toe (JavaFX)
package com.hkamran;
import java.util.Arrays;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class App extends Application {
@Override
public void start(Stage stage) {
OnePlayer ticTacToe = new OnePlayer();
Button topLeft = new Button();
Button topCenter = new Button();
Button topRight = new Button();
Button centerLeft = new Button();
Button centerCenter = new Button();
Button centerRight = new Button();
Button bottomLeft = new Button();
Button bottomCenter = new Button();
Button bottomRight = new Button();
Button[] buttons = { topLeft, topCenter, topRight, centerLeft, centerCenter, centerRight, bottomLeft,
bottomCenter, bottomRight };
String[] buttonIds = { "topLeft", "topCenter", "topRight", "centerLeft", "centerCenter", "centerRight",
"bottomLeft",
"bottomCenter", "bottomRight" };
int[][] buttonPositions = { { 0, 0 }, { 0, 1 }, { 0, 2 }, { 1, 0 }, { 1, 1 }, { 1, 2 }, { 2, 0 }, { 2, 1 },
{ 2, 2 }, };
Text statusText = new Text();
statusText.setFill(Color.WHITE);
for (int buttonIndex = 0; buttonIndex < buttons.length; buttonIndex++) {
Button button = buttons[buttonIndex];
button.setText("_");
button.setBackground(new Background(new BackgroundFill(Color.BLACK, new CornerRadii(8.0),
Insets.EMPTY)));
button.setStyle("-fx-text-fill: white");
button.setId(buttonIds[buttonIndex]);
button.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
Button button = (Button) event.getSource();
if (button.getText().equals("_")) {
String winner = ticTacToe.checkWinner();
if (winner != "") {
statusText.setFill(Color.GREEN);
statusText.setText(winner);
} else {
int[] position = buttonPositions[Arrays.asList(buttonIds)
.indexOf(((Button) event.getSource()).getId())];
int[] output = ticTacToe.setPlayerState(position[0], position[1]);
if (output.length == 3) {
if (output[0] == 0) {
Button cpuButton = null;
for (int buttonPositionIndex = 0; buttonPositionIndex < buttonPositions.length; buttonPositionIndex++) {
int[] buttonPosition = buttonPositions[buttonPositionIndex];
if (buttonPosition[0] == output[1] && buttonPosition[1] == output[2]) {
cpuButton = buttons[buttonPositionIndex];
break;
}
}
if (cpuButton != null) {
cpuButton.setText(output[0] == ticTacToe.TOKEN_X ? "X" : "O");
} else {
statusText.setFill(Color.RED);
statusText.setText("No button for the CPU play was found");
}
} else {
button.setText(output[0] == ticTacToe.TOKEN_X ? "X" : "O");
}
winner = ticTacToe.checkWinner();
if (winner != "") {
statusText.setFill(Color.GREEN);
statusText.setText(winner);
}
} else if (output[0] == 1) {
statusText.setFill(Color.RED);
statusText.setText("That position is already occupied.");
} else if (output[0] == -1) {
statusText.setFill(Color.RED);
statusText.setText("An unknown error occurred.");
}
}
}
}
});
}
GridPane gridPane = new GridPane();
gridPane.setMinSize(100, 100);
gridPane.setPadding(new Insets(20));
gridPane.setBackground(
new Background(new BackgroundFill(Color.rgb(26, 26, 26), CornerRadii.EMPTY, Insets.EMPTY)));
gridPane.setVgap(7);
gridPane.setHgap(7);
gridPane.setAlignment(Pos.CENTER);
gridPane.add(topLeft, 0, 0);
gridPane.add(topCenter, 1, 0);
gridPane.add(topRight, 2, 0);
gridPane.add(centerLeft, 0, 1);
gridPane.add(centerCenter, 1, 1);
gridPane.add(centerRight, 2, 1);
gridPane.add(bottomLeft, 0, 2);
gridPane.add(bottomCenter, 1, 2);
gridPane.add(bottomRight, 2, 2);
VBox vBox = new VBox();
vBox.setMinSize(250, 250);
vBox.setAlignment(Pos.CENTER);
vBox.setBackground(new Background(new BackgroundFill(Color.rgb(26, 26, 26), CornerRadii.EMPTY, Insets.EMPTY)));
vBox.setPadding(new Insets(10));
vBox.setSpacing(8);
vBox.getChildren().add(gridPane);
vBox.getChildren().add(statusText);
Scene scene = new Scene(vBox);
stage.setTitle("Tic-Tac-Toe");
stage.setScene(scene);
stage.show();
}
public static void main(String args[]) {
launch(args);
}
}
package com.hkamran;
import java.util.ArrayList;
import java.util.Random;
public class OnePlayer extends TicTacToe {
private Random random = new Random();
/**
* Set a player's position
*
* Uses input for player 1 (human) and a random choice from empty positions
* (CPU)
*/
public int[] setPlayerState(int row, int column) {
int turn = this.turn;
int trueRow = row;
int trueColumn = column;
if (turn % 2 != 0) {
ArrayList<int[]> emptyPositions = this.getEmptyPositions();
int[] emptyPosition = emptyPositions.get(random.nextInt(emptyPositions.size()));
trueRow = emptyPosition[0];
trueColumn = emptyPosition[1];
}
int stateOutput = this.setState(turn % 2 == 0 ? this.TOKEN_X : this.TOKEN_O, trueRow + 1, trueColumn + 1);
if (stateOutput == 0) {
int[] output = { turn % 2 == 0 ? this.TOKEN_X : this.TOKEN_O, trueRow, trueColumn };
return output;
} else if (stateOutput == 1) {
int[] output = { 1 };
return output;
} else if (stateOutput == -1) {
int[] output = { -1 };
return output;
}
int[] output = { 0 };
return output;
}
/**
* The play method, which checks for wins after five turns
*/
public String checkWinner() {
if (this.turn >= 5) {
int winner = this.checkWin();
if (winner != -1) {
if (winner == this.TOKEN_X) {
return "Player 1 (X) wins!";
} else if (winner == this.TOKEN_O) {
return "Player 2 (O) wins!";
} else if (winner == 2) {
return "Stalemate.";
}
} else {
return "";
}
}
return "";
}
}
package com.hkamran;
import java.util.ArrayList;
public class TicTacToe {
private final int TOKEN_EMPTY = -1;
protected final int TOKEN_O = 0;
protected final int TOKEN_X = 1;
private int[][] state = { { TOKEN_EMPTY, TOKEN_EMPTY, TOKEN_EMPTY }, { TOKEN_EMPTY, TOKEN_EMPTY, TOKEN_EMPTY },
{ TOKEN_EMPTY, TOKEN_EMPTY, TOKEN_EMPTY }, };
protected int turn = 0;
protected boolean playing = true;
private final int[] TOKENS = { TOKEN_X, TOKEN_O };
/**
* Get the board's value at a specific index
*
* @param row (int) The row to check
* @param column (int) The column to check
* @return (String) <code>_</code> for an empty position, <code>O</code> for O,
* <code>X</code> for X, and <code>*</code> if there is an error
*/
private String getStateValue(int row, int column) {
if (row >= 0 && row <= 2 && column >= 0 && column <= 2) {
if (this.state[row][column] == TOKEN_EMPTY) {
return "_";
} else if (this.state[row][column] == TOKEN_O) {
return "O";
} else if (this.state[row][column] == TOKEN_X) {
return "X";
} else {
return "*";
}
} else {
return "*";
}
}
/**
* Print the board
*/
protected void printBoard() {
System.out.println("┌───────────┐");
System.out.println("│ " + this.getStateValue(0, 0) + " │ " + this.getStateValue(0, 1) + " │ "
+ this.getStateValue(0, 2) + " │");
System.out.println("┝───────────┥");
System.out.println("│ " + this.getStateValue(1, 0) + " │ " + this.getStateValue(1, 1) + " │ "
+ this.getStateValue(1, 2) + " │");
System.out.println("┝───────────┥");
System.out.println("│ " + this.getStateValue(2, 0) + " │ " + this.getStateValue(2, 1) + " │ "
+ this.getStateValue(2, 2) + " │");
System.out.println("└───────────┘");
}
/**
* Check the win for a specific token
*
* @param token (int) The token integer to check
* @return (int) 0 if any of the checks are true, -1 if not
*/
private int checkPlayerWin(int token) {
// Rows
for (int row = 0; row < this.state.length; row++) {
if (this.state[row][0] == token && this.state[row][1] == token && this.state[row][2] == token) {
this.playing = false;
return 0;
}
}
// Columns
for (int column = 0; column < this.state[0].length; column++) {
if (this.state[0][column] == token && this.state[1][column] == token && this.state[2][column] == token) {
this.playing = false;
return 0;
}
}
// Diagonal left-to-right
if (this.state[0][0] == token && this.state[1][1] == token && this.state[2][2] == token) {
this.playing = false;
return 0;
}
// Diagonal right-to-left
if (this.state[0][2] == token && this.state[1][1] == token && this.state[2][0] == token) {
this.playing = false;
return 0;
}
return -1;
}
/**
* Check if there is an winning move on the board
*
* @return (int) -1 if no winning move, <code>TOKEN_X</code> if X, and
* <code>TOKEN_O</code> if O, <code>2</code> if stalemate
*/
protected int checkWin() {
for (int token : this.TOKENS) {
int check = this.checkPlayerWin(token);
if (check == 0) {
return token;
}
}
// Stalemate
int positionsFilled = 0;
for (int row = 0; row < this.state.length; row++) {
for (int column = 0; column < this.state[row].length; column++) {
if (this.state[row][column] != TOKEN_EMPTY) {
positionsFilled += 1;
}
}
}
if (positionsFilled == Math.pow(this.state.length, 2)) {
return 2;
}
// If nothing has been triggered
return -1;
}
/**
* Sets a board place's state
*
* @param playerCode (int) Either <code>TOKEN_O</code> or <code>TOKEN_X</code>
* @param row (int) An integer between 1 and 3, representing the row of
* the piece's position
* @param column (int) An integer between 1 and 3, representing the column
* of the piece's position
* @return (int) 0 means the operation was successful, -1 means that there was
* an error with the parameters, and 1 means the spot was occupied
*/
protected int setState(int playerCode, int row, int column) {
if (row < 1 || row > 3 || column < 1 || column > 3 || (playerCode != TOKEN_O && playerCode != TOKEN_X)) {
return -1;
}
int trueRow = row - 1;
int trueColumn = column - 1;
if (this.state[trueRow][trueColumn] != TOKEN_EMPTY) {
return 1;
}
this.state[trueRow][trueColumn] = playerCode;
this.turn += 1;
return 0;
}
/**
* Get the state at a specified coordinate
*
* @param row (int) The row coordinate
* @param column (int) The column coordinate
* @return (int) The current state, or -1 if there's a parameter error
*/
protected int getState(int row, int column) {
if (row < 1 || row > 2 || column < 1 || column > 2) {
return -1;
}
return this.state[row][column];
}
/**
* Get the empty positions
*
* Used by the CPU's gameplay
*
* @return (ArrayList&lt;int[]&gt;) The empty positions
*/
protected ArrayList<int[]> getEmptyPositions() {
ArrayList<int[]> emptyLocations = new ArrayList<int[]>();
for (int row = 0; row < this.state.length; row++) {
for (int column = 0; column < this.state[row].length; column++) {
if (this.state[row][column] == TOKEN_EMPTY) {
int[] coordinates = { row, column };
emptyLocations.add(coordinates);
}
}
}
return emptyLocations;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment