4
\$\begingroup\$

I've completed this Tic Tac Toe simulator in java and so far it's working. It's just supposed to generate a pre-played game and announce the winner. I'm required to use a 2D array and this is the way I've found to get it done. But I ask more experienced developers if there is a way to do this that is a little less convoluted. Or for any pointers you may have.

PS: the display and winner label are in the rest of my code but that's not the main part I'm worried about. the int 1 stands for O and the int 2 stands for X.

public class gameBord {

    // tic tac toe bord
    public int[][] tBord = new int[3][3];
    public int counter; // to count through the 9 spaces
    public int x; // to hold the index of x
    public int y; // to hold the index of y
    public int xs; // to hold how many current x marks there are on the board
    public int os; // to hold # of y marks on bord (these are to make sure there aren't
    // more marks than the player has turns.

    public int xAndO; // holds the temp variable to indicate which letter will
    // be stored in the current index on the array

    public Random r; // a random int between 1 and 2 will be chosen to define x and o

    public String winMessage; // string to be displayed on lable to display winner

    public void game(){

        // 9 for the 9 squares in tic tac toe
        counter = 9;
        // x and y stand for the indexs in the array
        x = 0;
        y = 0;
        os = 0;
        xs = 0;

        System.out.println("clicked");

        // these while loops will loop through the rows this one being the last row
        while (counter > 0){

            // before the last row we go through the second row
            while (counter > 3){

                // before the second row we go through the first row (first 3 colmns)
                while (counter > 6){

                    // label which row you're in
                    System.out.println("Row 1");

                    // get a random number between one and two to assign to this box
                    // these numbers will stand for the X or O on the bord
                    r = new Random();
                    xAndO = r.nextInt(2)+1;

                    // count how many xs and os are on the bord no one letter should
                    // go over 4
                    if (xAndO == 1){
                        os++;
                    }
                    else{
                        xs++;
                    }

                    // prints out a one or two above the indexs it'll be stored in
                    System.out.println("number to store " + xAndO);

                    // stores the number in the current index
                    tBord[x][y] = xAndO;

                    // displays the current index
                    System.out.println("Index x =" + x);
                    System.out.println("Index y =" + y);

                    // counter goes down which moves us to the next block of the array
                    counter--;

                    // there are only 3 columns (x Index) so this number has to stay between 0 and 2 (3 total)
                    // this will go from 0 to 2 and the next will go from 2 to 0
                    if (x < 2){
                        x++;
                    }
                }

                // enters the second row by changing the y index
                y = 1;

                // indicate what row it's on
                System.out.println("Row 2");

                // as long as one player hasnt placed too many xs or os
                // if there are 4 xs or os already on the bord that player
                // cannot make anymore moves so the rest will be the one
                // that doesn't already have 4
                if ( xs >= 4){
                    xAndO = 1;
                }else if(os >= 4){
                    xAndO = 2;
                }
                else if (xs < 4 && os < 4){
                    // get a random number between one and two to assign to this box
                    // these numbers will stand for the X or O on the bord

                    // get a new random between 1 and 2 and set it
                    xAndO = r.nextInt(2)+1;

                    // continue adding the total xs and os
                    if (xAndO == 1){
                        os++;
                    }
                    else{
                        xs++;
                    }
                }

                // show which number will be saved above the index it'll be saved under
                System.out.println("number to store " + xAndO);

                // save the number at that index
                tBord[x][y] = xAndO;

                // display the index it was saved under
                System.out.println("Index x =" + x);
                System.out.println("Index y =" + y);    

                // continue counting down the number of spaces in the array
                counter--;

                // this one counts down the x index since the second column starts at x = 2
                if (x > 0){
                    x--;
                }
            }

            // last row the counter is now at 3 because there are 3 blocks left in the array
            y = 2;

            // show which row this index is on
            System.out.println("Row 3");

            // as long as one player hasnt placed too many xs or os
            if ( xs >= 4){
                xAndO = 1;
            }else if(os >= 4){
                xAndO = 2;
            }
            else if (xs < 4 && os < 4){
                // get a random number between one and two to assign to this box
                // these numbers will stand for the X or O on the bord
                r = new Random();
                xAndO = r.nextInt(2)+1;

                if (xAndO == 1){
                    os++;
                }
                else{
                    xs++;
                }
            }        

            // indicate which number will be stored at this index
            System.out.println("number to store " + xAndO);

            // store the number
            tBord[x][y] = xAndO;

            // indicate which index the number will be stored under
            System.out.println("Index x =" + x);
            System.out.println("Index y =" + y);  

            // count down until all array spots have a value
            counter--; 

            // count down the index of the columns
            if (x < 2){
                x++;
            }
        }

        this.checkForWinner();
        System.out.println("DONE BUILDING GAME");
    }

OUTPUT:

clicked
Row 1
number to store 1
Index x =0
Index y =0
Row 1
number to store 1
Index x =1
Index y =0
Row 1
number to store 1
Index x =2
Index y =0
Row 2
number to store 1
Index x =2
Index y =1
Row 2
number to store 2
Index x =1
Index y =1
Row 2
number to store 2
Index x =0
Index y =1
Row 3
number to store 2
Index x =0
Index y =2
Row 3
number to store 2
Index x =1
Index y =2
Row 3
number to store 2
Index x =2
Index y =2
DONE BUILDING GAME

\$\endgroup\$
  • 2
    \$\begingroup\$ Do you know about for-loops? \$\endgroup\$ – corvus_192 Oct 26 '16 at 15:50
  • \$\begingroup\$ And just to make sure, the purpose of this code is only to fill the board randomly? \$\endgroup\$ – corvus_192 Oct 26 '16 at 16:19
  • 1
    \$\begingroup\$ Can you include the output from a sample run and explain how to interpret it? \$\endgroup\$ – 200_success Oct 26 '16 at 16:27
  • \$\begingroup\$ @ corvus_192 I do I just didn't think to use them \$\endgroup\$ – Allie Marie Oct 26 '16 at 16:37
  • \$\begingroup\$ and yes that is the purpose \$\endgroup\$ – Allie Marie Oct 26 '16 at 16:37
4
\$\begingroup\$

Code structure

The nested while loops obscure the structure of your code. In method game(), all iterations of middle loop are being performed during the first iteration of the outer loop, before the rest of its body, and all iterations of the innermost loop are performed during the first iteration of the middle loop, before the rest of that loop's body. That's effectively three separate, unnested loops running in sequence, and it would be much clearer to write it that way.

Variable scope and protection

Class gameBord has multiple instance variables that should instead be local variables of method game(). In fact, the only ones that appear to make sense as instance variables are tBord and winMessage, and possibly also r. The others have no business being part of the state of a gameBord; they are details of a particular run of method game(), and therfore should be local variables of that method.

Furthermore, few, if any, of the variables that are retained at class level should be public. As a matter of style, convention, and good practice, member variables should normally be private, with accessor methods where appropriate. There are exceptions, but you've presented no reason for me to think that any of those are in play here.

Improper use of Random

You initialize a new instance of Random for each random number you want to generate. This is at best wasteful, but it may actually be biting you by producing results much less uniformly distributed than you presumably hope to get. The correct approach is to instantiate Random once, and then to draw all (pseudo)random numbers you need from that one instance.

Convoluted algorithm

You're just filling the board, not simulating a game move-by-move. With that being the case, there are far simpler ways to go about the task than the one you have implemented. For example,

