5
\$\begingroup\$

This below program will be further enhanced for TicTacToe game with Human and Computer as players.

Currently this program is written for choosing best move for a given grid position.

package project2.tictactoe;


enum TicTacToe{
    X, O, NULL;

    @Override
    public String toString(){
        switch(this){
        case X:
            return "X";
        case O:
            return "O";
        default:
            return "NULL";

        }
    }

}


public class TicTacToeGame{

    TicTacToe[][] twoDimArray = new TicTacToe[3][3];

    TicTacToeGame(){
        for(int i = 0; i < 3; i++){
            for(int j = 0; j < 3; j++){
                this.twoDimArray[i][j] = TicTacToe.NULL;
            }
        }
    }

    private static final Boolean HUMAN_TURN = false;
    private static final Boolean COMPUTER_TURN = true;


    private static String bestMove = null;


    boolean gameDone(){
        for(int i = 0; i < 3; i++){

            if(this.twoDimArray[i][0] != TicTacToe.NULL){
                if(this.twoDimArray[i][0] == this.twoDimArray[i][1]){
                    if(this.twoDimArray[i][1] == this.twoDimArray[i][2]){
                        return true;
                    }
                }
            }


            if(this.twoDimArray[0][i] != TicTacToe.NULL){
                if(this.twoDimArray[0][i] == this.twoDimArray[1][i]){
                    if(this.twoDimArray[1][i] == this.twoDimArray[2][i]){
                        return true;
                    }
                }
            }


            if(i == 1){
                /* Diagnol check */
                if(this.twoDimArray[i-1][0] != TicTacToe.NULL){
                    if(this.twoDimArray[i-1][0] == this.twoDimArray[i][i]){
                        if(this.twoDimArray[i][i] == this.twoDimArray[i+1][i+1]){
                            return true;
                        }
                    }
                }


                if(this.twoDimArray[i+1][0] != TicTacToe.NULL){
                    if(this.twoDimArray[i+1][0] == this.twoDimArray[i][i]){
                        if(this.twoDimArray[i][i] == this.twoDimArray[i-1][i+1]){
                            return true;
                        }
                    }
                }

            }


        } /* end for-loop*/
        return false;
    }


    byte chooseBestMove(boolean side){
        byte bestScore;

        if(this.gameDone()){
            if( side == HUMAN_TURN){
                return +1; //actually computer side won here, so +1
            }else{
                return -1; //actually human side won here, so -1.
            }
        }else{

            if(side == COMPUTER_TURN){
                bestScore = -2; // this is little mathematics to maintain best score
            }else{
                bestScore = +2;
            }


            for(int i=0; i<9; ++i)
                    if(this.twoDimArray[i/3][i%3] == TicTacToe.NULL)
                    {
                        /*Make a new possible move and modify the array on heap*/
                        if(side == TicTacToeGame.COMPUTER_TURN){
                            this.twoDimArray[i/3][i%3] = TicTacToe.O;
                        }else{
                            this.twoDimArray[i/3][i%3] = TicTacToe.X;
                        }

                        byte score = chooseBestMove(!side);

                        if(side == TicTacToeGame.COMPUTER_TURN){
                            if(score > bestScore){
                                bestScore = score;
                                bestMove = "Place \"" + TicTacToe.O + "\" at position [" + i/3 + "][" + i%3 + "]";
                            }
                        }

                        if(side == TicTacToeGame.HUMAN_TURN){
                            if(score < bestScore){
                                bestScore = score;
                                bestMove = "Place \"" + TicTacToe.X + "\" at position [" + i/3 + "][" + i%3 + "]";
                            }
                        }

                        /* Undo the possible move */
                        if(side == TicTacToeGame.COMPUTER_TURN){
                            this.twoDimArray[i/3][i%3] = TicTacToe.NULL;
                        }else{
                            this.twoDimArray[i/3][i%3] = TicTacToe.NULL;
                        }

                    }
        }
        return bestScore; 
    }


    void printElements(){
        for(int i = 0; i < 3; i++){
            System.out.println(" " + twoDimArray[i][0] + " | " + twoDimArray[i][1] + " | " + twoDimArray[i][2]);
            if(i != 2)
                System.out.println("------------------");
        }
    }

