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_유지예] 미션 제출합니다. #30

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

## 프로젝트 개요
- 오목게임을 안드로이드 앱으로 구현한 프로젝트.

## 기능목록
1. UI 초기화
- 설정 초기화
2. 클릭 리스너 설정하기
- 오목 판의 클릭 리스너 재설정
3. 돌 놓기 기능
- 오목판에 돌 배치하기
4. 차례 변경 기능
- 하얀 돌과 검은 돌 차례 바꾸기
5. 승리 여부 표시
- 5개 이상의 돌이 놓여 게임이 끝났음을 알리고 재시작
6. 오목판 그리기
- 해당 판은 기존 xml 파일 활용
7. 오목판 배치하
- view 파일 활용
8. 돌 그리기
- View 파일 안에 하얀 돌, 검은 돌 그리기
- 해당 돌은 기존 xml 파일 활용
9. 돌 놓는 이벤트 처리하기
- 오목판 위에 돌 놓는 것 처리하기
- 오목판 위에 돌을 놓음으로써 뒤의 오목판 이미지가 사라지지 않도록 설정하는 것 주의하기
10. 돌 놓기 기능
- 9의 기능 처리
11. 승리 조건 체크하기
- 같은 색의 돌이 가로,세로,대각선 중에서 하나라도 5개 이상이 되었을 때 게임 종료
- 해당 돌이 같은 색인지, 사이의 같은 돌의 개수 체크하기
- 승리 조건 판단
12. 레이아웃 작성하기
- android_main.xml
13. test 돌리기
- test code 작성
58 changes: 58 additions & 0 deletions app/src/main/java/nextstep/omok/GameView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package nextstep.omok

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.view.MotionEvent
import android.view.View

class GameView(context: Context) : View(context) {
private val boardSize = 15
private val cellSize = 70f
private val boardPaint = Paint().apply { color = Color.BLACK }
private val blackPaint = Paint().apply { color = Color.BLACK }
private val whitePaint = Paint().apply { color = Color.WHITE }
private val game = OmokGame(boardSize)

override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
drawBoard(canvas)
drawStones(canvas)
}

private fun drawBoard(canvas: Canvas) {
for (i in 0 until boardSize) {
canvas.drawLine(i * cellSize, 0f, i * cellSize, boardSize * cellSize, boardPaint)
canvas.drawLine(0f, i * cellSize, boardSize * cellSize, i * cellSize, boardPaint)
}
}

private fun drawStones(canvas: Canvas) {
for (i in 0 until boardSize) {
for (j in 0 until boardSize) {
when (game.getStone(i, j)) {
Stone.BLACK -> canvas.drawCircle(
i * cellSize + cellSize / 2, j * cellSize + cellSize / 2, cellSize / 2, blackPaint
)
Stone.WHITE -> canvas.drawCircle(
i * cellSize + cellSize / 2, j * cellSize + cellSize / 2, cellSize / 2, whitePaint
)
else -> {}
}
}
}
}

override fun onTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_DOWN) {
val x = (event.x / cellSize).toInt()
val y = (event.y / cellSize).toInt()
if (game.placeStone(x, y)) {
invalidate()
}
return true
}
return super.onTouchEvent(event)
}
}
114 changes: 109 additions & 5 deletions app/src/main/java/nextstep/omok/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,23 +1,127 @@
package nextstep.omok

import android.os.Bundle
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TableLayout
import android.widget.TableRow
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.children
import androidx.appcompat.app.AlertDialog

class MainActivity : AppCompatActivity() {
private val boardSize = 15
private var game = OmokGame(boardSize)
private var currentTurn = Stone.BLACK

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

private fun startNewGame() {
val board = findViewById<TableLayout>(R.id.board)
board
.children
board.removeAllViews()
initializeBoard(board)
setupBoardClickListeners(board)
}

private fun initializeBoard(board: TableLayout) {
for (i in 0 until boardSize) {
val tableRow = TableRow(this)
for (j in 0 until boardSize) {
val frameLayout = createBoardCell(i, j)
tableRow.addView(frameLayout)
}
board.addView(tableRow)
}
}

private fun createBoardCell(row: Int, col: Int): FrameLayout {
val frameLayout = FrameLayout(this).apply {
layoutParams = TableRow.LayoutParams(70, 70)
}
val boardImageView = ImageView(this).apply {
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
setImageResource(getCellDrawable(row, col))
}
val stoneImageView = ImageView(this).apply {
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
setImageResource(android.R.color.transparent) // 초기에는 투명한 이미지로 설정
}
frameLayout.addView(boardImageView)
frameLayout.addView(stoneImageView)
return frameLayout
}

private fun setupBoardClickListeners(board: TableLayout) {
board.children
.filterIsInstance<TableRow>()
.flatMap { it.children }
.filterIsInstance<ImageView>()
.forEach { view -> view.setOnClickListener { view.setImageResource(R.drawable.black_stone) } }
.filterIsInstance<FrameLayout>()
.forEachIndexed { index, frameLayout ->
val x = index % boardSize
val y = index / boardSize
setupCellClickListener(frameLayout, x, y)
}
}

private fun setupCellClickListener(frameLayout: FrameLayout, x: Int, y: Int) {
frameLayout.setOnClickListener {
handleBoardClick(x, y, frameLayout.getChildAt(1) as ImageView)
}
}

private fun handleBoardClick(x: Int, y: Int, stoneImageView: ImageView) {
if (game.placeStone(x, y)) {
updateStoneImage(stoneImageView)
if (game.checkWin(x, y)) {
showGameOverDialog()
} else {
switchTurn()
}
}
}

private fun updateStoneImage(stoneImageView: ImageView) {
stoneImageView.setImageResource(if (currentTurn == Stone.BLACK) R.drawable.black_stone else R.drawable.white_stone)
}

private fun switchTurn() {
currentTurn = if (currentTurn == Stone.BLACK) Stone.WHITE else Stone.BLACK
}

private fun showGameOverDialog() {
AlertDialog.Builder(this)
.setTitle("게임 종료")
.setMessage("게임이 종료되었습니다. 새로운 게임을 시작하세요.")
.setPositiveButton("OK") { _, _ ->
game = OmokGame(boardSize)
currentTurn = Stone.BLACK
startNewGame()
}
.setCancelable(false)
.show()
}

private fun getCellDrawable(row: Int, col: Int): Int {
return when {
row == 0 && col == 0 -> R.drawable.board_top_left
row == 0 && col == boardSize - 1 -> R.drawable.board_top_right
row == boardSize - 1 && col == 0 -> R.drawable.board_bottom_left
row == boardSize - 1 && col == boardSize - 1 -> R.drawable.board_bottom_right
row == 0 -> R.drawable.board_top
row == boardSize - 1 -> R.drawable.board_bottom
col == 0 -> R.drawable.board_left
col == boardSize - 1 -> R.drawable.board_right
else -> R.drawable.board_center
}
}
}
}
61 changes: 61 additions & 0 deletions app/src/main/java/nextstep/omok/OmokGame.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package nextstep.omok

