-
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 과제 제출 - 점프 #34
base: main
Are you sure you want to change the base?
Changes from 33 commits
da90527
a1ad7ff
ca9d324
c0b32cb
be1f118
3012f26
450e186
76a9bbe
1350d00
82b774b
6077df0
b978226
83d0f29
4ccbeb9
29474bf
e1c13e9
13ece19
961f2b6
4ed2af8
d1dcaea
ebcec6b
a004c58
3dc51ba
cc4f992
126c6c8
f2b6817
832b82a
db374da
e91405c
e15dd60
dc59ce2
7079129
43b3e08
59449a3
11352b8
01ecb31
fc9ef84
9685a01
ee43ca3
d086b9a
0b8b734
84eb915
86f8ec2
d259f53
c18b653
a55d0ff
02ba623
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 |
---|---|---|
@@ -1,89 +1,34 @@ | ||
# 미션 - 워들 | ||
|
||
## 🔍 진행 방식 | ||
### 구현 목록 | ||
- 단어 불러오기 | ||
- 매일 바뀌는 정답단어 선택 | ||
- 단어 입력 | ||
- 입력시 단어목록에 있나 검증 | ||
- 비교 로직 | ||
- 같을때, 있을떄, 없을때 따른 비교결과 | ||
- 게임로직 | ||
- 6회 초과시 게임오버 | ||
- 정답시 게임클리어 | ||
- ui | ||
- 비교 결과, 라운드 출력 | ||
|
||
### 리팩터링 포인트 | ||
- [x] final 붙혀주기 | ||
- [x] Inputword 에 availableWords 상태로 가지고 있을 필요는 없다. | ||
- [x] InputWord와 Answer를 합치기? | ||
- [x] match 에 for 문안 함수로 발라내기 | ||
- [x] isEndGame 플래그 변수 네이밍 변경 | ||
- [x] 검증 | ||
- [x] 5글자 검증 | ||
- [x] 영단어 검증 | ||
- [x] 예외 try catch | ||
- [x] GameManager에 있는 List<MatchResults> 의 변수명 변경 | ||
- [x] HintView에 3depth 줄이기 | ||
- [x] InputWord의 마지막 테스트함수 이름 | ||
- [x] MatchResult에 inputChar가 쓰이지 않고 있어서 클래스 자체를 지우고 그냥 Hint만 쓰기 | ||
- ~~[ ] 구조 변경~~ | ||
- ~~[ ] GameManager의 의존성 줄여두기~~ | ||
- ~~[ ] String 말고 char 배열로 쓸지? -> 좀 더 고민해보기~~ | ||
- ~~[ ] 분리된 view 부분 합칠지?~~ | ||
|
||
- 미션은 **과제 진행 요구 사항**, **기능 요구 사항**, **프로그래밍 요구 사항** 세 가지로 구성되어 있다. | ||
- 세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다. | ||
- **기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다.** | ||
|
||
--- | ||
|
||
## 🚀 기능 요구 사항 | ||
|
||
선풍적인 인기를 끌었던 영어 단어 맞추기 게임이다. | ||
|
||
- 6x5 격자를 통해서 5글자 단어를 6번 만에 추측한다. | ||
- 플레이어가 답안을 제출하면 프로그램이 정답과 제출된 단어의 각 알파벳 종류와 위치를 비교해 판별한다. | ||
- 판별 결과는 흰색의 타일이 세 가지 색(초록색/노란색/회색) 중 하나로 바뀌면서 표현된다. | ||
- 맞는 글자는 초록색, 위치가 틀리면 노란색, 없으면 회색 | ||
- 두 개의 동일한 문자를 입력하고 그중 하나가 회색으로 표시되면 해당 문자 중 하나만 최종 단어에 나타난다. | ||
- 정답과 답안은 `words.txt`에 존재하는 단어여야 한다. | ||
- 정답은 매일 바뀌며 ((현재 날짜 - 2021년 6월 19일) % 배열의 크기) 번째의 단어이다. | ||
|
||
### 입출력 요구 사항 | ||
|
||
#### 실행 결과 예시 | ||
|
||
``` | ||
WORDLE을 6번 만에 맞춰 보세요. | ||
시도의 결과는 타일의 색 변화로 나타납니다. | ||
정답을 입력해 주세요. | ||
hello | ||
|
||
⬜⬜🟨🟩⬜ | ||
|
||
정답을 입력해 주세요. | ||
label | ||
|
||
⬜⬜🟨🟩⬜ | ||
🟨⬜⬜⬜🟩 | ||
|
||
정답을 입력해 주세요. | ||
spell | ||
|
||
⬜⬜🟨🟩⬜ | ||
🟨⬜⬜⬜🟩 | ||
🟩🟩⬜🟩🟩 | ||
|
||
정답을 입력해 주세요. | ||
spill | ||
|
||
4/6 | ||
|
||
⬜⬜🟨🟩⬜ | ||
🟨⬜⬜⬜🟩 | ||
🟩🟩⬜🟩🟩 | ||
🟩🟩🟩🟩🟩 | ||
``` | ||
|
||
--- | ||
|
||
## 🎯 프로그래밍 요구 사항 | ||
|
||
- JDK 21 버전에서 실행 가능해야 한다. | ||
- 프로그램 실행의 시작점은 `Application`의 `main()`이다. | ||
- `build.gradle` 파일은 변경할 수 없으며, **제공된 라이브러리 이외의 외부 라이브러리는 사용하지 않는다.** | ||
- 프로그램 종료 시 `System.exit()`를 호출하지 않는다. | ||
- 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 등의 이름을 바꾸거나 이동하지 않는다. | ||
- 자바 코드 컨벤션을 지키면서 프로그래밍한다. | ||
- 기본적으로 [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html)를 원칙으로 한다. | ||
- 단, 들여쓰기는 '2 spaces'가 아닌 '4 spaces'로 한다. | ||
- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. | ||
- 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. | ||
- 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다. | ||
- 3항 연산자를 쓰지 않는다. | ||
- 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라. | ||
- JUnit 5와 AssertJ를 이용하여 정리한 기능 목록이 정상적으로 작동하는지 테스트 코드로 확인한다. | ||
- 테스트 도구 사용법이 익숙하지 않다면 아래 문서를 참고하여 학습한 후 테스트를 구현한다. | ||
- [JUnit 5 User Guide](https://junit.org/junit5/docs/current/user-guide) | ||
- [AssertJ User Guide](https://assertj.github.io/doc) | ||
- [AssertJ Exception Assertions](https://www.baeldung.com/assertj-exception-assertion) | ||
- [Guide to JUnit 5 Parameterized Tests](https://www.baeldung.com/parameterized-tests-junit-5) | ||
- 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다. | ||
- 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다. | ||
- else 예약어를 쓰지 않는다. | ||
- else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다. | ||
- 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다. | ||
- 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다. | ||
- 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 분리해 구현한다. | ||
- 힌트: MVC 패턴 기반으로 구현한 후, View와 Controller를 제외한 Model에 대한 단위 테스트 추가에 집중한다. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import config.FileConfig; | ||
import controller.GameManager; | ||
import infra.WordLoader; | ||
|
||
import java.util.List; | ||
|
||
public class WordleApplication { | ||
public static void main(String[] args) { | ||
List<String> words = WordLoader.read(FileConfig.FILE_PATH); | ||
GameManager gameManager = new GameManager(words); | ||
|
||
gameManager.start(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package config; | ||
|
||
public class FileConfig { | ||
public final static String FILE_PATH = "src/main/resources/words.txt"; | ||
} | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package controller; | ||
|
||
import domain.MatchResult; | ||
import domain.MatchResults; | ||
import domain.Round; | ||
import domain.Word; | ||
import ui.GuideTextView; | ||
import ui.HintView; | ||
import ui.InputView; | ||
import ui.RoundView; | ||
|
||
import java.time.LocalDate; | ||
import java.util.List; | ||
|
||
public class GameManager { | ||
private final static int ROUND_LIMIT = 6; | ||
private final Round round; | ||
private final MatchResults matchResults; | ||
private final Word answer; | ||
private boolean isWinning; | ||
|
||
private final List<String> availableWords; | ||
|
||
private final GuideTextView guideTextView; | ||
private final InputView inputView; | ||
private final HintView hintView; | ||
private final RoundView roundView; | ||
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. 넵넵 과한것같네요 책임을 다른곳에 맡겨볼게요! |
||
|
||
public GameManager(List<String> availableWords) { | ||
this.answer = Word.createAnswer(LocalDate.now(), availableWords); | ||
this.availableWords = availableWords; | ||
this.round = new Round(ROUND_LIMIT); | ||
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. 외부에서 동적으로 바뀔수 있다고 생각해서 주입받게 해봤는데 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. 확장성을 위한 좋은 접근이네요! 그럼 마침 Config 패키지를 만드셨으니, 게임관련 설정을 따로 모아두는 방법은 어떨까요? :) |
||
this.matchResults = new MatchResults(); | ||
this.guideTextView = new GuideTextView(); | ||
this.inputView = new InputView(); | ||
this.hintView = new HintView(); | ||
this.roundView = new RoundView(); | ||
Comment on lines
+21
to
+24
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 관련을 묶을 수 있다면 묶어진 객체가 하나 더 나와도 좋을 것 같아요 👍 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. 음 단순히 코드를 읽는 독자 입장에서, 점프님은 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. 하나의 컴포넌트 개념으로 구성하신 점은 너무 훌륭하네요 👍 Controller 개념인 말씀하신대로 View 의 구현체가 하나의 컴포넌트처럼 작동하게끔 만들수도 있고, 더 나아가 콘솔에서 웹으로 바꿔진다 했을때, HTML 을 내려주는 방식으로 바꿀때 제 개인적 관점이니 혹시 다른 의견이 있으시거나 설명이 부족하다면 얘기를 나눠보면 좋을 것 같아요 👍 |
||
} | ||
|
||
|
||
public void start() { | ||
guideTextView.render(round.getLimit()); | ||
while(isNotWinning() && round.isNotFinalRound()) { | ||
startRound(); | ||
} | ||
} | ||
|
||
private void startRound() { | ||
String input = inputView.input(); | ||
|
||
Word inputWord; | ||
try { | ||
inputWord = Word.createInput(input, this.availableWords); | ||
} catch (IllegalArgumentException e) { | ||
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. 좋습니다~ |
||
System.out.println(e.getMessage()); | ||
return; | ||
} | ||
|
||
checkAnswer(inputWord); | ||
|
||
if(isWinning()) { | ||
roundView.render(round.getCurrent(),round.getLimit()); | ||
} | ||
|
||
hintView.render(this.matchResults); | ||
round.goNext(); | ||
} | ||
|
||
private void checkAnswer(Word inputWord) { | ||
MatchResult matchResultOfInput = answer.match(inputWord); | ||
this.matchResults.add(matchResultOfInput); | ||
setWinning(matchResultOfInput.isWinning()); | ||
} | ||
|
||
private boolean isNotWinning(){ | ||
return !isWinning(); | ||
} | ||
|
||
private boolean isWinning() { | ||
return isWinning; | ||
} | ||
|
||
private void setWinning(boolean winning) { | ||
isWinning = winning; | ||
} | ||
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,22 @@ | ||
package domain; | ||
|
||
public enum Hint { | ||
|
||
CORRECT("🟩"), //\uD83D\\uDFE9 | ||
EXIST("🟨"), //\uD83D\\uDFE8 | ||
NOT_EXIST("⬜"); | ||
|
||
private final String tile; | ||
Comment on lines
+3
to
+9
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. 게임의 결과를 보여줄 타일들을 Enum 타입으로 정의 해놓으셨네요 👍 개인적으로 🟩 와 같은 타일의 모양들은 view 의 영역이라 생각되어서 도메인 영역과 분리되면 좋을 것 같다 생각하는데 어떻게 생각하시나요? 😃 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. 맞습니다. 장단점이 분명 존재하죠 👍 이런식의 접근은 어떨까요?
점프님 생각은 어떠신가요? 😃 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. 그럴수도 있겠네요! 😮 저는 Front-Back 작업을 둘 다 하다보니 둘을 최대한 분리하는 관점으로 많이 생각하게 되는 것 같아요. 두 팀이 나눠져 있는데 prod 에 나갈때까지 누락 되는 부분이 있다면, 협업관점에서 문제가 있는 부분이 아닐까 라는 생각도 듭니다 🤔 한쪽에서만 관리하면 좋긴 하겠지만 상황에 맞춰 적절하게 선택을 하면 좋을 것 같습니다! 👍 |
||
|
||
Hint(String tile) { | ||
this.tile = tile; | ||
} | ||
|
||
public String getTile() { | ||
return tile; | ||
} | ||
|
||
public static boolean isCorrect(Hint hint) { | ||
return Hint.CORRECT.equals(hint); | ||
} | ||
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. enum 내부에 static 메서드라는 점이 아쉬운데요! 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,39 @@ | ||
package domain; | ||
|
||
import java.util.Iterator; | ||
import java.util.List; | ||
|
||
public class MatchResult implements Iterable<Hint>{ | ||
private final List<Hint> hints; | ||
|
||
public MatchResult(List<Hint> hints) { | ||
this.hints = hints; | ||
} | ||
Comment on lines
+6
to
+11
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.
|
||
|
||
@Override | ||
public Iterator<Hint> iterator() { | ||
return hints.iterator(); | ||
} | ||
|
||
public boolean isWinning() { | ||
return hints.stream().allMatch(Hint::isCorrect); | ||
} | ||
|
||
public void add(Hint hint) { | ||
hints.add(hint); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return super.hashCode(); | ||
} | ||
|
||
@Override | ||
public boolean equals(Object obj) { | ||
if(obj == null) return false; | ||
if(obj.getClass() != this.getClass()) return false; | ||
return this.hints.equals(((MatchResult) obj).hints); | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package domain; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Iterator; | ||
import java.util.List; | ||
|
||
public class MatchResults implements Iterable<MatchResult> { | ||
private final List<MatchResult> results; | ||
|
||
public MatchResults() { | ||
this.results = new ArrayList<>(); | ||
} | ||
|
||
@Override | ||
public Iterator<MatchResult> iterator() { | ||
return this.results.iterator(); | ||
} | ||
|
||
public void add(MatchResult matchResult) { | ||
this.results.add(matchResult); | ||
} | ||
} | ||
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,31 @@ | ||
package domain; | ||
|
||
public class Round { | ||
private final int limit; | ||
private int current; | ||
|
||
private Round(int limit, int current) { | ||
this.limit = limit; | ||
this.current = current; | ||
} | ||
|
||
public Round(int limit) { | ||
this(limit, 1); | ||
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. 놓친 케이스네요 감사합니다! |
||
} | ||
|
||
public void goNext() { | ||
this.current++; | ||
} | ||
|
||
public int getLimit() { | ||
return limit; | ||
} | ||
|
||
public int getCurrent() { | ||
return current; | ||
} | ||
|
||
public boolean isNotFinalRound() { | ||
return current <= limit; | ||
} | ||
} |
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.
FILE_PATH
를 config 로 관리하셨군요 👍