The other day, I started thinking of a new personal project to start that I wanted to do in Java. As I started to do it, I realized that I was constantly deleting classes, creating new ones, merging them, etc. I finally have come to the conclusion that my understanding of OOP is not very strong.
To practice, I thought the best thing to do would be to start with something simple. For that, I chose the game Tic Tac Toe.
Main.java
package com.sirpython.tictactoe;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
TicTacToeGame game = new TicTacToeGame();
Scanner input = new Scanner(System.in);
while(true) {
game.getBoard().display();
System.out.println("\nIt is " + game.getTurn().getSymbol() + "'s turn.");
int space = input.nextInt(10);
if(space < game.MIN_SPACE || space > game.MAX_SPACE) {
System.out.println("Please choose a space 0-8");
continue;
}
try {
game.place(space);
} catch(OccupiedSpaceException e) {
System.out.println(e.getMessage());
continue;
}
Player winner = game.getWinner();
if(winner != null) {
System.out.println("Winner: " + winner.getSymbol());
break;
}
game.switchTurns();
}
}
}
TicTacToeGame.java
package com.sirpython.tictactoe;
public class TicTacToeGame {
public final int MIN_SPACE = 0;
public final int MAX_SPACE = 8;
private final int[][] winConditions = { {0,1,2}, {3,4,5}, {6,7,8}, {0,3,6}, {1,4,7}, {2,5,8}, {0,4,8}, {2,4,6} };
private final Space[][] winSpaces;
private Board board;
private Player turn;
public TicTacToeGame() {
board = new Board();
turn = Player.X;
winSpaces = new Space[winConditions.length][winConditions[0].length];
for(int i = 0; i < winConditions.length; i++) {
for(int j = 0; j < winConditions[i].length; j++) {
winSpaces[i][j] = board.getSpace(winConditions[i][j]);
}
}
}
/**
* Attempts to place a player on a space
* @param space The space to place at
* @throws OccupiedSpaceException The desired space is taken
*/
public void place(int space) throws OccupiedSpaceException {
if(!board.isFree(space)) {
throw new OccupiedSpaceException();
}
board.getSpace(space).setOccupant(getTurn());
}
/**
* Returns the Player whose turn it is
* @return The Player whose turn it is
*/
public Player getTurn() {
return turn;
}
/**
* Switches whose turn it is.
*
* If it's X's turn, it's now O's.
* If it's O's turn, it's now X's.
*/
public void switchTurns() {
if(turn == Player.X) {
turn = Player.O;
} else if(turn == Player.O) {
turn = Player.X;
}
}
/**
* Returns the winner if there is one
* @return The winner or null
*/
public Player getWinner() {
for(int i = 0; i < winSpaces.length; i++) {
Player[] occupants = {winSpaces[i][0].getOccupant(),
winSpaces[i][1].getOccupant(),
winSpaces[i][2].getOccupant()};
if(occupants[0] == occupants[1] && occupants[1] == occupants[2]) {
return occupants[0];
}
}
return null;
}
/**
* Returns the board associated with this game
* @return The board
*/
public Board getBoard() {
return board;
}
}
Board.java
package com.sirpython.tictactoe;
public class Board {
public final int SIZE = 9;
public final int ROW_SIZE = 3;
private final Space[] spaces;
public Board() {
spaces = new Space[SIZE];
for(int i = 0; i < SIZE; i++) {
spaces[i] = new Space();
}
}
/**
* Displays the board in the form of a normal Tic Tac Toe board
*/
public void display() {
for(int i = 0; i < SIZE; i++) {
Player occupant = spaces[i].getOccupant();
/* If there is nothing occupying the space, print the space's number */
char toPrint = occupant == null ? (char) (i + '0') : occupant.getSymbol();
/* Print out a newline every third iteration so it looks like a tictactoe board */
System.out.print((i % ROW_SIZE == 0 && i != 0 ? "\n" : "") + toPrint);
}
}
/**
* Tells if a space is occupied
* @param space The number of the space to check
* @return If the space is occupied
*/
public boolean isFree(int space) {
return spaces[space].getOccupant() == null;
}
/**
* Returns the space
* @param space The number of the space
* @return The space
*/
public Space getSpace(int space) {
return spaces[space];
}
}
Space.java
package com.sirpython.tictactoe;
public class Space {
private Player occupant = null;
public Space() {
}
/**
* Sets the occupant field
* @param occupant The new value for the field
*/
public void setOccupant(Player occupant) {
this.occupant = occupant;
}
/**
* Gets the occupant field
* @return The occupant field value
*/
public Player getOccupant() {
return occupant;
}
}
Player.java
package com.sirpython.tictactoe;
public enum Player {
X('x'), O('o');
private final char symbol;
private Player(char symbol) {
this.symbol = symbol;
}
/**
* Returns this Player's symbol
* @return This Player's symbol
*/
public char getSymbol() {
return symbol;
}
}
OccupiedSpaceException.java
package com.sirpython.tictactoe;
public class OccupiedSpaceException extends Exception {
public OccupiedSpaceException() {
super("That space is already occupied");
}
}
Sample
Here is a sample game I played with myself:
012 345 678 It is x's turn. 0 <-- x12 345 678 It is o's turn. 4 <-- x12 3o5 678 It is x's turn. 6 <-- x12 3o5 x78 It is o's turn. 7 <-- x12 3o5 xo8 It is x's turn. 3 <-- Winner: x
Yay! I beat myself!
Note: The numbers pointed to by an <--
are user input.
Concerns
As questioned above, how is this code, OOP-wise? Do classes do what they should do and nothing else? Should some classes be refactored?
How is my handling of the win (
TicTacToe.java
)? Is it a good idea to have an entire variable dedicated to hold the square numbers that form a win condition so that it can be used to get the actual square/space? And, should this be in astatic
block rather than the constructor?How is my documentation? I was using the IntelliJ IDEA, which automatically generates a docstring for me to fill out. However, are there any practices that I am missing? I've occasionally seen HTML tags being used.
How is my connection between the classes? As in, is the way that my classes are talking to each other wise? I am mainly concerned about the
main
methods interaction withTicTacToeGame
, and that class's interaction withBoard
.Is my code idiomatic? I am not very familiar at all with Java, so I often have the API docs up in my browser as I am programming (is this bad?). Are there any classes that I could have used to simplify my code?
If I were writing this in a non-OOP language, I probably would've stored the two players in an array and done some tricks (
turn = (turn + 1) % 2
) to find out whose turn it was. However, since this in an OOP language and it's Java, I thought it would be best to store the players in an enum. Is this the best choice, or would it have been better to create an entire class?I created my own exception for handling occupied spaces. Would it have been better to use a built-in exception?
Any other things that jump out are encouraged.