  • assume player 1 goes first, so that the final board will contain five 1s and four 2s;
  • create a nine-element List of Integers with that many 1s and 2s in any order (does not need to be random; Collections.nCopies() could help here);
  • shuffle the List via Collections.shuffle();
  • read out the List into the board array via a simple loop.

That can be implemented in about 8 lines, as opposed to your 100-ish, with those 8 being largely self-documenting. Or just a few more if you want also to output the per-position messages that your current code does.

Impossible / incomplete results

Your simple approach to filling the board affords results that could never arise in a real game. The example run you presented in fact demonstrates this: players 1 and 2 both have threes-in-a-row. This may not be a concern in practice (i.e. that may be what you intend to do).

You also do not produce representations of many of the possible games -- specifically, those that end in fewer than 9 moves. This, too, might not be a concern in practice.

Spelling

You're at least consistent, but in English, a flat surface for playing a game upon is a "board", not a "bord". The misspelling makes no functional difference, but it is distracting.

Code style

The code style is pretty good in general, but the most widely accepted coding conventions call for class names to start with an initial capital letter: GameBoard.

\$\endgroup\$
  • \$\begingroup\$ The shorter option you offered sounds great, I personally wouldn't have used a 2D array but I have to for this. I'm still looking for a solution to not having 2 winners but the main thing was getting it to print correctly. Your while loop solution sounds interesting but I don't 100% know how you mean. I'm going to attempt it though and see if it doesn't click. Usually I have to start coding something to understand how it works. Thanks for helping out. \$\endgroup\$ – Allie Marie Oct 26 '16 at 20:11
  • \$\begingroup\$ I really appreciate all this advice. I'm still very new to programming so anything helps. \$\endgroup\$ – Allie Marie Oct 26 '16 at 20:12
1
\$\begingroup\$

This is an alternative approach to the problem.
My intent was to separate resposibilities to different classes and objects. Also it demonstrates the advanced usage of Javas enum type.

I explain some details wihin the code: This is the main class containig the board, the game loop and some user interface.

