[Kotlin] TicTacToe 게임 구현하기

leeeha·2022년 8월 2일
1

코틀린

목록 보기
16/29
post-thumbnail

출처: 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
    }

실행 결과

profile
습관이 될 때까지 📝

0개의 댓글