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] 조조그린(조한석) 미션 제출합니다. #9

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a07ca44
docs: README.md 기능목록 추가
jojogreen91 May 11, 2022
13a13cf
feat: Answer 글자길이를 검증하는 기능 구현
jojogreen91 May 11, 2022
c1d7d33
test: AnswerTest 테스트작성
jojogreen91 May 12, 2022
778f9c1
feat: Answer 가 `words.txt` 에 존재하는 단어인지 검증하는 기능
jojogreen91 May 12, 2022
0782fbf
feat: 오늘의 정답을 선택하는 기능
jojogreen91 May 12, 2022
58cbf75
feat: 답안과 정답을 비교하는 기능 구현
jojogreen91 May 12, 2022
551f64f
feat: 입출력 기능 구현
jojogreen91 May 12, 2022
f0c74c9
feat: Wordle 게임을 실행하는 기능 구현
jojogreen91 May 12, 2022
d9a554d
feat: 어플리케이션을 실행하는 기능 구현
jojogreen91 May 12, 2022
8f1f5f0
test: Game 테스트 구현
jojogreen91 May 13, 2022
5422144
test: 정답과 비교하는 테스트 케이스 추가 구현
jojogreen91 May 13, 2022
b496176
test: DslTest 구현
jojogreen91 May 18, 2022
baf4d46
chore: 포매팅 수정
jojogreen91 May 18, 2022
1645893
test: DslTest 테스트 수정
jojogreen91 May 18, 2022
c945e41
test: DslTest 테스트 수정
jojogreen91 May 18, 2022
edb8423
refactor: 람다 매개변수 수정
jojogreen91 May 18, 2022
ababa6c
refactor: view 메서드 수정
jojogreen91 May 18, 2022
9c78be6
refactor: printTile 을 apply() 로 수정
jojogreen91 May 18, 2022
3d66ad9
test: DslTest 수정
jojogreen91 May 18, 2022
e2a6511
refactor: Words 클래스 수정
jojogreen91 May 18, 2022
a89d190
refactor: Results 의 get 에 방어적 복사 적용
jojogreen91 May 18, 2022
eb826ab
test: 테스트 수정
jojogreen91 May 18, 2022
cd01e8f
refactor: Results 수정
jojogreen91 May 18, 2022
e48beb2
refactor: Results 에 Backing properties 적용
jojogreen91 May 18, 2022
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
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# 미션 - 워들

## 📝 기능 목록

### 코틀린 워들 기능목록 / 페퍼, 조조그린

- [x] 시작메시지 출력하는 기능
- [x] 답안을 입력받는 기능
- [x] 입력값은 5자리 문자열로 제한하는 기능
- [x] 입력값은 `words.txt` 에 존재하는 단어만 가능
- [x] 답안은 6번 만 입력 가능
- [x] 오늘의 정답을 선택하는 기능
- [x] 정답은 `words.txt` 의 ((현재 날짜 - 2021년 6월 19일) % 배열의 크기) 번째의 단어
- [x] 답안이 각 문자가 정답의 문자와 어떻게 매칭되는지 찾는 기능
- [x] 두 개의 동일한 문자를 입력하고 그중 하나가 회색으로 표시되면 해당 문자 중 하나만 최종 단어에 표시
- [x] 확인된 정보에 맞게 출력하는 기능
- [x] 맞는 글자는 초록색, 위치가 틀리면 노란색, 없으면 회색으로 표시
- [x] 게임 결과를 출력하는 기능

## 🔍 진행 방식

- 미션은 **기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항** 세 가지로 구성되어 있다.
Expand Down
Empty file removed src/main/kotlin/.gitkeep
Empty file.
5 changes: 5 additions & 0 deletions src/main/kotlin/Application.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import wordle.controller.WordleController

fun main() {
WordleController().run()

Choose a reason for hiding this comment

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

image
ktlintCheck가 실패합니다ㅠ_ㅠ 한 번 확인하시고 수정해야할 것 같아요!

Copy link
Author

@jojogreen91 jojogreen91 May 18, 2022

Choose a reason for hiding this comment

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

이거 계속 수정하는데 자꾸 새로운게 튀어나오고 지적하는 부분이 뭘 수정하라는 건지 모르겠네요ㅠㅠ
일단 계속 해볼게요 파랑🥲

}
21 changes: 21 additions & 0 deletions src/main/kotlin/wordle/controller/WordleController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package wordle.controller

import wordle.domain.Answer
import wordle.domain.Game
import wordle.domain.pickTodayWord
import wordle.view.inputAnswer
import wordle.view.printResults
import wordle.view.printStartMessage
import java.time.LocalDate