    public class TickTackToe {
            // member property avoids parameter in methods,
            // but they build the state of the object. 
            // objects should be stateless by any change, so this is a tradeoff..
            private final char[][] board = new char[3][3];
            private final List<Position> positions = new ArrayList<>();

        public static void main(String[] args) {
            // create an actual object to avoid static methods
            new TickTackToe().play();
        }

        /** this is the entry to this class.
          * it is public and it only calls method either within this class
          * or at some other objects.
          * the names of te methods called here tell a story.
          */
        public void play() {
            // the list returned contains all available positions in the board.
            initializeGame();

            // make the order of elements random.
            // it is always better to use objects from a library then writing your own...
            Collections.shuffle(positions);

            // this is the actual logic
            Player winner = playRound();

            // the final output. 
            // this would also move to its own method if it was more than one line.
            // for the sake of the dummy Player I don't need a null check here.
            System.out.println(String.format("the winner is %s", winner));
        }

        /* the array of primitive char is initializes with 0x0,
         * I want it to be initialized with a space ' ' (0x20) to print nicely
         * I reuse the same loop to create position objects.
         */
        private void initializeGame() {
            for (int x = 0; x < 3; x++) 
                for (int y = 0; y < 3; y++) {
                    positions.add(new Position(x, y));
                    board[x][y] = ' ';
                }
        }

        private Player playRound() {
            // the starting player is chosen by random.
            Player currentPlayer = 
                 new Random().nextInt(2) == 0 ? Player.O : Player.X;

            // This iterates over the randomized list of available position.
            // each position exists only once in the list 
            // and so I do not need to check if a choosen position might be occupied.
            for (Position position : positions) {
                // set the current playeers mark at the selected position on board
                board[position.x][position.y] = currentPlayer.name().charAt(0);
                // output the current bord state
                show(board);

                // check, if one player already won.
                // this might not be needed within the first 4 iterations,
                // but its less readable if I surround this with another if statement
                if (isGameWon()) 
                    // if a player won it must be the current player,
                    // since she put the last mark
                    return currentPlayer;

                // this line is only reache if no player win yet.
                // as in real life the current player knows who is next.
                currentPlayer = currentPlayer.getOther();
            }
            // this line is only reached 
            // if all positions have been used and none of the players made a complete line
            // since I return the "special" user NOBOBY instead of null 
            // I avoid a null check in the calling method.
            return Player.NOBODY;
        }

        private boolean isGameWon() {
            // the board consists of a cupple of lines.
            // at this point I do not need to know which or how many.
            // all I want to know here is if there is any line occcupied by a single player
            Line[] liness = Line.values();
            for (Line line : lines) 
                if (line.isComplete(board)) 
                    return true;

            // none of the lines is completely owned by one player
            return false;
        }

        private void show(char[][] board) {
            for (char[] row : board) 
                System.out.println(Arrays.toString(row));
            System.out.println("---------");
        }
    }

The Player enum represents the game users. The one and only purpose in this implementation is to change the current user.

