-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Wordle] 차리(이찬주) 미션 제출합니다. #11
base: main
Are you sure you want to change the base?
Changes from 13 commits
38e9dde
80f767b
da55951
6377d1d
ba0bba1
17aca8c
35e26f7
80cdae7
d284131
052464f
b80b2e3
ce0abfe
d2b992b
0d96a6d
df8a5b0
80b7921
53f36be
7feb21f
c77d604
21c766d
4aa60e3
86c784f
04c9896
0a3af01
d5216d2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
## 기능 요구사항 | ||
### 입출력 | ||
- [x] 게임 방법 안내를 출력한다. | ||
- [x] `예측 단어`를 입력받는다. | ||
- [x] `예측 단어`가 유효한 단어가 아니면 안내 문구를 출력한 후 재입력받는다. | ||
- [x] 최대 6번 입력받을 수 있다. | ||
- [x] `예측 결과`를 출력한다. | ||
- [x] 맞는 글자는 초록색🟩, 위치만 틀리면 노란색🟨, 없으면 회색⬜으로 출력한다. | ||
- [x] 게임을 종료한다. | ||
- [x] 6번 이내에 `정답 단어`를 맞추지 못할 경우 정답 단어를 출력한 후에 종료한다. | ||
|
||
### 도메인 | ||
- [x] `유효한 단어`를 불러온다. | ||
- [x] `유효한 단어`란, `words.txt`에 존재하는 단어이다. | ||
- [x] `정답 단어`를 불러온다. | ||
- [x] 정답은 유효한 단어에서 뽑는다. | ||
- [x] 정답은 매일 바뀌며 `((현재 날짜 - 2021년 6월 19일) % 배열의 크기)` 번째의 단어이다. | ||
- [x] `예측 단어`와 `정답 단어`를 비교한다. | ||
- [x] 맞는 글자가 있는지 먼저 비교한다. | ||
- [x] 위치만 틀린 글자가 있는지 그 다음으로 비교한다. | ||
- [x] 나머지는 모두 틀린 글자로 간주한다. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package wordle | ||
|
||
import wordle.domain.Answer | ||
import wordle.domain.Color | ||
import wordle.domain.Color.GREEN | ||
import wordle.domain.Word | ||
import wordle.domain.WordPicker | ||
import wordle.view.InputView | ||
import wordle.view.OutputView | ||
|
||
fun main() { | ||
val wordPicker = WordPicker() | ||
val answer = Answer(wordPicker.pickTodayAnswer()) | ||
OutputView.printIntroduction() | ||
|
||
if (playWordle(answer)) { | ||
return | ||
} | ||
OutputView.printAnswer(answer) | ||
} | ||
|
||
private fun playWordle(answer: Answer): Boolean { | ||
repeat(6) { | ||
val guessWord = guessAnswer() | ||
val result = answer.compare(guessWord) | ||
OutputView.printResult(result) | ||
if (isAllGreen(result)) { | ||
OutputView.printCount(it + 1) | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
fun guessAnswer(): Word { | ||
return try { | ||
Word(InputView.inputGuess()) | ||
} catch (e: IllegalArgumentException) { | ||
OutputView.printError(e.message) | ||
guessAnswer() | ||
} | ||
} | ||
|
||
private fun isAllGreen(result: List<Color>): Boolean { | ||
return result.all { it == GREEN } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package wordle.domain | ||
|
||
private const val RANGE_START = 0 | ||
private const val RANGE_END = 4 | ||
|
||
class Answer(val word: Word) { | ||
|
||
fun compare(word: Word): List<Color> { | ||
val exactIndices = compareExact(word) | ||
val anyIndices = compareAny(word) | ||
return merge(exactIndices, anyIndices) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 단어를 비교하는 로직을 이해하기 쉽네요. 한 수 배우고 갑니다😲😲 |
||
} | ||
|
||
private fun compareExact(word: Word): List<Int> { | ||
return (RANGE_START..RANGE_END).filter { this.word.compareByIndex(word, it) } | ||
} | ||
|
||
private fun compareAny(word: Word): List<Int> { | ||
return (RANGE_START..RANGE_END).filter { isAnyMatch(word, it) } | ||
} | ||
|
||
private fun merge(greenIndices: List<Int>, yellowIndices: List<Int>): List<Color> { | ||
return (RANGE_START..RANGE_END).map { defineColor(it, greenIndices, yellowIndices) } | ||
} | ||
|
||
private fun defineColor(index: Int, greenIndices: List<Int>, yellowIndices: List<Int>): Color { | ||
if (greenIndices.contains(index)) { | ||
return Color.GREEN | ||
} | ||
if (yellowIndices.contains(index)) { | ||
return Color.YELLOW | ||
} | ||
return Color.GRAY | ||
} | ||
|
||
private fun isAnyMatch(word: Word, outerIndex: Int): Boolean { | ||
return (RANGE_START..RANGE_END).any { this.word.compareByIndex(word, it, outerIndex) } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package wordle.domain | ||
|
||
enum class Color(val representation: String) { | ||
GREEN("🟩"), | ||
YELLOW("🟨"), | ||
GRAY("⬜") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. UI영역에서는 초록색, 노란색, 회색으로 결과를 보여주는 부분을 도메인 로직에서 의존하는거 같아요. 당장은 아니더라도 한번 고민해보시면 좋을거 같습니다😊 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 도메인쪽에서 뷰 로직에 영향을 받는게 좋아보이지는 않네요. view쪽에서 when절로 처리하였습니다~ |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package wordle.domain | ||
|
||
import java.io.File | ||
|
||
private const val WORD_LENGTH = 5 | ||
|
||
data class Word(val word: String) { | ||
init { | ||
require(word.length == WORD_LENGTH) { "단어는 5글자여야 합니다." } | ||
require(isLowerCase(word)) { "단어는 소문자로 이루어져야 합니다." } | ||
require(contains(word)) { "유효하지 않은 단어입니다." } | ||
} | ||
|
||
private fun isLowerCase(value: String): Boolean { | ||
return value.all { it.isLowerCase() } | ||
} | ||
|
||
fun compareByIndex(other: Word, myIndex: Int, otherIndex: Int = myIndex): Boolean { | ||
return word[myIndex] == other.word[otherIndex] | ||
} | ||
|
||
companion object { | ||
private val CACHE: List<String> = File("src/main/resources/words.txt") | ||
.readLines() | ||
Comment on lines
+21
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 캐싱 사용💯💯 |
||
|
||
fun contains(word: String): Boolean { | ||
return CACHE.contains(word) | ||
} | ||
|
||
fun findWordByDay(day: Int): Word { | ||
return Word(CACHE[day % CACHE.size]) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package wordle.domain | ||
|
||
import java.time.LocalDate | ||
import java.time.Period | ||
|
||
private const val YEAR = 2021 | ||
private const val MONTH = 6 | ||
private const val DAY_OF_MONTH = 19 | ||
|
||
class WordPicker(private val today: LocalDate = LocalDate.now()) { | ||
fun pickTodayAnswer(): Word { | ||
val fixed = LocalDate.of(YEAR, MONTH, DAY_OF_MONTH) | ||
val day = Period.between(fixed, today).days | ||
return Word.findWordByDay(day) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package wordle.view | ||
|
||
object InputView { | ||
fun inputGuess(): String { | ||
println("정답을 입력해 주세요.") | ||
return readln() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package wordle.view | ||
|
||
import wordle.domain.Answer | ||
import wordle.domain.Color | ||
|
||
object OutputView { | ||
private val results: MutableList<List<Color>> = mutableListOf() | ||
|
||
fun printIntroduction() { | ||
println("WORDLE을 6번 만에 맞춰 보세요.") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 단어를 입력하는 횟수가 바뀌는 경우, 뷰 로직의 코드도 수정해야할거 같아요. 쉽게 관리할 수 있도록 변경하는건 어떨까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그렇네요. Game 클래스 내부의 companion object에서 상수로 관리하도록 처리하였습니다~ |
||
println("시도의 결과는 타일의 색 변화로 나타납니다.") | ||
} | ||
|
||
fun printResult(newResult: List<Color>) { | ||
results.add(newResult) | ||
for (result in results) { | ||
result.forEach { print(it.representation) } | ||
println() | ||
} | ||
println() | ||
} | ||
|
||
fun printAnswer(answer: Answer) { | ||
println("아쉽습니다! 정답은 ${answer.word.word}입니다.") | ||
} | ||
|
||
fun printCount(tryCount: Int) { | ||
println("$tryCount/6") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기서도 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 반영하였습니다~ |
||
} | ||
|
||
fun printError(message: String?) { | ||
println(message) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package wordle.domain | ||
|
||
import io.kotest.matchers.collections.shouldContainAll | ||
import org.junit.jupiter.api.DisplayName | ||
import org.junit.jupiter.api.Test | ||
|
||
class AnswerTest { | ||
|
||
@Test | ||
@DisplayName("완전히 일치하는 글자들과 틀린 글자가 존재하는 예측을 비교한다.") | ||
fun compareWithGreenAndGray() { | ||
val answer = Answer(Word("cigar")) | ||
|
||
answer.compare(Word("clear")) shouldContainAll listOf( | ||
Color.GREEN, | ||
Color.GRAY, | ||
Color.GRAY, | ||
Color.GREEN, | ||
Color.GREEN | ||
) | ||
} | ||
|
||
@Test | ||
@DisplayName("완전히 일치하는 글자들만 존재하는 예측을 비교한다. - 정답인 경우") | ||
fun compareWithAllGreens() { | ||
val answer = Answer(Word("cigar")) | ||
|
||
answer.compare(Word("cigar")) shouldContainAll | ||
listOf(Color.GREEN, Color.GREEN, Color.GREEN, Color.GREEN, Color.GREEN) | ||
} | ||
|
||
@Test | ||
@DisplayName("모든 종류의 글자들이 존재하는 예측을 비교한다.") | ||
fun compareWithAllColors() { | ||
val answer = Answer(Word("spill")) | ||
|
||
answer.compare(Word("hello")) shouldContainAll | ||
listOf(Color.GRAY, Color.GRAY, Color.YELLOW, Color.GREEN, Color.GRAY) | ||
} | ||
|
||
@Test | ||
@DisplayName("위치만 일치하는 글자와 틀린 글자들이 존재하는 예측을 비교한다.") | ||
fun compareWithYellowAndGray1() { | ||
val answer = Answer(Word("front")) | ||
|
||
answer.compare(Word("totem")) shouldContainAll | ||
listOf(Color.YELLOW, Color.YELLOW, Color.GRAY, Color.GRAY, Color.GRAY) | ||
} | ||
|
||
@Test | ||
@DisplayName("위치만 일치하는 글자들과 틀린 글자들이 존재하는 예측을 비교한다.") | ||
fun compareWithYellowAndGray2() { | ||
val answer = Answer(Word("totem")) | ||
|
||
answer.compare(Word("start")) shouldContainAll | ||
listOf(Color.GRAY, Color.YELLOW, Color.GRAY, Color.GRAY, Color.YELLOW) | ||
} | ||
|
||
@Test | ||
@DisplayName("전부 틀린 글자들이 존재하는 예측을 비교한다.") | ||
fun compareWithAllGrays() { | ||
val answer = Answer(Word("parry")) | ||
|
||
answer.compare(Word("biome")) shouldContainAll | ||
listOf(Color.GRAY, Color.GRAY, Color.GRAY, Color.GRAY, Color.GRAY) | ||
} | ||
|
||
@Test | ||
@DisplayName("전부 위치만 일치하는 글자들이 존재하는 예측을 비교한다.") | ||
fun compareWithAllYellows() { | ||
val answer = Answer(Word("parse")) | ||
|
||
answer.compare(Word("spear")) shouldContainAll | ||
listOf(Color.YELLOW, Color.YELLOW, Color.YELLOW, Color.YELLOW, Color.YELLOW) | ||
} | ||
|
||
@Test | ||
@DisplayName("test.") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이것저것 테스트해보다가 실수로 넣은것같네요.. 이 부분도 수정하였습니다~ |
||
fun test1() { | ||
val answer = Answer(Word("witch")) | ||
|
||
answer.compare(Word("timid")) shouldContainAll | ||
listOf(Color.YELLOW, Color.GREEN, Color.GRAY, Color.GRAY, Color.GRAY) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package wordle.domain | ||
|
||
import io.kotest.matchers.shouldBe | ||
import org.junit.jupiter.api.DisplayName | ||
import org.junit.jupiter.api.Test | ||
import java.time.LocalDate | ||
|
||
class WordPickerTest { | ||
|
||
@Test | ||
@DisplayName("오늘의 단어를 가져온다.") | ||
fun pickTodayAnswer() { | ||
val wordPicker = WordPicker(LocalDate.of(2021, 6, 20)) | ||
|
||
val todayAnswer = wordPicker.pickTodayAnswer() | ||
|
||
todayAnswer shouldBe Word("rebut") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package wordle.domain | ||
|
||
import io.kotest.assertions.throwables.shouldNotThrow | ||
import io.kotest.assertions.throwables.shouldThrow | ||
import io.kotest.matchers.shouldBe | ||
import org.junit.jupiter.api.DisplayName | ||
import org.junit.jupiter.api.Test | ||
|
||
class WordTest { | ||
|
||
@Test | ||
@DisplayName("단어는 5글자여야 한다.") | ||
fun constructor() { | ||
shouldThrow<IllegalArgumentException> { (Word("word")) } | ||
} | ||
|
||
@Test | ||
@DisplayName("단어는 소문자로 이루어져야 한다.") | ||
fun constructorWithLowercaseWord() { | ||
shouldThrow<IllegalArgumentException> { Word("CIGAR") } | ||
} | ||
|
||
@Test | ||
@DisplayName("유효한 단어여야 한다.") | ||
fun constructorWithValidWord() { | ||
shouldNotThrow<IllegalArgumentException> { Word("cigar") } | ||
} | ||
|
||
@Test | ||
@DisplayName("유효하지 않은 단어일 경우 예외를 발생시킨다.") | ||
fun constructorWithInvalidWord() { | ||
shouldThrow<IllegalArgumentException> { Word("abcde") } | ||
} | ||
|
||
@Test | ||
@DisplayName("다른 단어와 같은 인덱스로 비교한다") | ||
fun compareBySameIndex() { | ||
val wordA = Word("cigar") | ||
val wordB = Word("clear") | ||
|
||
wordA.compareByIndex(wordB, 0) shouldBe true | ||
} | ||
|
||
@Test | ||
@DisplayName("다른 단어와 다른 인덱스로 비교한다") | ||
fun compareByDifferentIndex() { | ||
val wordA = Word("cigar") | ||
val wordB = Word("clear") | ||
|
||
wordA.compareByIndex(wordB, 0, 1) shouldBe false | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
6
은 워들 게임에서 의미있는 숫자이지 않을까요?🤔There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Game
클래스를 만들어서 내부 Companion object에서 관리하도록 처리하였습니다~