0
\$\begingroup\$

This is the TicTacToe.java program that I've coded. How can I make this code more like a production type of code (except for Lack of Comments Part)?

Requirements: Design the basic Tic Tac Toe game

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

interface MoveMethod
{
    public int move();
}

class SimpleMoveStrategy implements MoveMethod
{
    private Solution game;
    public SimpleMoveStrategy(Solution t) {
        game = t ;
    }

    public int move() {

        for( int i = 0 ; i < Solution.N ; i ++ )
        {
            for( int j = 0 ; j < Solution.N ; j ++ )
            {
                if( game.board[i][j] == 0 )
                    return (i*3+j+1) ;
            }
        }
        return 0 ;
    }
}

class HumanMove implements MoveMethod
{
    private Solution game;
    public HumanMove(Solution t) {
        game = t ;
    }
    public int move() {

        String move_str ;
        int move_int = 0 ;
        boolean valid_input = false ;
        while(!valid_input) {
            System.out.print("Where to ? ");
            move_str = Solution.getUserInput() ;
            if( Character.isDigit(move_str.toCharArray()[0]) ) {
                move_int = Integer.parseInt(move_str);
                if( ( move_int <= (Solution.N)*(Solution.N) ) && move_int >= 1 ) {
                    valid_input = true ;
                    break ;
                }
            }

            if( !valid_input ) {
                System.out.println("Invalid input");
                continue ;
            }
        }
        return move_int ;
    }

}

class Solution
{
    protected static final int N = 3 ;
    private static final int HSPACE = 20 ;
    protected int[][] board;
    private static BufferedReader reader =
            new BufferedReader(new InputStreamReader(System.in)) ;

    class Player
    {
        private String name ;
        private int player_type ;
        private MoveMethod move_strategy ;

        public Player(String pname, int type, int order, MoveMethod move_s )
        {
            name = pname ;
            player_type = type ;
            move_strategy = move_s ;
        }

        public String getName() {
            return name ;
        }

        public int getPlayerType() {
            return player_type ;
        }

        public int getMove() {
            return move_strategy.move();
        }
    }

    private Player player1,player2;

    public Player getplayer1() {
        return player1 ;
    }

    public Player getplayer2() {
        return player2 ;
    }

    public static String getPosDescription(int pos) {
        String str = "";
        if( pos == 5 ) {
            str = "center" ;
            return str ;
        }

        if( (pos-1)/3 == 0 ) {
            str += "upper " ;
        }
        else if( (pos-1)/3 == 1 ) {
            str += "middle " ;
        }
        else
            str += "lower " ;

        if( (pos-1) % 3 == 0 )
            str += "left" ;
        else if( (pos-1)%3 == 1 )
            str += "middle" ;
        else
            str += "right" ;

        return str ;
    }