        enum Player {
            // The dummy Player avoids returning null and in turn null checks. 
            NOBODY {
                @Override
                Player getOther() {
                    return this; // The dummy player is not supposed to have a successor
                }
            },
            O {
                @Override
                Player getOther() {
                    return X;
                }
            },
            X {
                @Override
                Player getOther() {
                    return O;
                }
            };

            // every enum constant has its own behavior for this method.
            abstract Player getOther();
        }

the Position class is a simple DTO (data transfer object). It lets me store board positions in an easy to iterate collection. Its properties are package private for easier access.

        static class Position {
            final int x;
            final int y;

            public Position(int x, int y) {
                super();
                this.x = x;
                this.y = y;
            }
        }

The interface LoopCounter helps to reduce code duplicaton in the Line enum.

 interface LoopCounter{
    int get(int i);
 }

Each constant in this enum represents a certain line on the board. It checks if this line is completely occpied by the same player mark.

         enum Line {
            Column1 {
                @Override
                boolean isComplete(char[][] board) {
                    // get the first mark in line
                    char first = board[0][0];

                    // Java 8 Lambdas help saving lines:
                    LoopCounter x = (i)-> i;
                    LoopCounter y = (i)-> 0;
                    return isComplete(first,x,y);
                }
            },
            Column2 {
                @Override
                boolean isComplete(char[][] board) {
                   return isComplete(board[0][1],(i)->i,(i)->1);
                }
            },
            Column3 {
                @Override
                boolean isComplete(char[][] board) {
                   return isComplete(board[0][2],(i)->i,(i)->2);
                }
            },
            Diagonal1 {
                @Override
                boolean isComplete(char[][] board) {
                   return isComplete(board[0][0],(i)->i,(i)->i);
                }
            },
            Diagonal2 {
                @Override
                boolean isComplete(char[][] board) {
                   return isComplete(board[0][0],(i)->2-i,(i)->i);
                }
            },
            Row1 {
                @Override
                boolean isComplete(char[][] board) {
                   return isComplete(board[0][0],(i)->0,(i)->i);
                }
            },
            Row2 {
                @Override
                boolean isComplete(char[][] board) {
                   return isComplete(board[1][0],(i)->1,(i)->i);
                }
            },
            Row3 {
                @Override
                boolean isComplete(char[][] board) {
                   return isComplete(board[2][0],(i)->2,(i)->i);
                }
            };

            private boolean isOccupied(char first, loopCounter x, loopCounter y) {  
                    // iterate over the line
                    for (int i = 0; i < 3; i++) 

                        // check that the current mark is the same
                        //  as at the first position of this line
                        if (!isSame(first, board[x.get( i)][y.get(i)])) 

                            // I'm done when two position differ
                            return false;

                    // when the loop finishes 
                    // the line is completely occupied by one player 
                    return true;        
            }

            private boolean isSame(char first, char cs) {
                return ' ' != cs
                        && cs == first;
            }