class WordleController {

fun run() {
printStartMessage()
val game = Game(LocalDate.now().pickTodayWord())
while (game.isPlaying) {
game.playRound(Answer(inputAnswer()))
printResults(game)
}
}
}
60 changes: 60 additions & 0 deletions src/main/kotlin/wordle/domain/Answer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package wordle.domain

import wordle.domain.Mark.*

Choose a reason for hiding this comment

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

요렇게 wildcard import 사용하면 ktlintCheck 에러 떠요!

https://blog.leocat.kr/notes/2020/12/14/intellij-avoid-wildcard-imports-in-kotlin-with-intellij

요 블로그 참고하심 좋을 것 같아요!

나머지 에러 두 개는 import 순서 때문에 뜨네요. 포매팅 해주시면 에러 없앨 수 있을 것 같습니다!


class Answer(private val answer: String) {

init {
require(answer.length == WORD_SIZE) { "[ERROR] 부적절한 글자 길이입니다." }
require(answer.isInWords()) { "[ERROR] 목록에 존재하지 않는 단어입니다." }
}

fun compareToWord(word: String): List<Mark> {
val result = MutableList(WORD_SIZE) { NONE }
val wordTable = createWordTable(word)
matchExact(word, result, wordTable)
matchExist(result, wordTable)
return result
}

private fun createWordTable(word: String): HashMap<Char, Int> {
val wordTable = HashMap<Char, Int>()
for (char in word) {
wordTable[char] = wordTable.getOrDefault(char, 0) + 1
}
Comment on lines +22 to +24

Choose a reason for hiding this comment

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

getOrDefault를 사용하면 wordTable을 더 간편하게 초기화할 수 있군요..! 배워갑니다👍

return wordTable
}

private fun matchExact(word: String, result: MutableList<Mark>, wordTable: HashMap<Char, Int>) {
for (i in 0 until WORD_SIZE) {
markExact(i, word, result, wordTable)
}
}

private fun markExact(i: Int, word: String, result: MutableList<Mark>, wordTable: HashMap<Char, Int>) {
if (word[i] == answer[i]) {
result[i] = EXACT
wordTable.computeIfPresent(word[i]) { _, v -> v - 1 }
}
}

private fun matchExist(result: MutableList<Mark>, wordTable: HashMap<Char, Int>) {
for (index in 0 until WORD_SIZE) {
markExist(index, result, wordTable)
}
}

private fun markExist(index: Int, result: MutableList<Mark>, wordTable: HashMap<Char, Int>) {
if (isExist(index, result, wordTable, answer[index])) {
result[index] = EXIST
wordTable.computeIfPresent(answer[index]) { _, v -> v - 1 }
}
}

private fun isExist(
index: Int,
result: MutableList<Mark>,
wordTable: HashMap<Char, Int>,
charOfAnswer: Char,
) = result[index] == NONE && wordTable.containsKey(charOfAnswer) && wordTable[charOfAnswer] != 0
}
22 changes: 22 additions & 0 deletions src/main/kotlin/wordle/domain/Game.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package wordle.domain

class Game(private val word: String) {

val results: Results = Results()
var isPlaying: Boolean = true
private set

fun playRound(answer: Answer) {
val result = answer.compareToWord(word)
results.add(result)
if (isOver(result)) {
isPlaying = false
}
}

private fun isOver(result: List<Mark>) =
results.isLimit() || result.all { it == Mark.EXACT }

fun findTryCount() =
results.findTryCount()
}
8 changes: 8 additions & 0 deletions src/main/kotlin/wordle/domain/Mark.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package wordle.domain

enum class Mark {

NONE,
EXIST,
EXACT
}
20 changes: 20 additions & 0 deletions src/main/kotlin/wordle/domain/Results.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package wordle.domain

private const val LIMIT_SIZE = 6

class Results {

private val _results: MutableList<List<Mark>> = mutableListOf()
val results: List<List<Mark>>
get() = _results

fun add(result: List<Mark>) {
_results.add(result)
}
Comment on lines +7 to +13

Choose a reason for hiding this comment

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

내부에서는 MutableList를 외부에서는 List를 사용! 좋습니다!
언더바 사용도 💯💯 덕분에 저도 배워가요ㅎㅎㅎ


fun findTryCount() =
_results.size

fun isLimit() =
_results.size >= LIMIT_SIZE
}
16 changes: 16 additions & 0 deletions src/main/kotlin/wordle/domain/Words.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package wordle.domain

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

const val WORD_SIZE = 5

val BASIC_DATE = LocalDate.of(2021, 6, 19)!!
val WORDS = FileReader("src/main/resources/words.txt").readLines()

fun LocalDate.pickTodayWord() =
WORDS[(ChronoUnit.DAYS.between(BASIC_DATE, this) % WORDS.size).toInt()]

fun String.isInWords() =
WORDS.contains(this)
54 changes: 54 additions & 0 deletions src/main/kotlin/wordle/view/View.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package wordle.view

