Search code examples
javatic-tac-toe

How can I simplify my Java method or change it to be DRY


The below code works as expected, however I would like to simplify the method as it is currently too repetitive. My hopes in simplifying the code would be to develop a means to enhance the AI. I would have the AI check for patterns on the board and have it act accordingly.

public boolean checkWinner(){
        //CHECK EVERY POSSIBLE WINNING OUTCOME and return true if you find one
        //don't forget to check if the board is full (return true in that case)
            if(gameboard[0] == gameboard[1] && gameboard[1] == gameboard[2] ||
               gameboard[3] == gameboard[4] && gameboard[4] == gameboard[5] ||
               gameboard[6] == gameboard[7] && gameboard[7] == gameboard[8] ||
               gameboard[0] == gameboard[3] && gameboard[3] == gameboard[6] ||
               gameboard[1] == gameboard[4] && gameboard[4] == gameboard[7] ||
               gameboard[2] == gameboard[5] && gameboard[5] == gameboard[8] ||
               gameboard[0] == gameboard[4] && gameboard[4] == gameboard[8] ||
               gameboard[6] == gameboard[4] && gameboard[4] == gameboard[2] ){
                bigWinner();
                return true;
            }
            //loop through and look for tie
            for(int x = 0; x< gameboard.length; x++){
                if(gameboard[x] != 'O' && gameboard[x] != 'X'){
                    return false;
                }
            }
        System.out.println("Tie Game.");
        return true;
    }

The full code of the TicTacToe class is as follows. The Main class of the game displays a game menu and creates a new TicTacToe game.

package pkg2baresb;
import java.util.Scanner;
import java.util.concurrent.ThreadLocalRandom;


/**
 * This is the game logic for TicTacToe
 * @author baresb
 */
public class TicTacToe {
    //ENCAPUSLATED game variables. Super secret!
    private char[] gameboard;
    private boolean myTurn = true;
    
    /**
     * The TicTacToe's constructor
     */
    public TicTacToe(){
        //instantiate the previous declared value
        gameboard = new char[9];
        final int RADIX = 10;
        //loop through game array and start each spot with a dash
        for(int x = 0; x < gameboard.length; x++){
            gameboard[x] = Character.forDigit(x, RADIX);
         
        }
        System.out.println("TICTACTOE ONLINE.");
        
        //Loop until there's a winner
        while(!checkWinner()){
            printBoard();
            yourTurn();
        }
        printBoard();
        if(!checkWinner()) yourTurn();
        else{
            System.out.println("Game over.");
        }
    }
    
    /**
     * 
     * @return 
     */
    public boolean checkWinner(){
        //CHECK EVERY POSSIBLE WINNING OUTCOME and return true if you find one
        //don't forget to check if the board is full (return true in that case)
            if(gameboard[0] == gameboard[1] && gameboard[1] == gameboard[2] ||
               gameboard[3] == gameboard[4] && gameboard[4] == gameboard[5] ||
               gameboard[6] == gameboard[7] && gameboard[7] == gameboard[8] ||
               gameboard[0] == gameboard[3] && gameboard[3] == gameboard[6] ||
               gameboard[1] == gameboard[4] && gameboard[4] == gameboard[7] ||
               gameboard[2] == gameboard[5] && gameboard[5] == gameboard[8] ||
               gameboard[0] == gameboard[4] && gameboard[4] == gameboard[8] ||
               gameboard[6] == gameboard[4] && gameboard[4] == gameboard[2] ){
                bigWinner();
                return true;
            }
            //loop through and look for tie
            for(int x = 0; x< gameboard.length; x++){
                if(gameboard[x] != 'O' && gameboard[x] != 'X'){
                    return false;
                }
            }
        System.out.println("Tie Game.");
        return true;
    }
    
    /**
     * Use a scanner and take an input from the user
     */
    public void yourTurn(){
        //instantiate a Scanner
        Scanner s = new Scanner(System.in);
        //take an input on the position the player would like
        int selection;
        while(true){
            try{
                System.out.print("What position would you like to play: ");
                selection = s.nextInt();
                if(gameboard[selection] != 'X' && gameboard[selection] != 'O'){
                    gameboard[selection] = 'X';
                    break;
                }else{
                    System.out.println("That spot has already been taken");
                }
                
            }catch(Exception e){
                System.out.println("Invalid selection. Please try again.");
            }  
        }
        if(!checkWinner()) {
            this.myTurn = !myTurn;
            //hands things over to the computer
            computersTurn();
        }
    }
    
    /**
     * Announce who won using the myTurn boolean to remember whose turn it was
     * last
     */
    public void bigWinner(){
        if(myTurn){
            System.out.println("You won! Nice job!");
        }else{
            System.out.println("You have lost the game.");
        }
    }
    /**
     * Computer selects a random open slot and play its 'O'
     */
    public void computersTurn(){
        //OPTIONAL: MAKE THIS CODE SMARTER
        System.out.println("Now it's the computer player's turn");
        while(true){
            //generate a random number between 0 - 8
            int choice = ThreadLocalRandom.current().nextInt(0, 9);
            
            //is the space free?
            if(gameboard[choice] != 'X' && gameboard[choice] != 'O') {
                //if so, set it equal to an 'O'
                gameboard[choice] = 'O';
                //bust out of this while loop
                this.myTurn = !myTurn;
                break;
            }
            
        } //closes while loop   
    } //closes computersTurn
    
    public void printBoard(){
        for(int x = 1; x <= gameboard.length; x++){
            System.out.print(" | " + gameboard[x-1]);
            if(x % 3 == 0) System.out.println(" |");
        }
    }
}


Solution

  • To make your code less repetitive. You could create a method that takes a game board and a matrix as a parameter:

    public boolean checkWin(int[] gameBoard, int[][] winPossibilities) {
    
    }
    

    winPossibilities is all the rows/columns/diagonals that you want to check. In your case, it would be

    final int[][] possibilities = {
        {0, 1, 2},
        {3, 4, 5},
        {6, 7, 8},
        {0, 3, 6},
        {1, 4, 7},
        {2, 5, 8},
        {0, 4, 8},
        {6, 4, 2},
    };
    

    In the body of the method, you just need a simple algorithm like this:

    for (int[] indices: winPossibilities) {
        if (gameBoard[i[0]] == gameBoard[i[1]] && gameBoard[i[1]] == gameBoard[i[2]]) {
            return true;
        }
    }
    return false;
    

    You can call this method like this:

    if(checkWin(gameboard, possibilities)){
            bigWinner();
            return true;
    }
    //loop through and look for tie
    for(int x = 0; x< gameboard.length; x++){
        if(gameboard[x] != 'O' && gameboard[x] != 'X'){
            return false;
        }
    }
    System.out.println("Tie Game.");
    return true;
    

    One of the benefits that this provides is more flexibility. If you want to change the rules of tic tac toe and decides that you can't win by making a diagonal, just remove the last two items of the possibilities matrix!