From c1c6ab88ed5de4c50dd032ccbd53cc0220c2cbad Mon Sep 17 00:00:00 2001 From: nJiyeon <150491973+nJiyeon@users.noreply.github.com> Date: Mon, 3 Jun 2024 03:32:56 +0900 Subject: [PATCH 1/8] Update README.md --- README.md | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 08a8c8a..867aa30 100644 --- a/README.md +++ b/README.md @@ -1 +1,37 @@ -# android-omok-precourse \ No newline at end of file +# android-omok-precourse + +# 오목 게임 + +이 프로젝트는 안드로이드를 위한 코틀린으로 구현된 간단한 오목(Omok, Gomoku) 게임이다. +이 게임은 두 명의 플레이어가 19x19 오목판에 검은 돌과 흰 돌을 놓을 수 있게 하며, 전통적인 오목 규칙에 따라 진행됩니다. 추가로, 흑돌은 금수 규칙이 적용된다. + +## 주요 기능 + +- 19x19 오목판 +- 두 명의 플레이어: 흑돌과 백돌 +- 기본 오목 규칙과 흑돌 금수 규칙: + - 흑돌은 "삼삼" (3.3), "사사" (4.4), 또는 "장목" (6목) 금수로 인해 특정 위치에 돌을 놓을 수 없다 + - 백돌은 금수 규칙 없이 자유롭게 돌을 놓을 수 있다 +- 게임 재시작 버튼을 통해 게임을 초기화할 수 있다 +- 금수 위치에 돌을 놓을 경우나 승리자가 발생할 경우 토스트 메시지를 표시한다 + +## 코드 개요 +### 주요 구성 요소 +- OmokView.kt + : 게임 보드와 돌을 그리는 커스텀 뷰, 터치 이벤트를 처리 +- OmokGame.kt + : 보드 상태, 플레이어 턴, 금수 규칙을 관리하는 게임 로직 +- activity_main.xml + : OmokView와 재시작 버튼이 포함된 메인 레이아웃 파일 +- MainActivity.kt + : OmokView를 설정하고 재시작 버튼을 처리하는 메인 액티비티 + +### 상세 기능 +- OmokView.kt +보드 격자와 돌을 그림 +사용자 터치 이벤트를 관리하여 돌을 놓음 +금수 위치를 확인하고 토스트 메시지를 표시함 +승리 조건을 확인하고 승리 시 토스트 메시지를 표시함 +- OmokGame.kt +보드, 플레이어 턴, 금수 규칙을 포함한 게임 상태를 관리함 +금수 위치와 승리 조건을 감지하는 게임 로직을 구현함 From 15cdeaa6029c01688eed93e4ef7325e045bc39c2 Mon Sep 17 00:00:00 2001 From: nJiyeon <150491973+nJiyeon@users.noreply.github.com> Date: Mon, 3 Jun 2024 04:26:42 +0900 Subject: [PATCH 2/8] Update README.md --- README.md | 57 +++++++++++++++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 867aa30..fb8f5e4 100644 --- a/README.md +++ b/README.md @@ -3,35 +3,34 @@ # 오목 게임 이 프로젝트는 안드로이드를 위한 코틀린으로 구현된 간단한 오목(Omok, Gomoku) 게임이다. -이 게임은 두 명의 플레이어가 19x19 오목판에 검은 돌과 흰 돌을 놓을 수 있게 하며, 전통적인 오목 규칙에 따라 진행됩니다. 추가로, 흑돌은 금수 규칙이 적용된다. +이 게임은 두 명의 플레이어가 14x14 오목판에 검은 돌과 흰 돌을 놓을 수 있게 하며, 전통적인 오목 규칙에 따라 진행된다. 추가로, 흑돌은 금수 규칙이 적용된다. + ## 주요 기능 -- 19x19 오목판 -- 두 명의 플레이어: 흑돌과 백돌 -- 기본 오목 규칙과 흑돌 금수 규칙: - - 흑돌은 "삼삼" (3.3), "사사" (4.4), 또는 "장목" (6목) 금수로 인해 특정 위치에 돌을 놓을 수 없다 - - 백돌은 금수 규칙 없이 자유롭게 돌을 놓을 수 있다 -- 게임 재시작 버튼을 통해 게임을 초기화할 수 있다 -- 금수 위치에 돌을 놓을 경우나 승리자가 발생할 경우 토스트 메시지를 표시한다 - -## 코드 개요 -### 주요 구성 요소 -- OmokView.kt - : 게임 보드와 돌을 그리는 커스텀 뷰, 터치 이벤트를 처리 -- OmokGame.kt - : 보드 상태, 플레이어 턴, 금수 규칙을 관리하는 게임 로직 -- activity_main.xml - : OmokView와 재시작 버튼이 포함된 메인 레이아웃 파일 -- MainActivity.kt - : OmokView를 설정하고 재시작 버튼을 처리하는 메인 액티비티 - -### 상세 기능 -- OmokView.kt -보드 격자와 돌을 그림 -사용자 터치 이벤트를 관리하여 돌을 놓음 -금수 위치를 확인하고 토스트 메시지를 표시함 -승리 조건을 확인하고 승리 시 토스트 메시지를 표시함 -- OmokGame.kt -보드, 플레이어 턴, 금수 규칙을 포함한 게임 상태를 관리함 -금수 위치와 승리 조건을 감지하는 게임 로직을 구현함 +- **두 명의 플레이어**: 흑돌과 백돌. + +- **기본 오목 규칙**: 가로, 세로 또는 대각선으로 다섯 개의 돌을 먼저 놓으면 승리 +- **흑돌 금수 규칙**: + - "삼삼" (Double Three): 두 개의 열린 삼을 만드는 돌 놓기 금지 + + - "사사" (Double Four): 두 개의 열린 사를 만드는 돌 놓기 금지 + - "장목" (Overline): 여섯 개 이상의 돌을 연속으로 놓는 것 금지 +- **백돌 자유 규칙**: 백돌은 아무 곳에나 놓을 수 있음 +- **승리 및 금수 위치 알림**: 승리 조건 충족 시 및 금수 위치에 돌을 놓으려 할 때 토스트 메시지로 알림 + + + +## 파일 설명 + +### MainActivity.kt + +`MainActivity`는 안드로이드 액티비티로서, 게임 보드를 설정하고 사용자 입력을 처리한다. 게임의 주요 로직은 `OmokGame` 클래스로 구현한다. + +### OmokGame.kt + +`OmokGame` 클래스는 게임의 상태를 관리하고, 금수 규칙과 승리 조건을 확인하는 로직을 포함한다. + +### GameLogicTest.kt + +`GameLogicTest` 클래스는 JUnit 5와 AssertJ를 사용하여 `OmokGame`의 주요 기능을 테스트. 삼삼, 사사, 장목 금수 규칙과 승리 조건을 검증한다. From e7964d316b14ec15ea00c1a7e37ea078e8721a7a Mon Sep 17 00:00:00 2001 From: jiyeon Date: Mon, 3 Jun 2024 04:29:30 +0900 Subject: [PATCH 3/8] Implement Omok game logic in MainActivity --- .../main/java/nextstep/omok/MainActivity.kt | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/nextstep/omok/MainActivity.kt b/app/src/main/java/nextstep/omok/MainActivity.kt index e6cc7b8..28d506c 100644 --- a/app/src/main/java/nextstep/omok/MainActivity.kt +++ b/app/src/main/java/nextstep/omok/MainActivity.kt @@ -4,20 +4,54 @@ 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 boardSize = 15 + private lateinit var game: OmokGame + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - val board = findViewById(R.id.board) - board - .children + game = OmokGame(boardSize) + setupBoard() + } + + private fun setupBoard() { + val boardView = findViewById(R.id.board) + boardView.children .filterIsInstance() .flatMap { it.children } .filterIsInstance() - .forEach { view -> view.setOnClickListener { view.setImageResource(R.drawable.black_stone) } } + .forEachIndexed { index, view -> + val row = index / boardSize + val col = index % boardSize + view.setOnClickListener { handleCellClick(row, col, view) } + } + } + + private fun handleCellClick(row: Int, col: Int, view: ImageView) { + if (game.board[row][col] == 0) { + if (game.isForbidden(row, col)) { + Toast.makeText(this, "Forbidden move!", Toast.LENGTH_SHORT).show() + return + } + game.placeStone(row, col) + view.setImageResource(if (game.currentPlayer == 1) R.drawable.black_stone else R.drawable.white_stone) + if (game.checkWin(row, col, game.currentPlayer)) { + showWinner(game.currentPlayer) + } else { + game.switchPlayer() + } + } + } + + private fun showWinner(player: Int) { + val winner = if (player == 1) "Black" else "White" + Toast.makeText(this, "$winner wins!", Toast.LENGTH_SHORT).show() } } From 07d436dc2e0367a4930e116e2222a55c6759f9f3 Mon Sep 17 00:00:00 2001 From: jiyeon Date: Mon, 3 Jun 2024 04:31:29 +0900 Subject: [PATCH 4/8] Add OmokGame class to handle game logic --- app/src/main/java/nextstep/omok/OmokGame.kt | 123 ++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 app/src/main/java/nextstep/omok/OmokGame.kt 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..7ccc968 --- /dev/null +++ b/app/src/main/java/nextstep/omok/OmokGame.kt @@ -0,0 +1,123 @@ +package nextstep.omok + +class OmokGame(private val boardSize: Int) { + + val board = Array(boardSize) { Array(boardSize) { 0 } } + var currentPlayer = 1 // 1 for black, 2 for white + + fun placeStone(row: Int, col: Int): Boolean { + if (board[row][col] == 0) { + board[row][col] = currentPlayer + return true + } + return false + } + + fun switchPlayer() { + currentPlayer = if (currentPlayer == 1) 2 else 1 + } + + fun checkWin(row: Int, col: Int, player: Int): Boolean { + return checkDirection(row, col, player, 0, 1) >= 5 || // Horizontal + checkDirection(row, col, player, 1, 0) >= 5 || // Vertical + checkDirection(row, col, player, 1, 1) >= 5 || // Diagonal down-right + checkDirection(row, col, player, 1, -1) >= 5 // Diagonal up-right + } + + fun isForbidden(row: Int, col: Int): Boolean { + if (currentPlayer == 1) { + return isDoubleThree(row, col) || isDoubleFour(row, col) || isSixInARow(row, col) + } + return false + } + + private fun checkDirection(row: Int, col: Int, player: Int, dRow: Int, dCol: Int): Int { + var count = 1 + count += countStones(row, col, player, dRow, dCol) + count += countStones(row, col, player, -dRow, -dCol) + return count + } + + private fun countStones(row: Int, col: Int, player: Int, dRow: Int, dCol: Int): Int { + var count = 0 + var r = row + dRow + var c = col + dCol + while (r in 0 until boardSize && c in 0 until boardSize && board[r][c] == player) { + count++ + r += dRow + c += dCol + } + return count + } + + fun isDoubleThree(row: Int, col: Int): Boolean { + return countOpenThrees(row, col, 1, 0) + countOpenThrees(row, col, 0, 1) + + countOpenThrees(row, col, 1, 1) + countOpenThrees(row, col, 1, -1) > 1 + } + + private fun countOpenThrees(row: Int, col: Int, dRow: Int, dCol: Int): Int { + var count = 0 + if (isOpenThree(row, col, dRow, dCol)) count++ + if (isOpenThree(row, col, -dRow, -dCol)) count++ + return count + } + + private fun isOpenThree(row: Int, col: Int, dRow: Int, dCol: Int): Boolean { + val sequence = mutableListOf() + for (i in -4..4) { + val r = row + i * dRow + val c = col + i * dCol + if (r in 0 until boardSize && c in 0 until boardSize) { + sequence.add(board[r][c]) + } else { + sequence.add(-1) + } + } + val pattern = listOf(0, 1, 1, 1, 0) + for (i in 0..sequence.size - pattern.size) { + if (sequence.subList(i, i + pattern.size) == pattern) { + return true + } + } + return false + } + + fun isDoubleFour(row: Int, col: Int): Boolean { + return countOpenFours(row, col, 1, 0) + countOpenFours(row, col, 0, 1) + + countOpenFours(row, col, 1, 1) + countOpenFours(row, col, 1, -1) > 1 + } + + private fun countOpenFours(row: Int, col: Int, dRow: Int, dCol: Int): Int { + var count = 0 + if (isOpenFour(row, col, dRow, dCol)) count++ + if (isOpenFour(row, col, -dRow, -dCol)) count++ + return count + } + + private fun isOpenFour(row: Int, col: Int, dRow: Int, dCol: Int): Boolean { + val sequence = mutableListOf() + for (i in -4..4) { + val r = row + i * dRow + val c = col + i * dCol + if (r in 0 until boardSize && c in 0 until boardSize) { + sequence.add(board[r][c]) + } else { + sequence.add(-1) + } + } + val pattern = listOf(0, 1, 1, 1, 1, 0) + for (i in 0..sequence.size - pattern.size) { + if (sequence.subList(i, i + pattern.size) == pattern) { + return true + } + } + return false + } + + fun isSixInARow(row: Int, col: Int): Boolean { + return checkDirection(row, col, currentPlayer, 0, 1) > 5 || + checkDirection(row, col, currentPlayer, 1, 0) > 5 || + checkDirection(row, col, currentPlayer, 1, 1) > 5 || + checkDirection(row, col, currentPlayer, 1, -1) > 5 + } +} From 69c4c44950a993b3d361db791466837688a1a1bf Mon Sep 17 00:00:00 2001 From: jiyeon Date: Mon, 3 Jun 2024 04:32:16 +0900 Subject: [PATCH 5/8] Add OmokGame class to handle game logic --- .../test/java/nextstep/omok/GameLogicTest.kt | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 app/src/test/java/nextstep/omok/GameLogicTest.kt diff --git a/app/src/test/java/nextstep/omok/GameLogicTest.kt b/app/src/test/java/nextstep/omok/GameLogicTest.kt new file mode 100644 index 0000000..f64d5bc --- /dev/null +++ b/app/src/test/java/nextstep/omok/GameLogicTest.kt @@ -0,0 +1,93 @@ +package nextstep.omok + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class GameLogicTest { + + private val boardSize = 15 + private lateinit var game: OmokGame + + @BeforeEach + fun setUp() { + game = OmokGame(boardSize) + } + + @Test + fun `horizontal win check`() { + for (i in 0 until 5) { + game.board[0][i] = 1 + } + val result = game.checkWin(0, 2, 1) + assertThat(result).isTrue() + } + + @Test + fun `vertical win check`() { + for (i in 0 until 5) { + game.board[i][0] = 1 + } + val result = game.checkWin(2, 0, 1) + assertThat(result).isTrue() + } + + @Test + fun `diagonal down-right win check`() { + for (i in 0 until 5) { + game.board[i][i] = 1 + } + val result = game.checkWin(2, 2, 1) + assertThat(result).isTrue() + } + + @Test + fun `diagonal up-right win check`() { + for (i in 0 until 5) { + game.board[4 - i][i] = 1 + } + val result = game.checkWin(2, 2, 1) + assertThat(result).isTrue() + } + + @Test + fun `double three forbidden move`() { + // Set up a double three situation + game.board[1][2] = 1 + game.board[1][3] = 1 + game.board[1][5] = 1 + game.board[1][6] = 1 + game.board[2][3] = 1 + game.board[3][3] = 1 + game.board[4][3] = 1 + + val result = game.isDoubleThree(1, 4) + assertThat(result).isTrue() + } + + @Test + fun `double four forbidden move`() { + // Set up a double four situation + game.board[1][2] = 1 + game.board[1][3] = 1 + game.board[1][4] = 1 + game.board[1][6] = 1 + game.board[1][7] = 1 + game.board[1][8] = 1 + game.board[0][5] = 1 + game.board[2][5] = 1 + + val result = game.isDoubleFour(1, 5) + assertThat(result).isTrue() + } + + @Test + fun `six in a row forbidden move`() { + // Set up a six in a row situation + for (i in 0 until 6) { + game.board[0][i] = 1 + } + val result = game.isSixInARow(0, 2) + assertThat(result).isTrue() + } +} From 6dedd3399f54a807ca24f4f5c5e3f5ed09811988 Mon Sep 17 00:00:00 2001 From: nJiyeon <150491973+nJiyeon@users.noreply.github.com> Date: Mon, 3 Jun 2024 04:40:11 +0900 Subject: [PATCH 6/8] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fb8f5e4..a11f13f 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ # 오목 게임 이 프로젝트는 안드로이드를 위한 코틀린으로 구현된 간단한 오목(Omok, Gomoku) 게임이다. -이 게임은 두 명의 플레이어가 14x14 오목판에 검은 돌과 흰 돌을 놓을 수 있게 하며, 전통적인 오목 규칙에 따라 진행된다. 추가로, 흑돌은 금수 규칙이 적용된다. +이 게임은 두 명의 플레이어가 15 X 15 오목 판에 검은 돌과 흰 돌을 놓을 수 있게 하며, 전통적인 오목 규칙에 따라 진행된다. 추가로, 흑돌은 금수 규칙이 적용된다. ## 주요 기능 From 8a1d58c7c812fb6d7f35aa80b05303b370d2986e Mon Sep 17 00:00:00 2001 From: nJiyeon <150491973+nJiyeon@users.noreply.github.com> Date: Mon, 3 Jun 2024 04:40:30 +0900 Subject: [PATCH 7/8] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a11f13f..a2b3942 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # 오목 게임 -이 프로젝트는 안드로이드를 위한 코틀린으로 구현된 간단한 오목(Omok, Gomoku) 게임이다. +이 프로젝트는 안드로이드를 위한 코틀린으로 구현된 간단한 오목(Omok) 게임이다. 이 게임은 두 명의 플레이어가 15 X 15 오목 판에 검은 돌과 흰 돌을 놓을 수 있게 하며, 전통적인 오목 규칙에 따라 진행된다. 추가로, 흑돌은 금수 규칙이 적용된다. From 07968be211c78a3cd4c584d81f97fd060e4615e0 Mon Sep 17 00:00:00 2001 From: nJiyeon <150491973+nJiyeon@users.noreply.github.com> Date: Mon, 3 Jun 2024 04:52:23 +0900 Subject: [PATCH 8/8] Update README.md --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index a2b3942..7b163e5 100644 --- a/README.md +++ b/README.md @@ -27,10 +27,22 @@ `MainActivity`는 안드로이드 액티비티로서, 게임 보드를 설정하고 사용자 입력을 처리한다. 게임의 주요 로직은 `OmokGame` 클래스로 구현한다. + ### OmokGame.kt `OmokGame` 클래스는 게임의 상태를 관리하고, 금수 규칙과 승리 조건을 확인하는 로직을 포함한다. +- 보드에 돌을 놓기 +- 플레이어 턴 전환 +- 가로, 세로, 대각선 승리 조건 확인 +- 삼삼, 사사, 장목과 같은 금수 규칙 적용 ### GameLogicTest.kt `GameLogicTest` 클래스는 JUnit 5와 AssertJ를 사용하여 `OmokGame`의 주요 기능을 테스트. 삼삼, 사사, 장목 금수 규칙과 승리 조건을 검증한다. + +- 가로 승리 조건 +- 세로 승리 조건 +- 대각선 승리 조건 (우하향, 우상향) +- 흑돌 금수 규칙 (삼삼, 사사, 장목) + +