    public static void main(String[] args){

        TicTacToeGame objRef = new TicTacToeGame();
        boolean side = true; //first move is computer move

        /* For any Grid scenario set below*/
        objRef.twoDimArray[0][0] = TicTacToe.NULL;
        objRef.twoDimArray[0][1] = TicTacToe.NULL;
        objRef.twoDimArray[0][2] = TicTacToe.NULL;
        objRef.twoDimArray[1][0] = TicTacToe.NULL;
        objRef.twoDimArray[1][1] = TicTacToe.NULL;
        objRef.twoDimArray[1][2] = TicTacToe.NULL;
        objRef.twoDimArray[2][0] = TicTacToe.NULL;
        objRef.twoDimArray[2][1] = TicTacToe.NULL;
        objRef.twoDimArray[2][2] = TicTacToe.NULL;

        /* Choose best move */
        byte score = objRef.chooseBestMove(side);
        System.out.println(TicTacToeGame.bestMove);
    }
}

Does this program follow the elements of OOPs paradigm, abstraction and encapsulation?

I would require comments to refine this programming style to fit into OOP paradigm.

\$\endgroup\$
10
\$\begingroup\$

Does this program follow the elements of OOPs paradigm, abstraction and encapsulation?

Not really.

It started almost fine with a field contents enum (why override toString and not change actual output?), but then degenerated rapidly.

Your main class does everything: startup and lifecycle, IO, game logic, game data storage, game AI. While it is decomposed to different methods, having one class do everything meaningful does not follow the OOP paradigm.

You should try to decompose your program to several classes each doing just one job. Have one class with the main method starting the app. Have another controlling the game lifecycle. And another encapsulate all the data. Yet another could perform IO - presentation of this data to the user and acquiring input. Finally, for the task of making the actual move, you might have another class (or, rather, interface - so the live and the computer players were similar for the game engine, being different implementations of the same interface).

\$\endgroup\$
  • \$\begingroup\$ my definition of abstraction is, Abstraction is more about taking common functionality out of multiple places and putting it into a single common place instead and using inheritance (or something similar) to have that common functionality refer to the single abstract class(in java) so what would you check in my program, when i ask you, Does this single class program follow abstraction? Because am still not clear with word abstraction, when such single class programs are written, based on my definition of abstraction given above \$\endgroup\$ – overexchange Jan 23 '15 at 23:59
  • \$\begingroup\$ @overexchange in your code there is a single place to start with. Abstraction is depending on a contract instead of the implementation; you can't achieve it with a single-class design. \$\endgroup\$ – Jeor Mattan Jan 24 '15 at 0:17
  • \$\begingroup\$ @overexchange Abstraction is hiding the details of how something is done. For example, when you write a+b, you usually don't care what bytecode gets generated, what the machine-language opcode is, which transistors get activated, etc. All of those details are abstracted away so that you can focus on your programming task. Abstraction is, therefore, what allows us to accomplish wonderfully complex tasks without being overwhelmed by details. Your discussion of abstract classes is an unrelated usage of the word "abstract". \$\endgroup\$ – 200_success Jan 25 '15 at 5:24
7
\$\begingroup\$

General Review

I will just give a general review.

Naming

Some of your naming is horrible. Take a look at:

TicTacToe[][] twoDimArray = new TicTacToe[3][3];

twoDimArray would be better named gameBoard.

TicTacToeGame objRef = new TicTacToeGame();

objRef would be better named game.

Magic Numbers

You have the number 3 everywhere. Replace it with BOARD_SIZE and add:

private static final int BOARD_SIZE = 3;

