diff --git a/README.md b/README.md index 3150c08..c4a91c0 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,78 @@ # ๋ฏธ์…˜ - ์›Œ๋“ค -## ๐Ÿ” ์ง„ํ–‰ ๋ฐฉ์‹ +## ๐Ÿ” ์ง„ํ–‰ ์š”๊ตฌ ์‚ฌํ•ญ +- ๋ฏธ์…˜์€ ์•„๋ž˜์˜ ์„ธ ๊ฐ€์ง€ ์š”๊ตฌ ์‚ฌํ•ญ์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๊ณ , ๊ฐ ์š”๊ตฌ ์‚ฌํ•ญ๋“ค์„ ๋งŒ์กฑํ•˜๊ธฐ ์œ„ํ•ด ๋…ธ๋ ฅํ•œ๋‹ค. -- ๋ฏธ์…˜์€ **๊ณผ์ œ ์ง„ํ–‰ ์š”๊ตฌ ์‚ฌํ•ญ**, **๊ธฐ๋Šฅ ์š”๊ตฌ ์‚ฌํ•ญ**, **ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์š”๊ตฌ ์‚ฌํ•ญ** ์„ธ ๊ฐ€์ง€๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋‹ค. -- ์„ธ ๊ฐœ์˜ ์š”๊ตฌ ์‚ฌํ•ญ์„ ๋งŒ์กฑํ•˜๊ธฐ ์œ„ํ•ด ๋…ธ๋ ฅํ•œ๋‹ค. ํŠนํžˆ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์ „์— ๊ธฐ๋Šฅ ๋ชฉ๋ก์„ ๋งŒ๋“ค๊ณ , ๊ธฐ๋Šฅ ๋‹จ์œ„๋กœ ์ปค๋ฐ‹ ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ง„ํ–‰ํ•œ๋‹ค. +### [**1. ์ง„ํ–‰ ์š”๊ตฌ ์‚ฌํ•ญ**](#-๊ธฐ๋Šฅ-์š”๊ตฌ-์‚ฌํ•ญ) + +- ๋ฏธ์…˜์€ `ํŽ˜์–ดํ”„๋กœ๊ทธ๋ž˜๋ฐ`์œผ๋กœ ์ง„ํ–‰ํ•œ๋‹ค. + > ์œ ์Šค๋ฐฉ 5๊ธฐ - Team ๋Œ์“ฐ + > - boradol + > - yooth + +### [**2. ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์š”๊ตฌ ์‚ฌํ•ญ**](#-ํ”„๋กœ๊ทธ๋ž˜๋ฐ-์š”๊ตฌ-์‚ฌํ•ญ) +- ๊ตฌํ˜„ ์ „ ํ™˜๊ฒฝ๊ตฌ์„ฑ์ด ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์š”๊ตฌ ์‚ฌํ•ญ์„ ํ™•์ธํ•˜๊ณ  ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์š”๊ตฌ ์‚ฌํ•ญ์„ ๋ชฉ๋กํ™”ํ•œ๋‹ค. +- ๊ตฌํ˜„ ์ค‘ ํ•„์š”ํ•œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์š”๊ตฌ์‚ฌํ•ญ๋“ค์„ ๋งŒ์กฑํ•˜๊ธฐ ์œ„ํ•ด ๋…ธ๋ ฅํ•œ๋‹ค. +- ์ œ์ถœ ์ „ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์š”๊ตฌ ์‚ฌํ•ญ์„ ์ง€์ผฐ๋Š”์ง€ ํ™•์ธํ•œ๋‹ค. + +### [**3. ๊ธฐ๋Šฅ ์š”๊ตฌ ์‚ฌํ•ญ**](#-๊ธฐ๋Šฅ-์š”๊ตฌ-์‚ฌํ•ญ) +- ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์ „์— ๊ธฐ๋Šฅ ์š”๊ตฌ ์‚ฌํ•ญ์„ ์ฐธ๊ณ ํ•˜์—ฌ **๊ธฐ๋Šฅ ๋ชฉ๋ก**์„ ๋งŒ๋“ ๋‹ค. + - ํŽ˜์–ดํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์œ„ํ•ด **์šฉ์–ด๋ฅผ ์ •๋ฆฌ**ํ•œ๋‹ค. + - [**์šฉ์–ด ์ •๋ฆฌ**](#-์šฉ์–ด-์ •๋ฆฌ)์™€ [**๊ธฐ๋Šฅ ๋ชฉ๋ก**](#-๊ธฐ๋Šฅ-๋ชฉ๋ก)์€ ๊ณ„์† ์ˆ˜์ •๋  ์ˆ˜ ์žˆ๋‹ค. +- **๊ธฐ๋Šฅ ๋‹จ์œ„๋กœ ์ปค๋ฐ‹** ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ง„ํ–‰ํ•œ๋‹ค. - **๊ธฐ๋Šฅ ์š”๊ตฌ ์‚ฌํ•ญ์— ๊ธฐ์žฌ๋˜์ง€ ์•Š์€ ๋‚ด์šฉ์€ ์Šค์Šค๋กœ ํŒ๋‹จํ•˜์—ฌ ๊ตฌํ˜„ํ•œ๋‹ค.** +--- + +## ๐ŸŽฏ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์š”๊ตฌ ์‚ฌํ•ญ + +### ํ™˜๊ฒฝ +- Kotlin 1.9.23์—์„œ ์‹คํ–‰. + - **Java ์ฝ”๋“œ๊ฐ€ ์•„๋‹Œ Kotlin ์ฝ”๋“œ๋กœ๋งŒ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.** + +### ๊ตฌํ˜„ ์ „ ํ™•์ธ ์‚ฌํ•ญ +- [x] ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰์˜ ์‹œ์ž‘์ ์€ `Application`์˜ `main()`์ด๋‹ค. +- [x] `build.gradle.kts` ํŒŒ์ผ์€ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†์œผ๋ฉฐ, **์ œ๊ณต๋œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ด์™ธ์˜ ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.** + - `Unknown Kotlin JVM target: 21` ๋ฌธ์ œ๋กœ `kotlin 1.9.23`์œผ๋กœ ์˜ฌ๋ฆผ + - [๋ฌธ์ œํ•ด๊ฒฐ](https://github.com/gradle/gradle/issues/25574#issuecomment-1761314551) +- [x] ํ”„๋กœ๊ทธ๋žจ ์ข…๋ฃŒ ์‹œ `System.exit()` ๋˜๋Š” `exitProcess()`๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š๋Š”๋‹ค. +- [x] ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์š”๊ตฌ ์‚ฌํ•ญ์—์„œ ๋‹ฌ๋ฆฌ ๋ช…์‹œํ•˜์ง€ ์•Š๋Š” ํ•œ ํŒŒ์ผ, ํŒจํ‚ค์ง€ ๋“ฑ์˜ ์ด๋ฆ„์„ ๋ฐ”๊พธ๊ฑฐ๋‚˜ ์ด๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค. + +### ๊ตฌํ˜„ ์ค‘ ํ•„์š” ์‚ฌํ•ญ +- [x] ์ฝ”ํ‹€๋ฆฐ ์ฝ”๋“œ ์ปจ๋ฒค์…˜[(Kotlin Coding conventions)](https://kotlinlang.org/docs/coding-conventions.html)์„ ์ง€ํ‚ค๋ฉด์„œ ํ”„๋กœ๊ทธ๋ž˜๋ฐํ•œ๋‹ค. + - Commit ์ „์— `ktlint`๋ฅผ ์Šต๊ด€์ ์œผ๋กœ ํ™•์ธํ•œ๋‹ค. + - ```./gradlew ktlintCheck``` + - ```./gradlew addKtlintCheckGitPreCommitHook``` +- [x] indent(์ธ๋ดํŠธ, ๋“ค์—ฌ์“ฐ๊ธฐ) depth๋ฅผ 3์ด ๋„˜์ง€ ์•Š๋„๋ก ๊ตฌํ˜„ํ•œ๋‹ค. 2๊นŒ์ง€๋งŒ ํ—ˆ์šฉํ•œ๋‹ค. +- [x] ํ•จ์ˆ˜(๋˜๋Š” ๋ฉ”์„œ๋“œ)๊ฐ€ ํ•œ ๊ฐ€์ง€ ์ผ๋งŒ ํ•˜๋„๋ก ์ตœ๋Œ€ํ•œ ์ž‘๊ฒŒ ๋งŒ๋“ค์–ด๋ผ. +- [x] ํ•จ์ˆ˜(๋˜๋Š” ๋ฉ”์„œ๋“œ)์˜ ๊ธธ์ด๊ฐ€ 15๋ผ์ธ์„ ๋„˜์–ด๊ฐ€์ง€ ์•Š๋„๋ก ๊ตฌํ˜„ํ•œ๋‹ค. +- [x] ๋„๋ฉ”์ธ ๋กœ์ง์— ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค. + - `JUnit 5`์™€ `AssertJ`๋ฅผ ์ด์šฉํ•˜์—ฌ ์ •๋ฆฌํ•œ ๊ธฐ๋Šฅ ๋ชฉ๋ก์ด ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋กœ ํ™•์ธํ•œ๋‹ค. + - [JUnit 5 User Guide](https://junit.org/junit5/docs/current/user-guide) + - [AssertJ User Guide](https://assertj.github.io/doc) + - [AssertJ Exception Assertions](https://www.baeldung.com/assertj-exception-assertion) + - [Guide to JUnit 5 Parameterized Tests](https://www.baeldung.com/parameterized-tests-junit-5) + - ๋‹จ, UI(System.out, System.in, Scanner) ๋กœ์ง์€ ์ œ์™ธํ•œ๋‹ค. +- [x] ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜๋Š” ์ฝ”๋“œ์™€ UI๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ๋กœ์ง์„ ๋ถ„๋ฆฌํ•ด ๊ตฌํ˜„ํ•œ๋‹ค. + - ํžŒํŠธ: MVC ํŒจํ„ด ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌํ˜„ํ•œ ํ›„, View์™€ Controller๋ฅผ ์ œ์™ธํ•œ Model์— ๋Œ€ํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€์— ์ง‘์ค‘ํ•œ๋‹ค. + +### ์ œ์ถœ ์ „ ํ™•์ธ ์‚ฌํ•ญ +- [x] ์œ„์˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์š”๊ตฌ ์‚ฌํ•ญ์„ ์ค€์ˆ˜ํ–ˆ๋Š”์ง€ ํ™•์ธํ•œ๋‹ค. + - ์ฒดํฌ๋ฐ•์Šค๋ฅผ ๋ชจ๋‘ [x]๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ ํ•œ๋‹ค. --- ## ๐Ÿš€ ๊ธฐ๋Šฅ ์š”๊ตฌ ์‚ฌํ•ญ -์„ ํ’์ ์ธ ์ธ๊ธฐ๋ฅผ ๋Œ์—ˆ๋˜ ์˜์–ด ๋‹จ์–ด ๋งž์ถ”๊ธฐ ๊ฒŒ์ž„์ด๋‹ค. +### ์›Œ๋“ค ๊ฒŒ์ž„ ๊ทœ์น™ -- 6x5 ๊ฒฉ์ž๋ฅผ ํ†ตํ•ด์„œ 5๊ธ€์ž ๋‹จ์–ด๋ฅผ 6๋ฒˆ ๋งŒ์— ์ถ”์ธกํ•œ๋‹ค. -- ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ๋‹ต์•ˆ์„ ์ œ์ถœํ•˜๋ฉด ํ”„๋กœ๊ทธ๋žจ์ด ์ •๋‹ต๊ณผ ์ œ์ถœ๋œ ๋‹จ์–ด์˜ ๊ฐ ์•ŒํŒŒ๋ฒณ ์ข…๋ฅ˜์™€ ์œ„์น˜๋ฅผ ๋น„๊ตํ•ด ํŒ๋ณ„ํ•œ๋‹ค. -- ํŒ๋ณ„ ๊ฒฐ๊ณผ๋Š” ํฐ์ƒ‰์˜ ํƒ€์ผ์ด ์„ธ ๊ฐ€์ง€ ์ƒ‰(์ดˆ๋ก์ƒ‰/๋…ธ๋ž€์ƒ‰/ํšŒ์ƒ‰) ์ค‘ ํ•˜๋‚˜๋กœ ๋ฐ”๋€Œ๋ฉด์„œ ํ‘œํ˜„๋œ๋‹ค. - - ๋งž๋Š” ๊ธ€์ž๋Š” ์ดˆ๋ก์ƒ‰, ์œ„์น˜๊ฐ€ ํ‹€๋ฆฌ๋ฉด ๋…ธ๋ž€์ƒ‰, ์—†์œผ๋ฉด ํšŒ์ƒ‰ - - ๋‘ ๊ฐœ์˜ ๋™์ผํ•œ ๋ฌธ์ž๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ๊ทธ์ค‘ ํ•˜๋‚˜๊ฐ€ ํšŒ์ƒ‰์œผ๋กœ ํ‘œ์‹œ๋˜๋ฉด ํ•ด๋‹น ๋ฌธ์ž ์ค‘ ํ•˜๋‚˜๋งŒ ์ตœ์ข… ๋‹จ์–ด์— ๋‚˜ํƒ€๋‚œ๋‹ค. -- ์ •๋‹ต๊ณผ ๋‹ต์•ˆ์€ `words.txt`์— ์กด์žฌํ•˜๋Š” ๋‹จ์–ด์—ฌ์•ผ ํ•œ๋‹ค. -- ์ •๋‹ต์€ ๋งค์ผ ๋ฐ”๋€Œ๋ฉฐ ((ํ˜„์žฌ ๋‚ ์งœ - 2021๋…„ 6์›” 19์ผ) % ๋ฐฐ์—ด์˜ ํฌ๊ธฐ) ๋ฒˆ์งธ์˜ ๋‹จ์–ด์ด๋‹ค. +>์„ ํ’์ ์ธ ์ธ๊ธฐ๋ฅผ ๋Œ์—ˆ๋˜ ์˜์–ด ๋‹จ์–ด ๋งž์ถ”๊ธฐ ๊ฒŒ์ž„์ด๋‹ค. +>- 6x5 ๊ฒฉ์ž๋ฅผ ํ†ตํ•ด์„œ 5๊ธ€์ž ๋‹จ์–ด๋ฅผ 6๋ฒˆ ๋งŒ์— ์ถ”์ธกํ•œ๋‹ค. +>- ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ๋‹ต์•ˆ์„ ์ œ์ถœํ•˜๋ฉด ํ”„๋กœ๊ทธ๋žจ์ด ์ •๋‹ต๊ณผ ์ œ์ถœ๋œ ๋‹จ์–ด์˜ ๊ฐ ์•ŒํŒŒ๋ฒณ ์ข…๋ฅ˜์™€ ์œ„์น˜๋ฅผ ๋น„๊ตํ•ด ํŒ๋ณ„ํ•œ๋‹ค. +>- ํŒ๋ณ„ ๊ฒฐ๊ณผ๋Š” ํฐ์ƒ‰์˜ ํƒ€์ผ์ด ์„ธ ๊ฐ€์ง€ ์ƒ‰(์ดˆ๋ก์ƒ‰/๋…ธ๋ž€์ƒ‰/ํšŒ์ƒ‰) ์ค‘ ํ•˜๋‚˜๋กœ ๋ฐ”๋€Œ๋ฉด์„œ ํ‘œํ˜„๋œ๋‹ค. +> - ๋งž๋Š” ๊ธ€์ž๋Š” ์ดˆ๋ก์ƒ‰, ์œ„์น˜๊ฐ€ ํ‹€๋ฆฌ๋ฉด ๋…ธ๋ž€์ƒ‰, ์—†์œผ๋ฉด ํšŒ์ƒ‰ +> - ๋‘ ๊ฐœ์˜ ๋™์ผํ•œ ๋ฌธ์ž๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ๊ทธ์ค‘ ํ•˜๋‚˜๊ฐ€ ํšŒ์ƒ‰์œผ๋กœ ํ‘œ์‹œ๋˜๋ฉด ํ•ด๋‹น ๋ฌธ์ž ์ค‘ ํ•˜๋‚˜๋งŒ ์ตœ์ข… ๋‹จ์–ด์— ๋‚˜ํƒ€๋‚œ๋‹ค. +>- ์ •๋‹ต๊ณผ ๋‹ต์•ˆ์€ `words.txt`์— ์กด์žฌํ•˜๋Š” ๋‹จ์–ด์—ฌ์•ผ ํ•œ๋‹ค. +>- ์ •๋‹ต์€ ๋งค์ผ ๋ฐ”๋€Œ๋ฉฐ ((ํ˜„์žฌ ๋‚ ์งœ - 2021๋…„ 6์›” 19์ผ) % ๋ฐฐ์—ด์˜ ํฌ๊ธฐ) ๋ฒˆ์งธ์˜ ๋‹จ์–ด์ด๋‹ค. ### ์ž…์ถœ๋ ฅ ์š”๊ตฌ ์‚ฌํ•ญ @@ -27,6 +81,7 @@ ``` WORDLE์„ 6๋ฒˆ ๋งŒ์— ๋งž์ถฐ ๋ณด์„ธ์š”. ์‹œ๋„์˜ ๊ฒฐ๊ณผ๋Š” ํƒ€์ผ์˜ ์ƒ‰ ๋ณ€ํ™”๋กœ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. + ์ •๋‹ต์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”. hello @@ -48,38 +103,158 @@ spell ์ •๋‹ต์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”. spill -4/6 - โฌœโฌœ๐ŸŸจ๐ŸŸฉโฌœ ๐ŸŸจโฌœโฌœโฌœ๐ŸŸฉ ๐ŸŸฉ๐ŸŸฉโฌœ๐ŸŸฉ๐ŸŸฉ ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ + +์„ฑ๊ณต!! ์ •๋‹ต์ž…๋‹ˆ๋‹ค. 4/6 ``` ---- +### โœ๏ธ ์šฉ์–ด ์ •๋ฆฌ -## ๐ŸŽฏ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์š”๊ตฌ ์‚ฌํ•ญ +| ๊ตญ๋ฌธ | ์˜๋ฌธ | ์„ค๋ช… | +|-------------|-------------------|--------------------------------------------------------------------------------------------------------------| +| ์›Œ๋“ค ๊ฒŒ์ž„ | Wordle Game | ์•ŒํŒŒ๋ฒณ ์†Œ๋ฌธ์ž๋กœ๋งŒ ์ด๋ฃจ์–ด์ง„ 5๊ธ€์ž ์˜์–ด ๋‹จ์–ด๋ฅผ 6๋ฒˆ ๋งŒ์— ๋งž์ถ”๋Š” ๊ฒŒ์ž„์ด๋‹ค. | +| ๋‹จ์–ด | Word | ์›Œ๋“ค ๊ฒŒ์ž„์—์„œ ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ์ž…๋ ฅํ•ด์•ผ ํ•˜๋Š” ์ตœ์†Œ ๋‹จ์œ„์ด๋‹ค. | +| ์‚ฌ์ „ | Dictionary | ์œ ํšจํ•œ ๋‹จ์–ด๋“ค์˜ ๋ชฉ๋ก์ด๋‹ค. ์ด ๋ชฉ๋ก์—์„œ ์ •๋‹ต ๋‹จ์–ด๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ๋‹ต์•ˆ ๋‹จ์–ด์˜ ์œ ํšจํ•จ์„ ํ™•์ธํ•œ๋‹ค. | +| ๊ธฐ์ค€ ๋‚ ์งœ | Criterion Date | ๋‹จ์–ด ์‚ฌ์ „์—์„œ ํ•œ ๋‹จ์–ด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ณ„์‚ฐ์— ํ•„์š”ํ•œ ๊ธฐ์ค€ ๋‚ ์งœ์ด๋‹ค. | +| ๋‹ต์•ˆ ๋‹จ์–ด | Answer Word | ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ์ •๋‹ต์ด ๋˜๋Š” ๋‹จ์–ด๋ฅผ ๋งž์ถ”๊ธฐ ์œ„ํ•ด ์ž…๋ ฅํ•˜๋Š” ๋‹จ์–ด์ด๋‹ค. | +| ์˜ค๋Š˜์˜ ๋‹จ์–ด | Today Word | ์ •๋‹ต์ด ๋˜๋Š” ๋‹จ์–ด์ด๋‹ค. ๊ธฐ์ค€ ๋‚ ์งœ์™€ ์˜ค๋Š˜์˜ ๋‚ ์งœ๋ฅผ ๊ณ„์‚ฐํ•˜์—ฌ ๋‹จ์–ด ์‚ฌ์ „์—์„œ ๋‹จ์–ด ํ•˜๋‚˜๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค. ์ด ๋‹จ์–ด๋Š” ๋งค์ผ ๋ฐ”๋€๋‹ค. | +| ๊ธ€์ž | Letter | ๋‹ต์•ˆ ๋‹จ์–ด์™€ ์˜ค๋Š˜์˜ ๋‹จ์–ด๊ฐ€ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์ตœ์†Œ ๋‹จ์œ„์ด๋‹ค. ๊ธ€์ž ์—ฌ๋Ÿฌ ๊ฐœ๊ฐ€ ๋ชจ์—ฌ ๋‹จ์–ด๋ฅผ ๊ตฌ์„ฑํ•œ๋‹ค. ์˜ˆ) "hello"๋Š” 'h', 'e', 'l', 'o'์˜ ๊ธ€์ž๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ๋‹ค. | +| ์›Œ๋“ค ๊ฒŒ์ž„ ๋กœ์ง | Wordle Game Logic | ๋‹ต์•ˆ ๋‹จ์–ด์™€ ์˜ค๋Š˜์˜ ๋‹จ์–ด๋ฅผ ๋น„๊ตํ•œ ๋‹จ์–ด ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์•„ ์˜จ๋‹ค. | +| ๋‹จ์–ด ๋น„๊ต๊ธฐ | Word Comparator | ๋‹ต์•ˆ ๋‹จ์–ด์™€ ์˜ค๋Š˜์˜ ๋‹จ์–ด๋ฅผ ๊ฐ ๊ธ€์ž์˜ ์ผ์น˜๋ฅผ ๊ฒ€์ฆํ•˜๋Š” ๊ธฐ๋Šฅ์„ ํ•œ๋‹ค. | +| ์‹œ๋„ ๊ฐ€๋Šฅ ํšŸ์ˆ˜ | Try Count | ๋‹ต์•ˆ ๋‹จ์–ด๋ฅผ ์ž…๋ ฅ ๊ฐ€๋Šฅํ•œ ํšŸ์ˆ˜์ด๋‹ค. ์œ ํšจํ•œ ๋‹จ์–ด๋ฅผ ์ž…๋ ฅํ•  ๋•Œ๋งˆ๋‹ค 1ํšŒ์”ฉ ์ฐจ๊ฐ๋˜๋ฉฐ, 0ํšŒ๊ฐ€ ๋˜๋ฉด ๊ฒŒ์ž„์€ ๋ฌด์กฐ๊ฑด ์ข…๋ฃŒ๋œ๋‹ค. | +| ์ตœ๋Œ€ ์‹œ๋„ ๊ฐ€๋Šฅ ํšŸ์ˆ˜ | Max Try Count | ์œ ํšจํ•œ ๋‹ต์•ˆ์„ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋Š” ์ตœ๋Œ€ ํšŸ์ˆ˜์ด๋‹ค. | +| ์‹œ๋„ํ•œ ํšŸ์ˆ˜ | Attempt Count | ์œ ํšจํ•œ ๋‹ต์•ˆ ๋‹จ์–ด๋ฅผ ์ž…๋ ฅํ•œ ํšŸ์ˆ˜์ด๋‹ค. ([์ตœ๋Œ€ ์‹œ๋„ ๊ฐ€๋Šฅ ํšŸ์ˆ˜] - [์‹œ๋„ ๊ฐ€๋Šฅ ํšŸ์ˆ˜]) | +| ๋‹จ์–ด ๊ฒฐ๊ณผ | Word Result | ์œ ํšจํ•œ ๋‹ต์•ˆ ๋‹จ์–ด๋ฅผ ์ž…๋ ฅ ํ›„ ํ•ด๋‹น ๋‹ต์•ˆ ๋‹จ์–ด์— ๋Œ€ํ•œ ๋น„๊ต๋ฅผ ๊ฑฐ์นœ ํ›„ ๊ฐ ๊ธ€์ž ์ผ์น˜ ์ƒํƒœ ๋ชฉ๋ก์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. | +| ๋‹จ์–ด ๊ฒฐ๊ณผ ๋ชฉ๋ก | Word Results | ๊ธ€์ž ์ผ์น˜ ์ƒํƒœ ๋ชฉ๋ก๊ณผ ์‹œ๋„ ๊ฐ€๋Šฅ ํšŸ์ˆ˜๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๋ชฉ๋ก์ด๋‹ค. | +| ๊ธ€์ž ์ผ์น˜ ์ƒํƒœ | Letter Match | ๋‹ต์•ˆ ๋‹จ์–ด์™€ ์˜ค๋Š˜์˜ ๋‹จ์–ด๋ฅผ ์ด๋ฃจ๋Š” ๊ฐ ๊ธ€์ž๋“ค์˜ ์ผ์น˜ํ•˜๋Š” ์ƒํƒœ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ์œ ํ˜•์ด๋‹ค. | +| ์™„์ „ ์ผ์น˜ ๊ธ€์ž | Correct Letter | ๊ธ€์ž ์ผ์น˜ ์ƒํƒœ ์ค‘, ๊ฐ™์€ ์œ„์น˜์— ์žˆ๋Š” ๋‹ต์•ˆ ๋‹จ์–ด์™€ ์˜ค๋Š˜์˜ ๋‹จ์–ด์˜ ๊ธ€์ž๊ฐ€ ๊ฐ™์„ ๋•Œ์˜ ์ƒํƒœ์ด๋‹ค. | +| ๋ถ€๋ถ„ ์ผ์น˜ ๊ธ€์ž | Present Letter | ๊ธ€์ž ์ผ์น˜ ์ƒํƒœ ์ค‘, ๋‹ค๋ฅธ ์œ„์น˜์— ์žˆ๋Š” ๊ธ€์ž๊ฐ€ ์˜ค๋Š˜์˜ ๋‹จ์–ด์— ์กด์žฌํ•˜๋Š” ์ƒํƒœ์ด๋‹ค. ์ด ๋•Œ, ์˜ค๋Š˜์˜ ๋‹จ์–ด์—์„œ ํ•œ๋ฒˆ ๋น„๊ตํ•œ ๋ถ€๋ถ„ ์ผ์น˜ ๊ธ€์ž๋Š” ๋‹ค์Œ ์œ„์น˜์— ๊ฐ™์€ ๋ถ€๋ถ„ ์ผ์น˜ ๊ธ€์ž๊ฐ€ ๋‚˜์™€๋„ ๋น„๊ตํ•˜์ง€ ์•Š๋Š”๋‹ค. | +| ๋ถˆ์ผ์น˜ ๊ธ€์ž | Absent Letter | ๊ธ€์ž ์ผ์น˜ ์ƒํƒœ ์ค‘, ์˜ค๋Š˜์˜ ๋‹จ์–ด์— ํ•ด๋‹น ๊ธ€์ž๊ฐ€ ์—†๋Š” ์ƒํƒœ์ด๋‹ค. | +| ์ผ์น˜ ํ‘œ์‹œ ๊ธฐํ˜ธ | Match Marker | ์˜ค๋Š˜์˜ ๋‹จ์–ด ์ค‘ ๋‹ต์•ˆ์˜ ๋‹จ์–ด์™€ ๋ถˆ์ผ์น˜ ์ƒํƒœ๊ฐ€ ์•„๋‹Œ ๊ธ€์ž๋ฅผ ํ‘œ์‹œํ•˜๋Š” ๊ธฐํ˜ธ์ด๋‹ค. | +| ํƒ€์ผ | Tile | ๋‹จ์–ด ๋น„๊ต์˜ ๊ฐ ๊ธ€์ž๋ณ„ ๊ฒฐ๊ณผ๋ฅผ ํƒ€์ผ์˜ ์ƒ‰๊น”๋กœ ๋‚˜ํƒ€๋‚ธ๋‹ค. ๊ธ€์ž ์ผ์น˜ ์ƒํƒœ์™€ ๋Œ€์‘ ๋œ๋‹ค. | +| ์ดˆ๋ก์ƒ‰ ํƒ€์ผ | Green Tile | ๊ธ€์ž ์™„์ „ ์ผ์น˜ ์ƒํƒœ์˜ ๊ฒฐ๊ณผ๋ฅผ ๋‚˜ํƒ€๋‚ธ ์ƒ‰์ƒ์˜ ํƒ€์ผ์ด๋‹ค. | +| ๋…ธ๋ž€์ƒ‰ ํƒ€์ผ | Yellow Tile | ๊ธ€์ž ๋ถ€๋ถ„ ์ผ์น˜ ์ƒํƒœ์˜ ๊ฒฐ๊ณผ๋ฅผ ๋‚˜ํƒ€๋‚ธ ์ƒ‰์ƒ์˜ ํƒ€์ผ์ด๋‹ค. | +| ํšŒ์ƒ‰ ํƒ€์ผ | Grey Tile | ๊ธ€์ž ๋ถˆ์ผ์น˜ ์ƒํƒœ์˜ ๊ฒฐ๊ณผ๋ฅผ ๋‚˜ํƒ€๋‚ธ ์ƒ‰์ƒ์˜ ํƒ€์ผ์ด๋‹ค. | +| ์„ฑ๊ณต | Success | ์›Œ๋“ค ๊ฒŒ์ž„์—์„œ ๋‹ต์•ˆ์˜ ๋‹จ์–ด๊ฐ€ ์˜ค๋Š˜์˜ ๋‹จ์–ด์˜ ๋ชจ๋“  ๊ธ€์ž๊ฐ€ ์™„์ „ ์ผ์น˜ํ•œ ์ƒํƒœ์ด๋‹ค. ์‹œ๋„ํ•œ ํšŸ์ˆ˜๋ฅผ ์•Œ๋ ค์ฃผ๋ฉฐ ๊ฒŒ์ž„์ด ์ข…๋ฃŒ๋œ๋‹ค. | +| ์‹คํŒจ | Fail | ์›Œ๋“ค ๊ฒŒ์ž„์„ ์„ฑ๊ณตํ•˜์ง€ ๋ชปํ•œ ์ƒํƒœ์—์„œ ์‹œ๋„ ๊ฐ€๋Šฅ ํšŸ์ˆ˜๊ฐ€ 0ํšŒ ์ดํ•˜๊ฐ€ ๋˜๋ฉด ์›Œ๋“ค ๊ฒŒ์ž„์— ์‹คํŒจํ•œ๋‹ค. ์˜ค๋Š˜์˜ ๋‹จ์–ด๋ฅผ ์•Œ๋ ค์ฃผ๋ฉฐ ๊ฒŒ์ž„์ด ์ข…๋ฃŒ๋œ๋‹ค. | +| ๊ณ„์† | Continuous | ์›Œ๋“ค ๊ฒŒ์ž„์„ ์„ฑ๊ณตํ•˜์ง€ ๋ชปํ•œ ์ƒํƒœ์—์„œ ์‹œ๋„ ๊ฐ€๋Šฅ ํšŸ์ˆ˜๊ฐ€ 0ํšŒ๋ณด๋‹ค ํฌ๋ฉด ๊ฒŒ์ž„์„ ๊ณ„์† ์‹œ๋„ํ•ด๋ณผ ์ˆ˜ ์žˆ๋‹ค. | +| ๋‹ค์‹œ ์‹œ๋„ | Retry | ์œ ํšจํ•œ ๋‹จ์–ด๋ฅผ ์ž…๋ ฅํ•˜์ง€ ๋ชปํ•  ๋•Œ, ์‹œ๋„ ๊ฐ€๋Šฅ ํšŸ์ˆ˜๊ฐ€ ์ฐจ๊ฐ๋˜์ง€ ์•Š๋Š”๋‹ค. ์ด ๋•Œ, ์œ ํšจํ•˜์ง€ ์•Š์€ ์ด์œ ๋ฅผ ์•ˆ๋‚ดํ•˜๋ฉฐ ๋‹ค์‹œ ๋‹ต์•ˆ์„ ์ž…๋ ฅํ•˜๊ฒŒ ํ•œ๋‹ค. | +| | | | + + +### ๐Ÿ’ป ๊ธฐ๋Šฅ ๋ชฉ๋ก + +#### ์ถœ๋ ฅ(Output View) +- [x] ๊ฒŒ์ž„ ์‹œ์ž‘ ๋ฉ”์‹œ์ง€๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. + - `WORDLE์„ 6๋ฒˆ ๋งŒ์— ๋งž์ถฐ ๋ณด์„ธ์š”.\n์‹œ๋„์˜ ๊ฒฐ๊ณผ๋Š” ํƒ€์ผ์˜ ์ƒ‰ ๋ณ€ํ™”๋กœ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค.` +- [x] ํƒ€์ผ ๊ฒฐ๊ณผ๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. + - [x] ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ์ž…๋ ฅํ•œ ๋‹ต์•ˆ ๋‹จ์–ด์™€ ๋น„๊ตํ•œ ๋‹ต์•ˆ ๊ฒฐ๊ณผ์— ๋”ฐ๋ผ ๊ฐ ๊ธ€์ž์˜ ์ผ์น˜ ์ƒํƒœ์— ๋งž๊ฒŒ ํƒ€์ผ์„ ์ถœ๋ ฅํ•œ๋‹ค. + - `๐ŸŸฉ` ์ดˆ๋ก์ƒ‰: ์˜ค๋Š˜์˜ ๋‹จ์–ด์™€ ๋น„๊ตํ•˜์—ฌ ๋‹ต์•ˆ ๋‹จ์–ด์˜ ๊ธ€์ž๊ฐ€ '์™„์ „ ์ผ์น˜'์ธ ๊ฒฝ์šฐ + - `๐ŸŸจ` ๋…ธ๋ž€์ƒ‰: ์˜ค๋Š˜์˜ ๋‹จ์–ด์™€ ๋น„๊ตํ•˜์—ฌ ๋‹ต์•ˆ ๋‹จ์–ด์˜ ๊ธ€์ž๊ฐ€ '๋ถ€๋ถ„ ์ผ์น˜'์ธ ๊ฒฝ์šฐ + - `โฌœ๏ธ` ํšŒ์ƒ‰: ์˜ค๋Š˜์˜ ๋‹จ์–ด์™€ ๋น„๊ตํ•˜์—ฌ ๋‹ต์•ˆ ๋‹จ์–ด์˜ ๊ธ€์ž๊ฐ€ '๋ถˆ์ผ์น˜'์ธ ๊ฒฝ์šฐ + - [x] ์ง€๊ธˆ๊นŒ์ง€ ์ž…๋ ฅํ•œ ๋‹ต์•ˆ ๋‹จ์–ด์— ๋”ฐ๋ฅธ ๋‹จ์–ด ๊ฒฐ๊ณผ ๋ชฉ๋ก์„ ์ถœ๋ ฅํ•œ๋‹ค. +- [x] ๋‹ค์‹œ ์‹œ๋„ ํ•  ๋•Œ, ๋‹ค์‹œ ์‹œ๋„์˜ ๋ฉ”์‹œ์ง€์™€ ๊ทธ ์ด์œ ๋ฅผ ํ•จ๊ป˜ ์ถœ๋ ฅํ•œ๋‹ค. +- [x] ๊ฒŒ์ž„์˜ ๊ฒฐ๊ณผ์— ๋”ฐ๋ผ ๋ฉ”์‹œ์ง€๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค + - [x] ์„ฑ๊ณต ๋ฉ”์‹œ์ง€๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. `{์‹œ๋„ํ•œ ํšŸ์ˆ˜} / {์ตœ๋Œ€ ์‹œ๋„ ๊ฐ€๋Šฅ ํšŸ์ˆ˜}`๋„ ํ•จ๊ป˜ ์ถœ๋ ฅํ•œ๋‹ค. + - [x] ์‹คํŒจ ๋ฉ”์‹œ์ง€๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. `{์˜ค๋Š˜์˜ ๋‹จ์–ด}`๋ฅผ ํ•จ๊ป˜ ์ถœ๋ ฅํ•œ๋‹ค. + +#### ์ž…๋ ฅ(Input View) +- [x] ๋‹ต์•ˆ ๋‹จ์–ด๋ฅผ ์ž…๋ ฅ๋ฐ›๋Š”๋‹ค. ์ž…๋ ฅ๋ฐ›๊ธฐ ์ „์— ๋‹ต์•ˆ ์ž…๋ ฅ์„ ์œ„ํ•œ ๋ฉ”์‹œ์ง€๋ฅผ ๋จผ์ € ์ถœ๋ ฅํ•œ๋‹ค. + - `์ •๋‹ต์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.` + +#### ์›Œ๋“ค ๊ฒŒ์ž„(Wordle Game) +- [x] ๋‹จ์–ด ๊ฒฐ๊ณผ ๋ชฉ๋ก์„ ๊ฐ€์ง„๋‹ค. +- [x] ์œ ํšจํ•œ ๋‹ต์•ˆ ๋‹จ์–ด๋ฅผ ์ตœ๋Œ€ 6๋ฒˆ ์ž…๋ ฅ๋ฐ›์•„ ๊ฒŒ์ž„์„ ํ•œ๋‹ค. + - [x] ๊ฒŒ์ž„์— ์•„์ง ์„ฑ๊ณตํ•˜์ง€ ๋ชปํ•œ ์ƒํƒœ์—์„œ ์‹œ๋„ ๊ฐ€๋Šฅ ํšŸ์ˆ˜๊ฐ€ 0๋ณด๋‹ค ํฌ๋ฉด ๊ฒŒ์ž„์„ ๊ณ„์† ํ•  ์ˆ˜ ์žˆ๋‹ค. + - [x] ๋‹จ์–ด๋ฅผ ์ž…๋ ฅ๋ฐ›์•„ ๋‹ต์•ˆ ๋‹จ์–ด๋ฅผ ๋งŒ๋“ ๋‹ค. + - ์œ ํšจํ•œ ๋‹ต์•ˆ ๋‹จ์–ด๋ฅผ ์ž…๋ ฅ ํ•˜์˜€์„ ๋•Œ, + - [x] ๊ฒŒ์ž„ ๊ฒ€์ฆ ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. + - [x] ํƒ€์ผ ๊ฒฐ๊ณผ ๋ชฉ๋ก์„ ์ถœ๋ ฅํ•œ๋‹ค. + - [x] ์‹œํ–‰ ๊ฐ€๋Šฅ ํšŸ์ˆ˜๋ฅผ 1ํšŒ ์ฐจ๊ฐํ•œ๋‹ค. + - ์œ ํšจํ•˜์ง€ ์•Š์€ ๋‹ต์•ˆ ๋‹จ์–ด๋ฅผ ์ž…๋ ฅ ํ•˜์˜€์„ ๋•Œ, + - [x] ๋‹ต์•ˆ ๋‹จ์–ด ์ž…๋ ฅ์„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์•ผ ํ•œ๋‹ค. ๋‹ค์‹œ ์‹œ๋„ ํ•˜๋Š” ์ด์œ ๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. +- [x] ๋‹จ์–ด ๊ฒฐ๊ณผ ๋ชฉ๋ก๊ณผ ๋Œ€์‘ํ•˜๋Š” ํƒ€์ผ ๊ฒฐ๊ณผ ๋ชฉ๋ก์„ ์ถœ๋ ฅํ•œ๋‹ค. + - [x] ๋‹ต์•ˆ ๋‹จ์–ด 1๋ฒˆ ์ž…๋ ฅํ•˜๋Š” ๊ฒฝ์šฐ + - [x] ๋‹ต์•ˆ ๋‹จ์–ด๋ฅผ 6๋ฒˆ ์ž…๋ ฅํ•˜๊ณ  ๊ฒŒ์ž„์— ์‹คํŒจํ•˜๋Š” ๊ฒฝ์šฐ +- [x] ๊ฒŒ์ž„์ด ์ข…๋ฃŒ ๋œ๋‹ค. ๊ฒŒ์ž„์˜ ์„ฑ๊ณต๊ณผ ์‹คํŒจ ์—ฌ๋ถ€๋ฅผ ํŒ๋ณ„ํ•œ๋‹ค. + - ๊ฒŒ์ž„์— ์„ฑ๊ณต ํ•˜์˜€์„ ๋•Œ + - [x] ํ•ด๋‹น ๋‹ต์•ˆ ๋‹จ์–ด์˜ ๋‹จ์–ด ๊ฒฐ๊ณผ๊ฐ€ ๋ชจ๋‘ '์™„์ „ ์ผ์น˜ ๊ธ€์ž'์ผ ๋•Œ์ด๋‹ค. + - ๊ด€๋ จ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ + - ๊ฒŒ์ž„์— ์‹คํŒจ ํ•˜์˜€์„ ๋•Œ + - [x] ๊ฒŒ์ž„์— ์„ฑ๊ณตํ•˜์ง€ ์•Š๊ณ , ์‹œ๋„ํšŸ์ˆ˜๊ฐ€ 0ํšŒ๊ฐ€ ๋  ๋•Œ์ด๋‹ค. + +#### ๊ธ€์ž(Letter) +- [x] ๊ธ€์ž๋Š” ํ•˜๋‚˜์˜ ๋ฌธ์ž๋ฅผ ๊ฐ€์ง„๋‹ค. +- [x] ๊ธ€์ž๋Š” ์†Œ๋ฌธ์ž ์•ŒํŒŒ๋ฒณ์ด๋‚˜ ์ผ์น˜ ํ‘œ์‹œ ๊ธฐํ˜ธ('#')๋งŒ ์œ ํšจํ•˜๋‹ค. +- [x] ์ผ์น˜ ํ‘œ์‹œ ๊ธฐํ˜ธ๊ฐ€ ๋“ค์–ด๊ฐ„ ๊ธ€์ž๋กœ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค. + +#### ๋‹จ์–ด(Word) +- [x] ๋‹จ์–ด๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๊ธ€์ž๋กœ ์ด๋ฃจ์–ด์ง„๋‹ค. +- [x] ๋‹จ์–ด๋Š” ๊ณต๋ฐฑ๋งŒ ์ž…๋ ฅํ•  ์ˆ˜ ์—†๋‹ค. +- [x] ๋‹จ์–ด์˜ ๊ธธ์ด๋Š” 5์ž ์ด์–ด์•ผ ํ•œ๋‹ค. +- [x] ๋‹จ์–ด๋Š” ์‚ฌ์ „์— ์žˆ๋Š” ๋‹จ์–ด์ด์–ด์•ผ ํ•œ๋‹ค. + +#### ์‚ฌ์ „(Dictionary) +- [x] ์‚ฌ์ „ ๋‹จ์–ด ๋ชฉ๋ก์—์„œ ๋‹จ์–ด ํ•˜๋‚˜๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค. + - `DictionaryFileLoader`๋ฅผ ์ด์šฉํ•˜์—ฌ `words.txt`ํŒŒ์ผ์—์„œ ์‚ฌ์ „์˜ ๋‹จ์–ด ๋ชฉ๋ก์„ ๋ถˆ๋Ÿฌ์˜จ๋‹ค. +- [x] ์‚ฌ์ „์— ๋‹จ์–ด๊ฐ€ ํฌํ•จ๋˜๋Š”์ง€ ํŒ๋ณ„ํ•œ๋‹ค. + - 'hello'๋Š” ์‚ฌ์ „์— ํฌํ•จ๋œ ๋‹จ์–ด์ด๋‹ค + - 'abced'๋Š” ์‚ฌ์ „์— ํฌํ•จ๋˜์ง€ ์•Š์€ ๋‹จ์–ด์ด๋‹ค. + +#### ์˜ค๋Š˜์˜ ๋‹จ์–ด(Today Word) +- [x] ์˜ค๋Š˜์˜ ๋‹จ์–ด๋Š” ํ˜„์žฌ ๋‚ ์งœ๋ฅผ ์ž…๋ ฅํ•˜์—ฌ ์‚ฌ์ „์—์„œ ํ•œ ๋‹จ์–ด๋ฅผ ๋ถˆ๋Ÿฌ์˜จ๋‹ค. ์ด๋•Œ, ์˜ค๋Š˜์˜ ๋‹จ์–ด๋Š” ๋งค์ผ ๋ฐ”๋€๋‹ค. + - [x] ์‚ฌ์ „ ๋‹จ์–ด ๋ชฉ๋ก์˜ ((ํ˜„์žฌ ๋‚ ์งœ - 2021๋…„ 6์›” 19์ผ) % ๋ฐฐ์—ด์˜ ํฌ๊ธฐ) ๋ฒˆ์งธ์˜ ๋‹จ์–ด์ด๋‹ค. + +#### ์›Œ๋“ค ๊ฒŒ์ž„ ๋กœ์ง(Wordle Game Logic) +- [x] ์˜ค๋Š˜์˜ ๋‹จ์–ด์™€ ๋‹ต์•ˆ ๋‹จ์–ด๋ฅผ ๋น„๊ตํ•˜์—ฌ ๋‹จ์–ด ๊ฒฐ๊ณผ๋ฅผ ์–ป๋Š”๋‹ค. + - ๋‹จ์–ด ๊ฒฐ๊ณผ ์ค‘ ๊ธ€์ž ์ผ์น˜ ์ƒํƒœ๊ฐ€ ํ•˜๋‚˜๋งŒ ๋‹ค๋ฅธ ๊ฒฝ์šฐ + - [x] ํ•˜๋‚˜์˜ ๊ธ€์ž ๊ฒฐ๊ณผ๋งŒ '๋ถ€๋ถ„ ์ผ์น˜ ๊ธ€์ž'์ผ ๋•Œ ๋น„๊ตํ•œ๋‹ค. + - [x] ํ•˜๋‚˜์˜ ๊ธ€์ž ๊ฒฐ๊ณผ๋งŒ '์™„์ „ ์ผ์น˜ ๊ธ€์ž'์ผ ๋•Œ ๋น„๊ตํ•œ๋‹ค. - '์™„์ „ ์ผ์น˜ ๊ธ€์ž'๋ถ€ํ„ฐ ๋น„๊ตํ•œ๋‹ค + - ๋‹ต์•ˆ ๋‹จ์–ด์— ๊ฐ™์€ ๊ธ€์ž๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ + - [x] ๋‘ ๋ฒˆ ๊ฐ™์€ ๊ธ€์ž๊ฐ€ ์—ฐ์†๋˜๋Š” ๋‹ต์•ˆ ๋‹จ์–ด์—์„œ, ์˜ค๋Š˜์˜ ๋‹จ์–ด์™€ ํ•œ ๊ธ€์ž๋งŒ '๋ถ€๋ถ„ ์ผ์น˜ ๊ธ€์ž'์ผ ๋•Œ ๋น„๊ตํ•œ๋‹ค. + - [x] ๋‘ ๋ฒˆ ๊ฐ™์€ ๊ธ€์ž๊ฐ€ ์—ฐ์†๋˜๋Š” ๋‹ต์•ˆ ๋‹จ์–ด์—์„œ, ์˜ค๋Š˜์˜ ๋‹จ์–ด์™€ ๋‘ ๊ธ€์ž '๋ถ€๋ถ„ ์ผ์น˜ ๊ธ€์ž'์ผ ๋•Œ ๋น„๊ตํ•œ๋‹ค. + - [x] ๋‘ ๋ฒˆ ๊ฐ™์€ ๊ธ€์ž๊ฐ€ ์—ฐ์†๋˜๋Š” ๋‹ต์•ˆ ๋‹จ์–ด์—์„œ, '๋ถ€๋ถ„ ์ผ์น˜ ๊ธ€์ž'์™€ '์™„์ „ ์ผ์น˜ ๊ธ€์ž'๋ฅผ ๊ฐ€์งˆ ๋•Œ ๋น„๊ตํ•œ๋‹ค. + - [x] ๊ฐ™์€ ๊ธ€์ž๊ฐ€ ์—ฐ์† ํ•˜์ง€ ์•Š๋Š” ๋‹ต์•ˆ ๋‹จ์–ด์—์„œ, '๋ถ€๋ถ„ ์ผ์น˜ ๊ธ€์ž'์™€ '์™„์ „ ์ผ์น˜ ๊ธ€์ž'๋ฅผ ๊ฐ€์งˆ ๋•Œ ๋น„๊ตํ•œ๋‹ค. + - [x] ์„ธ ๊ฐœ์˜ ๊ฐ™์€ ๊ธ€์ž๋ฅผ ๊ฐ€์ง€๋Š” ๋‹ต์•ˆ ๋‹จ์–ด์—์„œ ํ•˜๋‚˜์˜ ๊ธ€์ž ๊ฒฐ๊ณผ๋งŒ '๋ถ€๋ถ„ ์ผ์น˜ ๊ธ€์ž'์ผ ๋•Œ ๋น„๊ตํ•œ๋‹ค. + - [x] ๋„ค ๊ฐœ์˜ ๊ธ€์ž ๊ฒฐ๊ณผ๊ฐ€ '์™„์ „ ์ผ์น˜ ๊ธ€์ž'์ผ ๋•Œ ๋น„๊ตํ•œ๋‹ค. + - ๋‹จ์–ด ๊ฒฐ๊ณผ ์ค‘ ๊ธ€์ž ์ผ์น˜ ์ƒํƒœ๊ฐ€ ๋ชจ๋‘ ๊ฐ™์€ ๊ฒฝ์šฐ + - [x] ๋ชจ๋“  ๊ธ€์ž ๊ฒฐ๊ณผ๊ฐ€ '๋ถˆ์ผ์น˜ ์ƒํƒœ'์ผ ๋•Œ ๋น„๊ตํ•œ๋‹ค + - [x] ๋ชจ๋“  ๊ธ€์ž ๊ฒฐ๊ณผ๊ฐ€ '๋ถ€๋ถ„ ์ผ์น˜ ๊ธ€์ž'์ผ ๋•Œ ๋น„๊ตํ•œ๋‹ค + - [x] ๋ชจ๋“  ๊ธ€์ž ๊ฒฐ๊ณผ๊ฐ€ '์™„์ „ ์ผ์น˜ ๊ธ€์ž'์ผ ๋•Œ ๋น„๊ตํ•œ๋‹ค + +#### ๋‹จ์–ด ๋น„๊ต๊ธฐ(Word Comparator) +- ๋‹จ์–ด ๋น„๊ต๊ธฐ๋Š” ๊ธ€์ž ๋ชฉ๋ก๊ณผ ๋‹จ์–ด ๊ฒฐ๊ณผ๋ฅผ ์ƒํƒœ๋กœ ๊ฐ€์ง„๋‹ค. +- '์™„์ „ ์ผ์น˜ ๊ธ€์ž'๋กœ ๋งค์นญ ์‹œํ‚ค๊ธฐ + - [x] ์™„์ „ ์ผ์น˜ ๊ธ€์ž๋ฅผ ๋น„๊ตํ•œ๋‹ค. ์™„์ „ ์ผ์น˜ ๊ธ€์ž์˜ ์ƒํƒœ๋Š” ABSENT -> CORRECT ๋กœ ๋ณ€๊ฒฝ๋œ๋‹ค. + - [x] ์œ„์น˜์™€ ๊ธ€์ž๊ฐ€ ๊ฐ™์€ ๊ฒฝ์šฐ ์ฒดํฌํ•œ๋‹ค. +- '๋ถ€๋ถ„ ์ผ์น˜ ๊ธ€์ž'๋กœ ๋งค์นญ ์‹œํ‚ค๊ธฐ + - [x] '์™„์ „ ์ผ์น˜ ๊ธ€์ž'๋งค์นญ ํ›„์— ๋™์ž‘ํ•œ๋‹ค. + - [x] ๋ถ€๋ถ„ ์ผ์น˜ ๊ธ€์ž๋ฅผ ๋น„๊ตํ•œ๋‹ค. ๋ถ€๋ถ„ ์ผ์น˜ ๊ธ€์ž์˜ ์ƒํƒœ๋Š” ABSENT -> PRESENT ๋กœ ๋ณ€๊ฒฝ๋œ๋‹ค. + - [x] ํ•ด๋‹น ๋‹จ์–ด ๋น„๊ต๊ธฐ ๊ธ€์ž๋Š” '์™„์ „ ์ผ์น˜ ๊ธ€์ž'๊ฐ€ ์•„๋‹ˆ์—ฌ์•ผ ํ•œ๋‹ค. + - [x] ๊ฐ ๊ธ€์ž๋Š” ๋‹ค๋ฅธ ์œ„์น˜์—ฌ์•ผ ํ•œ๋‹ค. + - [x] ๋น„๊ตํ•˜๋ ค๋Š” ๋‹ต์•ˆ ๋‹จ์–ด์˜ ๊ธ€์ž๋Š” ๋น„๊ต๊ธฐ ๊ธ€์ž ๋ชฉ๋ก์— ์žˆ๋Š” ๊ธ€์ž์—ฌ์•ผ ํ•œ๋‹ค. +- [x] '์™„์ „ ์ผ์น˜ ๊ธ€์ž'๊ฑฐ๋‚˜ '๋ถ€๋ถ„ ์ผ์น˜ ๊ธ€์ž'์ด๋ฉด ํ•ด๋‹น ๋‹จ์–ด ๋น„๊ต๊ธฐ ๊ธ€์ž์— ์ผ์น˜ ํ‘œ์‹œ ๊ธฐํ˜ธ('#')๋กœ ๋ณ€๊ฒฝ๋œ๋‹ค. + +#### ์‹œ๋„ ๊ฐ€๋Šฅ ํšŸ์ˆ˜(Try Count) +- [x] ์‹œ๋„ ๊ฐ€๋Šฅ ํšŒ์ˆ˜๋Š” ํšŸ์ˆ˜๋ฅผ ๊ฐ€์ง„๋‹ค. ์ตœ์ดˆ ์ƒ์„ฑํ•  ๋•Œ์—๋Š” 6ํšŒ์ด๋‹ค. +- [x] ์‹œ๋„ํ•œ ํšŸ์ˆ˜๋ฅผ ๊ตฌํ•  ์ˆ˜ ์žˆ๋‹ค. +- [x] ์‹œ๋„ ๊ฐ€๋Šฅ ํšŸ์ˆ˜๊ฐ€ ๋‚จ์•˜๋Š”์ง€ ์•Œ ์ˆ˜ ์žˆ๋‹ค. +- [x] ์‹œ๋„ ๊ฐ€๋Šฅ ํšŸ์ˆ˜๋ฅผ 1ํšŒ์”ฉ ์ฐจ๊ฐํ•  ์ˆ˜ ์žˆ๋‹ค. +- [x] ์‹œ๋„ ๊ฐ€๋Šฅ ํšŸ์ˆ˜๋Š” 0ํšŒ๋ณด๋‹ค ์ปค์•ผ ํ•œ๋‹ค. + +#### ๋‹จ์–ด ๊ฒฐ๊ณผ(Word Result) +- [x] 5๊ฐœ์˜ ๊ธ€์ž ์ผ์น˜ ์ƒํƒœ ๋ชฉ๋ก์œผ๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ๋‹ค. + - ์›Œ๋“ค ๊ฒŒ์ž„ ๋กœ์ง์˜ ๊ฒฐ๊ณผ๋ฅผ ๊ด€๋ฆฌํ•œ๋‹ค. +- [x] ์ฒ˜์Œ์— ์ƒ์„ฑํ•  ๋•Œ, ๊ธ€์ž ์ผ์น˜ ์ƒํƒœ๋Š” ๋ถˆ์ผ์น˜ ์ƒํƒœ์ด๋‹ค. +- [x] ํ•ด๋‹น ๊ธ€์ž์˜ ๊ธ€์ž ์ผ์น˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค. +- [x] ํ•ด๋‹น ๊ธ€์ž๊ฐ€ '์™„์ „ ์ผ์น˜ ์ƒํƒœ'์ธ์ง€ ํ™•์ธํ•œ๋‹ค. +- [x] ๋ชจ๋“  ๊ธ€์ž ์ผ์น˜ ์ƒํƒœ๊ฐ€ '์™„์ „ ์ผ์น˜ ์ƒํƒœ'์ด๋ฉด, ํ•ด๋‹น ๋‹จ์–ด๋Š” ์˜ค๋Š˜์˜ ๋‹จ์–ด์™€ ๊ฐ™๋‹ค.(๊ฒŒ์ž„ ์„ฑ๊ณต ์—ฌ๋ถ€๊ฐ€ ๋œ๋‹ค) -- Kotlin 1.9.0์—์„œ ์‹คํ–‰ ๊ฐ€๋Šฅํ•ด์•ผ ํ•œ๋‹ค. -- **Java ์ฝ”๋“œ๊ฐ€ ์•„๋‹Œ Kotlin ์ฝ”๋“œ๋กœ๋งŒ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.** -- ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰์˜ ์‹œ์ž‘์ ์€ `Application`์˜ `main()`์ด๋‹ค. -- `build.gradle.kts` ํŒŒ์ผ์€ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†์œผ๋ฉฐ, **์ œ๊ณต๋œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ด์™ธ์˜ ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.** -- ํ”„๋กœ๊ทธ๋žจ ์ข…๋ฃŒ ์‹œ `System.exit()` ๋˜๋Š” `exitProcess()`๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š๋Š”๋‹ค. -- ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์š”๊ตฌ ์‚ฌํ•ญ์—์„œ ๋‹ฌ๋ฆฌ ๋ช…์‹œํ•˜์ง€ ์•Š๋Š” ํ•œ ํŒŒ์ผ, ํŒจํ‚ค์ง€ ๋“ฑ์˜ ์ด๋ฆ„์„ ๋ฐ”๊พธ๊ฑฐ๋‚˜ ์ด๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค. -- ์ฝ”ํ‹€๋ฆฐ ์ฝ”๋“œ ์ปจ๋ฒค์…˜์„ ์ง€ํ‚ค๋ฉด์„œ ํ”„๋กœ๊ทธ๋ž˜๋ฐํ•œ๋‹ค. - - ๊ธฐ๋ณธ์ ์œผ๋กœ [Kotlin Coding conventions](https://kotlinlang.org/docs/coding-conventions.html)๋ฅผ ์›์น™์œผ๋กœ ํ•œ๋‹ค. -- indent(์ธ๋ดํŠธ, ๋“ค์—ฌ์“ฐ๊ธฐ) depth๋ฅผ 3์ด ๋„˜์ง€ ์•Š๋„๋ก ๊ตฌํ˜„ํ•œ๋‹ค. 2๊นŒ์ง€๋งŒ ํ—ˆ์šฉํ•œ๋‹ค. - - ์˜ˆ๋ฅผ ๋“ค์–ด while๋ฌธ ์•ˆ์— if๋ฌธ์ด ์žˆ์œผ๋ฉด ๋“ค์—ฌ์“ฐ๊ธฐ๋Š” 2์ด๋‹ค. - - ํžŒํŠธ: indent(์ธ๋ดํŠธ, ๋“ค์—ฌ์“ฐ๊ธฐ) depth๋ฅผ ์ค„์ด๋Š” ์ข‹์€ ๋ฐฉ๋ฒ•์€ ํ•จ์ˆ˜(๋˜๋Š” ๋ฉ”์„œ๋“œ)๋ฅผ ๋ถ„๋ฆฌํ•˜๋ฉด ๋œ๋‹ค. -- ํ•จ์ˆ˜(๋˜๋Š” ๋ฉ”์„œ๋“œ)๊ฐ€ ํ•œ ๊ฐ€์ง€ ์ผ๋งŒ ํ•˜๋„๋ก ์ตœ๋Œ€ํ•œ ์ž‘๊ฒŒ ๋งŒ๋“ค์–ด๋ผ. -- JUnit 5์™€ AssertJ๋ฅผ ์ด์šฉํ•˜์—ฌ ์ •๋ฆฌํ•œ ๊ธฐ๋Šฅ ๋ชฉ๋ก์ด ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋กœ ํ™•์ธํ•œ๋‹ค. - - ํ…Œ์ŠคํŠธ ๋„๊ตฌ ์‚ฌ์šฉ๋ฒ•์ด ์ต์ˆ™ํ•˜์ง€ ์•Š๋‹ค๋ฉด ์•„๋ž˜ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ํ•™์Šตํ•œ ํ›„ ํ…Œ์ŠคํŠธ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. - - [JUnit 5 User Guide](https://junit.org/junit5/docs/current/user-guide) - - [AssertJ User Guide](https://assertj.github.io/doc) - - [AssertJ Exception Assertions](https://www.baeldung.com/assertj-exception-assertion) - - [Guide to JUnit 5 Parameterized Tests](https://www.baeldung.com/parameterized-tests-junit-5) -- ํ•จ์ˆ˜(๋˜๋Š” ๋ฉ”์„œ๋“œ)์˜ ๊ธธ์ด๊ฐ€ 15๋ผ์ธ์„ ๋„˜์–ด๊ฐ€์ง€ ์•Š๋„๋ก ๊ตฌํ˜„ํ•œ๋‹ค. - - ํ•จ์ˆ˜(๋˜๋Š” ๋ฉ”์„œ๋“œ)๊ฐ€ ํ•œ ๊ฐ€์ง€ ์ผ๋งŒ ์ž˜ ํ•˜๋„๋ก ๊ตฌํ˜„ํ•œ๋‹ค. -- ๋„๋ฉ”์ธ ๋กœ์ง์— ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค. ๋‹จ, UI(System.out, System.in, Scanner) ๋กœ์ง์€ ์ œ์™ธํ•œ๋‹ค. - - ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜๋Š” ์ฝ”๋“œ์™€ UI๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ๋กœ์ง์„ ๋ถ„๋ฆฌํ•ด ๊ตฌํ˜„ํ•œ๋‹ค. - - ํžŒํŠธ: MVC ํŒจํ„ด ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌํ˜„ํ•œ ํ›„, View์™€ Controller๋ฅผ ์ œ์™ธํ•œ Model์— ๋Œ€ํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€์— ์ง‘์ค‘ํ•œ๋‹ค. +#### ๋‹จ์–ด ๊ฒฐ๊ณผ ๋ชฉ๋ก(Word Results) +- [x] ๋‹จ์–ด๊ฒฐ๊ณผ์™€ ์‹œ๋„ํšŸ์ˆ˜๋ฅผ ๊ฐ€์ง„๋‹ค. ์ด๋•Œ, ๋‹จ์–ด๊ฒฐ๊ณผ ๋ชฉ๋ก์€ ๋นˆ ๋ชฉ๋ก์ด๊ณ , ์‹œ๋„ํ•œ ํšŸ์ˆ˜๋Š” 0์ด๋‹ค. +- [x] ๋‹จ์–ด ๊ฒฐ๊ณผ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด, ์‹œ๋„ํšŸ์ˆ˜๋Š” 1ํšŒ ์ฆ๊ฐ€ํ•œ๋‹ค. +- [x] ๋‹จ์–ด ๊ฒฐ๊ณผ ๋ชฉ๋ก์—์„œ ๋‹จ์–ด ๊ฒฐ๊ณผ๊ฐ€ ์„ฑ๊ณต์ธ ์ƒํƒœ๊ฐ€ ์ถ”๊ฐ€๋˜๋ฉด ๊ฒŒ์ž„์€ ์„ฑ๊ณตํ•œ๋‹ค. +- [x] ๋‹จ์–ด ๊ฒฐ๊ณผ ๋ชฉ๋ก์—์„œ ์•„์ง ๊ฒŒ์ž„ ์„ฑ๊ณตํ•˜์ง€ ๋ชปํ•˜๊ณ  ์‹œ๋„ ๊ฐ€๋Šฅ ํšŸ์ˆ˜๊ฐ€ 1 ์ด์ƒ์ด๋ฉด ๊ฒŒ์ž„์„ ๊ณ„์†ํ•  ์ˆ˜ ์žˆ๋‹ค. diff --git a/build.gradle.kts b/build.gradle.kts index 19a215a..ab9c11f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("jvm") version "1.9.0" + kotlin("jvm") version "1.9.23" id("org.jlleitschuh.gradle.ktlint") version "12.1.0" } @@ -14,9 +14,12 @@ repositories { mavenCentral() } +val junitJupiterVersion = "5.10.2" +val assertJVersion = "3.25.3" + dependencies { - testImplementation("org.junit.jupiter", "junit-jupiter", "5.10.2") - testImplementation("org.assertj", "assertj-core", "3.25.3") + testImplementation("org.junit.jupiter", "junit-jupiter", junitJupiterVersion) + testImplementation("org.assertj", "assertj-core", assertJVersion) } tasks { diff --git a/src/main/kotlin/.gitkeep b/src/main/kotlin/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/kotlin/wordle/Application.kt b/src/main/kotlin/wordle/Application.kt new file mode 100644 index 0000000..7cdfcc2 --- /dev/null +++ b/src/main/kotlin/wordle/Application.kt @@ -0,0 +1,7 @@ +package wordle + +import wordle.controller.WordleGameController + +fun main() { + WordleGameController().run() +} diff --git a/src/main/kotlin/wordle/application/WordleGame.kt b/src/main/kotlin/wordle/application/WordleGame.kt new file mode 100644 index 0000000..51cf861 --- /dev/null +++ b/src/main/kotlin/wordle/application/WordleGame.kt @@ -0,0 +1,40 @@ +package wordle.application + +import wordle.domain.TodayWord +import wordle.domain.Word +import wordle.domain.WordResults +import wordle.domain.WordleGameLogic +import wordle.view.inputAnswerWord +import wordle.view.printFail +import wordle.view.printRetry +import wordle.view.printSuccess +import wordle.view.printWordResults +import java.time.LocalDate + +class WordleGame(gameStartDate: LocalDate) { + private val todayWord = TodayWord(gameStartDate) + private val wordleGameLogic = WordleGameLogic(todayWord) + private val results = WordResults() + + fun play() { + while (results.isContinuousGame()) { + try { + val answerWord = Word(inputAnswerWord()) + val result = wordleGameLogic.compare(answerWord) + results.addResults(result) + printWordResults(results) + } catch (e: IllegalStateException) { + printRetry(e.message) + } + } + printGameResult() + } + + private fun printGameResult() { + if (results.isSuccessfulGame()) { + printSuccess(results.attemptCount) + return + } + printFail(todayWord) + } +} diff --git a/src/main/kotlin/wordle/controller/WordleGameController.kt b/src/main/kotlin/wordle/controller/WordleGameController.kt new file mode 100644 index 0000000..aa05af0 --- /dev/null +++ b/src/main/kotlin/wordle/controller/WordleGameController.kt @@ -0,0 +1,15 @@ +package wordle.controller + +import wordle.application.WordleGame +import wordle.view.printStartingGameMessage +import java.time.LocalDate + +class WordleGameController { + private val gameStartDate: LocalDate = LocalDate.now() + private val wordleGame: WordleGame = WordleGame(gameStartDate) + + fun run() { + printStartingGameMessage() + wordleGame.play() + } +} diff --git a/src/main/kotlin/wordle/domain/AnswerWord.kt b/src/main/kotlin/wordle/domain/AnswerWord.kt new file mode 100644 index 0000000..ee35e0f --- /dev/null +++ b/src/main/kotlin/wordle/domain/AnswerWord.kt @@ -0,0 +1,7 @@ +package wordle.domain + +typealias AnswerWord = Word + +fun AnswerWord(answerWord: String): AnswerWord { + return Word(answerWord) +} diff --git a/src/main/kotlin/wordle/domain/Dictionary.kt b/src/main/kotlin/wordle/domain/Dictionary.kt new file mode 100644 index 0000000..a929d76 --- /dev/null +++ b/src/main/kotlin/wordle/domain/Dictionary.kt @@ -0,0 +1,8 @@ +package wordle.domain + +import wordle.infra.contains +import wordle.infra.dictionaryWord + +fun isDictionaryWord(word: String): Boolean = contains(word) + +fun dictionaryElementAt(index: Int): String = dictionaryWord(index) diff --git a/src/main/kotlin/wordle/domain/Letter.kt b/src/main/kotlin/wordle/domain/Letter.kt new file mode 100644 index 0000000..102e79f --- /dev/null +++ b/src/main/kotlin/wordle/domain/Letter.kt @@ -0,0 +1,23 @@ +package wordle.domain + +import wordle.exception.WordleExceptionCode.LETTER_INVALID_CHARACTER_TYPE + +data class Letter(private val value: Char) { + init { + check(isAlphabet() || isMatchMarker()) { LETTER_INVALID_CHARACTER_TYPE.message } + } + + fun changeMatchMarker(): Letter = MATCH_MARKER_LETTER + + fun value(): String = value.toString() + + private fun isAlphabet(): Boolean = value in ALPHABET + + private fun isMatchMarker(): Boolean = value == MATCH_MARKER + + companion object { + private const val MATCH_MARKER = '#' + private val ALPHABET = ('a'..'z').toSet() + private val MATCH_MARKER_LETTER = Letter(MATCH_MARKER) + } +} diff --git a/src/main/kotlin/wordle/domain/LetterMatch.kt b/src/main/kotlin/wordle/domain/LetterMatch.kt new file mode 100644 index 0000000..bca68c0 --- /dev/null +++ b/src/main/kotlin/wordle/domain/LetterMatch.kt @@ -0,0 +1,7 @@ +package wordle.domain + +enum class LetterMatch { + CORRECT, + PRESENT, + ABSENT, +} diff --git a/src/main/kotlin/wordle/domain/TodayWord.kt b/src/main/kotlin/wordle/domain/TodayWord.kt new file mode 100644 index 0000000..2bbb60c --- /dev/null +++ b/src/main/kotlin/wordle/domain/TodayWord.kt @@ -0,0 +1,19 @@ +package wordle.domain + +import wordle.infra.dictionaryWord +import java.time.LocalDate +import java.time.temporal.ChronoUnit + +typealias TodayWord = Word + +private val CRITERION_DATE: LocalDate = LocalDate.of(2021, 6, 19) + +fun TodayWord(today: LocalDate): TodayWord { + return Word(extractDictionaryWord(today)) +} + +private fun extractDictionaryWord(date: LocalDate): String { + val calculatedIndex = ChronoUnit.DAYS.between(CRITERION_DATE, date).toInt() + + return dictionaryWord(calculatedIndex) +} diff --git a/src/main/kotlin/wordle/domain/TryCount.kt b/src/main/kotlin/wordle/domain/TryCount.kt new file mode 100644 index 0000000..cd001db --- /dev/null +++ b/src/main/kotlin/wordle/domain/TryCount.kt @@ -0,0 +1,16 @@ +package wordle.domain + +import wordle.exception.WordleExceptionCode.TRY_COUNT_HAS_NOT_REMAINDER + +data class TryCount(private var count: Int = MAX_TRY_COUNT) { + val attempts get() = MAX_TRY_COUNT - count + + fun isRemainder(): Boolean = count in 1..MAX_TRY_COUNT + + fun minus() { + check(isRemainder()) { TRY_COUNT_HAS_NOT_REMAINDER.message } + this.count -= 1 + } +} + +const val MAX_TRY_COUNT = 6 diff --git a/src/main/kotlin/wordle/domain/Word.kt b/src/main/kotlin/wordle/domain/Word.kt new file mode 100644 index 0000000..62a56d2 --- /dev/null +++ b/src/main/kotlin/wordle/domain/Word.kt @@ -0,0 +1,21 @@ +package wordle.domain + +import wordle.exception.WordleExceptionCode.WORD_INVALID_LENGTH +import wordle.exception.WordleExceptionCode.WORD_IS_NOT_IN_DICTIONARY +import wordle.exception.WordleExceptionCode.WORD_NOT_ALLOW_SPACE + +data class Word(private val word: List) : List by word { + fun letters(): String = word.joinToString("", transform = Letter::value) +} + +const val WORD_LENGTH = 5 + +fun Word(word: String): Word { + check(word.isNotBlank()) { WORD_NOT_ALLOW_SPACE.message } + check(isValidLength(word)) { WORD_INVALID_LENGTH.message } + check(isDictionaryWord(word)) { WORD_IS_NOT_IN_DICTIONARY.message } + + return Word(word.toCharArray().map { Letter(it) }) +} + +private fun isValidLength(word: String) = word.length == WORD_LENGTH diff --git a/src/main/kotlin/wordle/domain/WordComparator.kt b/src/main/kotlin/wordle/domain/WordComparator.kt new file mode 100644 index 0000000..16e97d0 --- /dev/null +++ b/src/main/kotlin/wordle/domain/WordComparator.kt @@ -0,0 +1,60 @@ +package wordle.domain + +class WordComparator( + private val markerLetters: MutableList, + private val wordResult: WordResult = WordResult(), +) { + fun matchCorrect(answerWord: Word): WordComparator = + apply { + markerLetters.forEachIndexed { index, _ -> changeCorrectMatch(index, answerWord[index]) } + } + + fun matchPresent(answerWord: Word): WordComparator = + apply { + markerLetters.forEachIndexed { index, _ -> changePresentMatch(index, answerWord[index]) } + } + + fun result(): WordResult = wordResult + + private fun changeCorrectMatch( + index: Int, + answerLetter: Letter, + ) { + if (isCorrectLetter(index, answerLetter)) { + wordResult.changeMatchType(index, LetterMatch.CORRECT) + changeMarkerLetter(index) + } + } + + private fun changePresentMatch( + index: Int, + answerLetter: Letter, + ) { + if (isPresentLatter(index, answerLetter)) { + wordResult.changeMatchType(index, LetterMatch.PRESENT) + changeMarkerLetterIndexOf(answerLetter) + } + } + + private fun isCorrectLetter( + index: Int, + answerLetter: Letter, + ) = markerLetters[index] == answerLetter + + private fun isPresentLatter( + index: Int, + answerLetter: Letter, + ) = !(wordResult.isCorrectLetterMatch(index) || isCorrectLetter(index, answerLetter)) && (answerLetter in markerLetters) + + private fun changeMarkerLetter(index: Int) { + markerLetters[index] = markerLetters[index].changeMatchMarker() + } + + private fun changeMarkerLetterIndexOf(letter: Letter) { + changeMarkerLetter(markerLetters.indexOf(letter)) + } +} + +fun WordComparator(todayWord: Word): WordComparator { + return WordComparator(todayWord.map(Letter::copy).toMutableList()) +} diff --git a/src/main/kotlin/wordle/domain/WordResult.kt b/src/main/kotlin/wordle/domain/WordResult.kt new file mode 100644 index 0000000..f77a7d1 --- /dev/null +++ b/src/main/kotlin/wordle/domain/WordResult.kt @@ -0,0 +1,22 @@ +package wordle.domain + +import wordle.exception.WordleExceptionCode.WORD_RESULT_INVALID_LENGTH + +data class WordResult(private val result: MutableList = MutableList(WORD_LENGTH) { LetterMatch.ABSENT }) { + init { + check(result.size == WORD_LENGTH) { WORD_RESULT_INVALID_LENGTH.message } + } + + fun changeMatchType( + index: Int, + matchType: LetterMatch, + ) { + result[index] = matchType + } + + fun isCorrectLetterMatch(index: Int): Boolean = result[index] == LetterMatch.CORRECT + + fun isSuccessfulWordResult() = result.all { matchType -> matchType == LetterMatch.CORRECT } + + fun matches(): List = result.toList() +} diff --git a/src/main/kotlin/wordle/domain/WordResults.kt b/src/main/kotlin/wordle/domain/WordResults.kt new file mode 100644 index 0000000..e98b7fb --- /dev/null +++ b/src/main/kotlin/wordle/domain/WordResults.kt @@ -0,0 +1,19 @@ +package wordle.domain + +class WordResults( + private val results: MutableList = mutableListOf(), + private val tryCount: TryCount = TryCount(), +) { + val attemptCount: Int get() = tryCount.attempts + + fun addResults(result: WordResult) { + tryCount.minus() + results.add(result) + } + + fun isContinuousGame(): Boolean = !isSuccessfulGame() && tryCount.isRemainder() + + fun isSuccessfulGame(): Boolean = (tryCount.attempts != 0) && results.last().isSuccessfulWordResult() + + fun wordResults(): List> = results.map { it.matches() } +} diff --git a/src/main/kotlin/wordle/domain/WordleGameLogic.kt b/src/main/kotlin/wordle/domain/WordleGameLogic.kt new file mode 100644 index 0000000..e2baf0b --- /dev/null +++ b/src/main/kotlin/wordle/domain/WordleGameLogic.kt @@ -0,0 +1,11 @@ +package wordle.domain + +class WordleGameLogic(private val todayWord: Word) { + fun compare(answerWord: Word): WordResult = + todayWord.comparator() + .matchCorrect(answerWord) + .matchPresent(answerWord) + .result() + + private fun Word.comparator(): WordComparator = WordComparator(this) +} diff --git a/src/main/kotlin/wordle/exception/WordleExceptionCode.kt b/src/main/kotlin/wordle/exception/WordleExceptionCode.kt new file mode 100644 index 0000000..cf1f51c --- /dev/null +++ b/src/main/kotlin/wordle/exception/WordleExceptionCode.kt @@ -0,0 +1,12 @@ +package wordle.exception + +import wordle.domain.WORD_LENGTH + +enum class WordleExceptionCode(val message: String) { + WORD_NOT_ALLOW_SPACE("๋‹จ์–ด๋Š” ๊ณต๋ฐฑ๋งŒ ์ž…๋ ฅํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."), + WORD_INVALID_LENGTH("๋‹จ์–ด์˜ ๊ธธ์ด๋Š” ${WORD_LENGTH}์ž ์ž…๋‹ˆ๋‹ค."), + WORD_IS_NOT_IN_DICTIONARY("Wordle Game์—์„œ ์œ ํšจํ•œ ๋‹จ์–ด๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค."), + LETTER_INVALID_CHARACTER_TYPE("์œ ํšจํ•˜์ง€ ์•Š์€ ๊ธ€์ž ํ˜•์‹์ž…๋‹ˆ๋‹ค."), + TRY_COUNT_HAS_NOT_REMAINDER("์‹œํ–‰ ํšŸ์ˆ˜๋Š” 0๋ณด๋‹ค ์ž‘์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."), + WORD_RESULT_INVALID_LENGTH("๋‹จ์–ด ๊ฒฐ๊ณผ์˜ ๊ธ€์ž ์ผ์น˜ ์ƒํƒœ ๋ชฉ๋ก๋“ค์€ ๋‹จ์–ด ๊ธธ์ด์ธ ${WORD_LENGTH}์™€ ์ผ์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค."), +} diff --git a/src/main/kotlin/wordle/infra/DictionaryFileLoader.kt b/src/main/kotlin/wordle/infra/DictionaryFileLoader.kt new file mode 100644 index 0000000..8d4bcba --- /dev/null +++ b/src/main/kotlin/wordle/infra/DictionaryFileLoader.kt @@ -0,0 +1,25 @@ +package wordle.infra + +import java.nio.charset.StandardCharsets + +private const val NEW_LINE = "\n" +private const val WORDS_FILE_PATH = "words.txt" +private val classLoader: ClassLoader = Thread.currentThread().contextClassLoader +private val dictionaryWords: List by lazy { loadDictionaryWords() } +private val dictionaryWordSet: Set by lazy { dictionaryWords.toSet() } +val dictionaryWordsSize = dictionaryWords.size + +fun contains(word: String): Boolean = dictionaryWordSet.contains(word) + +fun dictionaryWord(index: Int): String { + check(dictionaryWords.isNotEmpty()) { NoSuchElementException("Dictionary File is Empty") } + + return dictionaryWords[index % dictionaryWordsSize] +} + +private fun loadDictionaryWords(): List = + classLoader.getResourceAsStream(WORDS_FILE_PATH) + ?.bufferedReader(StandardCharsets.UTF_8) + ?.use { it.readText().split(NEW_LINE) } + ?.filter { it.isNotBlank() } + ?: throw IllegalArgumentException("Dictionary File not found: $WORDS_FILE_PATH") diff --git a/src/main/kotlin/wordle/view/InputView.kt b/src/main/kotlin/wordle/view/InputView.kt new file mode 100644 index 0000000..bc2cd40 --- /dev/null +++ b/src/main/kotlin/wordle/view/InputView.kt @@ -0,0 +1,6 @@ +package wordle.view + +fun inputAnswerWord(): String { + println("๐Ÿš€ ์ •๋‹ต์„ ์ž…๋ ฅํ•˜์„ธ์š”. : ") + return readln() +} diff --git a/src/main/kotlin/wordle/view/OutputView.kt b/src/main/kotlin/wordle/view/OutputView.kt new file mode 100644 index 0000000..fcdf2a4 --- /dev/null +++ b/src/main/kotlin/wordle/view/OutputView.kt @@ -0,0 +1,31 @@ +package wordle.view + +import wordle.domain.LetterMatch +import wordle.domain.MAX_TRY_COUNT +import wordle.domain.Word +import wordle.domain.WordResults + +private const val ENTER = "\n" +private const val SPACE = "" + +fun printStartingGameMessage() { + println("๐ŸŽฎ WORDLE์„ ${MAX_TRY_COUNT}๋ฒˆ ๋งŒ์— ๋งž์ถฐ ๋ณด์„ธ์š”.$ENTER๐Ÿ“Œ ์‹œ๋„์˜ ๊ฒฐ๊ณผ๋Š” ํƒ€์ผ์˜ ์ƒ‰ ๋ณ€ํ™”๋กœ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค.๐Ÿฅณ$ENTER") +} + +fun printWordResults(results: WordResults) { + println(results.wordResults().joinToString(ENTER) { printTiles(it) } + ENTER) +} + +fun printRetry(message: String?) { + println("๐Ÿฅฒ ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”! : ${message ?: SPACE}$ENTER") +} + +fun printSuccess(attemptCount: Int) { + println("๐ŸŽ‰ ์„ฑ๊ณต์ž…๋‹ˆ๋‹ค. $attemptCount / $MAX_TRY_COUNT") +} + +fun printFail(todayWord: Word) { + println("๐Ÿ‘ป ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์˜ค๋Š˜์˜ ๋‹จ์–ด๋Š” [ ${todayWord.letters()} ] ์ž…๋‹ˆ๋‹ค.") +} + +private fun printTiles(it: List) = it.joinToString(SPACE) { Tile.of(it).color } diff --git a/src/main/kotlin/wordle/view/Tile.kt b/src/main/kotlin/wordle/view/Tile.kt new file mode 100644 index 0000000..3b25a34 --- /dev/null +++ b/src/main/kotlin/wordle/view/Tile.kt @@ -0,0 +1,14 @@ +package wordle.view + +import wordle.domain.LetterMatch + +enum class Tile(val matchType: LetterMatch, val color: String) { + GREEN(LetterMatch.CORRECT, "๐ŸŸฉ"), + YELLOW(LetterMatch.PRESENT, "๐ŸŸจ"), + GREY(LetterMatch.ABSENT, "โฌœ"), + ; + + companion object { + fun of(matchType: LetterMatch): Tile = entries.find { tile -> tile.matchType == matchType } ?: GREY + } +} diff --git a/src/test/kotlin/.gitkeep b/src/test/kotlin/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/test/kotlin/wordle/application/WordleGameTest.kt b/src/test/kotlin/wordle/application/WordleGameTest.kt new file mode 100644 index 0000000..4435003 --- /dev/null +++ b/src/test/kotlin/wordle/application/WordleGameTest.kt @@ -0,0 +1,189 @@ +package wordle.application + +import org.assertj.core.api.AssertionsForClassTypes.assertThat +import org.junit.jupiter.api.Test +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.PrintStream +import java.time.LocalDate + +private val GAME_START_DATE = LocalDate.of(2021, 6, 20) + +class WordleGameTest { + @Test + fun `6๋ฒˆ ์ด๋‚ด ์˜ค๋Š˜์˜ ๋‹จ์–ด๋ฅผ ๋งž์ถ”๋ฉด ์„ฑ๊ณต ์•ˆ๋‚ด ๋ฌธ๊ตฌ๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค`() { + val inputAnswerWords = successConsoleInput + val (printStream, outputStream) = printStreamByteArrayOutputStreamPair(inputAnswerWords) + + WordleGame(GAME_START_DATE).play() + printStream(printStream) + + assertThat(outputStream.toString().trimIndent()).isEqualTo(successConsolePrint.trimIndent()) + } + + @Test + fun `6๋ฒˆ ์ด๋‚ด ์˜ค๋Š˜์˜ ๋‹จ์–ด๋ฅผ ๋งž์ถ”์ง€ ๋ชปํ•˜๋ฉด ์‹คํŒจ ์•ˆ๋‚ด ๋ฌธ๊ตฌ์™€ ์˜ค๋Š˜์˜ ๋‹จ์–ด๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค`() { + val inputAnswerWords = failConsoleInput + val (printStream, outputStream) = printStreamByteArrayOutputStreamPair(inputAnswerWords) + + WordleGame(GAME_START_DATE).play() + printStream(printStream) + + assertThat(outputStream.toString().trimIndent()).isEqualTo(failConsolePrint.trimIndent()) + } + + @Test + fun `๋‹ต์•ˆ ์ž…๋ ฅ ์‹œ ๊ณต๋ฐฑ์„ ์ž…๋ ฅํ•˜๋ฉด ๋‹ค์‹œ ์ž…๋ ฅ์„ ๋ฐ›๋Š”๋‹ค`() { + val inputAnswerWords = "$blank$br$wordSpill" + val (printStream, outputStream) = printStreamByteArrayOutputStreamPair(inputAnswerWords) + + WordleGame(GAME_START_DATE).play() + printStream(printStream) + + assertThat(outputStream.toString().trimIndent()).isEqualTo(blankConsolePrint.trimIndent()) + } + + @Test + fun `๋‹ต์•ˆ ์ž…๋ ฅ ์‹œ ์ •ํ•ด์ง„ ๋‹จ์–ด์˜ ๊ธธ์ด์™€ ๋‹ค๋ฅธ ๋‹จ์–ด๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ๋‹ค์‹œ ์ž…๋ ฅ์„ ๋ฐ›๋Š”๋‹ค`() { + val inputAnswerWords = "$invalidLengthWord$br$wordSpill" + val (printStream, outputStream) = printStreamByteArrayOutputStreamPair(inputAnswerWords) + + WordleGame(GAME_START_DATE).play() + printStream(printStream) + + assertThat(outputStream.toString().trimIndent()).isEqualTo(invalidLengthConsolePrint.trimIndent()) + } + + @Test + fun `๋‹ต์•ˆ ์ž…๋ ฅ ์‹œ ๋‹จ์–ด ์‚ฌ์ „์— ์—†๋Š” ๋‹จ์–ด๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ๋‹ค์‹œ ์ž…๋ ฅ์„ ๋ฐ›๋Š”๋‹ค`() { + val inputAnswerWords = "$invalidWord$br$wordSpill" + val (printStream, outputStream) = printStreamByteArrayOutputStreamPair(inputAnswerWords) + + WordleGame(GAME_START_DATE).play() + printStream(printStream) + + assertThat(outputStream.toString().trimIndent()).isEqualTo(invalidWordConsolePrint.trimIndent()) + } + + private fun printStreamByteArrayOutputStreamPair(inputAnswerWords: String): Pair { + val printStream = System.out + val outputStream = ByteArrayOutputStream() + val inputStream = ByteArrayInputStream(inputAnswerWords.toByteArray()) + + System.setOut(PrintStream(outputStream)) + System.setIn(inputStream) + + return Pair(printStream, outputStream) + } + + private fun printStream(printStream: PrintStream) { + System.setOut(printStream) + System.setIn(System.`in`) + } + + private val answerWordHello = "hello" + private val answerWordLabel = "label" + private val answerWordSpell = "spell" + private val wordSpill = "spill" + private val br = "\n" + private val blank = "" + private val invalidLengthWord = "word" + private val invalidWord = "abcde" + private val successConsoleInput = "$answerWordHello$br$answerWordLabel$br$answerWordSpell$br$wordSpill" + private val failConsoleInput = + "$answerWordHello$br$answerWordLabel$br$answerWordSpell$br$answerWordHello$br$answerWordLabel$br$answerWordSpell" + + private val successConsolePrint = + """ + ๐Ÿš€ ์ •๋‹ต์„ ์ž…๋ ฅํ•˜์„ธ์š”. : + โฌœโฌœ๐ŸŸจ๐ŸŸฉโฌœ + + ๐Ÿš€ ์ •๋‹ต์„ ์ž…๋ ฅํ•˜์„ธ์š”. : + โฌœโฌœ๐ŸŸจ๐ŸŸฉโฌœ + ๐ŸŸจโฌœโฌœโฌœ๐ŸŸฉ + + ๐Ÿš€ ์ •๋‹ต์„ ์ž…๋ ฅํ•˜์„ธ์š”. : + โฌœโฌœ๐ŸŸจ๐ŸŸฉโฌœ + ๐ŸŸจโฌœโฌœโฌœ๐ŸŸฉ + ๐ŸŸฉ๐ŸŸฉโฌœ๐ŸŸฉ๐ŸŸฉ + + ๐Ÿš€ ์ •๋‹ต์„ ์ž…๋ ฅํ•˜์„ธ์š”. : + โฌœโฌœ๐ŸŸจ๐ŸŸฉโฌœ + ๐ŸŸจโฌœโฌœโฌœ๐ŸŸฉ + ๐ŸŸฉ๐ŸŸฉโฌœ๐ŸŸฉ๐ŸŸฉ + ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ + + ๐ŸŽ‰ ์„ฑ๊ณต์ž…๋‹ˆ๋‹ค. 4 / 6 + """ + + private val failConsolePrint = + """ + ๐Ÿš€ ์ •๋‹ต์„ ์ž…๋ ฅํ•˜์„ธ์š”. : + โฌœโฌœ๐ŸŸจ๐ŸŸฉโฌœ + + ๐Ÿš€ ์ •๋‹ต์„ ์ž…๋ ฅํ•˜์„ธ์š”. : + โฌœโฌœ๐ŸŸจ๐ŸŸฉโฌœ + ๐ŸŸจโฌœโฌœโฌœ๐ŸŸฉ + + ๐Ÿš€ ์ •๋‹ต์„ ์ž…๋ ฅํ•˜์„ธ์š”. : + โฌœโฌœ๐ŸŸจ๐ŸŸฉโฌœ + ๐ŸŸจโฌœโฌœโฌœ๐ŸŸฉ + ๐ŸŸฉ๐ŸŸฉโฌœ๐ŸŸฉ๐ŸŸฉ + + ๐Ÿš€ ์ •๋‹ต์„ ์ž…๋ ฅํ•˜์„ธ์š”. : + โฌœโฌœ๐ŸŸจ๐ŸŸฉโฌœ + ๐ŸŸจโฌœโฌœโฌœ๐ŸŸฉ + ๐ŸŸฉ๐ŸŸฉโฌœ๐ŸŸฉ๐ŸŸฉ + โฌœโฌœ๐ŸŸจ๐ŸŸฉโฌœ + + ๐Ÿš€ ์ •๋‹ต์„ ์ž…๋ ฅํ•˜์„ธ์š”. : + โฌœโฌœ๐ŸŸจ๐ŸŸฉโฌœ + ๐ŸŸจโฌœโฌœโฌœ๐ŸŸฉ + ๐ŸŸฉ๐ŸŸฉโฌœ๐ŸŸฉ๐ŸŸฉ + โฌœโฌœ๐ŸŸจ๐ŸŸฉโฌœ + ๐ŸŸจโฌœโฌœโฌœ๐ŸŸฉ + + ๐Ÿš€ ์ •๋‹ต์„ ์ž…๋ ฅํ•˜์„ธ์š”. : + โฌœโฌœ๐ŸŸจ๐ŸŸฉโฌœ + ๐ŸŸจโฌœโฌœโฌœ๐ŸŸฉ + ๐ŸŸฉ๐ŸŸฉโฌœ๐ŸŸฉ๐ŸŸฉ + โฌœโฌœ๐ŸŸจ๐ŸŸฉโฌœ + ๐ŸŸจโฌœโฌœโฌœ๐ŸŸฉ + ๐ŸŸฉ๐ŸŸฉโฌœ๐ŸŸฉ๐ŸŸฉ + + ๐Ÿ‘ป ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์˜ค๋Š˜์˜ ๋‹จ์–ด๋Š” [ spill ] ์ž…๋‹ˆ๋‹ค. + """ + + private val blankConsolePrint = + """ + ๐Ÿš€ ์ •๋‹ต์„ ์ž…๋ ฅํ•˜์„ธ์š”. : + ๐Ÿฅฒ ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”! : ๋‹จ์–ด๋Š” ๊ณต๋ฐฑ๋งŒ ์ž…๋ ฅํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. + + ๐Ÿš€ ์ •๋‹ต์„ ์ž…๋ ฅํ•˜์„ธ์š”. : + ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ + + ๐ŸŽ‰ ์„ฑ๊ณต์ž…๋‹ˆ๋‹ค. 1 / 6 + """ + + private val invalidLengthConsolePrint = + """ + ๐Ÿš€ ์ •๋‹ต์„ ์ž…๋ ฅํ•˜์„ธ์š”. : + ๐Ÿฅฒ ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”! : ๋‹จ์–ด์˜ ๊ธธ์ด๋Š” 5์ž ์ž…๋‹ˆ๋‹ค. + + ๐Ÿš€ ์ •๋‹ต์„ ์ž…๋ ฅํ•˜์„ธ์š”. : + ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ + + ๐ŸŽ‰ ์„ฑ๊ณต์ž…๋‹ˆ๋‹ค. 1 / 6 + """ + + private val invalidWordConsolePrint = + """ + ๐Ÿš€ ์ •๋‹ต์„ ์ž…๋ ฅํ•˜์„ธ์š”. : + ๐Ÿฅฒ ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”! : Wordle Game์—์„œ ์œ ํšจํ•œ ๋‹จ์–ด๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. + + ๐Ÿš€ ์ •๋‹ต์„ ์ž…๋ ฅํ•˜์„ธ์š”. : + ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ + + ๐ŸŽ‰ ์„ฑ๊ณต์ž…๋‹ˆ๋‹ค. 1 / 6 + """ +} diff --git a/src/test/kotlin/wordle/domain/AnswerWordTest.kt b/src/test/kotlin/wordle/domain/AnswerWordTest.kt new file mode 100644 index 0000000..87498fb --- /dev/null +++ b/src/test/kotlin/wordle/domain/AnswerWordTest.kt @@ -0,0 +1,13 @@ +package wordle.domain + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +class AnswerWordTest { + @Test + fun `(์„ฑ๊ณต) ๋‹ต์•ˆ ๋‹จ์–ด๋Š” ๋ฌธ์ž์—ด์„ ์ž…๋ ฅ๋ฐ›์•„ ๋‹จ์–ด๋ฅผ ์ƒ์„ฑํ•œ๋‹ค`() { + val actual = AnswerWord("hello") + + assertThat(actual).isEqualTo(Word("hello")) + } +} diff --git a/src/test/kotlin/wordle/domain/DictionaryKtTest.kt b/src/test/kotlin/wordle/domain/DictionaryKtTest.kt new file mode 100644 index 0000000..bafc43e --- /dev/null +++ b/src/test/kotlin/wordle/domain/DictionaryKtTest.kt @@ -0,0 +1,34 @@ +package wordle.domain + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Assertions.assertAll +import org.junit.jupiter.api.Test +import wordle.infra.dictionaryWordsSize + +private const val CONTAINS_WORD = "hello" + +class DictionaryKtTest { + @Test + fun `(์„ฑ๊ณต) ์‚ฌ์ „์— ํฌํ•จ๋œ ๋ฌธ์ž์—ด์ธ์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค`() { + val notInWord = "abced" + + assertAll( + { assertThat(isDictionaryWord(CONTAINS_WORD)).isTrue() }, + { assertThat(isDictionaryWord(notInWord)).isFalse() }, + ) + } + + @Test + fun `(์„ฑ๊ณต) ์‚ฌ์ „ ์•ˆ์˜ ๋‹จ์–ด๋ชฉ๋ก ์ค‘ ์ธ๋ฑ์Šค์— ํ•ด๋‹นํ•˜๋Š” ๋ฌธ์ž์—ด์„ ๊ฐ€์ ธ์˜จ๋‹ค`() { + val index = 0 + + assertThat(dictionaryElementAt(index)).isEqualTo(CONTAINS_WORD) + } + + @Test + fun `(์„ฑ๊ณต) ๋‹จ์–ด ๋ชฉ๋ก ์ธ๋ฑ์Šค์˜ ์ตœ๋Œ“๊ฐ’๋ณด๋‹ค ํฐ ์ธ๋ฑ์Šค๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ๋‹จ์–ด ๋ชฉ๋ก์˜ ์ธ๋ฑ์Šค๋Š” ๋‹ค์‹œ 0๋ถ€ํ„ฐ ์‹œ์ž‘ํ•œ๋‹ค`() { + val index = dictionaryWordsSize + + assertThat(dictionaryElementAt(index)).isEqualTo(CONTAINS_WORD) + } +} diff --git a/src/test/kotlin/wordle/domain/LetterTest.kt b/src/test/kotlin/wordle/domain/LetterTest.kt new file mode 100644 index 0000000..465b743 --- /dev/null +++ b/src/test/kotlin/wordle/domain/LetterTest.kt @@ -0,0 +1,42 @@ +package wordle.domain + +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource + +class LetterTest { + @Test + fun `(์„ฑ๊ณต) ์•ŒํŒŒ๋ฒณ ์†Œ๋ฌธ์ž ๊ธ€์ž๋ฅผ ์ƒ์„ฑํ•œ๋‹ค`() { + val actual = Letter('a') + + assertThat(actual).isEqualTo(Letter('a')) + } + + @Test + fun `(์„ฑ๊ณต) ์ผ์น˜ ํ‘œ์‹œ ๊ธฐํ˜ธ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค`() { + val actual = Letter(MATCH_MARKER) + + assertThat(actual).isEqualTo(Letter(MATCH_MARKER)) + } + + @ValueSource(strings = [" ", "1", "A", "๋‚˜", "@"]) + @ParameterizedTest + fun `(์˜ˆ์™ธ) ์œ ํšจํ•˜์ง€ ์•Š์€ ๊ธ€์ž๋ฅผ ์ƒ์„ฑํ•˜๋ฉด ์˜ˆ์™ธ ๋ฐœ์ƒํ•œ๋‹ค`(letter: Char) { + assertThatThrownBy { (Letter(letter)) } + .isInstanceOf(IllegalStateException::class.java) + .hasMessage("์œ ํšจํ•˜์ง€ ์•Š์€ ๊ธ€์ž ํ˜•์‹์ž…๋‹ˆ๋‹ค.") + } + + @Test + fun `(์„ฑ๊ณต) ์ฒดํฌ ํ‘œ์‹œ ๊ธ€์ž๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค`() { + val letter = Letter('b') + + val actual = letter.changeMatchMarker() + + assertThat(actual).isEqualTo(Letter(MATCH_MARKER)) + } +} + +private const val MATCH_MARKER = '#' diff --git a/src/test/kotlin/wordle/domain/TodayWordTest.kt b/src/test/kotlin/wordle/domain/TodayWordTest.kt new file mode 100644 index 0000000..41dc53b --- /dev/null +++ b/src/test/kotlin/wordle/domain/TodayWordTest.kt @@ -0,0 +1,30 @@ +package wordle.domain + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import java.time.LocalDate + +class TodayWordTest { + @Test + fun `(์„ฑ๊ณต) ์˜ค๋Š˜์˜ ๋‹จ์–ด๋Š” ์˜ค๋Š˜ ๋‚ ์งœ๋ฅผ ์ž…๋ ฅ๋ฐ›์•„ ๋‹จ์–ด๋ฅผ ์ƒ์„ฑํ•œ๋‹ค`() { + val today = LocalDate.of(2021, 6, 19) + + val actual = TodayWord(today) + + assertThat(actual).isEqualTo(Word("hello")) + } + + @Test + fun `(์„ฑ๊ณต) ์˜ค๋Š˜์˜ ๋‹จ์–ด๋Š” ๋งค์ผ ๋ฐ”๋€๋‹ค`() { + val criterionDate = LocalDate.of(2024, 6, 17) + val wordsSet = mutableSetOf() + + repeat(365) { + val currentDate = criterionDate.plusDays(it.toLong()) + val word = TodayWord(currentDate) + wordsSet.add(word) + } + + assertThat(wordsSet).hasSize(365) + } +} diff --git a/src/test/kotlin/wordle/domain/TryCountTest.kt b/src/test/kotlin/wordle/domain/TryCountTest.kt new file mode 100644 index 0000000..3bb8898 --- /dev/null +++ b/src/test/kotlin/wordle/domain/TryCountTest.kt @@ -0,0 +1,65 @@ +package wordle.domain + +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertAll +import org.junit.jupiter.api.assertDoesNotThrow + +class TryCountTest { + @Test + fun `์‹œ๋„ ๊ฐ€๋Šฅ ํšŸ์ˆ˜๋ฅผ ์ƒ์„ฑํ•œ๋‹ค - ์ตœ๋Œ€ ์‹œ๋„ ๊ฐ€๋Šฅ ํšŸ์ˆ˜๋ฅผ ๊ฐ€์ง„๋‹ค`() { + val actual = TryCount() + + assertThat(actual).isEqualTo(TryCount(MAX_TRY_COUNT)) + } + + @Test + fun `์‹œ๋„ํ•œ ํšŸ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค`() { + val tryCount = TryCount() + + val actual = tryCount.attempts + + assertThat(actual).isZero() + } + + @Test + fun `์‹œ๋„ ๊ฐ€๋Šฅ ํšŸ์ˆ˜๊ฐ€ ์žˆ๋‹ค๋ฉด true ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค`() { + val tryCount = TryCount() + tryCount.minus() + + val actual = tryCount.isRemainder() + + assertThat(actual).isTrue() + } + + @Test + fun `์‹œ๋„ ๊ฐ€๋Šฅ ํšŸ์ˆ˜๊ฐ€ ์—†๋‹ค๋ฉด false ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค`() { + val tryCount = TryCount(0) + + val actual = tryCount.isRemainder() + + assertThat(actual).isFalse() + } + + @Test + fun `์‹œ๋„ ๊ฐ€๋Šฅ ํšŸ์ˆ˜๋ฅผ 1ํšŒ ์ฐจ๊ฐํ•˜๋ฉด ์‹œ๋„ํ•œ ํšŸ์ˆ˜๋Š” 1ํšŒ ์ฆ๊ฐ€ํ•œ๋‹ค`() { + val tryCount = TryCount(6) + + assertDoesNotThrow { tryCount.minus() } + + assertAll( + { assertThat(tryCount).isEqualTo(TryCount(5)) }, + { assertThat(tryCount.attempts).isEqualTo(1) }, + ) + } + + @Test + fun `์‹œ๋„ ๊ฐ€๋Šฅ ํšŸ์ˆ˜๊ฐ€ 0 ๋ณด๋‹ค ์ž‘์œผ๋ฉด, ์˜ˆ์™ธ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค`() { + val tryCount = TryCount(0) + + assertThatThrownBy { tryCount.minus() } + .isInstanceOf(IllegalStateException::class.java) + .hasMessage("์‹œํ–‰ ํšŸ์ˆ˜๋Š” 0๋ณด๋‹ค ์ž‘์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.") + } +} diff --git a/src/test/kotlin/wordle/domain/WordResultTest.kt b/src/test/kotlin/wordle/domain/WordResultTest.kt new file mode 100644 index 0000000..2be2609 --- /dev/null +++ b/src/test/kotlin/wordle/domain/WordResultTest.kt @@ -0,0 +1,55 @@ +package wordle.domain + +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertAll + +class WordResultTest { + @Test + fun `(์„ฑ๊ณต) ๋‹จ์–ด ๊ฒฐ๊ณผ๋Š” ๊ธ€์ž ์ผ์น˜ ์ƒํƒœ ๋ชฉ๋ก ์—†์ด ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค - ๋ชจ๋‘ '๋ถˆ์ผ์น˜ ์ƒํƒœ'๋ฅผ ๊ฐ€์ง„๋‹ค`() { + val wordResult = WordResult() + + assertThat(wordResult).isEqualTo(MutableList(WORD_LENGTH) { LetterMatch.ABSENT }.toWordResult()) + } + + @Test + fun `(์„ฑ๊ณต) ํ•ด๋‹น ๊ธ€์ž์˜ ๊ธ€์ž ์ผ์น˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค`() { + val wordResult = WordResult() + + wordResult.changeMatchType(0, LetterMatch.CORRECT) + wordResult.changeMatchType(2, LetterMatch.PRESENT) + wordResult.changeMatchType(4, LetterMatch.CORRECT) + + assertThat(wordResult).isEqualTo( + mutableListOf( + LetterMatch.CORRECT, + LetterMatch.ABSENT, + LetterMatch.PRESENT, + LetterMatch.ABSENT, + LetterMatch.CORRECT, + ).toWordResult(), + ) + } + + @Test + fun `(์„ฑ๊ณต) ํ•ด๋‹น ๊ธ€์ž๊ฐ€ '์™„์ „ ์ผ์น˜ ์ƒํƒœ'์ธ์ง€ ํ™•์ธํ•œ๋‹ค`() { + val wordResult = WordResult() + + wordResult.changeMatchType(1, LetterMatch.CORRECT) + + assertAll( + { assertThat(wordResult.isCorrectLetterMatch(0)).isFalse() }, + { assertThat(wordResult.isCorrectLetterMatch(1)).isTrue() }, + ) + } + + @Test + fun `(์˜ˆ์™ธ) ๋‹จ์–ด ๊ฒฐ๊ณผ ๋ชฉ๋ก์ด ๋‹จ์–ด ๊ธธ์ด์™€ ์ผ์น˜ํ—ค์•ผ ํ•œ๋‹ค`() { + assertThatThrownBy { WordResult(mutableListOf()) } + .isInstanceOf(IllegalStateException::class.java) + .hasMessage("๋‹จ์–ด ๊ฒฐ๊ณผ์˜ ๊ธ€์ž ์ผ์น˜ ์ƒํƒœ ๋ชฉ๋ก๋“ค์€ ๋‹จ์–ด ๊ธธ์ด์ธ ${WORD_LENGTH}์™€ ์ผ์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.") + } +} + +fun MutableList.toWordResult(): WordResult = WordResult(this) diff --git a/src/test/kotlin/wordle/domain/WordResultsTest.kt b/src/test/kotlin/wordle/domain/WordResultsTest.kt new file mode 100644 index 0000000..bbeeacb --- /dev/null +++ b/src/test/kotlin/wordle/domain/WordResultsTest.kt @@ -0,0 +1,107 @@ +package wordle.domain + +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.jupiter.api.Assertions.assertAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource + +class WordResultsTest { + @Test + fun `(์„ฑ๊ณต) ๋‹จ์–ด ๊ฒฐ๊ณผ ๋ชฉ๋ก์€ ๋นˆ ๋‹จ์–ด ๊ฒฐ๊ณผ ๋ชฉ๋ก๊ณผ ์‹œ๋„ํ•œ ํšŸ์ˆ˜๊ฐ€ 0์ด๋‹ค`() { + val actual = WordResults() + + assertAll( + { assertThat(actual.wordResults()).hasSize(0) }, + { assertThat(actual.attemptCount).isZero() }, + ) + } + + @Test + fun `(์„ฑ๊ณต) ๋‹จ์–ด ๊ฒฐ๊ณผ ๋ชฉ๋ก์€ ๋‹จ์–ด ๊ฒฐ๊ณผ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ , ์‹œ๋„ํšŸ์ˆ˜๊ฐ€ 1ํšŒ ์ฆ๊ฐ€ํ•œ๋‹ค`() { + val wordResults = WordResults() + + wordResults.addResults(WordResult()) + + assertAll( + { assertThat(wordResults.wordResults()).hasSize(1) }, + { assertThat(wordResults.attemptCount).isEqualTo(1) }, + ) + } + + @ValueSource(ints = [1, 2, 3, 4, 5]) + @ParameterizedTest + fun `(์„ฑ๊ณต) ๋‹จ์–ด ๊ฒฐ๊ณผ ๋ชฉ๋ก์„ ์ถ”๊ฐ€ํ•  ๋•Œ๋งˆ๋‹ค ๊ฒŒ์ž„์„ ๊ณ„์† ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ์ƒํƒœ์ธ์ง€ ๋ฐ˜ํ™˜ํ•œ๋‹ค`(attemptCount: Int) { + val wordResults = WordResults() + + repeat(attemptCount) { + wordResults.addResults(WordResult()) + } + + assertThat(wordResults.isContinuousGame()).isTrue() + } + + @Test + fun `(์„ฑ๊ณต) ๋‹จ์–ด ๊ฒฐ๊ณผ ๋ชฉ๋ก์ด ์ตœ๋Œ€๋กœ ์ถ”๊ฐ€๋˜์—ˆ์„ ๋•Œ ๊ฒŒ์ž„์„ ๊ณ„์†ํ•  ์ˆ˜ ์—†๋Š” ์ƒํƒœ๊ฐ€ ๋œ๋‹ค`() { + val wordResults = WordResults() + + repeat(MAX_TRY_COUNT) { + wordResults.addResults(WordResult()) + } + + assertThat(wordResults.isContinuousGame()).isFalse() + } + + @Test + fun `(์˜ˆ์™ธ) ๋‹จ์–ด ๊ฒฐ๊ณผ ๋ชฉ๋ก์„ ๋”์ด์ƒ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์—†์œผ๋ฉด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค`() { + val wordResults = WordResults() + + assertThatThrownBy { + repeat(MAX_TRY_COUNT + 1) { + wordResults.addResults(WordResult()) + } + }.isInstanceOf(IllegalStateException::class.java) + .hasMessage("์‹œํ–‰ ํšŸ์ˆ˜๋Š” 0๋ณด๋‹ค ์ž‘์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.") + } + + @Test + fun `(์„ฑ๊ณต) ๋‹จ์–ด ๊ฒฐ๊ณผ ๋ชฉ๋ก ๋งˆ์ง€๋ง‰์— ๋ชจ๋‘ '์™„์ „ ์ผ์น˜ ์ƒํƒœ'๊ฐ€ ์žˆ์–ด์•ผ ๋‹จ์–ด ๋งž์ถ”๊ธฐ ๊ฒŒ์ž„์— ์„ฑ๊ณตํ•œ๋‹ค`() { + val wordResults = WordResults() + wordResults.addResults(WordResult()) + wordResults.addResults(oneAbsentWordResult) + wordResults.addResults(onePresentWordResult) + wordResults.addResults(MutableList(WORD_LENGTH) { LetterMatch.CORRECT }.toWordResult()) + + assertThat(wordResults.isSuccessfulGame()).isTrue() + } + + @Test + fun `(์„ฑ๊ณต) ๋‹จ์–ด ๊ฒฐ๊ณผ๊ฐ€ ๋ชจ๋‘ '์™„์ „ ์ผ์น˜ ์ƒํƒœ'๊ฐ€ ์•„๋‹ˆ๋ฉด ๋‹จ์–ด ๋งž์ถ”๊ธฐ ๊ฒŒ์ž„์— ์„ฑ๊ณต์—ฌ๋ถ€๊ฐ€ False์ด๋‹ค`() { + val wordResults = WordResults() + wordResults.addResults(oneAbsentWordResult) + wordResults.addResults(oneAbsentWordResult) + wordResults.addResults(oneAbsentWordResult) + wordResults.addResults(onePresentWordResult) + + assertThat(wordResults.isSuccessfulGame()).isFalse() + } + + private val oneAbsentWordResult = + mutableListOf( + LetterMatch.ABSENT, + LetterMatch.CORRECT, + LetterMatch.CORRECT, + LetterMatch.CORRECT, + LetterMatch.CORRECT, + ).toWordResult() + + private val onePresentWordResult = + mutableListOf( + LetterMatch.PRESENT, + LetterMatch.CORRECT, + LetterMatch.CORRECT, + LetterMatch.CORRECT, + LetterMatch.CORRECT, + ).toWordResult() +} diff --git a/src/test/kotlin/wordle/domain/WordTest.kt b/src/test/kotlin/wordle/domain/WordTest.kt new file mode 100644 index 0000000..b4b419c --- /dev/null +++ b/src/test/kotlin/wordle/domain/WordTest.kt @@ -0,0 +1,42 @@ +package wordle.domain + +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EmptySource +import org.junit.jupiter.params.provider.ValueSource + +class WordTest { + @Test + fun `(์„ฑ๊ณต) ๋‹จ์–ด๋ฅผ ์ƒ์„ฑํ•œ๋‹ค`() { + val actual = Word("hello") + + assertThat(actual).isEqualTo(Word("hello")) + } + + @EmptySource + @ValueSource(strings = [" ", " "]) + @ParameterizedTest + fun `(์˜ˆ์™ธ) ๋‹จ์–ด๋Š” ๊ณต๋ฐฑ๋งŒ ์ž…๋ ฅํ•  ์ˆ˜ ์—†๋‹ค`(invalidWord: String) { + assertThatThrownBy { (Word(invalidWord)) } + .isInstanceOf(IllegalStateException::class.java) + .hasMessage("๋‹จ์–ด๋Š” ๊ณต๋ฐฑ๋งŒ ์ž…๋ ฅํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.") + } + + @ValueSource(strings = ["word", "aaaaaa"]) + @ParameterizedTest + fun `(์˜ˆ์™ธ) ๋‹จ์–ด์˜ ๊ธธ์ด๋Š” 5์ž ์ด์–ด์•ผ ํ•œ๋‹ค`(invalidWord: String) { + assertThatThrownBy { (Word(invalidWord)) } + .isInstanceOf(IllegalStateException::class.java) + .hasMessage("๋‹จ์–ด์˜ ๊ธธ์ด๋Š” ${WORD_LENGTH}์ž ์ž…๋‹ˆ๋‹ค.") + } + + @ValueSource(strings = ["aaaaa", "abced"]) + @ParameterizedTest + fun `(์˜ˆ์™ธ) ๋‹จ์–ด๋Š” ์‚ฌ์ „์— ์žˆ๋Š” ๋‹จ์–ด์ด์–ด์•ผ ํ•œ๋‹ค`(invalidWord: String) { + assertThatThrownBy { (Word(invalidWord)) } + .isInstanceOf(IllegalStateException::class.java) + .hasMessage("Wordle Game์—์„œ ์œ ํšจํ•œ ๋‹จ์–ด๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.") + } +} diff --git a/src/test/kotlin/wordle/domain/WordleGameLogicTest.kt b/src/test/kotlin/wordle/domain/WordleGameLogicTest.kt new file mode 100644 index 0000000..5fa5127 --- /dev/null +++ b/src/test/kotlin/wordle/domain/WordleGameLogicTest.kt @@ -0,0 +1,188 @@ +package wordle.domain + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test + +class WordleGameLogicTest { + private lateinit var wordleGameLogic: WordleGameLogic + + @BeforeEach + fun setUp() { + val todayWord = Word("spill") + wordleGameLogic = WordleGameLogic(todayWord) + } + + @Nested + inner class `๋‹จ์–ด ๊ฒฐ๊ณผ ์ค‘ ๊ธ€์ž ์ผ์น˜ ์ƒํƒœ๊ฐ€ ํ•˜๋‚˜๋งŒ ๋‹ค๋ฅธ ๊ฒฝ์šฐ` { + @Test + fun `(์„ฑ๊ณต) ํ•˜๋‚˜์˜ ๊ธ€์ž ๊ฒฐ๊ณผ๋งŒ '๋ถ€๋ถ„ ์ผ์น˜ ๊ธ€์ž'์ผ ๋•Œ ๋น„๊ตํ•œ๋‹ค`() { + val answerWord = Word("proxy") + + val result = wordleGameLogic.compare(answerWord) + + assertThat(result).isEqualTo( + mutableListOf( + LetterMatch.PRESENT, + LetterMatch.ABSENT, + LetterMatch.ABSENT, + LetterMatch.ABSENT, + LetterMatch.ABSENT, + ).toWordResult(), + ) + } + + @Test + fun `(์„ฑ๊ณต) ํ•˜๋‚˜์˜ ๊ธ€์ž ๊ฒฐ๊ณผ๋งŒ '์™„์ „ ์ผ์น˜ ๊ธ€์ž'์ผ ๋•Œ ๋น„๊ตํ•œ๋‹ค - '์™„์ „ ์ผ์น˜ ๊ธ€์ž'๋ถ€ํ„ฐ ๋น„๊ตํ•œ๋‹ค`() { + val answerWord = Word("royal") + + val result = wordleGameLogic.compare(answerWord) + + assertThat(result).isEqualTo( + mutableListOf( + LetterMatch.ABSENT, + LetterMatch.ABSENT, + LetterMatch.ABSENT, + LetterMatch.ABSENT, + LetterMatch.CORRECT, + ).toWordResult(), + ) + } + } + + @Nested + inner class `๋‹ต์•ˆ ๋‹จ์–ด์— ๊ฐ™์€ ๊ธ€์ž๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ` { + @Test + fun `(์„ฑ๊ณต) ๋‘ ๋ฒˆ ๊ฐ™์€ ๊ธ€์ž๊ฐ€ ์—ฐ์†๋˜๋Š” ๋‹ต์•ˆ ๋‹จ์–ด์—์„œ, ์˜ค๋Š˜์˜ ๋‹จ์–ด์™€ ํ•œ ๊ธ€์ž๋งŒ '๋ถ€๋ถ„ ์ผ์น˜ ๊ธ€์ž'์ผ ๋•Œ ๋น„๊ตํ•œ๋‹ค`() { + val answerWord = Word("truss") + + val result = wordleGameLogic.compare(answerWord) + + assertThat(result).isEqualTo( + mutableListOf( + LetterMatch.ABSENT, + LetterMatch.ABSENT, + LetterMatch.ABSENT, + LetterMatch.PRESENT, + LetterMatch.ABSENT, + ).toWordResult(), + ) + } + + @Test + fun `(์„ฑ๊ณต) ๋‘ ๋ฒˆ ๊ฐ™์€ ๊ธ€์ž๊ฐ€ ์—ฐ์†๋˜๋Š” ๋‹ต์•ˆ ๋‹จ์–ด์—์„œ, ์˜ค๋Š˜์˜ ๋‹จ์–ด์™€ ๋‘ ๊ธ€์ž '๋ถ€๋ถ„ ์ผ์น˜ ๊ธ€์ž'์ผ ๋•Œ ๋น„๊ตํ•œ๋‹ค`() { + val answerWord = Word("llama") + + val result = wordleGameLogic.compare(answerWord) + + assertThat(result).isEqualTo( + mutableListOf( + LetterMatch.PRESENT, + LetterMatch.PRESENT, + LetterMatch.ABSENT, + LetterMatch.ABSENT, + LetterMatch.ABSENT, + ).toWordResult(), + ) + } + + @Test + fun `(์„ฑ๊ณต) ๋‘ ๋ฒˆ ๊ฐ™์€ ๊ธ€์ž๊ฐ€ ์—ฐ์†๋˜๋Š” ๋‹ต์•ˆ ๋‹จ์–ด์—์„œ, '๋ถ€๋ถ„ ์ผ์น˜ ๊ธ€์ž'์™€ '์™„์ „ ์ผ์น˜ ๊ธ€์ž'๋ฅผ ๊ฐ€์งˆ ๋•Œ ๋น„๊ตํ•œ๋‹ค`() { + val answerWord = Word("hello") + + val result = wordleGameLogic.compare(answerWord) + + assertThat(result).isEqualTo( + mutableListOf( + LetterMatch.ABSENT, + LetterMatch.ABSENT, + LetterMatch.PRESENT, + LetterMatch.CORRECT, + LetterMatch.ABSENT, + ).toWordResult(), + ) + } + + @Test + fun `(์„ฑ๊ณต) ๊ฐ™์€ ๊ธ€์ž๊ฐ€ ์—ฐ์† ํ•˜์ง€ ์•Š๋Š” ๋‹ต์•ˆ ๋‹จ์–ด์—์„œ, '๋ถ€๋ถ„ ์ผ์น˜ ๊ธ€์ž'์™€ '์™„์ „ ์ผ์น˜ ๊ธ€์ž'๋ฅผ ๊ฐ€์งˆ ๋•Œ ๋น„๊ตํ•œ๋‹ค`() { + val answerWord = Word("label") + + val result = wordleGameLogic.compare(answerWord) + + assertThat(result).isEqualTo( + mutableListOf( + LetterMatch.PRESENT, + LetterMatch.ABSENT, + LetterMatch.ABSENT, + LetterMatch.ABSENT, + LetterMatch.CORRECT, + ).toWordResult(), + ) + } + + @Test + fun `(์„ฑ๊ณต) ์„ธ ๊ฐœ์˜ ๊ฐ™์€ ๊ธ€์ž๋ฅผ ๊ฐ€์ง€๋Š” ๋‹ต์•ˆ ๋‹จ์–ด์—์„œ ํ•˜๋‚˜์˜ ๊ธ€์ž ๊ฒฐ๊ณผ๋งŒ '๋ถ€๋ถ„ ์ผ์น˜ ๊ธ€์ž'์ผ ๋•Œ ๋น„๊ตํ•œ๋‹ค`() { + val answerWord = Word("puppy") + + val result = wordleGameLogic.compare(answerWord) + + assertThat(result).isEqualTo( + mutableListOf( + LetterMatch.PRESENT, + LetterMatch.ABSENT, + LetterMatch.ABSENT, + LetterMatch.ABSENT, + LetterMatch.ABSENT, + ).toWordResult(), + ) + } + + @Test + fun `(์„ฑ๊ณต) ๋„ค ๊ฐœ์˜ ๊ธ€์ž ๊ฒฐ๊ณผ๊ฐ€ '์™„์ „ ์ผ์น˜ ๊ธ€์ž'์ผ ๋•Œ ๋น„๊ตํ•œ๋‹ค`() { + val answerWord = Word("spell") + + val result = wordleGameLogic.compare(answerWord) + + assertThat(result).isEqualTo( + mutableListOf( + LetterMatch.CORRECT, + LetterMatch.CORRECT, + LetterMatch.ABSENT, + LetterMatch.CORRECT, + LetterMatch.CORRECT, + ).toWordResult(), + ) + } + } + + @Nested + inner class `๋‹จ์–ด ๊ฒฐ๊ณผ ์ค‘ ๊ธ€์ž ์ผ์น˜ ์ƒํƒœ๊ฐ€ ๋ชจ๋‘ ๊ฐ™์€ ๊ฒฝ์šฐ` { + @Test + fun `(์„ฑ๊ณต) ๋ชจ๋“  ๊ธ€์ž ๊ฒฐ๊ณผ๊ฐ€ '๋ถˆ์ผ์น˜ ์ƒํƒœ'์ผ ๋•Œ ๋น„๊ตํ•œ๋‹ค`() { + val answerWord = Word("major") + + val result = wordleGameLogic.compare(answerWord) + + assertThat(result).isEqualTo(MutableList(WORD_LENGTH) { LetterMatch.ABSENT }.toWordResult()) + } + + @Test + fun `(์„ฑ๊ณต) ๋ชจ๋“  ๊ธ€์ž ๊ฒฐ๊ณผ๊ฐ€ '๋ถ€๋ถ„ ์ผ์น˜ ๊ธ€์ž'์ผ ๋•Œ ๋น„๊ตํ•œ๋‹ค`() { + val answerWord = Word("illps") + + val result = wordleGameLogic.compare(answerWord) + + assertThat(result).isEqualTo(MutableList(WORD_LENGTH) { LetterMatch.PRESENT }.toWordResult()) + } + + @Test + fun `(์„ฑ๊ณต) ๋ชจ๋“  ๊ธ€์ž ๊ฒฐ๊ณผ๊ฐ€ '์™„์ „ ์ผ์น˜ ๊ธ€์ž'์ผ ๋•Œ ๋น„๊ตํ•œ๋‹ค`() { + val answerWord = Word("spill") + + val result = wordleGameLogic.compare(answerWord) + + assertThat(result).isEqualTo(MutableList(WORD_LENGTH) { LetterMatch.CORRECT }.toWordResult()) + } + } +} diff --git a/src/test/resources/words.txt b/src/test/resources/words.txt new file mode 100644 index 0000000..2015d7d --- /dev/null +++ b/src/test/resources/words.txt @@ -0,0 +1,371 @@ +hello +spill +cigar +rebut +sissy +humph +awake +blush +focal +evade +naval +serve +heath +dwarf +model +karma +stink +grade +quiet +bench +abate +feign +major +death +fresh +crust +stool +colon +abase +marry +react +batty +pride +floss +helix +croak +staff +paper +unfed +whelp +trawl +outdo +adobe +crazy +sower +repay +digit +crate +cluck +spike +mimic +pound +maxim +linen +unmet +flesh +booby +forth +first +stand +belly +ivory +seedy +print +yearn +drain +bribe +stout +panel +crass +flume +offal +agree +error +swirl +argue +bleed +delta +flick +totem +wooer +front +shrub +parry +biome +lapel +start +greet +goner +golem +lusty +loopy +round +audit +lying +gamma +labor +islet +civic +forge +corny +moult +basic +salad +agate +spicy +spray +essay +fjord +spend +kebab +guild +aback +motor +alone +hatch +hyper +thumb +dowry +ought +belch +dutch +pilot +tweed +comet +jaunt +enema +steed +abyss +growl +fling +dozen +boozy +erode +world +gouge +click +briar +great +altar +pulpy +blurt +coast +duchy +groin +fixer +group +rogue +badly +smart +pithy +gaudy +chill +heron +vodka +finer +surer +radio +rouge +perch +retch +wrote +clock +tilde +store +prove +bring +solve +cheat +grime +exult +usher +epoch +triad +break +rhino +viral +conic +masse +sonic +vital +trace +using +peach +champ +baton +brake +pluck +craze +gripe +weary +picky +acute +ferry +aside +tapir +troll +unify +rebus +boost +truss +siege +tiger +banal +slump +crank +gorge +query +drink +favor +abbey +tangy +panic +solar +shire +proxy +point +robot +prick +wince +crimp +knoll +sugar +whack +mount +perky +could +wrung +light +those +moist +shard +pleat +aloft +skill +elder +frame +humor +pause +ulcer +ultra +robin +cynic +aroma +caulk +shake +dodge +swill +tacit +other +thorn +trove +bloke +vivid +chant +choke +rupee +nasty +mourn +ahead +brine +cloth +hoard +sweet +month +lapse +watch +today +focus +smelt +tease +cater +movie +saute +allow +renew +their +slosh +purge +chest +depot +epoxy +nymph +found +shall +harry +stove +lowly +snout +trope +fewer +shawl +natal +comma +foray +scare +stair +black +squad +royal +chunk +mince +shame +cheek +ample +flair +foyer +cargo +oxide +plant +olive +inert +askew +heist +shown +zesty +hasty +trash +fella +larva +forgo +story +hairy +train +homer +badge +midst +canny +fetus +butch +farce +slung +tipsy +metal +yield +delve +being +scour +glass +gamer +scrap +money +hinge +album +vouch +asset +tiara +crept +bayou +atoll +manor +creak +showy +phase +froth +depth +gloom +flood +trait +girth +piety +payer +goose +float +donor +atone +label +spell +llama +puppy +illps