출처: https://www.boostcourse.org/mo132/lecture/83803?isDesc=false
앞서 배운 코틀린 문법을 복습할 겸, TicTacToe 미니 프로젝트를 풀어보았다. 다른 부분은 다 풀었는데, 가로, 세로, 대각선 방향에 모두 같은 말을 놓는 승리 조건 검사를 어떻게 구현해야 할지 모르겠어서 여러 풀이를 참고하였다.
var x: Int = 0
var y: Int = 0
fun main() {
var board = //___(1) 3 x 3의 Array 배열 정의___
initBoard(board)
val names = arrayOf("Player 1", "Player 2")
val marks = arrayOf('O', 'X')
play(board, names, marks)
}
// 격자판을 공백으로 초기화
fun initBoard(board: Array<CharArray>) {
//___(2) 반복문을 이용해 3 x 3 배열을 공백(' ')으로 채우기___
}
fun printBoard(board: Array<CharArray>) {
// 가로 줄번호
print(" ")
for (x in 0..2) print("$x ")
println()
// 세로 줄번호 및 플레이어 표기
for (y in 0..2) {
print("$y ")
for (x in 0..2) {
print("${board[y][x]}")
if (x != 2) print("|")
}
println()
// 세로 격자
if (y != 2) {
print(" ")
for (x in 0..2) {
print("-")
if (x != 2) print("+")
}
println()
}
}
}
// 격자 범위에 있는지 검사
val isInRange = //___(3) x와 y가 격자 범위에 있는지 판별 (람다식 이용)___
// 격자의 칸이 빈 곳인지 검사
fun isValid(board: Array<CharArray>, x: Int, y: Int): Boolean {
return isInRange(x, y) && board[y][x] == ' '
}
fun playerInput(name: String, board: Array<CharArray>): Boolean {
print("$name 입력(줄,칸): ")
val input: String? = readLine()
//___(4) 입력 받은 값을 split( )함수를 이용해 x와 y로 분리 저장___
y = //__________
x = //__________
if (!isValid(board, x, y)) return false
return true
}
// 해당 칸이 승리할 수 있는지 검사
fun isWin(board: Array<CharArray>, x: Int, y: Int): Boolean {
// 가로, 세로, 대각선 방향에 대한 x, y 변화량
val dx = arrayOf(-1, 1, 0, 0, -1, 1, 1, -1)
val dy = arrayOf(0, 0, -1, 1, -1, 1, -1, 1)
for (d in 0..3) {
var count = 1
for (b in 0..1) {
val index = 2 * d + b
var cx = x + dx[index]
var cy = y + dy[index]
while (isInRange(cx, cy)) {
if (board[cy][cx] == board[y][x]) count++
cx += dx[index]
cy += dy[index]
}
}
if (count == 3) return true
}
return false
}
// 플레이 진행하기
fun play(board: Array<CharArray>, name: Array<String>, marks: Array<Char>) {
var round = 0
var turn = 0
while (true) {
println("\n ${turn + 1}번째 턴\n")
printBoard(board)
if (!playerInput(name[turn], board))
continue
board[y][x] = marks[turn]
round++
// ___(5) 승리 혹은 무승부 조건을 검사, 이 두 조건은 while루프를 끝낸다___
turn = //___(6) turn이 0이며 1로, 1이면 0으로 바꿔서 플레이어 판단___
}
}
처음에 이 코드의 isWin 함수를 참고하여 코드를 짜봤는데 제대로 동작하지 않아서 다른 분의 풀이를 보았다,,
package mini_project
// 1번과 2번이 번갈아가면서 말을 놓는다.
// 가로, 세로, 대각선으로 같은 말을 먼저 놓으면 승리!
// 말이 이미 놓여있으면 새로 놓을 수 없음.
const val size = 3
data class Players(val name: String = "", val mark: Char = ' ')
// 보드판 초기화 함수
fun initBoard(): Array<CharArray> {
val board = Array(size) { CharArray(size) }
for (i in 0 until size)
for (j in 0 until size)
board[i][j] = ' '
return board
}
// 보드판 출력 함수
fun printBoard(board: Array<CharArray>) {
// 첫번째 줄
print(" ")
for (i in 0 until size) print(" $i")
println()
// 두번째 줄부터 보드판
for (i in 0 until size) {
print("$i ")
for (j in 0 until size) {
print(board[i][j])
if (j != size - 1) print('|')
else println()
}
if (i != size - 1) println(" -+-+-")
else println()
}
}
// 보드판 범위 내의 좌표인지 검사
fun isInRange(x: Int, y: Int) = x in 0..2 && y in 0..2
// 격자 칸이 비어있는지 검사
fun isValid(board: Array<CharArray>, x: Int, y: Int) = board[x][y] == ' '
fun playerInput(board: Array<CharArray>, player: Players): Boolean {
val x: Int
val y: Int
try {
print("${player.name} 입력(줄,칸): ")
val input = readLine()!!.split(',').map { it.toInt() }
x = input[0]
y = input[1]
} catch (e: Exception) {
println(e.message)
return false
}
// 유효한 값인 경우, 해당 위치에 말을 놓는다.
return if (isInRange(x, y) && isValid(board, x, y)) {
board[x][y] = player.mark
true
} else
false
}
fun isWin(board: Array<CharArray>, player1: Players, player2: Players): Boolean {
fun isWinner(player: Players): Boolean {
val mark = player.mark
// 가로, 세로 방향
for (i in 0..2) {
if (board[i][0] == mark && board[i][1] == mark && board[i][2] == mark) return true
if (board[0][i] == mark && board[1][i] == mark && board[2][i] == mark) return true
}
// 대각선 방향
if (board[0][0] == mark && board[1][1] == mark && board[2][2] == mark) return true
if (board[0][2] == mark && board[1][1] == mark && board[2][0] == mark) return true
return false
}
return when {
isWinner(player1) -> {
printBoard(board)
println("${player1.name} 승리!")
true
}
isWinner(player2) -> {
printBoard(board)
println("${player2.name} 승리!")
true
}
else -> {
false
}
}
}
fun play(board: Array<CharArray>, player1: Players, player2: Players) {
var turn = 1
while (turn < 10 && !isWin(board, player1, player2)) {
println("${turn}번째 턴")
printBoard(board)
val result = if(turn % 2 == 1)
playerInput(board, player1)
else
playerInput(board, player2)
if (result)
turn++
}
// 무승부인 경우
if (turn == 10) printBoard(board)
}
fun main() {
val board = initBoard()
val player1 = Players("Player1", 'O')
val player2 = Players("Player2", 'X')
play(board, player1, player2)
}
보드판 크기가 3밖에 되지 않기 때문에, 아래처럼 직관적으로 이해하기 쉬운 코드로 작성하는 것도 괜찮은 거 같다!
하지만, 보드판의 크기가 커지면 모범 답안 예시처럼 dx, dy 배열을 이용하여, 가로, 세로, 대각선 방향에 동일한 말이 놓여있는지 검사하는 방식으로 코드를 짜야 할 것이다.
fun isWinner(player: Players): Boolean {
val mark = player.mark
// 가로, 세로 방향
for (i in 0..2) {
if (board[i][0] == mark && board[i][1] == mark && board[i][2] == mark) return true
if (board[0][i] == mark && board[1][i] == mark && board[2][i] == mark) return true
}
// 대각선 방향
if (board[0][0] == mark && board[1][1] == mark && board[2][2] == mark) return true
if (board[0][2] == mark && board[1][1] == mark && board[2][0] == mark) return true
return false
}