diff --git a/.gitignore b/.gitignore
index 6c0187813..347e252ef 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,32 +1,33 @@
-HELP.md
-.gradle
+# Gradle files
+.gradle/
build/
-!gradle/wrapper/gradle-wrapper.jar
-!**/src/main/**
-!**/src/test/**
-### STS ###
-.apt_generated
-.classpath
-.factorypath
-.project
-.settings
-.springBeans
-.sts4-cache
+# Local configuration file (sdk path, etc)
+local.properties
-### IntelliJ IDEA ###
-.idea
-*.iws
+# Log/OS Files
+*.log
+
+# Android Studio generated files and folders
+captures/
+.externalNativeBuild/
+.cxx/
+*.apk
+output.json
+
+# IntelliJ
*.iml
-*.ipr
-out/
+.idea/
+misc.xml
+deploymentTargetDropDown.xml
+render.experimental.xml
+
+# Keystore files
+*.jks
+*.keystore
-### NetBeans ###
-/nbproject/private/
-/nbbuild/
-/dist/
-/nbdist/
-/.nb-gradle/
+# Google Services (e.g. APIs or Firebase)
+google-services.json
-### VS Code ###
-.vscode/
+# Android Profiling
+*.hprof
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
new file mode 100644
index 000000000..22d36f8c2
--- /dev/null
+++ b/app/build.gradle.kts
@@ -0,0 +1,47 @@
+plugins {
+ id("com.android.application")
+ id("org.jetbrains.kotlin.android")
+}
+
+android {
+ namespace = "woowacourse.omok"
+ compileSdk = 33
+
+ defaultConfig {
+ applicationId = "woowacourse.omok"
+ minSdk = 26
+ targetSdk = 33
+ versionCode = 1
+ versionName = "1.0"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ getByName("release") {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+}
+
+dependencies {
+ implementation(project(":domain"))
+ implementation("androidx.core:core-ktx:1.9.0")
+ implementation("androidx.appcompat:appcompat:1.6.0")
+ implementation("com.google.android.material:material:1.7.0")
+ implementation("androidx.constraintlayout:constraintlayout:2.1.4")
+ testImplementation("junit:junit:4.13.2")
+ androidTestImplementation("androidx.test.ext:junit:1.1.5")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/src/main/kotlin/.gitkeep b/app/src/androidTest/java/woowacourse/omok/.gitkeep
similarity index 100%
rename from src/main/kotlin/.gitkeep
rename to app/src/androidTest/java/woowacourse/omok/.gitkeep
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..ca6ae7bc0
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/woowacourse/omok/Converter.kt b/app/src/main/java/woowacourse/omok/Converter.kt
new file mode 100644
index 000000000..5860bd2ee
--- /dev/null
+++ b/app/src/main/java/woowacourse/omok/Converter.kt
@@ -0,0 +1,26 @@
+package woowacourse.omok
+
+import domain.Board
+import domain.Color
+import domain.Position
+
+object Converter {
+ fun colorToString(color: Color): String {
+ return when (color) {
+ Color.BLACK -> "흑"
+ Color.WHITE -> "백"
+ }
+ }
+
+ fun indexToPosition(index: Int): Position {
+ val boardSize = Board.getSize()
+ val x = index / boardSize
+ val y = index % boardSize
+ return Position(x, y)
+ }
+
+ fun positionToIndex(position: Position): Int {
+ val boardSize = Board.getSize()
+ return position.x * boardSize + position.y
+ }
+}
diff --git a/app/src/main/java/woowacourse/omok/MainActivity.kt b/app/src/main/java/woowacourse/omok/MainActivity.kt
new file mode 100644
index 000000000..0749865ca
--- /dev/null
+++ b/app/src/main/java/woowacourse/omok/MainActivity.kt
@@ -0,0 +1,112 @@
+package woowacourse.omok
+
+import android.os.Bundle
+import android.widget.ImageView
+import android.widget.TableLayout
+import android.widget.TableRow
+import android.widget.TextView
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.children
+import domain.* // ktlint-disable no-wildcard-imports
+
+class MainActivity : AppCompatActivity() {
+
+ private val turnTextView: TextView by lazy { findViewById(R.id.turn) }
+ private val boardCoordinateViews: List by lazy {
+ findViewById(R.id.board)
+ .children
+ .filterIsInstance()
+ .flatMap { it.children }
+ .filterIsInstance()
+ .toList()
+ }
+ private val omokDbHelper: OmokDbHelper by lazy {
+ OmokDbHelper(this)
+ }
+ private val omokGame: OmokGame by lazy {
+ omokDbHelper.getOmokGame(rule = RenjuRuleAdapter())
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+
+ printInitBoard(boardCoordinateViews, omokGame.board)
+ showTurn(omokGame.currentColor)
+ gameStart()
+ }
+
+ private fun printInitBoard(boardCoordinateViews: List, board: Board) {
+ board.stones.values.forEach { stone ->
+ val index = Converter.positionToIndex(stone.position)
+ val stoneImage = getStoneImage(stone.color)
+ boardCoordinateViews[index].setImageResource(stoneImage)
+ }
+ }
+
+ private fun printStone(view: ImageView, stone: Stone) {
+ view.setImageResource(getStoneImage(stone.color))
+ }
+
+ private fun getStoneImage(color: Color): Int {
+ return when (color) {
+ Color.BLACK -> R.drawable.black_stone
+ Color.WHITE -> R.drawable.white_stone
+ }
+ }
+
+ private fun printWinner(color: Color) {
+ when (color) {
+ Color.BLACK -> showToastMessage(WINNER_MESSAGE.format(BLACK))
+ Color.WHITE -> showToastMessage(WINNER_MESSAGE.format(WHITE))
+ }
+ }
+
+ private fun showTurn(color: Color) {
+ turnTextView.text = TURN_MESSAGE.format(Converter.colorToString(color))
+ }
+
+ private fun reset(allCoordinate: List, omokGame: OmokGame) {
+ omokGame.resetGame()
+ allCoordinate.forEach { it.setImageResource(0) }
+ omokDbHelper.deleteOmokDatabase()
+ }
+
+ private fun showToastMessage(message: String): Unit =
+ Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
+
+ private fun gameStart(): Unit = boardCoordinateViews.forEachIndexed { index, view ->
+ clickBoard(index, view)
+ }
+
+ private fun clickBoard(index: Int, view: ImageView) {
+ view.setOnClickListener {
+ if (omokGame.isRunning()) gameRunning(index, view)
+ if (!omokGame.isRunning()) gameFinished()
+ }
+ }
+
+ private fun gameRunning(index: Int, view: ImageView) {
+ val stone = omokGame.getStone(Converter.indexToPosition(index))
+ val isSuccess = omokGame.placeTo(stone)
+ if (isSuccess) {
+ printStone(view, stone)
+ omokDbHelper.updateOmokDatabase(stone)
+ }
+ showTurn(omokGame.currentColor)
+ }
+
+ private fun gameFinished() {
+ omokGame.getWinnerColor()?.let { printWinner(it) }
+ reset(boardCoordinateViews, omokGame)
+ showTurn(omokGame.currentColor)
+ }
+
+ companion object {
+ private const val WINNER_MESSAGE = "%s의 승리입니다"
+ private const val TURN_MESSAGE = "%s의 차례입니다"
+ private const val BLACK = "흑"
+ private const val WHITE = "백"
+ }
+}
diff --git a/app/src/main/java/woowacourse/omok/OmokContract.kt b/app/src/main/java/woowacourse/omok/OmokContract.kt
new file mode 100644
index 000000000..d9157e987
--- /dev/null
+++ b/app/src/main/java/woowacourse/omok/OmokContract.kt
@@ -0,0 +1,10 @@
+package woowacourse.omok
+
+import android.provider.BaseColumns
+
+object OmokContract : BaseColumns {
+ const val TABLE_NAME = "omok"
+ const val TABLE_COLUMN_COLOR = "color"
+ const val TABLE_COLUMN_X = "x"
+ const val TABLE_COLUMN_Y = "y"
+}
diff --git a/app/src/main/java/woowacourse/omok/OmokDbHelper.kt b/app/src/main/java/woowacourse/omok/OmokDbHelper.kt
new file mode 100644
index 000000000..41a70d7dd
--- /dev/null
+++ b/app/src/main/java/woowacourse/omok/OmokDbHelper.kt
@@ -0,0 +1,88 @@
+package woowacourse.omok
+
+import android.content.ContentValues
+import android.content.Context
+import android.database.Cursor
+import android.database.sqlite.SQLiteDatabase
+import android.database.sqlite.SQLiteOpenHelper
+import domain.* // ktlint-disable no-wildcard-imports
+
+class OmokDbHelper(context: Context?) :
+ SQLiteOpenHelper(context, "${OmokContract.TABLE_NAME}", null, 1) {
+
+ private val omokWritableDb: SQLiteDatabase = this.writableDatabase
+ override fun onCreate(db: SQLiteDatabase?) {
+ db?.execSQL(
+ "CREATE TABLE ${OmokContract.TABLE_NAME} (" +
+ "${OmokContract.TABLE_COLUMN_COLOR} varchar(2) not null," +
+ "${OmokContract.TABLE_COLUMN_X} int not null," +
+ "${OmokContract.TABLE_COLUMN_Y} int not null," +
+ "UNIQUE (x,y)" +
+ ");",
+ )
+ }
+
+ override fun onUpgrade(db: SQLiteDatabase?, p1: Int, p2: Int) {
+ db?.execSQL("DROP TABLE IF EXISTS ${OmokContract.TABLE_NAME}")
+ onCreate(db)
+ }
+
+ fun getOmokGame(rule: Rule): OmokGame {
+ val stones = getAllStonesInDatabase()
+ return OmokGame(Board(stones, rule))
+ }
+
+ fun updateOmokDatabase(stone: Stone) {
+ val contentValues = getStoneContentValues(stone)
+ omokWritableDb.insert(OmokContract.TABLE_NAME, null, contentValues)
+ }
+
+ fun deleteOmokDatabase() {
+ omokWritableDb.delete(OmokContract.TABLE_NAME, null, null)
+ }
+
+ private fun getAllStonesSearchCursor(): Cursor {
+ return omokWritableDb.rawQuery("SELECT * FROM ${OmokContract.TABLE_NAME}", null)
+ }
+
+ private fun getAllStonesInDatabase(): Stones {
+ val cursor = getAllStonesSearchCursor()
+ var stones = Stones(listOf())
+ while (cursor.moveToNext()) {
+ val color =
+ cursor.getString(cursor.getColumnIndexOrThrow(OmokContract.TABLE_COLUMN_COLOR))
+ val x = cursor.getInt(cursor.getColumnIndexOrThrow(OmokContract.TABLE_COLUMN_X))
+ val y = cursor.getInt(cursor.getColumnIndexOrThrow(OmokContract.TABLE_COLUMN_Y))
+ stones = stones.addStone(Stone(stringToColorInDb(color), Position(x, y)))
+ }
+ return stones
+ }
+
+ private fun getStoneContentValues(stone: Stone): ContentValues {
+ val values = ContentValues()
+ values.put("color", colorToStringInDb(stone.color))
+ values.put("x", stone.position.x)
+ values.put("y", stone.position.y)
+ return values
+ }
+
+ private fun colorToStringInDb(color: Color): String {
+ return when (color) {
+ Color.BLACK -> BLACK_STONE_COLOR
+ Color.WHITE -> WHITE_STONE_COLOR
+ }
+ }
+
+ private fun stringToColorInDb(message: String): Color {
+ return when (message) {
+ BLACK_STONE_COLOR -> Color.BLACK
+ WHITE_STONE_COLOR -> Color.WHITE
+ else -> throw IllegalArgumentException("잘못된 색")
+ }
+ }
+
+ companion object {
+ const val BLACK_STONE_COLOR = "흑돌"
+ const val WHITE_STONE_COLOR = "백돌"
+ }
+}
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 000000000..7706ab9e6
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/black_stone.xml b/app/src/main/res/drawable/black_stone.xml
new file mode 100644
index 000000000..e41ab996a
--- /dev/null
+++ b/app/src/main/res/drawable/black_stone.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/board_bottom.xml b/app/src/main/res/drawable/board_bottom.xml
new file mode 100644
index 000000000..bdaed0648
--- /dev/null
+++ b/app/src/main/res/drawable/board_bottom.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/board_bottom_left.xml b/app/src/main/res/drawable/board_bottom_left.xml
new file mode 100644
index 000000000..9a84958f5
--- /dev/null
+++ b/app/src/main/res/drawable/board_bottom_left.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/board_bottom_right.xml b/app/src/main/res/drawable/board_bottom_right.xml
new file mode 100644
index 000000000..7bfc5610f
--- /dev/null
+++ b/app/src/main/res/drawable/board_bottom_right.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/board_center.xml b/app/src/main/res/drawable/board_center.xml
new file mode 100644
index 000000000..160d32551
--- /dev/null
+++ b/app/src/main/res/drawable/board_center.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/board_left.xml b/app/src/main/res/drawable/board_left.xml
new file mode 100644
index 000000000..77ea5a9b7
--- /dev/null
+++ b/app/src/main/res/drawable/board_left.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/board_right.xml b/app/src/main/res/drawable/board_right.xml
new file mode 100644
index 000000000..c7ba218de
--- /dev/null
+++ b/app/src/main/res/drawable/board_right.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/board_top.xml b/app/src/main/res/drawable/board_top.xml
new file mode 100644
index 000000000..b57c40320
--- /dev/null
+++ b/app/src/main/res/drawable/board_top.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/board_top_left.xml b/app/src/main/res/drawable/board_top_left.xml
new file mode 100644
index 000000000..329e5f399
--- /dev/null
+++ b/app/src/main/res/drawable/board_top_left.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/board_top_right.xml b/app/src/main/res/drawable/board_top_right.xml
new file mode 100644
index 000000000..0d5b25d3b
--- /dev/null
+++ b/app/src/main/res/drawable/board_top_right.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 000000000..07d5da9cb
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/white_stone.xml b/app/src/main/res/drawable/white_stone.xml
new file mode 100644
index 000000000..5682b872f
--- /dev/null
+++ b/app/src/main/res/drawable/white_stone.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 000000000..7962c6dc8
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,1195 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 000000000..6b78462d6
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 000000000..6b78462d6
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml
new file mode 100644
index 000000000..b3e26b4c6
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 000000000..c209e78ec
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..b2dfe3d1b
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 000000000..4f0f1d64e
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..62b611da0
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 000000000..948a3070f
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..1b9a6956b
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 000000000..28d4b77f9
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..9287f5083
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 000000000..aa7d6427e
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..9126ae37c
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
new file mode 100644
index 000000000..9cc0341f6
--- /dev/null
+++ b/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 000000000..ca1931bca
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 000000000..ea74e9730
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Omok
+
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 000000000..2735be95d
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 000000000..148c18b65
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 000000000..0c4f95cab
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/src/test/kotlin/.gitkeep b/app/src/test/java/woowacourse/omok/.gitkeep
similarity index 100%
rename from src/test/kotlin/.gitkeep
rename to app/src/test/java/woowacourse/omok/.gitkeep
diff --git a/build.gradle.kts b/build.gradle.kts
index e78e72956..bf92af74f 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,32 +1,15 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
- kotlin("jvm") version "1.8.10"
- id("org.jlleitschuh.gradle.ktlint") version "10.3.0"
-}
-
-group = "camp.nextstep.edu"
-version = "1.0-SNAPSHOT"
+ val agpVersion = "7.4.0"
+ id("com.android.application") version agpVersion apply false
+ id("com.android.library") version agpVersion apply false
-repositories {
- mavenCentral()
-}
-
-dependencies {
- testImplementation("org.junit.jupiter", "junit-jupiter", "5.8.2")
- testImplementation("org.assertj", "assertj-core", "3.22.0")
- testImplementation("io.kotest", "kotest-runner-junit5", "5.2.3")
+ val kotlinVersion = "1.8.10"
+ kotlin("android") version kotlinVersion apply false
+ kotlin("jvm") version kotlinVersion apply false
+ id("org.jlleitschuh.gradle.ktlint") version "10.3.0"
}
-tasks {
- compileKotlin {
- kotlinOptions.jvmTarget = "11"
- }
- compileTestKotlin {
- kotlinOptions.jvmTarget = "11"
- }
- test {
- useJUnitPlatform()
- }
- ktlint {
- verbose.set(true)
- }
+allprojects {
+ apply(plugin = "org.jlleitschuh.gradle.ktlint")
}
diff --git a/domain/.gitignore b/domain/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/domain/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts
new file mode 100644
index 000000000..b314ca2af
--- /dev/null
+++ b/domain/build.gradle.kts
@@ -0,0 +1,27 @@
+plugins {
+ kotlin("jvm")
+}
+
+group = "camp.nextstep.edu"
+version = "1.0-SNAPSHOT"
+
+dependencies {
+ testImplementation("org.junit.jupiter", "junit-jupiter", "5.8.2")
+ testImplementation("org.assertj", "assertj-core", "3.22.0")
+ testImplementation("io.kotest", "kotest-runner-junit5", "5.2.3")
+}
+
+tasks {
+ compileKotlin {
+ kotlinOptions.jvmTarget = "11"
+ }
+ compileTestKotlin {
+ kotlinOptions.jvmTarget = "11"
+ }
+ test {
+ useJUnitPlatform()
+ }
+ ktlint {
+ verbose.set(true)
+ }
+}
diff --git a/src/main/kotlin/Application.kt b/domain/src/main/kotlin/Application.kt
similarity index 70%
rename from src/main/kotlin/Application.kt
rename to domain/src/main/kotlin/Application.kt
index 8ffb22a6b..a1d60e5ac 100644
--- a/src/main/kotlin/Application.kt
+++ b/domain/src/main/kotlin/Application.kt
@@ -1,5 +1,3 @@
-package domain
-
fun main() {
Controller().run()
}
diff --git a/domain/src/main/kotlin/Controller.kt b/domain/src/main/kotlin/Controller.kt
new file mode 100644
index 000000000..aa14cc270
--- /dev/null
+++ b/domain/src/main/kotlin/Controller.kt
@@ -0,0 +1,21 @@
+import domain.Board
+import domain.OmokGame
+import domain.RenjuRuleAdapter
+import view.InputView
+import view.OutputView
+
+class Controller {
+ fun run() {
+ OutputView.printStart()
+ val omokGame = OmokGame(Board(rule = RenjuRuleAdapter()))
+ OutputView.printCurrentState(omokGame)
+ while (omokGame.isRunning()) {
+ val position = InputView.inputPosition()
+ val stone = omokGame.getStone(position)
+ val isSuccess = omokGame.placeTo(stone)
+ if (isSuccess) OutputView.printCurrentState(omokGame)
+ }
+ val winner = omokGame.getWinnerColor()
+ if (winner != null) OutputView.printResult(winner, omokGame.board)
+ }
+}
diff --git a/domain/src/main/kotlin/domain/Board.kt b/domain/src/main/kotlin/domain/Board.kt
new file mode 100644
index 000000000..bb9fafc54
--- /dev/null
+++ b/domain/src/main/kotlin/domain/Board.kt
@@ -0,0 +1,30 @@
+package domain
+
+class Board(
+ initStones: Stones = Stones(listOf()),
+ private val rule: Rule,
+) {
+ var stones: Stones = initStones
+ private set
+
+ fun placeStone(stone: Stone) {
+ stones = stones.addStone(stone)
+ }
+
+ fun canPlace(stone: Stone): Boolean {
+ return !stones.isContainSamePositionStone(stone.position)
+ }
+
+ fun getWinnerColor(): Color? {
+ return rule.getWinner(stones)
+ }
+
+ fun removeAllStones() {
+ stones = Stones(listOf())
+ }
+
+ companion object {
+ private const val BOARD_SIZE = 15
+ fun getSize(): Int = BOARD_SIZE
+ }
+}
diff --git a/src/main/kotlin/domain/Color.kt b/domain/src/main/kotlin/domain/Color.kt
similarity index 100%
rename from src/main/kotlin/domain/Color.kt
rename to domain/src/main/kotlin/domain/Color.kt
diff --git a/domain/src/main/kotlin/domain/OmokGame.kt b/domain/src/main/kotlin/domain/OmokGame.kt
new file mode 100644
index 000000000..56d29bc9e
--- /dev/null
+++ b/domain/src/main/kotlin/domain/OmokGame.kt
@@ -0,0 +1,55 @@
+package domain
+
+class OmokGame(val board: Board) {
+ var currentColor: Color = INITIAL_COLOR
+ private set
+ private var currentState: State = State.Running
+
+ init {
+ changeCurrentColor()
+ }
+
+ private fun changeStateFinished() {
+ if (board.getWinnerColor() != null) currentState = State.Finished
+ }
+
+ fun getWinnerColor(): Color? {
+ return board.getWinnerColor()
+ }
+
+ fun placeTo(stone: Stone): Boolean {
+ if (!board.canPlace(stone)) return false
+ board.placeStone(stone)
+ changeCurrentColor()
+ changeStateFinished()
+ return true
+ }
+
+ fun getStone(position: Position): Stone {
+ return Stone(currentColor, position)
+ }
+
+ private fun changeCurrentColor() {
+ val lastStone = board.stones.getLastStone()
+ lastStone?.let { lastStone ->
+ currentColor = when (lastStone.color) {
+ Color.BLACK -> Color.WHITE
+ Color.WHITE -> Color.BLACK
+ }
+ }
+ }
+
+ fun isRunning(): Boolean {
+ return currentState == State.Running
+ }
+
+ fun resetGame() {
+ board.removeAllStones()
+ currentState = State.Running
+ currentColor = INITIAL_COLOR
+ }
+
+ companion object {
+ private val INITIAL_COLOR = Color.BLACK
+ }
+}
diff --git a/src/main/kotlin/domain/Position.kt b/domain/src/main/kotlin/domain/Position.kt
similarity index 100%
rename from src/main/kotlin/domain/Position.kt
rename to domain/src/main/kotlin/domain/Position.kt
diff --git a/domain/src/main/kotlin/domain/RenjuRuleAdapter.kt b/domain/src/main/kotlin/domain/RenjuRuleAdapter.kt
new file mode 100644
index 000000000..cab16e1ea
--- /dev/null
+++ b/domain/src/main/kotlin/domain/RenjuRuleAdapter.kt
@@ -0,0 +1,79 @@
+package domain
+
+import library.* // ktlint-disable no-wildcard-imports
+
+class RenjuRuleAdapter() : Rule {
+
+ private val fourFourRule = FourFourRule(Board.getSize())
+ private val threeThreeRule = ThreeThreeRule(Board.getSize())
+ private val blackWinRule = BlackWinRule(Board.getSize())
+ private val whiteWinRule = WhiteWinRule(Board.getSize())
+ private val moreThanFiveRule = MoreThanFiveRule(Board.getSize())
+
+ override fun getWinner(stones: Stones): Color? {
+ val board = generateCustomBoard(stones)
+ val lastPlacedStone = stones.getLastStone() ?: return null
+ if (isBlackWin(board, lastPlacedStone)) return Color.BLACK
+ if (isWhiteWin(board, lastPlacedStone)) return Color.WHITE
+ return null
+ }
+
+ private fun isBlackWin(board: List>, lastPlacedStone: Stone): Boolean {
+ if (lastPlacedStone.color != Color.BLACK) return false
+ return blackWinRule.validate(
+ board,
+ Pair(lastPlacedStone.position.x, lastPlacedStone.position.y),
+ )
+ }
+
+ private fun isWhiteWin(board: List>, lastPlacedStone: Stone): Boolean {
+ if (lastPlacedStone.color == Color.WHITE) {
+ return whiteWinRule.validate(
+ board,
+ Pair(lastPlacedStone.position.x, lastPlacedStone.position.y),
+ )
+ }
+ return isBlackLose(board, lastPlacedStone)
+ }
+
+ private fun isBlackLose(board: List>, lastPlacedStone: Stone): Boolean {
+ return isFourFour(board, lastPlacedStone) or isThreeThree(
+ board,
+ lastPlacedStone,
+ ) or isMoreThanFive(board, lastPlacedStone)
+ }
+
+ private fun isFourFour(board: List>, stone: Stone): Boolean {
+ return fourFourRule.validate(
+ board,
+ Pair(stone.position.x, stone.position.y),
+ )
+ }
+
+ private fun isThreeThree(board: List>, stone: Stone): Boolean {
+ return threeThreeRule.validate(
+ board,
+ Pair(stone.position.x, stone.position.y),
+ )
+ }
+
+ private fun isMoreThanFive(board: List>, stone: Stone): Boolean {
+ return moreThanFiveRule.validate(
+ board,
+ Pair(stone.position.x, stone.position.y),
+ )
+ }
+
+ private fun generateCustomBoard(stones: Stones): List> {
+ val libraryBoard = List(Board.getSize()) {
+ MutableList(Board.getSize()) { 0 }
+ }
+ stones.values.forEach {
+ when (it.color) {
+ Color.BLACK -> libraryBoard[it.position.y][it.position.x] = 1
+ Color.WHITE -> libraryBoard[it.position.y][it.position.x] = 2
+ }
+ }
+ return libraryBoard
+ }
+}
diff --git a/domain/src/main/kotlin/domain/Rule.kt b/domain/src/main/kotlin/domain/Rule.kt
new file mode 100644
index 000000000..2153fb08e
--- /dev/null
+++ b/domain/src/main/kotlin/domain/Rule.kt
@@ -0,0 +1,5 @@
+package domain
+
+interface Rule {
+ fun getWinner(stones: Stones): Color?
+}
diff --git a/domain/src/main/kotlin/domain/State.kt b/domain/src/main/kotlin/domain/State.kt
new file mode 100644
index 000000000..5089c5fe6
--- /dev/null
+++ b/domain/src/main/kotlin/domain/State.kt
@@ -0,0 +1,6 @@
+package domain
+
+sealed class State {
+ object Running : State()
+ object Finished : State()
+}
diff --git a/domain/src/main/kotlin/domain/Stone.kt b/domain/src/main/kotlin/domain/Stone.kt
new file mode 100644
index 000000000..888c5af81
--- /dev/null
+++ b/domain/src/main/kotlin/domain/Stone.kt
@@ -0,0 +1,3 @@
+package domain
+
+data class Stone(val color: Color, val position: Position)
diff --git a/domain/src/main/kotlin/domain/Stones.kt b/domain/src/main/kotlin/domain/Stones.kt
new file mode 100644
index 000000000..53482bdef
--- /dev/null
+++ b/domain/src/main/kotlin/domain/Stones.kt
@@ -0,0 +1,33 @@
+package domain
+
+class Stones(val values: List) {
+
+ init {
+ checkHasDuplicatePosition()
+ }
+
+ fun addStone(stone: Stone): Stones {
+ return Stones(values.plus(stone))
+ }
+
+ fun isContainSamePositionStone(position: Position): Boolean {
+ return values.any { it.position == position }
+ }
+
+ fun getLastStone(): Stone? {
+ return values.lastOrNull()
+ }
+
+ private fun checkHasDuplicatePosition() {
+ check(values.all { stone -> getCountSamePositionStone(stone) == ONLY_ONE_POSITION }) { DUPLICATE_POSITION_ERROR }
+ }
+
+ private fun getCountSamePositionStone(stone: Stone): Int {
+ return values.count { it.position == stone.position }
+ }
+
+ companion object {
+ private const val DUPLICATE_POSITION_ERROR = "같은 위치에 있는 돌이 존재할 수 없어요!"
+ private const val ONLY_ONE_POSITION = 1
+ }
+}
diff --git a/domain/src/main/kotlin/library/BlackWinRule.kt b/domain/src/main/kotlin/library/BlackWinRule.kt
new file mode 100644
index 000000000..2462440bf
--- /dev/null
+++ b/domain/src/main/kotlin/library/BlackWinRule.kt
@@ -0,0 +1,22 @@
+package library
+
+import domain.Rule
+
+class BlackWinRule(boardSize: Int) : OmokRule(boardSize) {
+ override fun validate(board: List>, position: Pair): Boolean =
+ directions.map { direction -> checkWhiteWin(board, position, direction) }.contains(true)
+
+ private fun checkWhiteWin(
+ board: List>,
+ position: Pair,
+ direction: Pair
+ ): Boolean {
+ val oppositeDirection = direction.let { (dx, dy) -> Pair(-dx, -dy) }
+ val (stone1, blink1) = search(board, position, oppositeDirection)
+ val (stone2, blink2) = search(board, position, direction)
+ return when {
+ blink1 + blink2 == 0 && stone1 + stone2 == 4 -> true
+ else -> false
+ }
+ }
+}
\ No newline at end of file
diff --git a/domain/src/main/kotlin/library/FourFourRule.kt b/domain/src/main/kotlin/library/FourFourRule.kt
new file mode 100644
index 000000000..6f016cd2d
--- /dev/null
+++ b/domain/src/main/kotlin/library/FourFourRule.kt
@@ -0,0 +1,46 @@
+package library
+
+class FourFourRule(boardSize: Int) : OmokRule(boardSize) {
+ override fun validate(board: List>, position: Pair): Boolean =
+ countOpenThrees(board, position) >= 2
+
+ private fun countOpenThrees(board: List>, position: Pair): Int =
+ directions.sumOf { direction -> checkOpenFour(board, position, direction) }
+
+ private fun checkOpenFour(
+ board: List>,
+ position: Pair,
+ direction: Pair,
+ ): Int {
+ val (x, y) = position
+ val (dx, dy) = direction
+ val oppositeDirection = direction.let { (dx, dy) -> Pair(-dx, -dy) }
+ val (stone1, blink1) = search(board, position, oppositeDirection)
+ val (stone2, blink2) = search(board, position, direction)
+ val leftDown = stone1 + blink1
+ val left = dx * (leftDown + 1)
+ val down = dy * (leftDown + 1)
+ val rightUp = stone2 + blink2
+ val right = dx * (rightUp + 1)
+ val up = dy * (rightUp + 1)
+ when {
+ blink1 + blink2 == 2 && stone1 + stone2 == 4 -> return 2
+ blink1 + blink2 == 2 && stone1 + stone2 == 5 -> return 2
+ stone1 + stone2 != 3 -> return 0
+ blink1 + blink2 == 2 -> return 0
+ }
+ val leftDownValid = when {
+ dx != 0 && x - dx * leftDown in xEdge -> 0
+ dy != 0 && y - dy * leftDown in yEdge -> 0
+ board[y - down][x - left] == opponentStone -> 0
+ else -> 1
+ }
+ val rightUpValid = when {
+ dx != 0 && x + (dx * rightUp) in xEdge -> 0
+ dy != 0 && y + (dy * rightUp) in yEdge -> 0
+ board[y + up][x + right] == opponentStone -> 0
+ else -> 1
+ }
+ return if (leftDownValid + rightUpValid >= 1) 1 else 0
+ }
+}
\ No newline at end of file
diff --git a/domain/src/main/kotlin/library/MoreThanFiveRule.kt b/domain/src/main/kotlin/library/MoreThanFiveRule.kt
new file mode 100644
index 000000000..c47b528fc
--- /dev/null
+++ b/domain/src/main/kotlin/library/MoreThanFiveRule.kt
@@ -0,0 +1,20 @@
+package library
+
+class MoreThanFiveRule(boardSize: Int) : OmokRule(boardSize) {
+ override fun validate(board: List>, position: Pair): Boolean =
+ directions.map { direction -> checkBlackWin(board, position, direction) }.contains(true)
+
+ private fun checkBlackWin(
+ board: List>,
+ position: Pair,
+ direction: Pair
+ ): Boolean {
+ val oppositeDirection = direction.let { (dx, dy) -> Pair(-dx, -dy) }
+ val (stone1, blink1) = search(board, position, oppositeDirection)
+ val (stone2, blink2) = search(board, position, direction)
+ return when {
+ blink1 + blink2 == 0 && stone1 + stone2 > 4 -> true
+ else -> false
+ }
+ }
+}
\ No newline at end of file
diff --git a/domain/src/main/kotlin/library/OmokRule.kt b/domain/src/main/kotlin/library/OmokRule.kt
new file mode 100644
index 000000000..e3a0df30a
--- /dev/null
+++ b/domain/src/main/kotlin/library/OmokRule.kt
@@ -0,0 +1,80 @@
+package library
+
+abstract class OmokRule(
+ boardSize: Int,
+ private val currentStone: Int = BLACK_STONE,
+ val opponentStone: Int = WHITE_STONE,
+) {
+
+ private val maxX = boardSize - 1
+ private val maxY = boardSize - 1
+ protected val xEdge = listOf(MIN_X, maxX)
+ protected val yEdge = listOf(MIN_Y, maxY)
+
+ abstract fun validate(board: List>, position: Pair): Boolean
+ protected val directions = listOf(Pair(1, 0), Pair(1, 1), Pair(0, 1), Pair(1, -1))
+ protected fun search(
+ board: List>,
+ position: Pair,
+ direction: Pair,
+ ): Pair {
+ var (x, y) = position
+ val (dx, dy) = direction
+ var stone = 0
+ var blink = 0
+ var blinkCount = 0
+ while (willExceedBounds(x, y, dx, dy).not()) {
+ x += dx
+ y += dy
+ when (board[y][x]) {
+ currentStone -> {
+ stone++
+ blink = blinkCount
+ }
+ opponentStone -> break
+ EMPTY_STONE -> {
+ if (blink == 1) break
+ if (blinkCount++ == 1) break
+ }
+ else -> throw IllegalArgumentException("스톤 케이스를 에러")
+ }
+ }
+ return Pair(stone, blink)
+ }
+
+ protected fun countToWall(
+ board: List>,
+ position: Pair,
+ direction: Pair,
+ ): Int {
+ var (x, y) = position
+ val (dx, dy) = direction
+ var distance = 0
+ while (willExceedBounds(x, y, dx, dy).not()) {
+ x += dx
+ y += dy
+ when (board[y][x]) {
+ in listOf(currentStone, EMPTY_STONE) -> distance++
+ opponentStone -> break
+ else -> throw IllegalArgumentException()
+ }
+ }
+ return distance
+ }
+
+ private fun willExceedBounds(x: Int, y: Int, dx: Int, dy: Int): Boolean = when {
+ dx > 0 && x == maxX -> true
+ dx < 0 && x == MIN_X -> true
+ dy > 0 && y == maxY -> true
+ dy < 0 && y == MIN_Y -> true
+ else -> false
+ }
+
+ companion object {
+ protected const val EMPTY_STONE = 0
+ const val BLACK_STONE = 1
+ const val WHITE_STONE = 2
+ const val MIN_X = 0
+ const val MIN_Y = 0
+ }
+}
\ No newline at end of file
diff --git a/domain/src/main/kotlin/library/ThreeThreeRule.kt b/domain/src/main/kotlin/library/ThreeThreeRule.kt
new file mode 100644
index 000000000..575517b30
--- /dev/null
+++ b/domain/src/main/kotlin/library/ThreeThreeRule.kt
@@ -0,0 +1,43 @@
+package library
+
+class ThreeThreeRule(boardSize: Int) : OmokRule(boardSize) {
+ override fun validate(board: List>, position: Pair): Boolean =
+ countOpenThrees(board, position) >= 2
+
+ private fun countOpenThrees(board: List>, position: Pair): Int =
+ directions.sumOf { direction -> checkOpenThree(board, position, direction) }
+
+ private fun checkOpenThree(
+ board: List>,
+ position: Pair,
+ direction: Pair,
+ ): Int {
+ val (x, y) = position
+ val (dx, dy) = direction
+ val oppositeDirection = direction.let { (dx, dy) -> Pair(-dx, -dy) }
+ val (stone1, blink1) = search(board, position, oppositeDirection)
+ val (stone2, blink2) = search(board, position, direction)
+ val leftDown = stone1 + blink1
+ val left = dx * (leftDown + 1)
+ val down = dy * (leftDown + 1)
+ val rightUp = stone2 + blink2
+ val right = dx * (rightUp + 1)
+ val up = dy * (rightUp + 1)
+ return when {
+ stone1 + stone2 != 2 -> 0
+ blink1 + blink2 == 2 -> 0
+ dx != 0 && x - dx * leftDown in xEdge -> 0
+ dy != 0 && y - dy * leftDown in yEdge -> 0
+ dx != 0 && x + dx * rightUp in xEdge -> 0
+ dy != 0 && y + dy * rightUp in yEdge -> 0
+ board[y - down][x - left] == WHITE_STONE -> 0
+ board[y + up][x + right] == WHITE_STONE -> 0
+ countToWall(board, position, oppositeDirection) + countToWall(
+ board,
+ position,
+ direction
+ ) <= 5 -> 0
+ else -> 1
+ }
+ }
+}
\ No newline at end of file
diff --git a/domain/src/main/kotlin/library/WhiteWinRule.kt b/domain/src/main/kotlin/library/WhiteWinRule.kt
new file mode 100644
index 000000000..14f89315a
--- /dev/null
+++ b/domain/src/main/kotlin/library/WhiteWinRule.kt
@@ -0,0 +1,20 @@
+package library
+
+class WhiteWinRule(boardSize: Int) : OmokRule(boardSize,WHITE_STONE, BLACK_STONE) {
+ override fun validate(board: List>, position: Pair): Boolean =
+ directions.map { direction -> checkBlackWin(board, position, direction) }.contains(true)
+
+ private fun checkBlackWin(
+ board: List>,
+ position: Pair,
+ direction: Pair
+ ): Boolean {
+ val oppositeDirection = direction.let { (dx, dy) -> Pair(-dx, -dy) }
+ val (stone1, blink1) = search(board, position, oppositeDirection)
+ val (stone2, blink2) = search(board, position, direction)
+ return when {
+ blink1 + blink2 == 0 && stone1 + stone2 >= 4 -> true
+ else -> false
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/view/AlphabetCoordinate.kt b/domain/src/main/kotlin/view/AlphabetCoordinate.kt
similarity index 100%
rename from src/main/kotlin/view/AlphabetCoordinate.kt
rename to domain/src/main/kotlin/view/AlphabetCoordinate.kt
diff --git a/src/main/kotlin/view/BoardParts.kt b/domain/src/main/kotlin/view/BoardParts.kt
similarity index 100%
rename from src/main/kotlin/view/BoardParts.kt
rename to domain/src/main/kotlin/view/BoardParts.kt
diff --git a/src/main/kotlin/view/InputView.kt b/domain/src/main/kotlin/view/InputView.kt
similarity index 100%
rename from src/main/kotlin/view/InputView.kt
rename to domain/src/main/kotlin/view/InputView.kt
diff --git a/src/main/kotlin/view/OutputView.kt b/domain/src/main/kotlin/view/OutputView.kt
similarity index 80%
rename from src/main/kotlin/view/OutputView.kt
rename to domain/src/main/kotlin/view/OutputView.kt
index 96da44aeb..f314b546c 100644
--- a/src/main/kotlin/view/OutputView.kt
+++ b/domain/src/main/kotlin/view/OutputView.kt
@@ -1,23 +1,20 @@
package view
-import domain.Board
-import domain.Color
-import domain.Position
-import domain.Stones
+import domain.*
object OutputView {
- fun printCurrentState(board: Board) {
- printBoard(board)
- printTurn(board.getCurrentTurn())
- printLastPosition(board.getLastPosition())
+ fun printCurrentState(omokGame: OmokGame) {
+ printBoard(omokGame.board)
+ printTurn(omokGame.currentColor)
+ printLastPosition(omokGame.board.stones.getLastStone())
}
fun printStart() {
println("오목 게임을 시작합니다.")
}
- fun printBoard(board: Board) {
+ private fun printBoard(board: Board) {
val customBoard = generateCustomBoard(board.stones)
customBoard.forEachIndexed { y, colors ->
print("${Board.getSize() - y} ".padStart(4, ' '))
@@ -40,19 +37,19 @@ object OutputView {
println()
}
- fun printTurn(color: Color) {
+ private fun printTurn(color: Color) {
when (color) {
Color.BLACK -> print("흑의 차례입니다.")
Color.WHITE -> print("백의 차례입니다.")
}
}
- fun printLastPosition(position: Position?) {
- if (position == null) {
+ private fun printLastPosition(stone: Stone?) {
+ if (stone == null) {
println()
return
}
- println(" (마지막 돌의 위치: ${AlphabetCoordinate.convertAlphabet(position.x)}${position.y})")
+ println(" (마지막 돌의 위치: ${AlphabetCoordinate.convertAlphabet(stone.position.x)}${stone.position.y})")
}
fun printResult(color: Color, board: Board) {
@@ -68,7 +65,7 @@ object OutputView {
MutableList(Board.getSize()) { 0 }
}
stones.values.forEach {
- if (it.isBlack()) {
+ if (it.color== Color.BLACK) {
initBoard[Board.getSize() - it.position.y][it.position.x] = BLACK
} else {
initBoard[Board.getSize() - it.position.y][it.position.x] = WHITE
diff --git a/domain/src/test/kotlin/domain/BoardTest.kt b/domain/src/test/kotlin/domain/BoardTest.kt
new file mode 100644
index 000000000..f981641fe
--- /dev/null
+++ b/domain/src/test/kotlin/domain/BoardTest.kt
@@ -0,0 +1,218 @@
+package domain
+
+import org.assertj.core.api.Assertions
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+
+class BoardTest {
+ private fun Stone(color: Color, vararg positions: Int): Stone {
+ return Stone(color, Position(positions[0], positions[1]))
+ }
+
+ @Test
+ fun `보드에 바둑돌을 놓으면,보드에 바둑돌이 추가된다`() {
+ // given
+ val board = Board(rule = RenjuRuleAdapter())
+ val newStone = Stone(Color.WHITE, Position(1, 2))
+ // when
+ board.placeStone(newStone)
+ // then
+ val actual = board.stones.values
+ val expected = listOf(newStone)
+ Assertions.assertThat(actual).isEqualTo(expected)
+ }
+
+ @Test
+ fun `같은 위치에 바둑돌은 놓을 수 없다`() {
+ // given
+ val stone = Stone(Color.WHITE, Position(1, 2))
+ val board = Board(Stones(listOf(stone)), rule = RenjuRuleAdapter())
+ val samePositionStone = Stone(Color.BLACK, Position(1, 2))
+ // when
+ board.placeStone(samePositionStone)
+ val actual = board.stones.values.size
+ val expected = 1
+ // then
+ assertThat(actual).isEqualTo(expected)
+ }
+
+ @Test
+ fun `다른 위치에 바둑돌을 놓을 수 있다`() {
+ // given
+ val stone = Stone(Color.WHITE, Position(1, 2))
+ val board = Board(Stones(listOf(stone)), rule = RenjuRuleAdapter())
+ val differentPositionStone = Stone(Color.WHITE, Position(2, 3))
+ // when
+ board.placeStone(differentPositionStone)
+ val actual = board.stones.values.size
+ val expected = 2
+ // then
+ assertThat(actual).isEqualTo(expected)
+ }
+
+ // 1
+ // 2 ◎ ●
+ // 3 ◎ ●
+ // 4 ◎ ● ●
+ // 5 ◎
+ // 6 ?
+ // 1 2 3 4 5 6
+ @Test
+ fun `흑의 오목이 완성되면 흑의의 승리이다`() {
+ // given
+ val board = generateBlackWinOmokBoard()
+ val stone = Stone(Color.BLACK, 1, 6)
+ // when
+ val actual = board.getWinnerColor()
+ // then
+ assertThat(actual).isEqualTo(Color.BLACK)
+ }
+
+ private fun generateBlackWinOmokBoard(): Board {
+ val board = Board(rule = RenjuRuleAdapter()).apply {
+ placeStone(Stone(Color.BLACK, 1, 2))
+ placeStone(Stone(Color.WHITE, 2, 2))
+ placeStone(Stone(Color.BLACK, 1, 3))
+ placeStone(Stone(Color.WHITE, 2, 3))
+ placeStone(Stone(Color.BLACK, 1, 4))
+ placeStone(Stone(Color.WHITE, 2, 4))
+ placeStone(Stone(Color.BLACK, 1, 5))
+ placeStone(Stone(Color.WHITE, 4, 8))
+ placeStone(Stone(Color.BLACK, 1, 6))
+ }
+ return board
+ }
+
+ // 1
+ // 2 ● ◎
+ // 3 ● ◎
+ // 4 ● ◎ ◎ ◎
+ // 5 ●
+ // 6 ?
+ // 1 2 3 4 5 6
+ @Test
+ fun `백의 오목이 완성되면 백의 승리이다`() {
+ // given
+ val board = generateWhiteWinOmokBoard()
+ // when
+ val actual = board.getWinnerColor()
+ // then
+ assertThat(actual).isEqualTo(Color.WHITE)
+ }
+
+ private fun generateWhiteWinOmokBoard(): Board {
+ val board = Board(rule = RenjuRuleAdapter()).apply {
+ placeStone(Stone(Color.BLACK, 2, 2))
+ placeStone(Stone(Color.WHITE, 1, 2))
+ placeStone(Stone(Color.BLACK, 2, 3))
+ placeStone(Stone(Color.WHITE, 1, 3))
+ placeStone(Stone(Color.BLACK, 2, 4))
+ placeStone(Stone(Color.WHITE, 1, 4))
+ placeStone(Stone(Color.BLACK, 2, 5))
+ placeStone(Stone(Color.WHITE, 1, 5))
+ placeStone(Stone(Color.BLACK, 2, 10))
+ placeStone(Stone(Color.WHITE, 1, 6))
+ }
+ return board
+ }
+
+ // 1
+ // 2 ● ◎
+ // 3 ● ◎
+ // 4 ● ? ◎ ◎ ●
+ // 5
+ // 6
+ // 1 2 3 4 5 6
+ @Test
+ fun `흑이 33이면 백의 승리이다(흑의 패배이다)`() {
+ // given
+ val board = generateThreeThreeBoard()
+ // when
+ val actual = board.getWinnerColor()
+ // then
+ assertThat(actual).isEqualTo(Color.WHITE)
+ }
+
+ private fun generateThreeThreeBoard(): Board {
+ val board = Board(rule = RenjuRuleAdapter()).apply {
+ placeStone(Stone(Color.BLACK, 2, 2))
+ placeStone(Stone(Color.WHITE, 4, 7))
+ placeStone(Stone(Color.BLACK, 2, 3))
+ placeStone(Stone(Color.WHITE, 4, 8))
+ placeStone(Stone(Color.BLACK, 3, 4))
+ placeStone(Stone(Color.WHITE, 5, 13))
+ placeStone(Stone(Color.BLACK, 4, 4))
+ placeStone(Stone(Color.WHITE, 6, 10))
+ placeStone(Stone(Color.BLACK, 2, 4))
+ }
+ return board
+ }
+
+ // 3 ? ◎ ◎ ◎ ●
+ // 4 ◎
+ // 5
+ // 6 ◎
+ // 7 ● ● ● ● ◎ ●
+ // 3 4 5 6 7 8 9 10
+ @Test
+ fun `흑이 44이면 백의 승리이다(흑의 패배이다)`() {
+ // given
+ val board = generateFourFourBoard()
+ // when
+ val actual = board.getWinnerColor()
+ // then
+ assertThat(actual).isEqualTo(Color.WHITE)
+ }
+
+ private fun generateFourFourBoard(): Board {
+ val board = Board(rule = RenjuRuleAdapter()).apply {
+ placeStone(Stone(Color.BLACK, 4, 3))
+ placeStone(Stone(Color.WHITE, 3, 7))
+ placeStone(Stone(Color.BLACK, 6, 3))
+ placeStone(Stone(Color.WHITE, 4, 7))
+ placeStone(Stone(Color.BLACK, 7, 3))
+ placeStone(Stone(Color.WHITE, 5, 7))
+ placeStone(Stone(Color.BLACK, 4, 4))
+ placeStone(Stone(Color.WHITE, 6, 7))
+ placeStone(Stone(Color.BLACK, 6, 6))
+ placeStone(Stone(Color.WHITE, 8, 7))
+ placeStone(Stone(Color.BLACK, 7, 7))
+ placeStone(Stone(Color.WHITE, 8, 3))
+ placeStone(Stone(Color.BLACK, 3, 3))
+ }
+ return board
+ }
+
+ // 3 ◎ ◎ ? ◎ ◎ ◎
+ // 4
+ // 5
+ // 6
+ // 7 ● ● ● ● ●
+ // 3 4 5 6 7 8 9 10
+ @Test
+ fun `흑이 장목이면 백의 승리이다(흑의 패배이다)`() {
+ // given
+ val board = generateMoreFiveBoard()
+ // when
+ val actual = board.getWinnerColor()
+ // then
+ assertThat(actual).isEqualTo(Color.WHITE)
+ }
+
+ private fun generateMoreFiveBoard(): Board {
+ val board = Board(rule = RenjuRuleAdapter()).apply {
+ placeStone(Stone(Color.BLACK, 3, 3))
+ placeStone(Stone(Color.WHITE, 3, 7))
+ placeStone(Stone(Color.BLACK, 4, 3))
+ placeStone(Stone(Color.WHITE, 4, 7))
+ placeStone(Stone(Color.BLACK, 6, 3))
+ placeStone(Stone(Color.WHITE, 5, 7))
+ placeStone(Stone(Color.BLACK, 7, 3))
+ placeStone(Stone(Color.WHITE, 6, 7))
+ placeStone(Stone(Color.BLACK, 8, 3))
+ placeStone(Stone(Color.WHITE, 8, 7))
+ placeStone(Stone(Color.BLACK, 5, 3))
+ }
+ return board
+ }
+}
diff --git a/domain/src/test/kotlin/domain/OmokGameTest.kt b/domain/src/test/kotlin/domain/OmokGameTest.kt
new file mode 100644
index 000000000..f31686f58
--- /dev/null
+++ b/domain/src/test/kotlin/domain/OmokGameTest.kt
@@ -0,0 +1,120 @@
+package domain
+
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+
+internal class OmokGameTest {
+ private fun Stone(color: Color, vararg positions: Int): Stone {
+ return Stone(color, Position(positions[0], positions[1]))
+ }
+
+ @Test
+ fun `흑이 이겼을 경우 검정색을 반환한다`() {
+ // given
+ val board = generateBlackWinOmokBoard()
+ val omokGame = OmokGame(board)
+ // when
+ val stone = omokGame.getStone(Position(1, 6))
+ omokGame.placeTo(stone)
+ val actual = omokGame.getWinnerColor()
+ val expected = Color.BLACK
+ // then
+ assertThat(actual).isEqualTo(expected)
+ }
+
+ private fun generateBlackWinOmokBoard(): Board {
+ val board = Board(rule = RenjuRuleAdapter()).apply {
+ placeStone(Stone(Color.BLACK, 1, 2))
+ placeStone(Stone(Color.WHITE, 2, 2))
+ placeStone(Stone(Color.BLACK, 1, 3))
+ placeStone(Stone(Color.WHITE, 2, 3))
+ placeStone(Stone(Color.BLACK, 1, 4))
+ placeStone(Stone(Color.WHITE, 2, 4))
+ placeStone(Stone(Color.BLACK, 1, 5))
+ placeStone(Stone(Color.WHITE, 4, 8))
+ }
+ return board
+ }
+
+ @Test
+ fun `백이 이겼을 경우 하얀색을 반환한다`() {
+ // given
+ val board = generateWhiteWinOmokBoard()
+ val omokGame = OmokGame(board)
+ // when
+ val stone = omokGame.getStone(Position(1, 6))
+ omokGame.placeTo(stone)
+ val actual = omokGame.getWinnerColor()
+ val expected = Color.WHITE
+ // then
+ assertThat(actual).isEqualTo(expected)
+ }
+
+ private fun generateWhiteWinOmokBoard(): Board {
+ val board = Board(rule = RenjuRuleAdapter()).apply {
+ placeStone(Stone(Color.BLACK, 2, 2))
+ placeStone(Stone(Color.WHITE, 1, 2))
+ placeStone(Stone(Color.BLACK, 2, 3))
+ placeStone(Stone(Color.WHITE, 1, 3))
+ placeStone(Stone(Color.BLACK, 2, 4))
+ placeStone(Stone(Color.WHITE, 1, 4))
+ placeStone(Stone(Color.BLACK, 2, 5))
+ placeStone(Stone(Color.WHITE, 1, 5))
+ placeStone(Stone(Color.BLACK, 2, 10))
+ }
+ return board
+ }
+
+ @Test
+ fun `게임을 초기화 하면 돌의 개수는 0 이된다`() {
+ // given
+ val board = generateWhiteWinOmokBoard()
+ val omokGame = OmokGame(board)
+
+ // when
+ omokGame.resetGame()
+ val actual = omokGame.board.stones.values.size
+ val expected = 0
+
+ // then
+ assertThat(actual).isEqualTo(expected)
+ }
+
+ @Test
+ fun `게임을 초기화 하면 게임의 턴은 흑이된다`() {
+ // given
+ val board = generateWhiteWinOmokBoard()
+ val omokGame = OmokGame(board)
+ // when
+ omokGame.resetGame()
+ val actual = omokGame.currentColor
+ val expected = Color.BLACK
+
+ // then
+ assertThat(actual).isEqualTo(expected)
+ }
+
+ @Test
+ fun `마지막에 위치한 돌이 검정색이라면, 다음 차례는 흰색이다`() {
+ val board = Board(Stones(listOf(Stone(Color.BLACK, 1, 1))), RenjuRuleAdapter())
+ val omokGame = OmokGame(board)
+ // when
+ val actual = omokGame.currentColor
+ val expected = Color.WHITE
+
+ // then
+ assertThat(actual).isEqualTo(expected)
+ }
+
+ @Test
+ fun `마지막에 위치한 돌이 흰색이라면, 다음 차례는 검정색이다`() {
+ val board = Board(Stones(listOf(Stone(Color.WHITE, 1, 1))), RenjuRuleAdapter())
+ val omokGame = OmokGame(board)
+ // when
+ val actual = omokGame.currentColor
+ val expected = Color.BLACK
+
+ // then
+ assertThat(actual).isEqualTo(expected)
+ }
+}
diff --git a/src/test/kotlin/domain/PositionTest.kt b/domain/src/test/kotlin/domain/PositionTest.kt
similarity index 100%
rename from src/test/kotlin/domain/PositionTest.kt
rename to domain/src/test/kotlin/domain/PositionTest.kt
diff --git a/domain/src/test/kotlin/domain/StoneTest.kt b/domain/src/test/kotlin/domain/StoneTest.kt
new file mode 100644
index 000000000..10f31da38
--- /dev/null
+++ b/domain/src/test/kotlin/domain/StoneTest.kt
@@ -0,0 +1,17 @@
+package domain
+
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+
+class StoneTest {
+ @Test
+ fun `바둑돌은 색상과 위치를 갖는다`() {
+ // given
+ val position = Position(1, 1)
+ val color = Color.BLACK
+ // when
+ val actual = Stone(color, position)
+ // then
+ assertThat(actual).isInstanceOf(Stone::class.java)
+ }
+}
diff --git a/domain/src/test/kotlin/domain/StonesTest.kt b/domain/src/test/kotlin/domain/StonesTest.kt
new file mode 100644
index 000000000..8f09fbbfc
--- /dev/null
+++ b/domain/src/test/kotlin/domain/StonesTest.kt
@@ -0,0 +1,36 @@
+package domain
+
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+
+class StonesTest {
+ @Test
+ fun `다른 위치에 있는 바둑돌을 추가 할 수 있다`() {
+ // given
+ val stones = makeStones()
+ val newStone = Stone(Color.BLACK, Position(5, 6))
+ // when
+ val actual = stones.addStone(newStone).values
+ val expected = makeStones().values + newStone
+ // then
+ assertThat(actual).isEqualTo(expected)
+ }
+
+ @Test
+ fun `같은 위치의 바둑돌을 포함하고 있을 수 없다`() {
+ assertThrows { makeDuplicateStones() }
+ }
+
+ private fun makeStones(): Stones {
+ val blackStone = Stone(Color.BLACK, Position(1, 2))
+ val whiteStone = Stone(Color.WHITE, Position(2, 3))
+ return Stones(listOf(blackStone, whiteStone))
+ }
+
+ private fun makeDuplicateStones(): Stones {
+ val blackStone = Stone(Color.BLACK, Position(1, 2))
+ val whiteStone = Stone(Color.WHITE, Position(2, 3))
+ return Stones(listOf(blackStone, whiteStone))
+ }
+}
diff --git a/gradle.properties b/gradle.properties
index 7fc6f1ff2..2cbd6d19d 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1 +1,23 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 0d04cb9e8..5c61d823d 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1 +1,17 @@
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
rootProject.name = "kotlin-omok"
+include(":app")
+include(":domain")
diff --git a/src/main/kotlin/Controller.kt b/src/main/kotlin/Controller.kt
deleted file mode 100644
index 3caf9c867..000000000
--- a/src/main/kotlin/Controller.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package domain
-
-import view.InputView
-import view.OutputView
-
-class Controller {
- fun run() {
- OutputView.printStart()
- val omokGame = OmokGame(Board())
- val winnerColor = omokGame.getWinnerColor(OutputView::printCurrentState, InputView::inputPosition)
- OutputView.printResult(winnerColor, omokGame.board)
- }
-}
diff --git a/src/main/kotlin/domain/Board.kt b/src/main/kotlin/domain/Board.kt
deleted file mode 100644
index 13460af0a..000000000
--- a/src/main/kotlin/domain/Board.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-package domain
-
-class Board(
- initStones: Stones = Stones(listOf()),
-) {
- var stones: Stones = initStones
- private set
- private val rule: Rule
- get() = RuleAdapter(stones, getCurrentTurn())
-
- fun placeStone(stone: Stone) {
- stones = stones.addStone(stone)
- }
-
- fun isEmpty(stone: Stone): Boolean {
- return !stones.isContainSamePositionStone(stone.position)
- }
-
- fun getLastPosition(): Position? {
- if (stones.values.isEmpty()) return null
- return stones.values.last().position
- }
-
- fun isBlackWin(stone: Stone): Boolean {
- return rule.checkBlackWin(stone)
- }
-
- fun isWhiteWin(stone: Stone): Boolean {
- if (rule.checkInvalid(stone)) return true
- return rule.checkWhiteWin(stone)
- }
-
- fun getCurrentTurn(): Color {
- if (stones.getBlackStonesCount() > stones.getWhiteStonesCount()) return Color.WHITE
- return Color.BLACK
- }
-
- companion object {
- private const val BOARD_SIZE = 15
- fun getSize(): Int = BOARD_SIZE
- }
-}
diff --git a/src/main/kotlin/domain/OmokGame.kt b/src/main/kotlin/domain/OmokGame.kt
deleted file mode 100644
index 178f2a39a..000000000
--- a/src/main/kotlin/domain/OmokGame.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package domain
-
-class OmokGame(val board: Board) {
- fun getWinnerColor(showCurrentState: (Board) -> Unit, getPosition: () -> Position): Color {
- val stone = getStone(showCurrentState, getPosition)
- val winnerColor = judgeWinner(stone)
- board.placeStone(stone)
- return winnerColor ?: return getWinnerColor(showCurrentState, getPosition)
- }
-
- private fun getStone(showCurrentState: (Board) -> Unit, getPosition: () -> Position): Stone {
- showCurrentState(board)
- val stone = Stone(board.getCurrentTurn(), getPosition())
- if (!board.isEmpty(stone)) return getStone(showCurrentState, getPosition)
- return stone
- }
-
- private fun judgeWinner(stone: Stone): Color? {
- when {
- board.isBlackWin(stone) -> return Color.BLACK
- board.isWhiteWin(stone) -> return Color.WHITE
- }
- return null
- }
-}
diff --git a/src/main/kotlin/domain/Rule.kt b/src/main/kotlin/domain/Rule.kt
deleted file mode 100644
index 4b8e869bd..000000000
--- a/src/main/kotlin/domain/Rule.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package domain
-
-interface Rule {
- fun checkThreeThree(stone: Stone): Boolean
- fun checkFourFour(stone: Stone): Boolean
- fun checkBlackWin(stone: Stone): Boolean
- fun checkWhiteWin(stone: Stone): Boolean
- fun checkMoreThanFive(stone: Stone): Boolean
- fun checkInvalid(stone: Stone): Boolean
-}
diff --git a/src/main/kotlin/domain/RuleAdapter.kt b/src/main/kotlin/domain/RuleAdapter.kt
deleted file mode 100644
index 5d435b9be..000000000
--- a/src/main/kotlin/domain/RuleAdapter.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-package domain
-
-import library.OmokRule
-
-class RuleAdapter(stones: Stones, currentColor: Color) : Rule {
- private val omokRule =
- OmokRule(
- generateCustomBoard(stones),
- colorToInt(currentColor),
- getOtherColorToInt(currentColor),
- Board.getSize(),
- )
-
- override fun checkThreeThree(stone: Stone): Boolean {
- return omokRule.checkThreeThree(stone.position.x, stone.position.y)
- }
-
- override fun checkFourFour(stone: Stone): Boolean {
- return omokRule.countFourFour(stone.position.x, stone.position.y)
- }
-
- override fun checkBlackWin(stone: Stone): Boolean {
- if (stone.isBlack()) return omokRule.validateBlackWin(stone.position.x, stone.position.y)
- return false
- }
-
- override fun checkWhiteWin(stone: Stone): Boolean {
- if (stone.isWhite()) return omokRule.validateWhiteWin(stone.position.x, stone.position.y)
- return false
- }
-
- override fun checkMoreThanFive(stone: Stone): Boolean {
- return omokRule.checkMoreThanFive(stone.position.x, stone.position.y)
- }
-
- override fun checkInvalid(stone: Stone): Boolean {
- if (stone.isBlack()) return checkThreeThree(stone) || checkFourFour(stone) || checkMoreThanFive(stone)
- return false
- }
-
- private fun generateCustomBoard(stones: Stones): List> {
- val libraryBoard = List(Board.getSize()) {
- MutableList(Board.getSize()) { 0 }
- }
- stones.values.forEach {
- if (it.isBlack()) {
- libraryBoard[it.position.y][it.position.x] = 1
- } else {
- libraryBoard[it.position.y][it.position.x] = 2
- }
- }
- return libraryBoard
- }
-
- private fun colorToInt(color: Color): Int {
- if (color == Color.BLACK) return 1
- return 2
- }
-
- private fun getOtherColorToInt(color: Color): Int {
- if (color == Color.BLACK) return 2
- return 1
- }
-}
diff --git a/src/main/kotlin/domain/Stone.kt b/src/main/kotlin/domain/Stone.kt
deleted file mode 100644
index 0a17d8f76..000000000
--- a/src/main/kotlin/domain/Stone.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package domain
-
-data class Stone(val color: Color, val position: Position) {
-
- fun isBlack(): Boolean {
- return color == Color.BLACK
- }
-
- fun isWhite(): Boolean {
- return color == Color.WHITE
- }
-}
diff --git a/src/main/kotlin/domain/Stones.kt b/src/main/kotlin/domain/Stones.kt
deleted file mode 100644
index 2847354f7..000000000
--- a/src/main/kotlin/domain/Stones.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package domain
-
-class Stones(values: List) {
- private val _values = values.deepCopy()
- val values: List
- get() = _values.deepCopy()
-
- private fun List.deepCopy(): List = map { it.copy() }
- fun addStone(stone: Stone): Stones {
- val newStones = values.toMutableList()
- newStones.add(stone)
- return Stones(newStones)
- }
-
- fun isContainSamePositionStone(position: Position): Boolean {
- return values.any { it.position == position }
- }
-
- fun getBlackStonesCount(): Int {
- if (values.isEmpty()) return 0
- return values.count { it.isBlack() }
- }
-
- fun getWhiteStonesCount(): Int {
- if (values.isEmpty()) return 0
- return values.count { it.isWhite() }
- }
-}
diff --git a/src/main/kotlin/library/OmokRule.kt b/src/main/kotlin/library/OmokRule.kt
deleted file mode 100644
index c7b52da79..000000000
--- a/src/main/kotlin/library/OmokRule.kt
+++ /dev/null
@@ -1,174 +0,0 @@
-package library
-
-class OmokRule(
- private val board: List>,
- private val currentStone: Int = BLACK_STONE,
- private val otherStone: Int = WHITE_STONE,
- private val boardSize: Int,
-) {
- private val directions = listOf(listOf(1, 0), listOf(1, 1), listOf(0, 1), listOf(1, -1))
- fun checkThreeThree(x: Int, y: Int): Boolean =
- directions.sumOf { direction -> checkOpenThree(x, y, direction[0], direction[1]) } >= 2
-
- fun countFourFour(x: Int, y: Int): Boolean =
- directions.sumOf { direction -> checkOpenFour(x, y, direction[0], direction[1]) } >= 2
-
- fun checkMoreThanFive(x: Int, y: Int): Boolean =
- directions.map { direction -> checkMoreThanFive(x, y, direction[0], direction[1]) }.contains(true)
-
- fun validateWhiteWin(x: Int, y: Int): Boolean =
- directions.map { direction -> checkWhiteWin(x, y, direction[0], direction[1]) }.contains(true)
-
- fun validateBlackWin(x: Int, y: Int): Boolean =
- directions.map { direction -> checkBlackWin(x, y, direction[0], direction[1]) }.contains(true)
-
- private fun checkOpenThree(x: Int, y: Int, dx: Int, dy: Int): Int {
- val (stone1, blink1) = search(x, y, -dx, -dy)
- val (stone2, blink2) = search(x, y, dx, dy)
-
- val leftDown = stone1 + blink1
- val left = dx * (leftDown + 1)
- val down = dy * (leftDown + 1)
-
- val rightUp = stone2 + blink2
- val right = dx * (rightUp + 1)
- val up = dy * (rightUp + 1)
-
- return when {
- stone1 + stone2 != 2 -> 0
- blink1 + blink2 == 2 -> 0
- dx != 0 && x - leftDown in listOf(MIN_X, boardSize - 1) -> 0
- dy != 0 && y - leftDown in listOf(MIN_Y, boardSize - 1) -> 0
- dx != 0 && x + rightUp in listOf(MIN_X, boardSize - 1) -> 0
- dy != 0 && y + rightUp in listOf(MIN_Y, boardSize - 1) -> 0
- board[y - down][x - left] == otherStone -> 0
- board[y + up][x + right] == otherStone -> 0
- countToWall(x, y, -dx, -dy) + countToWall(x, y, dx, dy) <= 5 -> 0
- else -> 1
- }
- }
-
- private fun countToWall(x: Int, y: Int, dx: Int, dy: Int): Int {
- var toRight = x
- var toTop = y
- var distance = 0
- while (true) {
- if (dx > 0 && toRight == boardSize - 1) break
- if (dx < 0 && toRight == MIN_X) break
- if (dy > 0 && toTop == boardSize - 1) break
- if (dy < 0 && toTop == MIN_X) break
- toRight += dx
- toTop += dy
- when (board[toTop][toRight]) {
- in listOf(currentStone, EMPTY_STONE) -> distance++
- otherStone -> break
- else -> throw IllegalArgumentException()
- }
- }
- return distance
- }
-
- private fun checkOpenFour(x: Int, y: Int, dx: Int, dy: Int): Int {
- val (stone1, blink1) = search(x, y, -dx, -dy)
- val (stone2, blink2) = search(x, y, dx, dy)
-
- val leftDown = stone1 + blink1
- val left = dx * (leftDown + 1)
- val down = dy * (leftDown + 1)
-
- val rightUp = stone2 + blink2
- val right = dx * (rightUp + 1)
- val up = dy * (rightUp + 1)
-
- when {
- blink1 + blink2 == 2 && stone1 + stone2 == 4 -> return 2
- blink1 + blink2 == 2 && stone1 + stone2 == 5 -> return 2
- stone1 + stone2 != 3 -> return 0
- blink1 + blink2 == 2 -> return 0
- }
-
- val leftDownValid = when {
- dx != 0 && x - dx * leftDown in listOf(MIN_X, boardSize - 1) -> 0
- dy != 0 && y - dy * leftDown in listOf(MIN_Y, boardSize - 1) -> 0
- board[y - down][x - left] == otherStone -> 0
- else -> 1
- }
- val rightUpValid = when {
- dx != 0 && x + (dx * rightUp) in listOf(MIN_X, boardSize - 1) -> 0
- dy != 0 && y + (dy * rightUp) in listOf(MIN_Y, boardSize - 1) -> 0
- board[y + up][x + right] == otherStone -> 0
- else -> 1
- }
-
- return if (leftDownValid + rightUpValid >= 1) 1 else 0
- }
-
- private fun checkMoreThanFive(x: Int, y: Int, dx: Int, dy: Int): Boolean {
- val (stone1, blink1) = search(x, y, -dx, -dy)
- val (stone2, blink2) = search(x, y, dx, dy)
-
- return when {
- blink1 + blink2 == 0 && stone1 + stone2 > 4 -> true
- else -> false
- }
- }
-
- private fun checkBlackWin(x: Int, y: Int, dx: Int, dy: Int): Boolean {
- val (stone1, blink1) = search(x, y, -dx, -dy)
- val (stone2, blink2) = search(x, y, dx, dy)
-
- return when {
- blink1 + blink2 == 0 && stone1 + stone2 == 4 -> true
- else -> false
- }
- }
-
- private fun checkWhiteWin(x: Int, y: Int, dx: Int, dy: Int): Boolean {
- val (stone1, blink1) = search(x, y, -dx, -dy)
- val (stone2, blink2) = search(x, y, dx, dy)
-
- return when {
- blink1 + blink2 == 0 && stone1 + stone2 >= 4 -> true
- else -> false
- }
- }
-
- private fun search(x: Int, y: Int, dx: Int, dy: Int): Pair {
- var toRight = x
- var toTop = y
- var stone = 0
- var blink = 0
- var blinkCount = 0
- while (true) {
- if (dx > 0 && toRight == boardSize - 1) break
- if (dx < 0 && toRight == MIN_X) break
- if (dy > 0 && toTop == boardSize - 1) break
- if (dy < 0 && toTop == MIN_X) break
- toRight += dx
- toTop += dy
- when (board[toTop][toRight]) {
- currentStone -> {
- stone++
- blink = blinkCount
- }
-
- otherStone -> break
- EMPTY_STONE -> {
- if (blink == 1) break
- if (blinkCount++ == 1) break
- }
-
- else -> throw IllegalArgumentException()
- }
- }
- return Pair(stone, blink)
- }
-
- companion object {
- private const val EMPTY_STONE = 0
- private const val BLACK_STONE = 1
- private const val WHITE_STONE = 2
- private const val MIN_X = 0
- private const val MIN_Y = 0
- }
-}
diff --git a/src/test/kotlin/domain/BoardTest.kt b/src/test/kotlin/domain/BoardTest.kt
deleted file mode 100644
index e99fd51e9..000000000
--- a/src/test/kotlin/domain/BoardTest.kt
+++ /dev/null
@@ -1,236 +0,0 @@
-package domain
-
-import org.assertj.core.api.Assertions
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.jupiter.api.Test
-
-class BoardTest {
- private fun Stone(color: Color, vararg positions: Int): Stone {
- return Stone(color, Position(positions[0], positions[1]))
- }
-
- @Test
- fun `보드에 바둑돌을 놓으면,보드에 바둑돌이 추가된다`() {
- // given
- val board = Board()
- val newStone = Stone(Color.WHITE, Position(1, 2))
- // when
- board.placeStone(newStone)
- // then
- val actual = board.stones.values
- val expected = listOf(newStone)
- Assertions.assertThat(actual).isEqualTo(expected)
- }
-
- @Test
- fun `같은 위치에 바둑돌은 놓을 수 없다`() {
- // given
- val stone = Stone(Color.WHITE, Position(1, 2))
- val board = Board(Stones(listOf(stone)))
- val samePositionStone = Stone(Color.BLACK, Position(1, 2))
- // when
- val actual = board.isEmpty(samePositionStone)
- // then
- Assertions.assertThat(actual).isFalse
- }
-
- @Test
- fun `다른 위치에 바둑돌을 놓을 수 있다`() {
- // given
- val stone = Stone(Color.WHITE, Position(1, 2))
- val board = Board()
- val differentPositionStone = Stone(Color.WHITE, Position(2, 3))
- // when
- val actual = board.isEmpty(differentPositionStone)
- // then
- Assertions.assertThat(actual).isTrue
- }
-
- @Test
- fun `보드에 바둑돌이 없다면, 마지막 바둑돌의 위치를 가져오려고 할 때 null을 반환한다`() {
- // given
- val board = Board()
- // when
- val actual = board.getLastPosition()
- val expected = null
- // then
- Assertions.assertThat(actual).isEqualTo(expected)
- }
-
- @Test
- fun `마지막으로 둔 바둑돌의 위치를 알 수 있다`() {
- // given
- val stone = Stone(Color.WHITE, Position(1, 2))
- val board = Board(Stones(listOf(stone)))
- // when
- val actual = board.getLastPosition()
- val expected = Position(1, 2)
- // then
- Assertions.assertThat(actual).isEqualTo(expected)
- }
-
- // 1
- // 2 ◎ ●
- // 3 ◎ ●
- // 4 ◎ ● ●
- // 5 ◎
- // 6 ?
- // 1 2 3 4 5 6
- @Test
- fun `흑의 오목이 완성되면 흑의의 승리이다`() {
- // given
- val board = generateBlackWinOmokBoard()
- val stone = Stone(Color.BLACK, 1, 6)
- // when
- val actual = board.isBlackWin(stone)
- // then
- assertThat(actual).isEqualTo(true)
- }
-
- private fun generateBlackWinOmokBoard(): Board {
- val board = Board().apply {
- placeStone(Stone(getCurrentTurn(), 1, 2))
- placeStone(Stone(getCurrentTurn(), 2, 2))
- placeStone(Stone(getCurrentTurn(), 1, 3))
- placeStone(Stone(getCurrentTurn(), 2, 3))
- placeStone(Stone(getCurrentTurn(), 1, 4))
- placeStone(Stone(getCurrentTurn(), 2, 4))
- placeStone(Stone(getCurrentTurn(), 1, 5))
- placeStone(Stone(getCurrentTurn(), 4, 8))
- }
- return board
- }
-
- // 1
- // 2 ● ◎
- // 3 ● ◎
- // 4 ● ◎ ◎ ◎
- // 5 ●
- // 6 ?
- // 1 2 3 4 5 6
- @Test
- fun `백의 오목이 완성되면 백의 승리이다`() {
- // given
- val board = generateWhiteWinOmokBoard()
- val stone = Stone(Color.WHITE, 1, 6)
- // when
- val actual = board.isWhiteWin(stone)
- // then
- assertThat(actual).isEqualTo(true)
- }
-
- private fun generateWhiteWinOmokBoard(): Board {
- val board = Board().apply {
- placeStone(Stone(getCurrentTurn(), 2, 2))
- placeStone(Stone(getCurrentTurn(), 1, 2))
- placeStone(Stone(getCurrentTurn(), 2, 3))
- placeStone(Stone(getCurrentTurn(), 1, 3))
- placeStone(Stone(getCurrentTurn(), 2, 4))
- placeStone(Stone(getCurrentTurn(), 1, 4))
- placeStone(Stone(getCurrentTurn(), 2, 5))
- placeStone(Stone(getCurrentTurn(), 1, 5))
- placeStone(Stone(getCurrentTurn(), 2, 10))
- }
- return board
- }
-
- // 1
- // 2 ● ◎
- // 3 ● ◎
- // 4 ● ? ◎ ◎ ●
- // 5
- // 6
- // 1 2 3 4 5 6
- @Test
- fun `흑이 33이면 백의 승리이다(흑의 패배이다)`() {
- // given
- val board = generateThreeThreeBoard()
- val stone = Stone(Color.BLACK, 2, 4)
- // when
- val actual = board.isWhiteWin(stone)
- // then
- assertThat(actual).isEqualTo(true)
- }
-
- private fun generateThreeThreeBoard(): Board {
- val board = Board().apply {
- placeStone(Stone(getCurrentTurn(), 2, 2))
- placeStone(Stone(getCurrentTurn(), 4, 7))
- placeStone(Stone(getCurrentTurn(), 2, 3))
- placeStone(Stone(getCurrentTurn(), 4, 8))
- placeStone(Stone(getCurrentTurn(), 3, 4))
- placeStone(Stone(getCurrentTurn(), 5, 13))
- placeStone(Stone(getCurrentTurn(), 4, 4))
- placeStone(Stone(getCurrentTurn(), 6, 10))
- }
- return board
- }
-
- // 3 ? ◎ ◎ ◎ ●
- // 4 ◎
- // 5
- // 6 ◎
- // 7 ● ● ● ● ◎ ●
- // 3 4 5 6 7 8 9 10
- @Test
- fun `흑이 44이면 백의 승리이다(흑의 패배이다)`() {
- // given
- val board = generateFourFourBoard()
- val stone = Stone(Color.BLACK, 3, 3)
- // when
- val actual = board.isWhiteWin(stone)
- // then
- assertThat(actual).isEqualTo(true)
- }
-
- private fun generateFourFourBoard(): Board {
- val board = Board().apply {
- placeStone(Stone(getCurrentTurn(), 4, 3))
- placeStone(Stone(getCurrentTurn(), 3, 7))
- placeStone(Stone(getCurrentTurn(), 6, 3))
- placeStone(Stone(getCurrentTurn(), 4, 7))
- placeStone(Stone(getCurrentTurn(), 7, 3))
- placeStone(Stone(getCurrentTurn(), 5, 7))
- placeStone(Stone(getCurrentTurn(), 4, 4))
- placeStone(Stone(getCurrentTurn(), 6, 7))
- placeStone(Stone(getCurrentTurn(), 6, 6))
- placeStone(Stone(getCurrentTurn(), 8, 7))
- placeStone(Stone(getCurrentTurn(), 7, 7))
- placeStone(Stone(getCurrentTurn(), 8, 3))
- }
- return board
- }
-
- // 3 ◎ ◎ ? ◎ ◎ ◎
- // 4
- // 5
- // 6
- // 7 ● ● ● ● ●
- // 3 4 5 6 7 8 9 10
- @Test
- fun `흑이 장목이면 백의 승리이다(흑의 패배이다)`() {
- // given
- val board = generateMoreFiveBoard()
- val stone = Stone(Color.BLACK, 5, 3)
- // when
- val actual = board.isWhiteWin(stone)
- // then
- assertThat(actual).isEqualTo(true)
- }
-
- private fun generateMoreFiveBoard(): Board {
- val board = Board().apply {
- placeStone(Stone(getCurrentTurn(), 3, 3))
- placeStone(Stone(getCurrentTurn(), 3, 7))
- placeStone(Stone(getCurrentTurn(), 4, 3))
- placeStone(Stone(getCurrentTurn(), 4, 7))
- placeStone(Stone(getCurrentTurn(), 6, 3))
- placeStone(Stone(getCurrentTurn(), 5, 7))
- placeStone(Stone(getCurrentTurn(), 7, 3))
- placeStone(Stone(getCurrentTurn(), 6, 7))
- placeStone(Stone(getCurrentTurn(), 8, 3))
- placeStone(Stone(getCurrentTurn(), 8, 7))
- }
- return board
- }
-}
diff --git a/src/test/kotlin/domain/OmokGameTest.kt b/src/test/kotlin/domain/OmokGameTest.kt
deleted file mode 100644
index ee76adc3e..000000000
--- a/src/test/kotlin/domain/OmokGameTest.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-package domain
-
-import org.assertj.core.api.Assertions
-import org.junit.jupiter.api.Test
-
-internal class OmokGameTest {
- private fun Stone(color: Color, vararg positions: Int): Stone {
- return Stone(color, Position(positions[0], positions[1]))
- }
-
- @Test
- fun `흑이 이겼을 경우 검정색을 반환한다`() {
- // given
- val board = generateBlackWinOmokBoard()
- val omokGame = OmokGame(board)
- // when
- val actual = omokGame.getWinnerColor({}, { Position(1, 6) })
- val expected = Color.BLACK
- // then
- Assertions.assertThat(actual).isEqualTo(expected)
- }
-
- private fun generateBlackWinOmokBoard(): Board {
- val board = Board().apply {
- placeStone(Stone(getCurrentTurn(), 1, 2))
- placeStone(Stone(getCurrentTurn(), 2, 2))
- placeStone(Stone(getCurrentTurn(), 1, 3))
- placeStone(Stone(getCurrentTurn(), 2, 3))
- placeStone(Stone(getCurrentTurn(), 1, 4))
- placeStone(Stone(getCurrentTurn(), 2, 4))
- placeStone(Stone(getCurrentTurn(), 1, 5))
- placeStone(Stone(getCurrentTurn(), 4, 8))
- }
- return board
- }
-
- @Test
- fun `백이 이겼을 경우 하얀색을 반환한다`() {
- // given
- val board = generateWhiteWinOmokBoard()
- val omokGame = OmokGame(board)
- // when
- val actual = omokGame.getWinnerColor({}, { Position(1, 6) })
- val expected = Color.WHITE
- // then
- Assertions.assertThat(actual).isEqualTo(expected)
- }
-
- private fun generateWhiteWinOmokBoard(): Board {
- val board = Board().apply {
- placeStone(Stone(getCurrentTurn(), 2, 2))
- placeStone(Stone(getCurrentTurn(), 1, 2))
- placeStone(Stone(getCurrentTurn(), 2, 3))
- placeStone(Stone(getCurrentTurn(), 1, 3))
- placeStone(Stone(getCurrentTurn(), 2, 4))
- placeStone(Stone(getCurrentTurn(), 1, 4))
- placeStone(Stone(getCurrentTurn(), 2, 5))
- placeStone(Stone(getCurrentTurn(), 1, 5))
- placeStone(Stone(getCurrentTurn(), 2, 10))
- }
- return board
- }
-
- @Test
- fun `처음 수와 두번째 수에 아무도 이기지못하고 3번째수에 백이 이겼을 때 하얀색을 반환한다`() {
- // given
- val board = generateWhiteWinOmokBoard()
- val omokGame = OmokGame(board)
- // when
- val actual = omokGame.getWinnerColor({}, { getPosition() })
- val expected = Color.WHITE
- // then
- Assertions.assertThat(actual).isEqualTo(expected)
- }
-
- private var count = 0
- private fun getPosition(): Position {
- count++
- if (count == 1) return Position(5, 10)
- if (count == 2) return Position(5, 11)
- return Position(1, 6)
- }
-}
diff --git a/src/test/kotlin/domain/StoneTest.kt b/src/test/kotlin/domain/StoneTest.kt
deleted file mode 100644
index f0b172295..000000000
--- a/src/test/kotlin/domain/StoneTest.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-package domain
-
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.jupiter.api.Test
-import org.junit.jupiter.params.ParameterizedTest
-import org.junit.jupiter.params.provider.CsvSource
-
-class StoneTest {
- @Test
- fun `바둑돌은 색상과 위치를 갖는다`() {
- // given
- val position = Position(1, 1)
- val color = Color.BLACK
- // when
- val actual = Stone(color, position)
- // then
- assertThat(actual).isInstanceOf(Stone::class.java)
- }
-
- @CsvSource(value = ["BLACK,true", "WHITE,false"])
- @ParameterizedTest
- fun `바둑돌이 검정색인지 확인한다`(color: Color, expected: Boolean) {
- // given
- val stone = Stone(color, Position(1, 1))
- // when
- val actual = stone.isBlack()
- // then
- assertThat(actual).isEqualTo(expected)
- }
-
- @CsvSource(value = ["BLACK,false", "WHITE,true"])
- @ParameterizedTest
- fun `바둑돌이 흰색인지 확인한다`(color: Color, expected: Boolean) {
- // given
- val stone = Stone(color, Position(1, 1))
- // when
- val actual = stone.isWhite()
- // then
- assertThat(actual).isEqualTo(expected)
- }
-}
diff --git a/src/test/kotlin/domain/StonesTest.kt b/src/test/kotlin/domain/StonesTest.kt
deleted file mode 100644
index d48848b66..000000000
--- a/src/test/kotlin/domain/StonesTest.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-package domain
-
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.jupiter.api.Test
-
-class StonesTest {
- @Test
- fun `바둑돌을 추가 할 수 있다`() {
- // given
- val stones = makeStones()
- val newStone = Stone(Color.BLACK, Position(5, 6))
- // when
- stones.addStone(newStone)
- val actual = stones
- val expected = stones.values + newStone
- // then
- assertThat(actual).isEqualTo(expected)
- }
-
- @Test
- fun `같은 위치의 바둑돌을 포함하고 있으면 True 이다`() {
- // given
- val stones = makeStones()
- val samePosition = Position(1, 2)
- // when
- val actual = stones.isContainSamePositionStone(samePosition)
- // then
- assertThat(actual).isTrue
- }
-
- @Test
- fun `같은 위치의 바둑돌을 포함하고 있지 않다면 false 이다`() {
- // given
- val stones = makeStones()
- val differentPosition = Position(7, 9)
- // when
- val actual = stones.isContainSamePositionStone(differentPosition)
- // then
- assertThat(actual).isFalse
- }
-
- private fun makeStones(): Stones {
- val blackStone = Stone(Color.BLACK, Position(1, 2))
- val whiteStone = Stone(Color.WHITE, Position(2, 3))
- return Stones(listOf(blackStone, whiteStone))
- }
-}