Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[부산대 Android_김주송] 미션 제출합니다. #29

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,21 @@
# android-omok-precourse
# android-omok-precourse

## 프로젝트 설명
이 프로젝트는 카카오테크캠퍼스 2기 2회차 미니과제로, Kotlin으로 작성한 오목 게임입니다. 두 사람이 번갈아 돌을 놓아 가로나 세로, 대각선으로 다섯 개의 연속된 돌을 먼저 만들면 승리하는 게임입니다.

## 게임 규칙
- 6목 이상의 장목도 착수 가능하며 승리 조건으로 인정합니다.
- 렌주 룰과 같은 복잡한 룰은 고려하지 않습니다.

## 구현 기능 목록
1. 보드 초기화 및 플레이어 초기화
2. 각 셀에 클릭 리스너 설정
3. 돌 위치 선정
- 유효한 자리 -> 돌 위치 확정
- 유효하지 않은 자리 -> 돌 위치 재선정 메시지 출력
4. 우승 조건 확인
- O -> 우승 메시지 출력 및 초기화
- X -> 플레이어 변경
5. 무승부 조건 확인
- O -> 무승부 메시지 출력 및 초기화
- X -> 플레이어 변경
62 changes: 57 additions & 5 deletions app/src/main/java/nextstep/omok/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<TableLayout>(R.id.board)
board
.children
board.children
.filterIsInstance<TableRow>()
.flatMap { it.children }
.filterIsInstance<ImageView>()
.forEach { view -> view.setOnClickListener { view.setImageResource(R.drawable.black_stone) } }
.forEachIndexed { rowIndex, row ->
row.children
.filterIsInstance<ImageView>()
.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()
}
}
69 changes: 69 additions & 0 deletions app/src/main/java/nextstep/omok/OmokGame.kt
Original file line number Diff line number Diff line change
@@ -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<Char?>(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 }}
}
}
61 changes: 61 additions & 0 deletions app/src/test/java/OmokGameTest.kt
Original file line number Diff line number Diff line change
@@ -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)
}
}