import wordle.domain.Mark.EXACT
import wordle.domain.Mark.NONE
import wordle.domain.Mark.EXIST
import wordle.domain.Results
import wordle.domain.Game
import wordle.domain.Mark

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

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

fun inputAnswer(): String {
printInputMessage()
return readln()
}

fun printResults(game: Game) {
if (!game.isPlaying) {
printTryCount(game.findTryCount())
}
printTiles(game.results)
}

fun printTryCount(tryCount: Int) {
println()
println("$tryCount/6")
}

fun printTiles(results: Results) {
println()
results.results.forEach {
printTile(it)
}
println()
}

private fun printTile(result: List<Mark>) {
StringBuilder().apply {
result.forEach {
when (it) {
NONE -> append("⬜")
EXIST -> append("\uD83D\uDFE8")
EXACT -> append("\uD83D\uDFE9")
}
}
println(toString())
}
}
Empty file removed src/test/kotlin/.gitkeep
Empty file.
92 changes: 92 additions & 0 deletions src/test/kotlin/study/DslTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package study

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

class DslTest {

Choose a reason for hiding this comment

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

DslTest까지 구현해주셨네요ㅎㅎ 좋습니다!


@Test
fun introduce() {
val person = introduce {
name("조조그린")
company("우아한테크코스")
skills {
soft("A passion for problem solving")
soft("Good communication skills")
hard("Java")
}
languages {
"Korean" level 5
"English" level 3
}
}

assertThat(person.toString()).isEqualTo("Person(name=조조그린, company=우아한테크코스, skills=Skills(softSkills=[A passion for problem solving, Good communication skills], hardSkills=[Java]), languages=Languages(languages=[Korean level : 5, English level : 3]))")
}
}

fun introduce(builder: PersonBuilder.() -> Unit): Person {
return PersonBuilder().apply(builder).build()
}

class PersonBuilder {
private lateinit var name: String
private lateinit var company: String
private lateinit var skills: Skills
private lateinit var languages: Languages

fun name(input: String) {
name = input
}

fun company(input: String) {
company = input
}

fun skills(builder: SkillsBuilder.() -> Unit) {
skills = SkillsBuilder().apply(builder).build()
}

fun languages(builder: LanguagesBuilder.() -> Unit) {
languages = LanguagesBuilder().apply(builder).build()
}

fun build(): Person {
return Person(name, company, skills, languages)
}
}

class SkillsBuilder {
private val softSkills: MutableList<String> = mutableListOf()
private val hardSkills: MutableList<String> = mutableListOf()

fun soft(input: String) {
this.softSkills.add(input)
}

fun hard(input: String) {
this.hardSkills.add(input)
}

fun build(): Skills {
return Skills(softSkills, hardSkills)
}
}

class LanguagesBuilder {
private var languages: MutableList<String> = mutableListOf()

infix fun String.level(value: Int) {
languages.add(plus(" level : ") + value)
}

fun build(): Languages {
return Languages(languages)
}
}

data class Person(val name: String, val company: String, val skills: Skills, val languages: Languages)

data class Skills(val softSkills: List<String>, val hardSkills: List<String>)

data class Languages(val languages: List<String>)
40 changes: 40 additions & 0 deletions src/test/kotlin/wordle/domain/AnswerTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package wordle.domain

import org.junit.jupiter.api.assertThrows
import wordle.domain.Mark.EXACT
import wordle.domain.Mark.NONE
import wordle.domain.Mark.EXIST
import io.kotest.matchers.throwable.shouldHaveMessage
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test

internal class AnswerTest {

@Test
fun 글자길이가_5가_아닌_경우_예외발생() {
assertThrows<IllegalArgumentException> { Answer("abcdef") }
.shouldHaveMessage("[ERROR] 부적절한 글자 길이입니다.")

Choose a reason for hiding this comment

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

shouldHaveMessage 제가 찾던 기능이네요! 배워갑니다ㅎㅎ

}

@Test
fun 주어진_단어목록에_존재하지_않는_경우_예외발생() {
assertThrows<IllegalArgumentException> { Answer("abcde") }
.shouldHaveMessage("[ERROR] 목록에 존재하지 않는 단어입니다.")
}

@Test
fun 답안과_정답을_비교_CASE_중복되는_문자_중_하나만_일치_할_때() {
val answer = Answer("groom")

assertThat(answer.compareToWord("goose"))
.isEqualTo(listOf(EXACT, NONE, EXACT, EXIST, NONE))
}

@Test
fun 답안과_정답을_비교_CASE_중복되는_문자가_존재하지만_정답의_개수가_더_많을_때() {
val answer = Answer("eerie")

assertThat(answer.compareToWord("sheen"))
.isEqualTo(listOf(EXIST, EXIST, NONE, NONE, NONE))
}
}
Loading