diff --git a/README.md b/README.md index 08a8c8a..03b5b10 100644 --- a/README.md +++ b/README.md @@ -1 +1,21 @@ -# android-omok-precourse \ No newline at end of file +# android-omok-precourse + +## 프로젝트 설명 +이 프로젝트는 카카오테크캠퍼스 2기 2회차 미니과제로, Kotlin으로 작성한 오목 게임입니다. 두 사람이 번갈아 돌을 놓아 가로나 세로, 대각선으로 다섯 개의 연속된 돌을 먼저 만들면 승리하는 게임입니다. + +## 게임 규칙 +- 6목 이상의 장목도 착수 가능하며 승리 조건으로 인정합니다. +- 렌주 룰과 같은 복잡한 룰은 고려하지 않습니다. + +## 구현 기능 목록 +1. 보드 초기화 및 플레이어 초기화 +2. 각 셀에 클릭 리스너 설정 +3. 돌 위치 선정 + - 유효한 자리 -> 돌 위치 확정 + - 유효하지 않은 자리 -> 돌 위치 재선정 메시지 출력 +4. 우승 조건 확인 + - O -> 우승 메시지 출력 및 초기화 + - X -> 플레이어 변경 +5. 무승부 조건 확인 + - O -> 무승부 메시지 출력 및 초기화 + - X -> 플레이어 변경 diff --git a/app/src/main/java/nextstep/omok/MainActivity.kt b/app/src/main/java/nextstep/omok/MainActivity.kt index e6cc7b8..c442f5b 100644 --- a/app/src/main/java/nextstep/omok/MainActivity.kt +++ b/app/src/main/java/nextstep/omok/MainActivity.kt @@ -4,20 +4,72 @@ import android.os.Bundle import android.widget.ImageView import android.widget.TableLayout import android.widget.TableRow +import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.core.view.children class MainActivity : AppCompatActivity() { + + private val game = OmokGame() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + initializeBoard() + } + private fun initializeBoard() { val board = findViewById(R.id.board) - board - .children + board.children .filterIsInstance() - .flatMap { it.children } - .filterIsInstance() - .forEach { view -> view.setOnClickListener { view.setImageResource(R.drawable.black_stone) } } + .forEachIndexed { rowIndex, row -> + row.children + .filterIsInstance() + .forEachIndexed { colIndex, cell -> + cell.setImageResource(0) + cell.tag = null + cell.setOnClickListener { onCellClicked(rowIndex, colIndex, cell) } + } + } + } + + private fun onCellClicked(row: Int, col: Int, cell: ImageView) { + if (game.placeStone(row, col)) { + placeStone(cell, game.currentPlayer) + if (game.checkWin(row, col)) { + showWinMessage() + resetBoard() + } else if (game.isBoardFull()) { + showDrawMessage() + resetBoard() + } else { + game.togglePlayer() + } + } else { + showInvalidMoveMessage() + } + } + + private fun placeStone(cell: ImageView, player: Char) { + val resource = if (player == PLAYER_BLACK) R.drawable.black_stone else R.drawable.white_stone + cell.setImageResource(resource) + cell.tag = player + } + + private fun showInvalidMoveMessage() { + Toast.makeText(this, "해당 위치에는 돌이 이미 존재합니다.\n다른 위치를 선택하세요.", Toast.LENGTH_SHORT).show() + } + + private fun showWinMessage() { + Toast.makeText(this, "${game.currentPlayer}가 승리하였습니다.", Toast.LENGTH_LONG).show() + } + + private fun showDrawMessage() { + Toast.makeText(this, "더 이상 돌을 놓을 위치가 존재하지 않습니다.\n무승부입니다.", Toast.LENGTH_LONG).show() + } + + private fun resetBoard() { + game.resetGame() + initializeBoard() } } diff --git a/app/src/main/java/nextstep/omok/OmokGame.kt b/app/src/main/java/nextstep/omok/OmokGame.kt new file mode 100644 index 0000000..e1eb933 --- /dev/null +++ b/app/src/main/java/nextstep/omok/OmokGame.kt @@ -0,0 +1,69 @@ +package nextstep.omok + +const val PLAYER_BLACK = 'B' +const val PLAYER_WHITE = 'W' +const val BOARD_SIZE = 15 + +class OmokGame { + var currentPlayer = PLAYER_BLACK + + private val board = Array(BOARD_SIZE) { Array(BOARD_SIZE) { null } } + + fun placeStone(row: Int, col: Int): Boolean { + if (board[row][col] == null) { + board[row][col] = currentPlayer + return true + } + return false + } + + fun checkWin(row: Int, col: Int): Boolean { + val directions = listOf( + listOf(0 to 1, 0 to -1), listOf(1 to 0, -1 to 0), listOf(1 to 1, -1 to -1), listOf(1 to -1, -1 to 1) + ) + for (direction in directions) { + var count = 1 + for ((dr, dc) in direction) { + count += countStonesInDirection(row, col, dr, dc) + } + if (count >= 5) { + return true + } + } + return false + } + + private fun countStonesInDirection(row: Int, col: Int, dr: Int, dc: Int): Int { + var count = 0 + var r = row + dr + var c = col + dc + + while (r in 0 until BOARD_SIZE && c in 0 until BOARD_SIZE && board[r][c] == currentPlayer) { + count++ + r += dr + c += dc + } + return count + } + + fun togglePlayer() { + currentPlayer = if (currentPlayer == PLAYER_BLACK) PLAYER_WHITE else PLAYER_BLACK + } + + fun resetGame() { + for (row in 0 until BOARD_SIZE) { + for (col in 0 until BOARD_SIZE) { + board[row][col] = null + } + } + currentPlayer = PLAYER_BLACK + } + + fun getStone(row: Int, col: Int): Char? { + return board[row][col] + } + + fun isBoardFull(): Boolean { + return board.all { row -> row.all { cell -> cell != null }} + } +} diff --git a/app/src/test/java/OmokGameTest.kt b/app/src/test/java/OmokGameTest.kt new file mode 100644 index 0000000..0f2f768 --- /dev/null +++ b/app/src/test/java/OmokGameTest.kt @@ -0,0 +1,61 @@ +package nextstep.omok + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class OmokGameTest { + + private lateinit var game: OmokGame + + @BeforeEach + fun setUp() { + game = OmokGame() + } + + @Test + fun testPlaceStone() { + assertTrue(game.placeStone(0, 0)) + assertEquals(PLAYER_BLACK, game.getStone(0, 0)) + assertFalse(game.placeStone(0, 0)) + } + + @Test + fun testTogglePlayer() { + game.togglePlayer() + assertEquals(PLAYER_WHITE, game.currentPlayer) + game.togglePlayer() + assertEquals(PLAYER_BLACK, game.currentPlayer) + } + + @Test + fun testCheckWin() { + for (i in 0 until 5) { + game.placeStone(0, i) + } + assertTrue(game.checkWin(0, 4)) + assertFalse(game.checkWin(1, 2)) + } + + @Test + fun testIsBoardFull() { + for (row in 0 until BOARD_SIZE) { + for (col in 0 until BOARD_SIZE) { + game.placeStone(row, col) + game.togglePlayer() + } + } + assertTrue(game.isBoardFull()) + } + + @Test + fun testResetGame() { + game.placeStone(0, 0) + game.resetGame() + assertNull(game.getStone(0, 0)) + assertEquals(PLAYER_BLACK, game.currentPlayer) + } +} \ No newline at end of file