본문 바로가기

🧑🏻‍💻 Dev/알고리즘

[프로그래머스] 혼자서 하는 틱택토 Java

https://school.programmers.co.kr/learn/courses/30/lessons/160585

 

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

이번 문제는 게임 과정에서 생길 수 있는 모든 반례를 찾는 문제로 판단된다.

 

문제를 간단하게 한번 분석해 보면 머쓱이가 혼자서 O와 X로 빙고 게임을 진행한다. 진행 중인 게임 상황이 입력으로 주어졌을 때, 그 게임이 정상적으로 나올 수 있는 상황이라면 1을 반환, 아니면 0을 반환하면 된다.

 

즉, 빙고 게임에서 나올 수 있는 모든 반례를 찾아서 해결해야 된다는 의미이다.

 

문제 분석

1. 후공(X)이 선공(O) 보다 먼저 나온 경우

이 경우는 간단하게 해결할 수 있다. 후공이 만약 선공보다 많이 나왔다면 X의 개수가 O의 개수보다 많을 것이다. 

if (xCount > oCount) {
    return 0;
}

 

 

2. 게임 순서가 선공(O), 후공(X) 한 번씩 번갈아서 진행되지 않은 경우

말이 조금 어려울 수 있는데, 선공 -> 후공 -> 선공 -> 후공 이렇게 되어야 하는데, 선공 -> 선공 -> 후공 -> 선공 이런 식으로 되었을 때를 처리해줘야 한다. 

 

후공(X)이 여러번 나왔을 경우는 1번 반례에서 해결이 되기 때문에 선공(O)이 여러 번 나왔을 경우만 신경 써주면 된다. 순서대로 게임이 진행되었다면 선공(O) 개수에서 후공(X) 개수를 뺐을 때 1 이하의 수가 나와야 한다. 

if (oCount - xCount > 1) {
    return 0;
}

 

 

3. 빙고를 다 맞춰 게임이 종료되었을 때의 조건

3.1 선공(O)이 승리한 경우

선공이 승리했을 경우를 보면 항상 O의 개수가 X의 개수보다 많아야 한다. 이 반대는 모두 불가능한 경우의 수이다.

if (O가 승리했을 때) {
    if (oCount <= xCount) {
        return 0;
    }
}

 

3.2 후공(X)이 승리한 경우

후공이 승리했을 경우를 보면 항상 O의 개수와 X의 개수가 동일하다. 개수가 다르다면 모두 불가능한 경우의 수이다.

if (X가 승리했을 때) {
    if (oCount != xCount) {
        return 0;
    }
}

 

3.3 둘 다 승리한 경우

이 경우를 가장 먼저 넣어줘야 한다. O도 승리하고, X도 승리했을 경우이다. 말이 안 되기 때문에 바로 0을 리턴해주자.

if (둘 다 승리했을 때) {
    return 0;
}

 

 

처음에 풀었던 풀이

