https://school.programmers.co.kr/learn/courses/30/lessons/160585
틱택토는 두 사람이 하는 게임으로 처음에 3x3의 빈칸으로 이루어진 게임판에 선공이 "O", 후공이 "X"를 번갈아가면서 빈칸에 표시하는 게임입니다. 가로, 세로, 대각선으로 3개가 같은 표시가 만들어지면 같은 표시를 만든 사람이 승리하고 게임이 종료되며 9칸이 모두 차서 더 이상 표시를 할 수 없는 경우에는 무승부로 게임이 종료됩니다.
할 일이 없어 한가한 머쓱이는 두 사람이 하는 게임인 틱택토를 다음과 같이 혼자서 하려고 합니다.
틱택토는 단순한 규칙으로 게임이 금방 끝나기에 머쓱이는 한 게임이 종료되면 다시 3x3 빈칸을 그린 뒤 다시 게임을 반복했습니다. 그렇게 틱택토 수 십 판을 했더니 머쓱이는 게임 도중에 다음과 같이 규칙을 어기는 실수를 했을 수도 있습니다.
게임 도중 게임판을 본 어느 순간 머쓱이는 본인이 실수를 했는지 의문이 생겼습니다. 혼자서 틱택토를 했기에 게임하는 과정을 지켜본 사람이 없어 이를 알 수는 없습니다. 그러나 게임판만 봤을 때 실제로 틱택토 규칙을 지켜서 진행했을 때 나올 수 있는 상황인지는 판단할 수 있을 것 같고 문제가 없다면 게임을 이어서 하려고 합니다.
머쓱이가 혼자서 게임을 진행하다 의문이 생긴 틱택토 게임판의 정보를 담고 있는 문자열 배열 board가 매개변수로 주어질 때, 이 게임판이 규칙을 지켜서 틱택토를 진행했을 때 나올 수 있는 게임 상황이면 1을 아니라면 0을 return 하는 solution 함수를 작성해 주세요.
board | result |
---|---|
["O.X", ".O.", "..X"] | 1 |
["OOO", "...", "XXX"] | 0 |
["...", ".X.", "..."] | 0 |
["...", "...", "..."] | 1 |
우선 일반적인 경우에 O
와 X
는 다음 식을 만족해야한다.
0 ≤
O
-X
< 2
턴 마다 돌아가면서 수를 두고, O
가 선수이기 때문이다.
그리고 게임이 끝이 났는지 확인을 해야한다. 게임이 끝난 것은 가로, 세로 혹은 대각선에 모두 같은 말이 있으면 끝난 것이다. 만약 경기가 끝났다면, 더 이상 수를 둘 수 없다. 이 과정에서 총 4가지의 경우가 나올 수 있다. 우선 O
, X
모두 경기를 끝낸 경우이다. 이 경우는 한 쪽이 승리를 했는데도 다른 쪽이 수를 둔 것이므로 나올 수 없는 상황이다. 다음은 O
혹은 X
둘 중 한 쪽만 경기가 끝난 경우이다. O
가 이긴 경우, X
가 둔 총 수보다 O
가 둔 총 수가 1 만큼 많아야 한다. X
가 이긴 경우, 두 수가 같아야한다. 마지막으로 O
, X
모두 경기를 끝내지 못한 경우, 첫 번째 식만 만족한다면, 가능한 경우이다.
자바로 이 문제를 다시 풀 때, 52~54번 케이스에서 통과하지 못했었는데, 그 이유는 해당링크 에서 찾을 수 있었다. 앞에서부터 차례대로 확인을 하고, 한 쪽이 승리한 경우, 바로 위의 경우대로 판단을 해버리고 결과를 리턴했기 때문이었다. 따라서 모든 라인을 검토한 뒤, 마지막에 판단을 하는 식으로 수정해주었다.
import java.util.*;
public class TicTacToeAlone {
private Set<String> winners;
public int solution(String[] board) {
winners = new HashSet<>();
return isPossible(board) ? 1 : 0;
}
private boolean isPossible(String[] board) {
List<String> lines = new ArrayList<>(Arrays.asList(board));
for (int i = 0; i < board.length; i++) {
StringBuilder temp = new StringBuilder();
for (String s : board) {
temp.append(s.charAt(i));
}
lines.add(temp.toString());
}
StringBuilder temp = new StringBuilder();
for (int i = 0; i < board.length; i++) {
temp.append(board[i].charAt(i));
}
lines.add(temp.toString());
temp = new StringBuilder();
for (int i = 0; i < board.length; i++) {
temp.append(board[i].charAt(2 - i));
}
lines.add(temp.toString());
int diff = countOX(board, 'O') - countOX(board, 'X');
if (diff < 0 || diff >= 2) return false;
for (String line : lines) {
checkWinner(line);
}
if (winners.size() >= 2) {
return false;
} else if (winners.contains("O")) {
return diff == 1;
} else if (winners.contains("X")) {
return diff == 0;
} else {
return true;
}
}
private void checkWinner(String line) {
char[] lts = line.toCharArray();
char f = lts[0];
if (f == '.') return;
if (f == lts[1] && f == lts[2]) {
winners.add("" + f);
}
}
private int countOX(String[] board, char OX) {
int o = 0;
for (String line : board) {
for (char c : line.toCharArray()) {
if (c == OX) o++;
}
}
return o;
}
}
def solution(board):
wrong, o, x = is_wrong_turn(board)
if wrong:
return 0
return is_end_condition(board, o, x)
def is_end_condition(board, o, x):
line_list = [list(c) for c in board]
winner_set = set()
for j in range(len(board)):
temp = []
for i in range(len(board)):
temp.append(board[i][j])
line_list.append(temp)
line_list.append([board[i][i] for i in range(len(board))])
line_list.append([board[i][2 - i] for i in range(len(board))])
for line in line_list:
if end_condition(line):
winner_set.add(line[0])
if len(winner_set) == 2:
return 0
elif "O" in winner_set:
return 1 if o - x == 1 else 0
elif "X" in winner_set:
return 1 if o == x else 0
return 1
def end_condition(line):
return line[0] == line[1] == line[2] and (line[0] != '.' and line[1] != '.' and line[2] != '.')
def is_wrong_turn(board):
o, x = 0, 0
for col in board:
for row in col:
if row == "O":
o += 1
elif row == "X":
x += 1
return not (0 <= o - x < 2), o, x