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
50 changes: 49 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,49 @@
# android-omok-precourse
# android-omok-precourse

## 오목 설명

- 바둑판에 돌을 놓아 가로, 세로, 대각선으로 5개 이상을 연속으로 놓으면 승리하는 게임이다.

- 흑돌과 백돌이 번갈아가며 착수하며 흑돌부터 착수한다.

- 본게임에서는 15x15 바둑판을 사용한다.

## 기능 목록

### game

- player가 바둑판을 누르면 게임을 진행한다.

- 이미 돌이 착수되었거나 승리자가 결정되었으면 게임을 진행할 수 없다.

### placeStone

- 바둑돌을 Click한 ImageView에 착수한다.

- Player1이면 흑돌, Player2이면 백돌을 착수한다.

### getPosition

- 바둑판의 index를 바탕으로 행과 열을 계산한다.

### checkWinner

- 바둑돌이 5개 이상 연속으로 놓였는지 확인 후 승리자를 결정한다.

- 흑의 바둑돌이 바둑판을 모두 덮으면 무승부로 결정한다.

### countStone

- 바둑판에 같은 바둑돌이 한 방향으로 몇개 놓았는지 확인한다.

### updateGameState

- 진행 중인 turn의 횟수를 계산한다.

- 현재 진행 중인 Player를 바꾼다.

### showGameState

- 현재 게임을 상황을 출력한다.

- Player의 차례나 승리 여부를 출력한다.
105 changes: 105 additions & 0 deletions app/src/androidTest/java/nextstep/omok/omokGameTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package nextstep.omok

import android.widget.ImageView
import android.widget.TableLayout
import android.widget.TableRow
import android.widget.TextView
import androidx.test.core.app.ActivityScenario
import org.junit.Assert.*
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

class MainActivityTest {

lateinit var scenario: ActivityScenario<MainActivity>

@BeforeEach
fun setup() {
scenario = ActivityScenario.launch(MainActivity::class.java)
}

@Test
fun testInitialState() {
scenario.onActivity { activity ->
val statusWindow = activity.findViewById<TextView>(R.id.StatusWindow)
assertEquals("흑의 차례", statusWindow.text.toString())
assertEquals(BLACK, activity.player)
assertEquals(EMPTY, activity.winner)
assertEquals(1, activity.turn)
for (row in activity.boardState) {
for (cell in row) {
assertEquals(EMPTY, cell)
}
}
}
}

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

assertEquals(EMPTY, activity.boardState[0][0])
activity.game(0, firstImageView)
assertEquals(BLACK, activity.boardState[0][0])
}
}

@Test
fun testSwitchTurn() {
scenario.onActivity { activity ->
val board = activity.findViewById<TableLayout>(R.id.board)
val firstCell = board.getChildAt(1) as TableRow
val firstImageView = firstCell.getChildAt(0) as ImageView

assertEquals(BLACK, activity.player)

activity.game(0, firstImageView)
assertEquals(WHITE, activity.player)

val secondImageView = firstCell.getChildAt(1) as ImageView
activity.game(1, secondImageView)
assertEquals(BLACK, activity.player)
}
}

@Test
fun testCheckWinner() {
scenario.onActivity { activity ->
val board = activity.findViewById<TableLayout>(R.id.board)
val firstRow = board.getChildAt(1) as TableRow
val secondRow = board.getChildAt(2) as TableRow
for (i in 0 until WINCOUNT * 2) {
if (i % 2 == 0) {
val cell = firstRow.getChildAt(i / 2) as ImageView
activity.game(i / 2, cell)
} else {
val cell = secondRow.getChildAt(i / 2) as ImageView
activity.game(BOARDSIZE + i / 2, cell)
}
}
assertEquals(BLACK, activity.winner)

val statusWindow = activity.findViewById<TextView>(R.id.StatusWindow)
assertEquals("흑의 승", statusWindow.text.toString())
}
}