Spacing

}else{

is horribly spaced. Instead, do the following:

} else {

Also:

for(int i=0; i<9; ++i)

do:

for (int i = 0; i < BOARD_SIZE * BOARD_SIZE; ++i)

There are tons of areas where there is more spacing required. Try to fix all of them (without over-spacing).

Braces

for(int i=0; i<9; ++i) // Replace with: for (int i = 0; i < BOARD_SIZE * BOARD_SIZE; ++i)
    if(this.twoDimArray[i/3][i%3] == TicTacToe.NULL)
    {
        // ...
    }

Never, ever do that. ALWAYS put braces. If you don't, horrible bugs can occur. Do the following:

for (int i = 0; i < BOARD_SIZE * BOARD_SIZE; ++i) {
    if(this.twoDimArray[i / BOARD_SIZE][i % BOARD_SIZE] == TicTacToe.NULL)
    {
        // ...
    }
}
\$\endgroup\$
  • \$\begingroup\$ These are horrible mistakes that I should have handled. "Ctrl+Shift+f" resolved many spacing issues on eclipse. \$\endgroup\$ – overexchange Jan 25 '15 at 4:07
6
\$\begingroup\$

Abstraction and encapsulation

I see that you have made an attempt. However, the abstraction is weak, and encapsulation is violated. The TicTacToeGame class does basically everything, including keeping track of the board state, handling the human behaviour, and performing the role of the AI. That is an anti-pattern called a "monster class".

The violation of encapsulation is evident from the objRef.twoDimArray[…][…] = TicTacToe.NULL; statements in main(). You shouldn't be reaching into the object and manipulating its innards like that (even if the compiler allows you to).

You also haven't modelled the problem with proper abstractions. There should be at least three objects: a board, a human player, and a computer player. A good object-oriented design would have each player decide how it wants to make its next move, taking the board state as input.

You have a TicTacToe enum, which doesn't really model anything. The NULL member is puzzling. It might make sense to have a NULL whose .toString() returns a " " (space), to eliminate a special case in printElements(), but that's not what you have done.

Another consequence of the weakness of the design is the abundance of if(side == COMPUTER_TURN) { … } else { … } switches in the code. If each side just "knew what to do", then you wouldn't need such switches.

Suggested solution

Here is a solution with abstraction and encapsulation. The TicTacToe class only ever exposes the board state in a read-only manner. Notice the TicTacToe.Player interface, which describes how a player must behave. I haven't included a ComputerPlayer class; that is left as an exercise.

There are actually three board representations involved here! The TicTacToe class uses a one-dimensional 9-element array, because it simplifies the implementation of .getWinner(). The ConsolePlayer class numbers the squares from 1 to 9, in an order that mimics a computer's number pad, because that is a better user interface. The two classes talk to each over using an interface with row={0,1,2} and col={0,1,2}, because that is a natural convention.

TicTacToe

public class TicTacToe {
    public interface Player {
        /**
         * Fills in rowCol with the desired move.
         *
         * @param rowCol An array of length 2, whose initial contents are
         *        irrelevant.  The first element is for the row (0 = top, 2 =
         *        bottom), and the second element is for the column (0 = left,
         *        2 = right).
         */
        void getMove(TicTacToe board, int[] rowCol);

        /**
         * Returns a consistent character representing the player.
         */
        char getSymbol();

        /**
         * The name of the player.
         */
        String toString();
    }

    public static class GameException extends RuntimeException {
        public GameException(String message) {
            super(message);
        }
    }

    private static final int[][] WINNING_STREAKS = {
        new int[] { 0, 1, 2 }, new int[] { 3, 4, 5 }, new int[] { 6, 7, 8 },
        new int[] { 0, 3, 6 }, new int[] { 1, 4, 7 }, new int[] { 2, 5, 8 },
        new int[] { 0, 4, 8 }, new int[] { 2, 4, 6 }
    };

    private Player[] board = new Player[9];
    private int movesLeft = 9;
    private Player[] players;
    private Player winner;
    private int whoseTurn;

    public TicTacToe(Player first, Player second) {
        this.players = new Player[] { first, second };
    }

    /**
     * Returns the player whose turn it is, or null if the game is over.
     */
    public Player getCurrentPlayer() {
        return (this.movesLeft <= 0 || this.getWinner() != null) ?
            null : this.players[this.whoseTurn];
    }

    /**
     * Returns the player who has won, or null if the game is a draw
     * or incomplete.
     */
    public Player getWinner() {
        if (this.winner == null) {
            for (int[] streaks : WINNING_STREAKS) {
                if ( this.board[streaks[0]] != null &&
                     this.board[streaks[0]] == this.board[streaks[1]] &&
                     this.board[streaks[1]] == this.board[streaks[2]] ) {
                    return this.winner = this.board[streaks[0]];
                }
            }
        }
        return this.winner;
    }

    /**
     * Returns the player who has played at a location, or null if empty.
     *
     * @param row 0 = top, 1 = middle, 2 = bottom
     * @param col 0 = left, 1 = middle, 2 = right
     */
    public Player getOccupantOfSquare(int row, int col) {
        return this.board[indexOf(row, col)];
    }

    /**
     * Elicits a move from the current player and executes it.
     */
    public void move() {
        Player currentPlayer = this.getCurrentPlayer();
        if (currentPlayer == null) {
            throw new GameException("Game is already over");
        }
        int[] rowCol = new int[2];
        currentPlayer.getMove(this, rowCol);
        int move = indexOf(rowCol[0], rowCol[1]);
        if (this.board[move] != null) {
            throw new GameException("Square is already occupied");
        }
        this.board[move] = currentPlayer;
        this.whoseTurn = (this.whoseTurn + 1) % this.players.length;
        this.movesLeft--;
    }

    /**
     * Textual representation of the board state.
     */
    public String toString() {
        StringBuilder sb = new StringBuilder(60);
        for (int row = 0; row < 3; row++) {
            if (row > 0) sb.append("\n---+---+---\n");
            for (int col = 0; col < 3; col++) {
                Player occupant = this.getOccupantOfSquare(row, col);
                sb.append(' ')
                  .append(occupant == null ? ' ' : occupant.getSymbol())
                  .append(' ');
                if (col < 2) sb.append('|');
            }
        }
        return sb.toString();
    }

    private static int indexOf(int row, int col) {
        if (!(0 <= row && row < 3)) throw new GameException("Invalid square");
        if (!(0 <= col && col < 3)) throw new GameException("Invalid square");
        return 3 * row + col;
    }

    public static void main(String[] args) {
        TicTacToe game = new TicTacToe(
            new ConsolePlayer('X', System.in, System.out),
            new ConsolePlayer('O', System.in, System.out)
        );
        while (game.getCurrentPlayer() != null) {
            System.out.println(game);
            System.out.println();
            game.move();
            System.out.println();
        }
        Player winner = game.getWinner();
        if (winner != null) {
            System.out.println(winner + " won!");
        } else {
            System.out.println("Nobody won");
        }
    }
}

ConsolePlayer

import java.io.InputStream;
import java.io.PrintStream;
import java.util.Scanner;

class ConsolePlayer implements TicTacToe.Player {
    private static final int[] KBD_TO_ROWCOL = new int[] {
        -1, 0x20, 0x21, 0x22, 0x10, 0x11, 0x12, 0x00, 0x01, 0x02
    };

    private char symbol;
    private Scanner in;
    private PrintStream out;

    public ConsolePlayer(char symbol, InputStream in, PrintStream out) {
        this.symbol = symbol;
        this.in = new Scanner(in);
        this.out = out;
    }

    public char getSymbol() { return this.symbol; }

    public String toString() {
        return "Human player " + this.getSymbol();
    }

    public void getMove(TicTacToe game, int[] rowCol) {
        do {
            try {
                this.out.print("Your move? ");
                int move = Integer.parseInt(this.in.nextLine());
                if (1 <= move && move <= 9) {
                    int row = KBD_TO_ROWCOL[move] >> 4;
                    int col = KBD_TO_ROWCOL[move] & 0xf;
                    if (game.getOccupantOfSquare(row, col) != null) {
                        this.out.println("Square " + move + " is already occupied.");
                        continue;
                    } else {
                        rowCol[0] = row;
                        rowCol[1] = col;
                        return;
                    }
                }
                // Invalid move, handled below
            } catch (NumberFormatException invalidMoveHandledBelow) {}
            this.out.println("Choose square 1 - 9, as in a number keypad:");
            this.out.println("   7 | 8 | 9 \n" +
                             "  ---+---+---\n" +
                             "   4 | 5 | 6 \n" +
                             "  ---+---+---\n" +
                             "   1 | 2 | 3 ");
        } while (true);
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.