patternjavaModerate
Tic Tac T-OO: Design and Implementation
Viewed 0 times
designtictacandimplementation
Problem
I want to learn OO design and as a start, I implemented Tic Tac Toe. I'd like to hear your thoughts on the design and implementation.
Classes:
```
package ood.tictac2;
class Player {
private String pName;
public Player(String name){
this.pName=name;
}
public String getPlayerName(){
return this.pName;
}
}
package ood.tictac2;
class Board{
private char board[][] = new char[3][3];
private int gridSpaceLeft=9;
public void initBoard(){
for(int row=0;row0);
}
public char getPos(int row, int col){
return board[row][col];
}
public void setPos(int row, int col, char x){
if(isAllowed(row, col)){
board[row][col]=x;
gridSpaceLeft--;
}
}
public boolean isAllowed(int row, int col){
return (board[row][col]=='-');
}
public boolean isGameOver(){
return checkRows() || checkCols() || checkDiag();
}
private boolean checkDiag() {
StringBuilder d1 = new StringBuilder();
StringBuilder d2 = new StringBuilder();
boolean isOver = false;
for(int row=0,col=2;row=0;row++,col--){
d1.append(board[row][row]);
d2.append(board[row][col]);
if (d1.toString().equals("xxx") || d1.toString().equals("ooo")) isOver=true;
if (d2.toString().equals("xxx") || d2.toString().equals("ooo")) isOver=true;
}
return isOver;
}
private boolean checkCols() {
boolean isOver = false;
StringBuilder sb;
for(int row=0;row players = new ArrayList<>();
private Board gameBoard;
private Scanner in = new Scanner(System.in);
private final char DASH = 'x';
private final
Classes:
Player- Has just a name as of now. I hope to develop this more to use for other similar situations.
Board- Has a 3x3 matrix and takes care of checking if game is over and if any legal move is left.
Controller- Contains two players and a game board.
```
package ood.tictac2;
class Player {
private String pName;
public Player(String name){
this.pName=name;
}
public String getPlayerName(){
return this.pName;
}
}
package ood.tictac2;
class Board{
private char board[][] = new char[3][3];
private int gridSpaceLeft=9;
public void initBoard(){
for(int row=0;row0);
}
public char getPos(int row, int col){
return board[row][col];
}
public void setPos(int row, int col, char x){
if(isAllowed(row, col)){
board[row][col]=x;
gridSpaceLeft--;
}
}
public boolean isAllowed(int row, int col){
return (board[row][col]=='-');
}
public boolean isGameOver(){
return checkRows() || checkCols() || checkDiag();
}
private boolean checkDiag() {
StringBuilder d1 = new StringBuilder();
StringBuilder d2 = new StringBuilder();
boolean isOver = false;
for(int row=0,col=2;row=0;row++,col--){
d1.append(board[row][row]);
d2.append(board[row][col]);
if (d1.toString().equals("xxx") || d1.toString().equals("ooo")) isOver=true;
if (d2.toString().equals("xxx") || d2.toString().equals("ooo")) isOver=true;
}
return isOver;
}
private boolean checkCols() {
boolean isOver = false;
StringBuilder sb;
for(int row=0;row players = new ArrayList<>();
private Board gameBoard;
private Scanner in = new Scanner(System.in);
private final char DASH = 'x';
private final
Solution
Here are some comments to your code:
Hope this doesn't feel to hush, but this was my thoughts when reading through your code. It does seem like it does the job, but I would reconsider some of the checks for when winning. But mostly my comments are related to code styling, which seems like nitpicking, but in the long run you are better of being consisten and writing clean and nice code. It will make your life much easier when revisiting your code at a later point in time, and you need to find a bug or flaw or whatever.
1st addition: Some additional comments
2nd addition: New method
Here is some code doing the latter variant of checking whether the last move was a win. I have not implemented any of the other stuff I mentioned, just added this one method (and if I'd change the entire class, then the
```
public boolean checkIfWinningMove(int row, int col, char piece) {
final int Dimension = 3;
//
- Simplify check functions – When you are checking for a winner you could change the logic slightly. First of all you could/should move the check against the
xxxoryyyoutside of the loop, as it will never hit when you are building up the line. Second, you could break out early if the current grid position is empty, as it then can not be a part of a complete line
- Alternate check function simplification – Instead of building new strings all the time, you could have two variables,
xHasWonandoHasWon. Set them to true before the loop, and falsify them if you hit the other piece or an empty space. If neither is set at end, you don't have a winner. You could also return early if both is false (or the current space is empty). This would also allow for you to see who has won, or do as you do in current version:return xHasWon || oHasWon
- Simplify diagonal check – When building the two diagonals, this can be done using a single variable, and then using the positions
board[i][i]andboard[dimension-i-1][i]where dimension in your case is3
- Consider allowing for larger dimensions – Instead of hard coding the dimension to a
3x3board, allow for the constructor to take a parameter indicating the dimension, and then changing all references where it is hard coded to use a the dynamic dimension
- Add a
Positiontype – Instead of returning anint[]consider returning a simple type holding the position
- Avoid magic numbers – When reading the next position you use
-1to keep looping, it is better to do awhile(true)orwhile (pos == null)instead of testing against-1. If using the everlasting loop, be sure tobreakout when you have a valid position
- Add spaces around if condition – To me it is a little jammed up when you write
if(gameBoard.isGameOver()){. I would suggest to open it up a littleif (gameBoard.isGameOver()) {as I find it easier to read both where the condition is and where the if block starts
- Be consistent with use of braces and indentation – Do avoid
iforforloops without braces, this will at some point in time create errors for you, and are already causing code to be harder to read. Please do not do stuff likeif (true) continueelse{..., this is a code smell
- Be consistent in spacing between functions – For some you have a line shift between functions, and some not. Choose either (hopefully with space), and stick to it
- Choose good variable names – Most of the time you've chosen good variables name, but then something like
temporcoccurs. A better alternative for the first one here would becurrentPlayer
- Reconsider some of the function names – This a minor, but to me some of the functions are not intuitive. Like the
checkXxxx, what do you check for? A row? or a winning row? or ??? AndpromptTurnwould be better with something likereadNextMove. Likewise withisAllowed, what is allowed? A better name (and close to standard naming) would beisEmpty
Hope this doesn't feel to hush, but this was my thoughts when reading through your code. It does seem like it does the job, but I would reconsider some of the checks for when winning. But mostly my comments are related to code styling, which seems like nitpicking, but in the long run you are better of being consisten and writing clean and nice code. It will make your life much easier when revisiting your code at a later point in time, and you need to find a bug or flaw or whatever.
1st addition: Some additional comments
- Check only cross section for win – Given that no one has won already, you can simplify and only check the row, column and possibly diagonal where the last marker was placed, and you only need to check for that marker type, the
xorythat was used. In other words if the last piece placed was anxin pos(0,1)you only need to check forxinrow[0], andcolumn[1]and no diagonal since it is in the middle position of first row
- A better naming standard – My main language is C#, not java, but there we follow this naming standard, which helps identifying the scope and availability of the variables (and also eliminates the need for using
thisas a qualifier):
_name– leading underscore is a private class variable
Name– Uppercase first letter for public class variables and properties
name– Lowercase first letter for method variables and/or parameters
- No prefix or postfix to indicate types, like
porlpszor whatever. Let the compiler worry about types...
2nd addition: New method
checkIfWinningMoveHere is some code doing the latter variant of checking whether the last move was a win. I have not implemented any of the other stuff I mentioned, just added this one method (and if I'd change the entire class, then the
Dimension would have been made a class variable).```
public boolean checkIfWinningMove(int row, int col, char piece) {
final int Dimension = 3;
//
Code Snippets
public boolean checkIfWinningMove(int row, int col, char piece) {
final int Dimension = 3;
// Assume all possible winning conditions are true ...
boolean winningRow = true;
boolean winningCol = true;
// ... at least if the position is on the diagonal
boolean winningDiagonal = row == col;
boolean winningReverseDiagonal = (Dimension - row - 1) == col;
// Falsify the assumption of all are winning, and if falsified
// don't check again for that winning condition
for (int i=0; i < Dimension; i++) {
if (winningRow && board[row][i] != piece) {
winningRow = false;
}
if (winningCol && board[i][col] != piece) {
winningCol = false;
}
if (winningDiagonal && board[i][i] != piece) {
winningDiagonal = false;
}
if (winningReverseDiagonal && board[Dimension-i-1][i] != piece) {
winningReverseDiagonal = false;
}
// If no winning conditions are left, break out early
if (!winningRow && !winningCol &&
!winningDiagonal && !winningReverseDiagonal ) {
break;
}
}
return winningRow || winningCol ||
winningDiagonal || winningReverseDiagonal;
}public void playGame(){
boolean flipTurns = true;
Player currentPlayer = players.get(flipTurns ? 0 : 1);
while(gameBoard.isGridSpaceLeft()){
int[] move= promptTurn(currentPlayer);
char playedPiece = flipTurns ? DASH : DOT;
gameBoard.setPos(move[0],move[1], playedPiece);
if (gameBoard.checkIfWinningMove(move[0], move[1], playedPiece)) {
System.out.println(currentPlayer.getPlayerName() + " is a winner!");
break;
}
gameBoard.printBoard();
flipTurns = !flipTurns;
currentPlayer = players.get(flipTurns ? 0 : 1);
}
System.out.println("Game over!");
}Context
StackExchange Code Review Q#101595, answer score: 10
Revisions (0)
No revisions yet.