    protected static String getUserInput() {
        String input = "" ;
        try {
            input = reader.readLine();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return input ;
    }

    public Solution()
    {
        board = new int[N][N];
        for( int i = 0 ; i < N ; i ++ ) {
            for( int j = 0 ; j < N ; j ++ ) {
                board[i][j] = 0 ;
            }
        }

        System.out.println("Enter player name");
        player1 = new Player(getUserInput(),2,0,new HumanMove(this));

        player2 = new Player("",1,1,new SimpleMoveStrategy(this));
        System.out.println("\nHuman player " + player1.getName() + " vs Computer Player " + player2.getName() + ":" ) ;
    }

    public boolean setMove(int move, int p_type)
    {
        int x_coord = (move-1)/3;
        int y_coord = (move-1)%3;

        if( board[x_coord][y_coord] == 0 )
        {
            board[x_coord][y_coord] = p_type ;
            return true ;
        }
        else
        {
            System.out.println("Invalid move");
            return false ;
        }
    }

    private enum WinConfig {
        DRAW, WIN, NONE
    }

    private WinConfig isWinningConfig()
    {
        WinConfig w = WinConfig.WIN ;
        // rows
        for( int i = 0 ; i < N ; i ++ )
        {
            if( (board[i][0] != 0) && (board[i][0] == board[i][1]) && (board[i][0] == board[i][2] ) )
            {
                return w ;
            }
        }
        // columns
        for( int i = 0 ; i < N ; i ++ )
        {
            if( (board[0][i] != 0) && (board[0][i] == board[1][i]) && (board[0][i] == board[2][i] ) )
            {
                return w ;
            }
        }
        // diags
        if( (board[0][0] != 0) && (board[0][0] == board[1][1]) && (board[0][0] == board[2][2] ) )
        {
            return w ;
        }

        if( (board[2][0] != 0) && (board[2][0] == board[1][1]) && (board[2][0] == board[0][2] ) )
        {
            return w ;
        }

        // draw
        w = WinConfig.DRAW ;
        for( int i = 0 ; i < N ; i ++ )
            for( int j = 0 ; j < N ; j ++ )
            {
                if( board[i][j] == 0 )
                    w = WinConfig.NONE ;
            }
        return w ;

    }

    private String getRowString(int row)
    {
        String s = "" ;
        for( int i = 0 ; i < N ; i ++ )
        {
            switch(board[row][i]) {
            case 0: s += " " ;
            break ;
            case 1: s += "O" ;
            break ;
            case 2: s += "X" ;
            }

            if( i != N-1 )
            {
                s += " | " ;
            }
        }

        s += String.format("%" + HSPACE + "s", "");

        for( int i = 0 ; i < N ; i ++ )
        {
            s += row*3+i+1 ;

            if( i == N-1 ) {
                s += "\n";
            }
            else {
                s += " | " ;
            }
        }
        return s;
    }

    public String toString()
    {
        String s = "";
        // iterate through the rows
        for( int i = 0 ; i < N ; i ++ ) {
            s += getRowString(i);
        }
        return s;
    }

    public static void main( String[] args )
    {
        System.out.println("Welcome to Tic-Tac-Toe.");
        System.out.println("");

        Solution game = new Solution();
        //        String move_str ;
        int move1 = 0 ;
        int move2 = 0 ;
        //        int player_type = 0s ;
        WinConfig w = WinConfig.NONE ;

        System.out.println("Please make your move selection by entering a number 1-9 corresponding to the movement key on the right.\n");
        System.out.println(game.toString()) ;

        while( game.isWinningConfig() == WinConfig.NONE  )
        {
            do {
                move1 = game.getplayer1().getMove();
            } while(!game.setMove(move1,game.getplayer1().getPlayerType() ));


            if( ( w = game.isWinningConfig() ) == WinConfig.WIN ) {
                System.out.println("");
                System.out.println(game.toString() );
                System.out.println("You have beaten my poor AI!");
                break ;
            }
            else if( w == WinConfig.DRAW ) {
                System.out.println("");
                System.out.println(game.toString()) ;
                System.out.println("Well played. It is a draw!");
                break ;
            }

            move2 = game.getplayer2().getMove();
            System.out.println("");
            System.out.println("You have put an X in the " + Solution.getPosDescription(move1) + ". I will put a O in the " + Solution.getPosDescription(move2) + "." ) ;
            game.setMove(move2, game.getplayer2().getPlayerType() ) ;

            if( game.isWinningConfig() == WinConfig.WIN ) {
                System.out.println("");
                System.out.println(game.toString() );
                System.out.println("I won. Thanks for playing.") ;
                break ;
            }
            System.out.println(game.toString());
        }

    }
}
\$\endgroup\$
1
  • 1
    \$\begingroup\$ I'm voting to close this question as off-topic because it appears to be a live interview question. \$\endgroup\$ – janos Oct 10 '16 at 19:38
2
\$\begingroup\$

Well,

First of all, your game logic is into a your main method. You should better move it into the Solution class. But of course this is not simple as moving a method.

Maybe should you think about encapsulation.

The simplest thing that you can do is extract all System.out and System.in usages into another class, let's say UserInterface so that you can move away the concerns about validating the user inputs and formatting (have a look at https://docs.oracle.com/javase/7/docs/api/java/util/Scanner.html)

Another quick win would be to move your int[][] board into his own class. So that you can clarify your code by replacing all the board[x][y]==0 by board.isFreeAt(x, y). You can also create three methods to verify if there is a complete row/column/diagonal, make them private and hide them behind a hasCompleteLine():boolean method in that new Board class. And replace the int that is used to know if the cell is empty or X or O by a char where \0 means empty or by an enum. This will clear your methods and make them more readable.

Remove the unused parameters from Player and rename all the snake_case_names by a camelCaseName in all classes to be more compliant.

That will at least give you more clarity and the encapsulation let you test your classes individually. You can also delegate the creation to your main method :

UserInterface ui = new UserInterface(System.in, System.out);
TicTacToe game = new TicTacToe(ui);
game.start();

This pattern (Create Ui, Create control, bind them together) looks like the ModelViewPresenter pattern that help you in decoupling the application logic and controls from the UI.

\$\endgroup\$
0
\$\begingroup\$

I would like to add more to the existing answer from "Anonymous". Since you said you wanted to make your code "more like a production type of code" I thought it would be good opportunity to point out that any production code would undergo rigorous testing and the first line of testing is typically automated unit tests. In Java that generally means JUnits.

I'd like to therefore suggest that it would be a good idea to make your input logic more generic. Rather than always reading from System.in you could read from an InputStream. If you do this and at the same time create a setter to set which InputStream you're reading from it will be easier to create automated tests (such as JUnit tests) because you can inject any InputStream. So, for example you could create an InputStream from a file and call your setter in your test class so that your code would read from that stream thereby simulating user input.

Below is a quick example of how this could work. I have written static methods but they do not need to be static necessarily. It was just quicker for me to write them that way.

This is a class to obtain user input:

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

public class ClassToTest {

    private static InputStream inputStream;

    public static void main(String[] args) {
        setInputStream(System.in);
        getInput();
    }

    public static void setInputStream(InputStream inStream){
        inputStream = inStream;
    }

    public static String getInput(){
        Scanner scan = new Scanner(inputStream);
        String next = scan.nextLine();
        scan.close();
        return next;
    }
}

Here is the JUnit (v4) Test:

import static org.junit.Assert.*;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

import org.junit.Test;

public class ClassTest{

    @Test
    public void test() {
        try {
            ClassToTest.setInputStream(Files.newInputStream(Paths.get("./src/inputStreamJunits/test1.txt"), StandardOpenOption.READ));
            String data = ClassToTest.getInput();
            assertEquals("This is a test!", data);
        } catch (IOException e) {
            e.printStackTrace();
            fail(e.toString());
        }
    }

}

This is the content of the test1.txt file:

This is a test!
\$\endgroup\$

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