Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,11 @@
# android-omok-precourse
# android-omok-precourse

오목은 두 사람이 번갈아 돌을 놓아 가로나 세로, 대각선으로 다섯 개의 연속된 돌을 먼저 만들면 승리하는 게임이다
초기 코드를 실행해보면 1) 오목판이 구현되어 있으며 2) 흑돌을 원하는 위치에 둘 수 있는 상황이다

## 구현할 기능
1. 오목판 초기화(initializeBoard)
2. 돌 번갈아 놓기(onCellClikced, placeStone)
3. 예외 처리 - 같은 위치에 돌을 놓지 못하게 하기(isCellOccupied)
4. 승리 조건 확인(checkWin/countStones)
5. 게임 종료 후 보드 초기화(resetBoard)
141 changes: 141 additions & 0 deletions app/src/androidTest/java/nextstep/omok/MainActivityTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package nextstep.omok

import android.widget.ImageView
import android.widget.TableLayout
import android.widget.TableRow
import androidx.core.view.children
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class MainActivityTest {

private lateinit var scenario: ActivityScenario<MainActivity>

@Before
fun setUp() {
scenario = ActivityScenario.launch(MainActivity::class.java)
}

private fun <T> getPrivateField(instance: Any, fieldName: String): T {
val field = instance.javaClass.getDeclaredField(fieldName)
field.isAccessible = true
return field.get(instance) as T
}

private fun callPrivateMethod(instance: Any, methodName: String, vararg args: Any?): Any? {
val parameterTypes = args.map {
when (it) {
is Int -> Int::class.javaPrimitiveType
else -> it?.javaClass
}
}.toTypedArray()
val method = instance.javaClass.getDeclaredMethod(methodName, *parameterTypes)
method.isAccessible = true
return method.invoke(instance, *args)
}


@Test
fun testInitializeBoard() {
scenario.onActivity { activity ->
val board = activity.findViewById<TableLayout>(R.id.board)
board.children.filterIsInstance<TableRow>().forEach { row ->
row.children.filterIsInstance<ImageView>().forEach { view ->
assertThat(view.drawable).isNull() // ImageView should be empty
}
}
}
}

@Test
fun testPlaceStone() {
scenario.onActivity { activity ->
val row = 0
val col = 0
val board = activity.findViewById<TableLayout>(R.id.board)
val cell = (board.getChildAt(row) as TableRow).getChildAt(col) as ImageView

activity.runOnUiThread {
cell.performClick()
}

val boardArray = getPrivateField<Array<IntArray>>(activity, "BOARD_ARRAY")
assertThat(boardArray[row][col]).isEqualTo(1) // Black stone should be placed

activity.runOnUiThread {
cell.performClick()
}

assertThat(boardArray[row][col]).isEqualTo(1) // Should not change on second click
}
}

@Test
fun testSwitchTurns() {
scenario.onActivity { activity ->
val row = 0
val col = 0
val nextRow = 0
val nextCol = 1
val board = activity.findViewById<TableLayout>(R.id.board)
val cell1 = (board.getChildAt(row) as TableRow).getChildAt(col) as ImageView
val cell2 = (board.getChildAt(nextRow) as TableRow).getChildAt(nextCol) as ImageView

activity.runOnUiThread {
cell1.performClick()
}

val boardArray = getPrivateField<Array<IntArray>>(activity, "BOARD_ARRAY")
assertThat(boardArray[row][col]).isEqualTo(1) // Black stone

activity.runOnUiThread {
cell2.performClick()
}

assertThat(boardArray[nextRow][nextCol]).isEqualTo(2) // White stone
}
}

@Test
fun testCheckWin() {
scenario.onActivity { activity ->
val boardArray = getPrivateField<Array<IntArray>>(activity, "BOARD_ARRAY")
boardArray[0][0] = 1
boardArray[0][1] = 1
boardArray[0][2] = 1
boardArray[0][3] = 1
boardArray[0][4] = 1

val hasWon = callPrivateMethod(activity, "checkWin", 0, 4) as Boolean
assertThat(hasWon).isTrue() // Black wins
}
}

@Test
fun testResetBoard() {
scenario.onActivity { activity ->
val boardArray = getPrivateField<Array<IntArray>>(activity, "BOARD_ARRAY")
boardArray[0][0] = 1

callPrivateMethod(activity, "resetBoard")

boardArray.forEach { row ->
row.forEach { cell ->
assertThat(cell).isEqualTo(0) // Board should be reset to empty
}
}

val board = activity.findViewById<TableLayout>(R.id.board)
board.children.filterIsInstance<TableRow>().forEach { row ->
row.children.filterIsInstance<ImageView>().forEach { view ->
assertThat(view.drawable).isNull() // ImageView should be empty
}
}
}
}
}
76 changes: 69 additions & 7 deletions app/src/main/java/nextstep/omok/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,78 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.children

class MainActivity : AppCompatActivity() {

private val BOARD_SIZE = 15
private val BOARD_ARRAY = Array(BOARD_SIZE) { IntArray(BOARD_SIZE) }
private var isBlackTurn = true

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initializeBoard()
}

private fun initializeBoard() {
findViewById<TableLayout>(R.id.board).apply {
children.filterIsInstance<TableRow>().forEachIndexed { rowIndex, row ->
row.children.filterIsInstance<ImageView>().forEachIndexed { colIndex, view ->
view.setOnClickListener { onCellClicked(view, rowIndex, colIndex) }
view.setImageResource(0)
}
}
}
}

private fun onCellClicked(view: ImageView, rowIndex: Int, colIndex: Int) {
if (isCellOccupied(rowIndex, colIndex)) {
return
}
placeStone(view, rowIndex, colIndex)
if (checkWin(rowIndex, colIndex)) {
resetBoard()
} else {
isBlackTurn = !isBlackTurn
}
}

private fun isCellOccupied(rowIndex: Int, colIndex: Int): Boolean {
return BOARD_ARRAY[rowIndex][colIndex] != 0
}

private fun placeStone(view: ImageView, rowIndex: Int, colIndex: Int) {
BOARD_ARRAY[rowIndex][colIndex] = if (isBlackTurn) 1 else 2
view.setImageResource(if (isBlackTurn) R.drawable.black_stone else R.drawable.white_stone)
}

private fun checkWin(row: Int, col: Int): Boolean {
val player = BOARD_ARRAY[row][col]
val directions = listOf(Pair(1, 0), Pair(0, 1), Pair(1, 1), Pair(1, -1))
return directions.any { direction ->
countStones(row, col, direction.first, direction.second, player)
+ countStones(row, col, -direction.first, -direction.second, player) > 3
}
}

private fun countStones(row: Int, col: Int, dRow: Int, dCol: Int, player: Int): Int {
var count = 0
var r = row + dRow
var c = col + dCol

while (r in 0 until BOARD_SIZE && c in 0 until BOARD_SIZE && BOARD_ARRAY[r][c] == player) {
count++
r += dRow
c += dCol
}
return count
}

val board = findViewById<TableLayout>(R.id.board)
board
.children
.filterIsInstance<TableRow>()
.flatMap { it.children }
.filterIsInstance<ImageView>()
.forEach { view -> view.setOnClickListener { view.setImageResource(R.drawable.black_stone) } }
private fun resetBoard() {
BOARD_ARRAY.forEach { row -> row.fill(0) }
findViewById<TableLayout>(R.id.board).children.filterIsInstance<TableRow>().forEach { row ->
row.children.filterIsInstance<ImageView>().forEach { view ->
view.setImageResource(0)
}
}
isBlackTurn = true
}
}