            abstract boolean isComplete(char[][] board);
        }
\$\endgroup\$
  • \$\begingroup\$ This is fantastic and solves my turn problem. I never thought to use enums, we have only just covered them. But making the board the randomized part makes a lot more sense. Means I probably need to restart completely though. This is a great perspective thank you! \$\endgroup\$ – Allie Marie Oct 27 '16 at 11:25
  • \$\begingroup\$ Once I finish the updated version I'll update my answer thank you. \$\endgroup\$ – Allie Marie Oct 27 '16 at 11:26
  • \$\begingroup\$ Not sure if that is clear: I did not randomize the borad. I made a list of all available positions in the board. Then I introduced randomness by shuffeling this list before each player gets the next entry in this list in its turn. \$\endgroup\$ – Timothy Truckle Oct 27 '16 at 12:19
0
\$\begingroup\$

Alright, I found my solution. This solution picks a random empty spot on the board to fill rather than choosing a random player. It makes the players take turns so that the correct amount of Xs and Os will be displayed. It uses a switch statement to determine the outcome of the game.

public class gameBord {

// tic tac toe bord
private int[][] tBoard;    
private final Random r = new Random(); // a random int between 1 and 2 will be chosen to define x and o  
private String winMessage; // string to be displayed on lable to display winner
private int foundWinner = 0;


// creates the board and moves
public void game(){

    // clear board
    tBoard = new int[3][3];

    // game has started and there has not been a winner
    foundWinner = 0;

    // Player X goes first the program determines who went last based on 
    // what this variable is set to. If it's set to 1 then O went last and it will
    // set it to 2 in the while loop.
    int player = 1;

    // x and y are the index's of the game board
    int x;
    int y;

    // while loop keeps going until someone gets 3 in a row
    while (foundWinner == 0){

        // generates a random index in the 2D array
        x = r.nextInt(3);
        y = r.nextInt(3);

        if (tBoard[x][y] == 0){

            // set the next player
            if (player == 1){
                player = 2;

                // places the x or o at the random location
                tBoard[x][y] = player;

            }else if (player == 2){
                player = 1;

                // places the x or o at the random location on the board
                // that does not already have an X or O in it.
                tBoard[x][y] = player;
            }  
        }  
        // checks to see if either player got 3 in a row.
        this.checkForWinner();

    }
}

Here is the updated method to determine if there is a winner or a tie

// checks through all possible wins and gets which letter has matched 3 in a row
// first then displays the winner
public void checkForWinner(){

    // an int to make sure all three spaces match
    int match = 0;

    // these if statements represent every possible way to win 3 in a row.
    if (tBoard[0][0] == tBoard[0][1] && tBoard[0][1] == tBoard[0][2]){

        // if the int = 3 that means there were 3 ones if it's 6 it means there were 3 2s
        match = (tBoard[0][0] + tBoard[0][1] + tBoard[0][2]);
    }
    if (tBoard[1][0] == tBoard[1][1] && tBoard[1][1] == tBoard[1][2]){
        match = (tBoard[1][0] + tBoard[1][1] + tBoard[1][2]);
    }
    if (tBoard[2][0] == tBoard[2][1] && tBoard[2][1] == tBoard[2][2]){
        match = (tBoard[2][0] + tBoard[2][1] + tBoard[2][2]);
    }
    if (tBoard[0][0] == tBoard[1][0] && tBoard[1][0] == tBoard[2][0]){
        match = (tBoard[0][0] + tBoard[1][0] + tBoard[2][0]);
    }
    if (tBoard[0][1] == tBoard[1][1] && tBoard[1][1] == tBoard[2][1]){
        match = (tBoard[0][1] + tBoard[1][1] + tBoard[2][1]);
    }
    if (tBoard[0][2] == tBoard[1][2] && tBoard[1][2] == tBoard[2][2]){
        match = (tBoard[0][2] + tBoard[1][2] + tBoard[2][2]);
    }  
    if (tBoard[0][0] == tBoard[1][1] && tBoard[1][1] == tBoard[2][2]){
        match = (tBoard[0][0] + tBoard[1][1] + tBoard[2][2]);
    }
    if (tBoard[0][2] == tBoard[1][1] && tBoard[1][1] == tBoard[2][0]){
        match = (tBoard[0][2] + tBoard[1][1] + tBoard[2][0]);
    }

    // if there are no more empty spaces on the board (no more 0's in the array)
    // and there has not been a winner yet, this declares it a cats game.
    if (!Arrays.deepToString(tBoard).contains("0")){
        match = 9;
    }

    // this prints out the 2D array to view locations of 1s and 2s in the array.
    System.out.println(Arrays.deepToString(tBoard));

    // this switch statement has 3 possible end game cases. if match is equal
    // to 3 then there are 3 ones in a row and O wins.
    // if match is equal to 6 that means there were 3 twos in a row and the
    // winner is X.
    // if all of the spaces are full and there is not three in a row then it's
    // a tie or "cat's game"
    // default means there are still spaces left and no winner so the while loop
    // in the game method will continue looping.
    switch (match){
        case 3: winMessage = "O wins the game.";
                foundWinner = 1;
        break;
        case 6: winMessage = "X wins the game.";
                foundWinner = 1;
        break;
        case 9: winMessage = "Cats game";
                foundWinner = 1;         
        break;
        default: foundWinner = 0;
        break;

        } 
}

OUTPUT:

https://imgur.com/a/mC13R
https://imgur.com/a/ZiIsT

\$\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.