@Test
fun testTieGame() {
scenario.onActivity { activity ->
activity.turn = BOARDSIZE * BOARDSIZE
val board = activity.findViewById<TableLayout>(R.id.board)
val lastCell = board.getChildAt(1) as TableRow
val lastImageView = lastCell.getChildAt(0) as ImageView

activity.game(0, lastImageView)
assertEquals(TIE, activity.winner)

val statusWindow = activity.findViewById<TextView>(R.id.StatusWindow)
assertEquals("무승부", statusWindow.text.toString())
}
}
}
111 changes: 109 additions & 2 deletions app/src/main/java/nextstep/omok/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,30 @@ import android.os.Bundle
import android.widget.ImageView
import android.widget.TableLayout
import android.widget.TableRow
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.children

const val BOARDSIZE = 15

const val EMPTY = 0
const val BLACK = 1
const val WHITE = 2
const val TIE = 3

const val WINCOUNT = 5

val derivative = arrayOf(
arrayOf(1, 0), arrayOf(0, 1), arrayOf(1, 1), arrayOf(1, -1)
)

class MainActivity : AppCompatActivity() {

var turn = 1
var player = BLACK
var winner = EMPTY
val boardState = Array(BOARDSIZE) { Array(BOARDSIZE) { EMPTY } }

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Expand All @@ -18,6 +38,93 @@ class MainActivity : AppCompatActivity() {
.filterIsInstance<TableRow>()
.flatMap { it.children }
.filterIsInstance<ImageView>()
.forEach { view -> view.setOnClickListener { view.setImageResource(R.drawable.black_stone) } }
.forEachIndexed { idx, view ->
view.setOnClickListener { game(idx, view) }
}

showGameState()
}

fun game(idx: Int, view: ImageView) {
val (row, column) = getPosition(idx)
if (winner != EMPTY || boardState[row][column] != EMPTY) return

placeStone(row, column, view)
checkWinner(row, column)
updateGameState()
showGameState()
}

fun placeStone(row: Int, column: Int, view: ImageView) {
if (player == BLACK) {
view.setImageResource(R.drawable.black_stone)
boardState[row][column] = BLACK
} else {
view.setImageResource(R.drawable.white_stone)
boardState[row][column] = WHITE
}
}

fun getPosition(idx: Int): Pair<Int, Int> {
val row: Int = idx / BOARDSIZE
val column: Int = idx % BOARDSIZE
return Pair(row, column)
}

fun checkWinner(row: Int, column: Int) {
if (turn == BOARDSIZE * BOARDSIZE) winner = TIE

for ((dx, dy) in derivative) {
var count = 1

count += countStone(row, column, dx, dy)
count += countStone(row, column, -dx, -dy)

if (count >= WINCOUNT) {
winner = player
}
}
}

fun countStone(row: Int, column: Int, dx: Int, dy: Int): Int {
var count = 0
var nx = row
var ny = column
while (true) {
nx += dx
ny += dy
if (nx < 0 || nx >= BOARDSIZE || ny < 0 || ny >= BOARDSIZE) break
if (boardState[nx][ny] != player) break
count += 1
}
return count
}

fun updateGameState() {
turn += 1
if (player == BLACK) {
player = WHITE
} else if (player == WHITE) {
player = BLACK
}
}

fun showGameState() {
var outputString = ""
val statusWindow = findViewById<TextView>(R.id.StatusWindow)
if (player == BLACK) {
outputString = "흑의 차례"
} else if (player == WHITE) {
outputString = "백의 차레"
}

if (winner == BLACK) {
outputString = "흑의 승"
} else if (winner == WHITE) {
outputString = "백의 승"
} else if (winner == TIE) {
outputString = "무승부"
}
statusWindow.text = outputString
}
}
}
14 changes: 14 additions & 0 deletions app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,19 @@
android:layout_height="match_parent"
android:stretchColumns="*">

<TableRow>
<TextView
android:id="@+id/StatusWindow"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:gravity="center"
android:text="게임 진행 중"
android:textSize="20dp"
android:textStyle="bold"
android:background="#F4CB62"/>
</TableRow>

<TableRow android:layout_weight="1">

<ImageView
Expand Down Expand Up @@ -1180,4 +1193,5 @@
</TableRow>

</TableLayout>

</LinearLayout>