enum class Stone {
EMPTY, BLACK, WHITE
}

class OmokGame(private val boardSize: Int) {
private val board: Array<Array<Stone>> = Array(boardSize) { Array(boardSize) { Stone.EMPTY } }
private var currentTurn = Stone.BLACK

fun getStone(x: Int, y: Int): Stone {
return if (x in 0 until boardSize && y in 0 until boardSize) {
board[x][y]
} else {
Stone.EMPTY
}
}

//돌 놓는 동작 성공 여부
fun placeStone(x: Int, y: Int): Boolean {
if (x !in 0 until boardSize || y !in 0 until boardSize || board[x][y] != Stone.EMPTY) {
return false
}
board[x][y] = currentTurn
val isWin = checkWin(x, y)
currentTurn = if (currentTurn == Stone.BLACK) Stone.WHITE else Stone.BLACK
return true
}

fun checkWin(x: Int, y: Int): Boolean {
val directions = listOf(
Pair(1, 0), Pair(0, 1), Pair(1, 1), Pair(1, -1)
)
val currentStone = board[x][y]
for ((dx, dy) in directions) {
var count = 1
count += countStonesInDirection(x, y, dx, dy, currentStone)
count += countStonesInDirection(x, y, -dx, -dy, currentStone)
if (count >= 5) {
return true
}
}
return false
}

private fun countStonesInDirection(x: Int, y: Int, dx: Int, dy: Int, stone: Stone): Int {
var count = 0
var nx = x + dx
var ny = y + dy
while (nx in 0 until boardSize && ny in 0 until boardSize && board[nx][ny] == stone) {
count++
nx += dx
ny += dy
}
return count
}
}




11 changes: 7 additions & 4 deletions app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">

<TableLayout
android:id="@+id/board"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:stretchColumns="*">
<!-- 15x15 오목판 행 및 셀을 추가 -->

<TableRow android:layout_weight="1">

Expand Down Expand Up @@ -1180,4 +1183,4 @@
</TableRow>

</TableLayout>
</LinearLayout>
</LinearLayout>
56 changes: 56 additions & 0 deletions app/src/test/java/nextstep/omok/OmokGameTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package nextstep.omok

import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.DisplayName

class OmokGameTest {

private val boardSize = 15

@Test
@DisplayName("빈 위치에 돌을 놓을 수 있는지 테스트")
fun testPlaceStone() {
val game = OmokGame(boardSize)
assertTrue(game.placeStone(0, 0)) // 빈 곳에 돌을 놓을 수 있어야 함
assertFalse(game.placeStone(0, 0)) // 이미 돌이 있는 곳에 다시 놓을 수 없어야 함
}

@Test
@DisplayName("정해진 위치에 돌이 놓였는지 테스트")
fun testGetStone() {
val game = OmokGame(boardSize)
game.placeStone(0, 0)
assertEquals(Stone.BLACK, game.getStone(0, 0))
game.placeStone(1, 1)
assertEquals(Stone.WHITE, game.getStone(1, 1))
}

@Test
@DisplayName("빈 위치 확인 테스트")
fun testIsEmpty() {
val game = OmokGame(boardSize)
assertTrue(game.getStone(0, 0) == Stone.EMPTY)
game.placeStone(0, 0)
assertFalse(game.getStone(0, 0) == Stone.EMPTY)
}
@Test
@DisplayName("현재 턴 변경 테스트")
fun testSwitchTurn() {
val game = OmokGame(boardSize)
game.placeStone(0, 0)
assertEquals(Stone.BLACK, game.getStone(0, 0))
game.placeStone(1, 1)
assertEquals(Stone.WHITE, game.getStone(1, 1))
}
@Test
@DisplayName("오목판 초기화 테스트")
fun testInitializeBoard() {
val game = OmokGame(boardSize)
for (i in 0 until boardSize) {
for (j in 0 until boardSize) {
assertEquals(Stone.EMPTY, game.getStone(i, j))
}
}
}
}