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] 호우팀(HoeStory) 미션 제출합니다. #18

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ group = "camp.nextstep.edu"
version = "1.0-SNAPSHOT"

kotlin {
jvmToolchain(21)
jvmToolchain(17)
}

repositories {
Expand Down
5 changes: 5 additions & 0 deletions src/main/kotlin/Computer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import java.time.LocalDate

fun main(args: Array<String>) {
Game.start(LocalDate.now())
}
27 changes: 27 additions & 0 deletions src/main/kotlin/Game.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import dictionary.Dictionary
import stage.Stage
import stage.step.word.Word
import view.Input
import view.Print
import java.time.LocalDate

class Game {
companion object {
fun start(now: LocalDate) {
val answer = Dictionary.findTodayWord(now)
val stage = Stage(answer = answer)
Print.start()
while (stage.state == Stage.State.PROGRESS) {
try {
Print.requestInput()
val inputValue = Input.write()
val value = Word.fromInput(inputValue) { Dictionary.hasWord(inputValue) }.value
stage.play(value)
Print.resultStage(stage, answer)
} catch (e: Exception) {
println(e.message)
}
cousim46 marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}
25 changes: 25 additions & 0 deletions src/main/kotlin/dictionary/Dictionary.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package dictionary

import java.io.File
import java.time.LocalDate

object Dictionary {
private const val PATH = "./src/main/resources"
private const val FILE_NAME = "words.txt"
private val words: List<String> = File(PATH, FILE_NAME).readLines()
private val BASE_DATE = LocalDate.of(2021, 6, 19)

fun hasWord(word: String): Boolean {
return words.contains(word)
}

operator fun get(index: Int): String {
return words[index]
}
Comment on lines +16 to +18
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

operator 키워드를 사용하여 연산자 오버로딩을 구현하셨네요! 좋습니다. 👍


fun findTodayWord(nowDate: LocalDate): String {
val calcDate = nowDate.toEpochDay().minus(BASE_DATE.toEpochDay())
val index: Int = (calcDate % words.size).toInt()
return words[index]
}
cousim46 marked this conversation as resolved.
Show resolved Hide resolved
}
4 changes: 4 additions & 0 deletions src/main/kotlin/exception/WordleException.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package exception

data class WordleException(override val message: String) : RuntimeException() {
}
30 changes: 30 additions & 0 deletions src/main/kotlin/stage/Stage.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package stage

import stage.step.Step

data class Stage(val answer: String, var state: State = State.PROGRESS) {
val STEP_SIZE = 6
val steps = mutableListOf<Step>()

enum class State {
PROGRESS, COMPLETE, FAIL
}
cousim46 marked this conversation as resolved.
Show resolved Hide resolved

fun play(word: String) {
if (!canPlay()) return
val step = Step.create(answer, word)
steps.add(step)

if (step.isCorrect) {
state = State.COMPLETE
}

if (steps.size == STEP_SIZE && state != State.COMPLETE) {
state = State.FAIL
}
}

fun canPlay(): Boolean {
return state === State.PROGRESS
}
}
58 changes: 58 additions & 0 deletions src/main/kotlin/stage/step/Step.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package stage.step

data class Step(val code: List<Result>) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

필드명을 code가 아닌 results 와 같이 좀 더 명확한 의미를 담는 이름으로 명명해보면 어떨까요? 🤔

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그게 좋을거 같네요! 클래스는 result인데 변수가 code인게 이상하네요! 수정하겠습니다!


val isCorrect = code.all { it == Result.CORRECT }

enum class Result(
val color: String
) {
CORRECT("\uD83D\uDFE9"),
MISMATCH("\uD83D\uDFE8"),
WRONG("⬜");
}

companion object {
fun create(answer: String, word: String): Step {
if (answer == word) {
return Step(Array(5) { Result.CORRECT }.toList())
}
val answerBuilder = StringBuilder(answer)

val correctResult = correct(answerBuilder = answerBuilder, word = word)
mismatch(answerBuilder = answerBuilder, word = word, codes = correctResult)
return Step(correctResult.toList())
}

fun correct(answerBuilder: StringBuilder, word: String): Array<Result> {
val codes = Array(5) { Result.WRONG }

word.filterIndexed { index, c -> answerBuilder[index] == c }.forEachIndexed { index, c ->
codes[index] = Result.CORRECT
}

(4 downTo 0).forEach {
if (codes[it] == Result.CORRECT) {
answerBuilder.deleteAt(it)
}
}
return codes
}

fun mismatch(
answerBuilder: StringBuilder,
word: String,
codes: Array<Result>
): Array<Result> {

word.forEachIndexed { index, c ->
if (codes[index] != Result.CORRECT && answerBuilder.contains(c)) {
codes[index] = Result.MISMATCH
val indexOf = answerBuilder.indexOf(c)
answerBuilder.deleteAt(indexOf)
}
}
return codes
}
}
}
30 changes: 30 additions & 0 deletions src/main/kotlin/stage/step/word/Word.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package stage.step.word

import exception.WordleException

data class Word(val value: String) {
companion object {
private val englishRegex = Regex("^[A-Za-z]*")
private fun validateInput(input: String) {
if (!input.matches(englishRegex)) {
throw WordleException("영문만 입력해야합니다.")
}
if (input.length != 5) {
throw WordleException("5글자여야 합니다.")
}
}

private fun checkInDict(isWordInDic: Boolean) {
if (!isWordInDic) {
throw WordleException("존재하지 않는 단어입니다.")
}
}

fun fromInput(input: String, dicPolicy: (s: String) -> Boolean = { _ -> true }): Word {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

람다를 활용하여 이렇게도 사용이 가능하군요! 배워갑니다..! 😃

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

우유님이랑 페어프로그래밍하면서 저도 배운 부분입니다 ㅎ,,

validateInput(input)
val word = input.lowercase()
checkInDict(dicPolicy(word))
return Word(word)
}
}
}
13 changes: 13 additions & 0 deletions src/main/kotlin/view/Input.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package view

import java.util.*

class Input {
companion object {
val scanner = Scanner(System.`in`)

fun write(): String {
return scanner.nextLine()
}
}
}
57 changes: 57 additions & 0 deletions src/main/kotlin/view/Print.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package view

import stage.Stage
import stage.step.Step

class Print {

companion object {
fun start() {
println(
"""
WORDLE을 6번 만에 맞춰 보세요.
시도의 결과는 타일의 색 변화로 나타납니다.
""".trimIndent()
)
}

fun requestInput() {
println("정답을 입력해 주세요.")
}


fun resultStage(stage: Stage, answer: String) {
when (stage.state) {
Stage.State.FAIL -> {
resultAllStep(stage)
println("answer = $answer")
}

Stage.State.COMPLETE -> {
println("${stage.steps.size}/6")
resultAllStep(stage)
}

Stage.State.PROGRESS -> {
resultAllStep(stage)
}
}
}

fun resultAllStep(stage: Stage) {
stage.steps.forEach { resultStep(it) }
}

fun resultStep(step: Step) {
val str: StringBuilder = StringBuilder()
step.code.forEach {
when (it) {
Step.Result.CORRECT -> Step.Result.CORRECT.color
Step.Result.MISMATCH -> Step.Result.MISMATCH.color
Step.Result.WRONG -> Step.Result.WRONG.color
}
}
println(str)
}
}
}
39 changes: 39 additions & 0 deletions src/test/kotlin/DictionaryTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import dictionary.Dictionary
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.ValueSource
import java.io.File
import java.time.LocalDate
import java.time.Period

class DictionaryTest {

@ParameterizedTest
@ValueSource(strings = ["asder", "wrfdx", "bljwq"])
fun `단어가 존재하지 않으면 false`(word: String) {
//when
val result = Dictionary.hasWord(word)

//then
assertThat(result).isFalse()
}

@ParameterizedTest
@ValueSource(strings = ["hello", "organ", "mercy"])
fun `단어가 존재하면 true`(word: String) {
//when
val result = Dictionary.hasWord(word)

//then
assertThat(result).isTrue()
}

@Test
fun `현재날짜 기준으로 단어를 추출한다`() {
val now = LocalDate.now()
val findTodayWord = Dictionary.findTodayWord(now)
//then
assertThat(findTodayWord).isNotNull()
}
}
66 changes: 66 additions & 0 deletions src/test/kotlin/stage/StageTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package stage

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test

/**
* stage는 answer을 입력받아서 생성된다
* stage를 play 할 때 마다 step이 만들어짐
* play를 할 때 step이 isCorrect -> true이면 맞춘 종료
* play를 6번 했는데 모든 step이 isCorrect -> false면 틀린 종료
*
* stage 상태: 진행중(Progress)/맞춘 종료(Complete)/틀린 종료(Fail) ->
* 워들 -> 정답: xxx (틀린여부)
* */
class StageTest {

@Test
fun `스테이지가 Progress일 때 play할 수 있음`() {
val stage = Stage("answer", Stage.State.PROGRESS)
assertThat(stage.canPlay()).isTrue()
}

@Test
fun `스테이지가 Complete일 때 더이상 play를 할 수 없음`() {
val stage = Stage("answer", Stage.State.COMPLETE)
assertThat(stage.canPlay()).isFalse()
val a = 1

}


@Test
fun `스테이지가 Fail일 때 더이상 play를 할 수 없음`() {
val stage = Stage("answer", Stage.State.FAIL)
assertThat(stage.canPlay()).isFalse()
}

@Test
fun `스테이지가 play시 정답을 맞추면(step이 isCorrect) Complete`() {
// given
val stage = Stage("answer", Stage.State.PROGRESS)

// when
stage.play("answer")

// then
assertThat(stage.state).isEqualTo(Stage.State.COMPLETE)
}

@Test
fun `스테이지가 play시 6번 모두 정답을 맞추지 못하면(step이 모두 isCorrect false) FAIL`() {
// given
val stage = Stage("answer", Stage.State.PROGRESS)

// when
stage.play("world")
stage.play("world")
stage.play("world")
stage.play("world")
stage.play("world")
stage.play("world")

// then
assertThat(stage.state).isEqualTo(Stage.State.FAIL)
}
}
Loading