class Solution {
    public int solution(String[] board) {
        int oCnt = 0;
        int xCnt = 0;

        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                if (board[i].charAt(j) == 'O') {
                    oCnt++;
                } else if (board[i].charAt(j) == 'X') {
                    xCnt++;
                }
            }
        }

        // 후공이 선공보다 먼저 나온경우
        if (xCnt > oCnt) {
            return 0;
        }

        // 순서대로 하나씩 증가하지 않은 경우
        if (oCnt - xCnt > 1) {
            return 0;
        }

        // 게임이 끝났는데 계속 진행된 경우
        String winner = getWinner(board);
        if (winner.equals("Error")) {
            return 0;
        }
        if (winner.equals("oWin")) {
            if (oCnt <= xCnt) {
                return 0;
            }
        }
        if (winner.equals("xWin")) {
            if (oCnt != xCnt) {
                return 0;
            }
        }

        return 1;
    }

    public String getWinner(String[] board) {
        boolean oWin = false;
        boolean xWin = false;
        for (int i = 0; i < 3; i++) {
            // 가로로 일치하는 경우
            if (board[i].equals("OOO")) {
                oWin = true;
            }
            // 세로로 일치하는 경우
            if (board[0].charAt(i) == 'O'
                && board[0].charAt(i) == board[1].charAt(i)
                && board[1].charAt(i) == board[2].charAt(i)) {
                oWin = true;
            }
        }

        if (board[0].charAt(0) == 'O' 
            && board[0].charAt(0) == board[1].charAt(1)
            && board[1].charAt(1) == board[2].charAt(2)) {
            oWin = true;
        }

        if (board[0].charAt(2) == 'O'
            && board[0].charAt(2) == board[1].charAt(1)
            && board[1].charAt(1) == board[2].charAt(0)) {
            oWin = true;
        }

        for (int i = 0; i < 3; i++) {
            // 가로로 일치하는 경우
            if (board[i].equals("XXX")) {
                xWin = true;
            }
            // 세로로 일치하는 경우
            if (board[0].charAt(i) == 'X'
                && board[0].charAt(i) == board[1].charAt(i)
                && board[1].charAt(i) == board[2].charAt(i)) {
                xWin = true;
            }
        }

        if (board[0].charAt(0) == 'X' 
            && board[0].charAt(0) == board[1].charAt(1)
            && board[1].charAt(1) == board[2].charAt(2)) {
            xWin = true;
        }

        if (board[0].charAt(2) == 'X'
            && board[0].charAt(2) == board[1].charAt(1)
            && board[1].charAt(1) == board[2].charAt(0)) {
            xWin = true;
        }

        if (oWin && xWin) {
            return "Error";
        } else if (oWin) {
            return "oWin";
        } else if (xWin) {
            return "xWin";
        }
        return "noWin";
    }
}

getWinner()라는 메소드를 만들어서 둘 다 승리했다면 "Error"를 반환하고, 선공이 승리했다면 "oWin"을 반환하고, 후공이 승리했다면 "xWin"을 반환하도록 했다. 

 

문제는 모두 통과되었지만 이렇게 작성했을 때 딱 눈에 보기에도 반복되는 코드가 너무 많고, 가독성이 떨어졌던 문제가 있었다. 

 

 

 

문제점 개선 후 리펙토링한 코드

class Solution {
    private final char O = 'O';
    private final char X = 'X';
    public int solution(String[] board) {
        int oCnt = 0;
        int xCnt = 0;
        
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                if (board[i].charAt(j) == O) {
                    oCnt++;
                } else if (board[i].charAt(j) == X) {
                    xCnt++;
                }
            }
        }
        
        // 후공이 선공보다 먼저 나온경우
        if (xCnt > oCnt) {
            return 0;
        }
        
        // 순서대로 하나씩 증가하지 않은 경우
        if (oCnt - xCnt > 1) {
            return 0;
        }
        
        // 게임이 끝났는데 계속 진행된 경우
        boolean oWin = getWinner(board, O);
        boolean xWin = getWinner(board, X);
        if (oWin && xWin) {
            return 0;
        }
        if (oWin && oCnt <= xCnt) {
            return 0;
        }
        if (xWin && oCnt != xCnt) {
            return 0;
        }
        
        return 1;
    }
    
    public boolean getWinner(String[] board, char value) {
        for (int i = 0; i < 3; i++) {
            // 가로로 일치하는 경우
            if (board[i].charAt(0) == value 
                && board[i].charAt(0) == board[i].charAt(1) 
                && board[i].charAt(1) == board[i].charAt(2)) {
                return true;
            }
            // 세로로 일치하는 경우
            if (board[0].charAt(i) == value
                && board[0].charAt(i) == board[1].charAt(i)
                && board[1].charAt(i) == board[2].charAt(i)) {
                return true;
            }
        }
        // 대각선 일치
        if (board[0].charAt(0) == value
            && board[0].charAt(0) == board[1].charAt(1)
            && board[1].charAt(1) == board[2].charAt(2)) {
            return true;
        }
        
        if (board[0].charAt(2) == value
            && board[0].charAt(2) == board[1].charAt(1)
            && board[1].charAt(1) == board[2].charAt(0)) {
            return true;
        }
        return false;
    }
}

1. 자주 사용했던 'O'와 'X'는 상수로서 처리해 줬다. 훨씬 가독성이 증가한 것을 느낄 수 있었다. 

2. boolean 타입을 반환하는 메서드로 변경하여 선공, 후공 승리를 나누었다. 이렇게 해서 코드의 반복을 줄일 수 있었다.

 

한눈에 보기에도 깔끔해진 코드로 작성할 수 있었다.