Created
March 25, 2022 19:23
-
-
Save hkamran80/a92c8c455aa8472597d1da633c37cd99 to your computer and use it in GitHub Desktop.
Tic-Tac-Toe (JavaFX)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ""; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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<int[]>) 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