Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Wordle] 링링, 파워, 망고, 블랙캣 미션 제출합니다. #17

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,35 @@ spill
🟩🟩⬜🟩🟩
🟩🟩🟩🟩🟩
```
## 📄기능 목록

### 도메인
#### 게임
- [x] 시도 회수를 다 사용하면 게임은 패배한다.
- [x] 게임 한 턴 당 결과를 계산 및 저장한다

#### 문제
- [x] words.txt 안에 존재해야한다.
- [x] 문제는 매일 바뀌어야한다.
- [x] ((현재 날짜 - 2021년 6월 19일) % 단어 전체의 개수) 번째의 단어
- [x] 정답을 맞추면 true, 틀리면 false 를 반환한다.
- [x] 힌트를 반환한다
- [x] 처음 Green 을 검사한다.(같은 인덱스와의 문자 검사)
- [x] Green이 맞으면 정답과 입력에서 해당 값을 빈 걸로 치환한다.
- [x] 순회하면서 나머지를 검사한다.
- [x] Yello가 맞으면 정답과 입력에서 해당 값을 빈 걸로 치환한다.

#### 시도 회수
- [x] 시도 회수는 총 6번 주워진다.
- [x] 몇 번 시도하였는지를 저장한다.

### 입력
- [x] 정답을 입력받는다.
- [x] 사용자가 5글자를 입력하지 않으면 재입력을 요구한다.
- [x] 사용자가 영어 이외의 글자를 입력하면 재입력을 요구한다.
- [x] word.txt 안에 존재하는 단어를 입력하지 않으면 재입력을 요구한다.

### 출력
- [x] 타일에는 초록색/노란색/회색이 존재한다
- [x] 최종 시도 횟수를 출력한다.

8 changes: 8 additions & 0 deletions src/main/kotlin/WordleApplication.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import controller.WordleController
import view.InputView
import view.OutputView

fun main() {
val wordleController = WordleController(InputView(), OutputView())
wordleController.play()
}
61 changes: 61 additions & 0 deletions src/main/kotlin/controller/WordleController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package controller

import domain.Hint
import domain.Question
import domain.TodayWordDictionary
import domain.TryCount
import view.InputView
import view.OutputView

class WordleController(val inputView: InputView, val outputView: OutputView) {

private val wordDictionary = TodayWordDictionary()
private val question: Question = Question(wordDictionary)
private val maxTryCount: TryCount = TryCount(MAX_TRY_COUNT)
private var tryCount: TryCount = TryCount()

fun play() {
val result: MutableList<List<Hint>> = mutableListOf()
outputView.printWelcomeMessage()

while (isGameNotEnd()) {
val userAnswer: String = readAnswer()
val hint: List<Hint> = question.getHint(userAnswer)

result.add(hint)
tryCount = tryCount.plus()

if (question.isAnswer(userAnswer)) {
outputView.printTryCount(tryCount.tryCount, maxTryCount.tryCount)
outputView.printHint(result)
return
}

outputView.printHint(result)
}
}

fun readAnswer(): String {
return repeatInput {
val readAnswer = inputView.readAnswer()
require(wordDictionary.contains(readAnswer)) { "word.txt 내의 단어를 선택해주세요" }
readAnswer
}
}

fun isGameNotEnd(): Boolean {
return !maxTryCount.isSame(tryCount)
}

private fun <T> repeatInput(input: () -> T): T {
return runCatching { input() }
.getOrElse { e ->
outputView.printErrorMessage(e.message)
repeatInput(input)
}
}

companion object {
private const val MAX_TRY_COUNT = 6
}
}
7 changes: 7 additions & 0 deletions src/main/kotlin/domain/Hint.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package domain

enum class Hint {
GRAY,
YELLOW,
GREEN,
}
64 changes: 64 additions & 0 deletions src/main/kotlin/domain/Question.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package domain

class Question(private val wordDictionary: WordDictionary) {

private val questionWord: String = wordDictionary.pickWord()

fun isAnswer(word: String): Boolean {
validateWord(word)
return checkAnswer(word)
}

private fun validateWord(word: String) {
require(wordDictionary.contains(word)) { word + NO_SUCH_WORD_MESSAGE }
}

private fun checkAnswer(word: String): Boolean {
return questionWord == word
}

fun getHint(word: String): List<Hint> {
validateWord(word)
val hint = MutableList(questionWord.length) { Hint.GRAY }.toMutableList()

val slicedQuestion: MutableList<Char> = questionWord.toMutableList()
val slicedWord: MutableList<Char> = word.toMutableList()

checkGreen(slicedWord, slicedQuestion, hint)
checkYellow(slicedWord, slicedQuestion, hint)

return hint
}

private fun checkYellow(
slicedWord: MutableList<Char>,
slicedQuestion: MutableList<Char>,
hint: MutableList<Hint>,
) {
slicedWord.forEachIndexed { index, word ->
if (word != BLANK && slicedQuestion.contains(word)) {
hint[index] = Hint.YELLOW
slicedQuestion[slicedQuestion.indexOf(word)] = BLANK
}
}
}

private fun checkGreen(
slicedWord: MutableList<Char>,
slicedQuestion: MutableList<Char>,
hint: MutableList<Hint>,
) {
slicedWord.forEachIndexed { index, _ ->
if (slicedQuestion[index] == slicedWord[index]) {
hint[index] = Hint.GREEN
slicedQuestion[index] = BLANK
slicedWord[index] = BLANK
}
}
}

companion object {
private const val NO_SUCH_WORD_MESSAGE = "word.txt 내의 단어를 선택해주세요"
private const val BLANK = ' '
}
}
26 changes: 26 additions & 0 deletions src/main/kotlin/domain/TodayWordDictionary.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package domain

import java.io.FileReader
import java.time.LocalDate
import java.time.temporal.ChronoUnit

class TodayWordDictionary : WordDictionary {

private val words: List<String> = FileReader(WORD_RESOURCE).readText().split(DELIMITERS)

override fun pickWord(): String {
val until = ChronoUnit.DAYS.between(TARGET_DATE, LocalDate.now())

return words[(until % words.size).toInt()]
}

override fun contains(target: String): Boolean {
return words.contains(target)
}

companion object {
private const val WORD_RESOURCE = "src/main/resources/words.txt"
private const val DELIMITERS = "\n"
private val TARGET_DATE = LocalDate.of(2021, 6, 19)
}
}
24 changes: 24 additions & 0 deletions src/main/kotlin/domain/TryCount.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package domain

data class TryCount(val tryCount: Int = 0) {

init {
validatePositive()
}

private fun validatePositive() {
require(tryCount >= 0) { ERROR_MESSAGE }
}

fun plus(): TryCount {
return TryCount(tryCount.inc())
}

fun isSame(other: TryCount): Boolean {
return this.tryCount == other.tryCount
}

companion object {
private const val ERROR_MESSAGE = "양수를 입력해주세요"
}
}
8 changes: 8 additions & 0 deletions src/main/kotlin/domain/WordDictionary.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package domain

interface WordDictionary {

fun pickWord(): String

fun contains(target: String): Boolean
}
9 changes: 9 additions & 0 deletions src/main/kotlin/view/InputView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package view

class InputView {

fun readAnswer(): String {
println("정답을 입력해 주세요.")
return readlnOrNull() ?: throw IllegalArgumentException("정답을 입력해 주세요.")
}
}
28 changes: 28 additions & 0 deletions src/main/kotlin/view/OutputView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package view

import domain.Hint
import view.utils.HintEmojiMapper

class OutputView {

fun printWelcomeMessage() {
println(
"WORDLE을 6번 만에 맞춰 보세요.\n" +
"시도의 결과는 타일의 색 변화로 나타납니다.",
)
}

fun printTryCount(userTryCount: Int, maxTryCount: Int) {
println("$userTryCount / $maxTryCount")
}

fun printHint(allHints: List<List<Hint>>) {
for (hints in allHints) {
println(HintEmojiMapper.emojiMapping(hints))
}
}

fun printErrorMessage(errorMessage: String?) {
println(errorMessage)
}
}
26 changes: 26 additions & 0 deletions src/main/kotlin/view/utils/HintEmojiMapper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package view.utils

import domain.Hint

enum class HintEmojiMapper(val hintEmoji: String, val hint: Hint) {

GRAY("⬜", Hint.GRAY),
YELLOW("🟨", Hint.YELLOW),
GREEN("🟩", Hint.GREEN),
;

companion object {
private const val CANNOT_FOUND_HINT_EMOJI_ERROR_MESSAGE = "해당하는 힌트 이모지를 찾을 수 없습니다."

fun emojiMapping(hints: List<Hint>): String {
return hints.joinToString("") { convertHintEmoji(it) }
}

private fun convertHintEmoji(hint: Hint): String {
val emojiMapper = HintEmojiMapper.values().find {
it.hint == hint
} ?: throw IllegalArgumentException(CANNOT_FOUND_HINT_EMOJI_ERROR_MESSAGE)
return emojiMapper.hintEmoji
}
}
}
12 changes: 12 additions & 0 deletions src/test/kotlin/domain/FixedWordDictionary.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package domain

class FixedWordDictionary(val word: String) : WordDictionary {

override fun pickWord(): String {
return word
}

override fun contains(target: String): Boolean {
return true
}
}
Loading