-
Notifications
You must be signed in to change notification settings - Fork 43
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
[5기] 3주차 Wordle 과제 제출 - 와잼 #38
base: main
Are you sure you want to change the base?
Changes from all commits
f41c990
ecce462
68be191
9664220
d225a84
b8ece65
76fdedf
d97c0c2
846ec02
7fdb063
d03c4b4
34c2333
60ca6a9
e3a595d
0a66559
aa72923
20cf464
151e2ad
e14a085
b201c59
f161544
5c08e85
bb6eb4f
e52073e
b647035
74acae5
7c56c0a
ee4a197
db4f022
553fefc
808df83
65b95af
a9e9e76
e848b10
6ca7944
4e50c35
bad65f2
f861ed0
1158a12
cc4afbf
8e397f0
3fbfa43
5f37a11
609a398
7e58797
a678cd3
d170631
261dbd5
8ba771c
d83dca1
ec811af
a34f2b1
7f4d669
2cb3d49
37cc83d
6ea0bd5
6732d19
90c6a7d
451a9a1
925115e
fd9b259
500da3b
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,163 @@ | ||
# 미션 - 워들 | ||
|
||
## 게임 진행 순서 | ||
|
||
- 단어장(`words.txt`)에 있는 단어를 읽어들인다. | ||
- 읽어들인 단어들에서 정답인 단어를 정한다. | ||
- 정답은 매일 바뀌며 ((현재 날짜 - 2021년 6월 19일) % 배열의 크기) 번째의 단어이다. | ||
- 문자 5개를 입력한다. | ||
- 5개가 아닌 경우 재입력을 받는다. | ||
- 단어장에 존재하지 않는 단어인 경우 재입력을 받는다. | ||
- 알파벳이 아닌 경우 재입력을 받는다. | ||
- 입력받은 문자와 정답을 비교한다. | ||
- 비교 결과는 타일이 초록색/노란색/회색 중 하나로 바뀌면서 표현된다. | ||
- 맞는 글자는 초록색, 위치가 틀리면 노란색, 없으면 회색 | ||
- 같은 문자가 2개 입력되었을 때, 해당 문자가 정답에 하나만 존재하지만 위치가 틀린 경우 첫번 째 문자만 노란색으로 표시된다. | ||
- 정답: lurid, 입력: hello, 결과: ⬜⬜🟨⬜⬜ | ||
- 6번 안에 맞추면 게임을 종료한다. | ||
- 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. 용어 사전 너무 좋네요! DDD 그 자체 👍 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. DDD 천재 호아님의 가르침! 🙇 |
||
|
||
| 한글명 | 영문명 | 설명 | | ||
|--------|----------------|------------------------------------------------| | ||
| 워들 | Wordle | 5글자 영어 단어 맞추기 게임 | | ||
| 단어장 | Word Book | 이 게임에서 사용될 수 있는 단어 모음 | | ||
| 입력 단어 | Input Word | 플레이어가 입력하는 5글자 단어 | | ||
| 정답 단어 | Answer Word | 오늘 게임의 정답인 5글자 단어 | | ||
| 글자 | Letter | 단어를 구성하는 알파벳 | | ||
| 영문 | Alphabet | 글자를 구성하는 최소단위 | | ||
| 위치 | Position | 단어를 구성하는 글자의 위치 | | ||
| 플레이어 | Player | 게임에 참여하는 사용자 | | ||
| 결과 | Result | 입력단어와 정답단어를 비교해서 표현되는 타일모음 | | ||
| 비교 | Compare | 입력단어와 정답단어의 글자와 위치를 비교하는 행위 | | ||
| 초록색 타일 | Green Tile | 글자와 위치가 동일한 경우 표현되는 타일 | | ||
| 노란색 타일 | Yellow Tile | 글자는 포함되지만 위치가 다른 경우 표현되는 타일 | | ||
| 회색 타일 | Gray Tile | 글자와 위치가 모두 다른 경우 표현되는 타일 | | ||
| 결과모음 | Results | 라운드가 진행될 때 마다 누적된 결과모음 | | ||
| 기록모음 | Record | 누적된 결과모음의 기록 | | ||
| 기준일 | Base Date | 오늘의 정답 단어를 계산하는 기준일(2021년 6월 19일) | | ||
| 정답 공식 | Answer Formula | 오늘의 정답 단어를 계산하는 공식 `(현재 날짜 - 기준일) % 단어장의 단어 수` | | ||
| 시작 | Start | 플레이어가 워들을 시작하는 행위 | | ||
| 종료 | End | 워들이 종료되는 행위(라운드가 전부 끝났거나, 그 전에 정답을 맞추면 종료된다) | | ||
|
||
## 모델링 | ||
|
||
### 클래스 다이어그램 | ||
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. 천재 호아님의 작품입니닷 😆 |
||
|
||
```mermaid | ||
--- | ||
title: Wordle | ||
--- | ||
classDiagram | ||
class Word { | ||
-List<Letter> letters | ||
+compare(Word inputWord) Results | ||
+equals(Object o) boolean | ||
} | ||
|
||
class WordComparator { | ||
-List<Letter> pendingLetters | ||
-Results results | ||
+WordComparator(letters: List<Letter>) | ||
+compare(inputWord: Word): Results | ||
-process(targetLetter: Letter, predicate: Predicate<Letter>, tile: Tile) | ||
-fillEmptyToGray(targetLetter: Letter) | ||
} | ||
|
||
Word --|> WordComparator | ||
|
||
class Letter { | ||
-Alphabet alphabet | ||
-Position position | ||
+isSameAlphabet(Letter letter) boolean | ||
+getPosition() Position | ||
+equals(Object o) boolean | ||
} | ||
|
||
class Alphabet { | ||
-char alphabet | ||
+equals(Object o) boolean | ||
} | ||
|
||
class Position { | ||
-char position | ||
+compareTo(Position position) boolean | ||
+equals(Object o) boolean | ||
} | ||
|
||
class Record { | ||
-List<Results> record | ||
+add(Results results) void | ||
+isEnd() boolean | ||
+isCountOver() boolean | ||
+existAllGreen() boolean | ||
+iterator() Iterator<Results> | ||
+size() int | ||
} | ||
class Results { | ||
-SortedSet<Result> results | ||
+add(Result result) void | ||
+isCheckedPosition(Position position) boolean | ||
+isAllGreen() boolean | ||
} | ||
class Result { | ||
-Tile tile | ||
-Position poistion | ||
+isSamePosition(Position position) boolean | ||
+equals(Object o) boolean | ||
+compareTo(Result result) int | ||
+isGreen() boolean | ||
+isYellow() boolean | ||
+isGray() boolean | ||
} | ||
class Tile { | ||
<<enumeration>> | ||
+GREEN | ||
+YELLOW | ||
+GRAY | ||
} | ||
class AnswerFormula { | ||
<<interface>> | ||
+calculate(int wordCount) int | ||
} | ||
class BaseAnswerFormula { | ||
+calculate(int wordCount) int | ||
} | ||
BaseAnswerFormula ..|> AnswerFormula | ||
class Wordle { | ||
-WordBook wordBook | ||
-InputView inputView | ||
-OutputView outputView | ||
-Record record | ||
-AnswerFormula answerFormula | ||
+startGame() void | ||
-runGame(Word answerWord) void | ||
-processTurn(Word answerWord) void | ||
-concludeGame() void | ||
-handleWrongAnswer(Runnable runnable) void | ||
} | ||
class WordBook { | ||
<<interface>> | ||
+pick(AnswerFormula formula) Word | ||
+exist(Word word) boolean | ||
+find(String target) Word | ||
} | ||
class FileWordBook { | ||
-List<Word> words | ||
+pick(AnswerFormula formula) Word | ||
+exist(Word word) boolean | ||
+find(String target) Word | ||
} | ||
FileWordBook ..|> WordBook | ||
``` | ||
|
||
## 🚀 세부 요구 사항 | ||
|
||
- 6x5 격자를 통해서 5글자 단어를 6번 만에 추측한다. | ||
- 플레이어가 답안을 제출하면 프로그램이 정답과 제출된 단어의 각 알파벳 종류와 위치를 비교해 판별한다. | ||
- 판별 결과는 흰색의 타일이 세 가지 색(초록색/노란색/회색) 중 하나로 바뀌면서 표현된다. | ||
- 맞는 글자는 초록색, 위치가 틀리면 노란색, 없으면 회색 | ||
- 두 개의 동일한 문자를 입력하고 그중 하나가 회색으로 표시되면 해당 문자 중 하나만 최종 단어에 나타난다. | ||
- 정답과 답안은 `words.txt`에 존재하는 단어여야 한다. | ||
- 정답은 매일 바뀌며 ((현재 날짜 - 2021년 6월 19일) % 배열의 크기) 번째의 단어이다. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package wordle; | ||
|
||
import wordle.application.Wordle; | ||
import wordle.domain.FileWordBook; | ||
import wordle.domain.WordBook; | ||
import wordle.infra.FileReader; | ||
import wordle.ui.ConsoleInputView; | ||
import wordle.ui.ConsoleOutputView; | ||
import wordle.ui.InputView; | ||
import wordle.ui.OutputView; | ||
|
||
public class Application { | ||
|
||
public static void main(String[] args) { | ||
WordBook wordBook = new FileWordBook(new FileReader()); | ||
InputView inputView = new ConsoleInputView(); | ||
OutputView outputView = new ConsoleOutputView(); | ||
Wordle wordle = new Wordle(wordBook, inputView, outputView); | ||
wordle.startGame(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package wordle.application; | ||
|
||
import wordle.domain.AnswerFormula; | ||
import wordle.domain.BaseAnswerFormula; | ||
import wordle.domain.Record; | ||
import wordle.domain.Results; | ||
import wordle.domain.Word; | ||
import wordle.domain.WordBook; | ||
import wordle.exception.WordleException; | ||
import wordle.exception.WordleInvalidInputException; | ||
import wordle.ui.InputView; | ||
import wordle.ui.OutputView; | ||
|
||
public class Wordle { | ||
|
||
private final WordBook wordBook; | ||
|
||
private final InputView inputView; | ||
|
||
private final OutputView outputView; | ||
|
||
private final Record record; | ||
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.
이런 관점으로도 보일 수 있겠네요! 😮 |
||
|
||
private final AnswerFormula answerFormula; | ||
|
||
public Wordle(final WordBook wordBook, final InputView inputView, final OutputView outputView) { | ||
this.wordBook = wordBook; | ||
this.inputView = inputView; | ||
this.outputView = outputView; | ||
this.record = new Record(); | ||
this.answerFormula = new BaseAnswerFormula(); | ||
} | ||
|
||
public void startGame() { | ||
final Word answerWord = wordBook.pick(answerFormula); | ||
outputView.welcome(); | ||
runGame(answerWord); | ||
concludeGame(); | ||
} | ||
|
||
private void runGame(final Word answerWord) { | ||
while (!record.isEnd()) { | ||
outputView.showRecord(record); | ||
handleWrongAnswer(() -> process(answerWord)); | ||
} | ||
} | ||
|
||
private void process(final Word answerWord) { | ||
outputView.askAnswer(); | ||
final Word inputWord = wordBook.find(inputView.input()); | ||
final Results results = answerWord.compare(inputWord); | ||
record.add(results); | ||
} | ||
|
||
private void concludeGame() { | ||
if (record.existAllGreen()) { | ||
outputView.successEnd(record); | ||
return; | ||
} | ||
outputView.failEnd(record); | ||
} | ||
|
||
private void handleWrongAnswer(final Runnable runnable) { | ||
try { | ||
runnable.run(); | ||
} catch (final WordleInvalidInputException e) { | ||
outputView.wrongAnswer(); | ||
} catch (final WordleException e) { | ||
outputView.unexpectedEnd(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package wordle.domain; | ||
|
||
import java.util.Objects; | ||
import wordle.exception.InvalidAlphabetException; | ||
|
||
public class Alphabet { | ||
|
||
private static final char MIN_ALPHABET = 'a'; | ||
private static final char MAX_ALPHABET = 'z'; | ||
private final char alphabet; | ||
|
||
public Alphabet(final char alphabet) { | ||
final char lowerAlphabet = Character.toLowerCase(alphabet); | ||
if (lowerAlphabet < MIN_ALPHABET || lowerAlphabet > MAX_ALPHABET) { | ||
throw new InvalidAlphabetException(); | ||
} | ||
|
||
this.alphabet = lowerAlphabet; | ||
} | ||
|
||
@Override | ||
public boolean equals(final Object o) { | ||
if (this == o) { | ||
return true; | ||
} | ||
if (o == null || getClass() != o.getClass()) { | ||
return false; | ||
} | ||
final Alphabet alphabet1 = (Alphabet) o; | ||
return alphabet == alphabet1.alphabet; | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(alphabet); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package wordle.domain; | ||
|
||
@FunctionalInterface | ||
public interface AnswerFormula { | ||
|
||
int calculate(int wordCount); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package wordle.domain; | ||
|
||
import java.time.LocalDate; | ||
import java.time.temporal.ChronoUnit; | ||
import wordle.exception.AnswerFormulaException; | ||
|
||
public class BaseAnswerFormula implements AnswerFormula { | ||
|
||
private static final LocalDate BASE = LocalDate.of(2021, 6, 19); | ||
private static final int MIN_WORD_COUNT = 1; | ||
|
||
public int calculate(int wordCount) { | ||
if (wordCount < MIN_WORD_COUNT) { | ||
throw new AnswerFormulaException(); | ||
} | ||
|
||
return (int) ChronoUnit.DAYS.between(BASE, LocalDate.now()) % wordCount; | ||
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. 구월님쪽 계산법도 아주 훌륭한 접근이었습니다! 👍 |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package wordle.domain; | ||
|
||
import java.util.List; | ||
import wordle.exception.WordNotExistException; | ||
import wordle.infra.FileReader; | ||
|
||
public class FileWordBook implements WordBook { | ||
|
||
public static final String FILE_PATH = "words.txt"; | ||
private final List<Word> words; | ||
|
||
public FileWordBook(FileReader fileReader) { | ||
this.words = fileReader.readByLine(FILE_PATH) | ||
.stream() | ||
.map(Word::new) | ||
.toList(); | ||
} | ||
|
||
@Override | ||
public Word pick(AnswerFormula answerFormula) { | ||
int index = answerFormula.calculate(words.size()); | ||
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. 용어사전 정리할때, 현실세계에 있는 단어들을 이용해 용어를 정리하다보니, 제가 얕게 아는 DDD 세계관에서 용어는 비개발자도 이해가 가능한 단어 이어야 한다는 점에서 저런 용어가 나온 것 같습니다. 그런데 단어장 or 사전에서 |
||
return words.get(index); | ||
} | ||
|
||
@Override | ||
public boolean exist(Word word) { | ||
return words.contains(word); | ||
} | ||
|
||
@Override | ||
public Word find(String target) { | ||
Word targetWord = new Word(target); | ||
return words.stream() | ||
.filter(targetWord::equals) | ||
.findFirst() | ||
.orElseThrow(WordNotExistException::new); | ||
} | ||
} |
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.
테스트코드를 보고서야 왜 이 의존성을 차마 제거할 수 없었는지 이해했습니다...🥹
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.
개인적 욕심인
ConsoleIntegrationTest
를 위한 선택이랍니다 하핫..