I've written my first project which is Tic Tac Toe. Everything is working correctly. I'm here to get some advice about my code. I want to be this code as much object oriented as it can be. I divided every method into new class to keep it clear.
The only thing which is "probably" un-object oriented is "static" board. I don't know how to get rid of it.
Main Class
package TicTacToeGame;
public class Main {
public static void main(String[] args) {
Game allGame = new Game();
allGame.StartGame();
}
}
Game Class
package TicTacToeGame;
import java.util.Scanner;
class Game {
PrintBoard map = new PrintBoard();
private FullPlace fullPlace = new FullPlace();
ChangePlayer playerChanger = new ChangePlayer();
WinnerConditions winner = new WinnerConditions();
Scanner input = new Scanner(System.in);
char[][] board = PrintBoard.board;
NoPlace noPlace = new NoPlace();
private int row, col;
void StartGame() {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
board[i][j] = '_';
}
}
map.printBoard();
while (winner.Winner()) {
row = input.nextInt();
col = input.nextInt();
if (row > 3 || col > 3) {
System.out.println("You've inputed place, which is out of the board!\nTry again!");
} else {
if (fullPlace.isFull(row, col)) {
System.err.println("The place is taken");
} else {
board[row - 1][col - 1] = playerChanger.getTurn();
map.printBoard();
playerChanger.whichPlayer();
}
}
}
playerChanger.whichPlayer();
System.out.println("the winner is " + playerChanger.getTurn());
}
}
Winner Condition Class
package TicTacToeGame;
public class WinnerConditions {
char[][] board = PrintBoard.board;
public boolean Winner() {
if (board[0][0] != '_' && board[0][0] == board[0][1] && board[0][0] == board[0][2])
return false;
if (board[1][0] != '_' && board[1][0] == board[1][1] && board[1][0] == board[1][2])
return false;
if (board[2][0] != '_' && board[2][0] == board[2][1] && board[2][0] == board[2][2])
return false;
if (board[0][0] != '_' && board[0][0] == board[1][0] && board[0][0] == board[2][0])
return false;
if (board[0][1] != '_' && board[0][1] == board[1][1] && board[0][1] == board[2][1])
return false;
if (board[0][2] != '_' && board[0][2] == board[1][2] && board[0][2] == board[2][2])
return false;
if (board[0][0] != '_' && board[0][0] == board[1][1] && board[0][0] == board[2][2])
return false;
if (board[0][2] != '_' && board[0][2] == board[1][1] && board[0][2] == board[2][0])
return false;
return true;
}
}
No Place Class
package TicTacToeGame;
public class NoPlace {
char[][] board = PrintBoard.board;
public boolean OutOfBoard(int row, int col) {
if (row > 3 || col > 3) {
System.err.println("There is no row or column");
return true;
}
return false;
}
}
Print Board Class
package TicTacToeGame;
public class PrintBoard {
public static char[][] board = new char[3][3];
public void printBoard() {
System.out.println("-------------");
for (int i = 0; i < 3; i++) {
System.out.print("| ");
for (int j = 0; j < 3; j++) {
System.out.print(board[i][j] + " | ");
}
System.out.println();
}
System.out.println("-------------");
}
}
Change Player Class
package TicTacToeGame;
public class ChangePlayer {
public char turn = 'X';
public void whichPlayer() {
if (turn == 'X') {
turn = 'O';
} else {
turn = 'X';
}
}
public char getTurn() {
return turn;
}
}
Full Place Class
package TicTacToeGame;
public class FullPlace {
char[][] board = PrintBoard.board;
public boolean isFull(int row, int col) {
if (board[row - 1][col - 1] == 'X' ||
board[row - 1][col - 1] == 'O') {
return true;
}
return false;
}
}
1 Answer 1
Thanks for sharing your code.
OOP
OOP doesn't mean to "split up" code into random classes.
The ultimate goal of OOP is to reduce code duplication, improve readability and support reuse as well as extending the code.
Doing OOP means that you follow certain principles which are (among others):
- information hiding / encapsulation
- single responsibility
- separation of concerns
- KISS (Keep it simple (and) stupid.)
- DRY (Don't repeat yourself.)
- "Tell! Don't ask."
- Law of demeter ("Don't talk to strangers!")
E.g.: in your class TicTacToeGame
you access a field in the class PrintBoard
. This is a violation of the information hiding / encapsulation principle.
The cause why you "can't get rid" of that static access is that you should not access this field from outside at all. The method in you FullPlace
class schould be in class PrintBoard
(which should not be named with the prefix "print").
In turn the classFullPlace
should't exist at all.
Naming
Finding good names is the hardest part in programming, so always take your time to think about the names of your identifiers.
Naming Conventions
Please read (and follow) the Java Naming Conventions
misleading naming
Some of your identifiers have somewhat misleading names, eg. ChangePlayer
and PrintBoard
which look like method names since they start with a verb but they are classes.
But classes don't do something. They are something which is able to do something.
Magic numbers
Your code has some magic numbers. This are literal values with a special meaning like here:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++)
You should either use constants with meaningful names like
private static final int GAME_FIELD_SIZE =3;
// ...
for (int i = 0; i < GAME_FIELD_SIZE; i++) {
for (int j = 0; j < GAME_FIELD_SIZE; j++) {
or use existing constraints like so:
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
FullPlace
, I'd create methods on the Game class instead. A helpful start for modeling is describing your task in English, and representing nouns as classes and verbs as methods. \$\endgroup\$PrintBoard
, since that's hardly the sole purpose of representing the board. You can avoid the static member by passing the board to any method that needs it. An object to keep track of which player is playing is OK too, and an object to hold both of those. With just console input and output, there really isn't much else in this game to use objects for. \$\endgroup\$