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

[5기] 3주차 Wordle 과제 제출 - 얄뭉 #37

Open
wants to merge 42 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
08a59e6
feat(domain): 기초 도메인 설계
Jun 11, 2024
b8a2253
feat(WordleApplication): GameManager 생성자 추가 및 일별 단어 선정 구현
Jun 11, 2024
e640bb7
feat: domain 병합
Jun 13, 2024
cd10e1b
test: domain 병합
Jun 13, 2024
41c538d
chore: Scacnner추가
Jun 13, 2024
18beffc
chore: 명시적인 변수명으로 변경
Jun 16, 2024
8534b1a
fix: 중복 단어가 있을 때 첫번째 idx를 리턴하는 오류오 인덱스의 단어 비교로 수정
Jun 16, 2024
f43ebb4
feat: 결과 출력을 위한 Iterator 상속 및 구현
Jun 16, 2024
f1d4626
chore: 불필요한 클래스 제거
Jun 16, 2024
3d86ff7
feat: 성공 시 라운드 노출을 위한 메소드 추가
Jun 16, 2024
bdc2094
feat: 메세지 노출 view 기능 구현
Jun 16, 2024
2b28b8d
feat: 게임 기능 구현
Jun 16, 2024
77f0923
refact: 힌트 노출 뎁스 메소드로 추출
Jun 16, 2024
1d5c4c7
refactor: Answer, InputWord를 같은 속성인 Word로 통합
Jun 16, 2024
babbf2c
refactor: MatchResults와 MatchResult를 일급컬랙션으로 변경
Jun 16, 2024
f01a856
refactor: 변경 사항에 맞게 게임 시작 기능 수정
Jun 16, 2024
aab91e4
refactor(test): 변경된 구조에 맞게 test 로직 변경
Jun 16, 2024
106c07d
chore: String을 Intstream으로 hint 결과값을 바로 리턴하도록 수정
Jun 16, 2024
e6da42b
chore: 불필요한 메서드 정리
Jun 16, 2024
62efc46
doc: README.md 구현목록 및 리펙터링 포인트 추가
Jun 17, 2024
a986ed0
chore: 상수가 아닌 변수 이름 lowerCamelCase변경
Jun 28, 2024
21c735d
chore: 오타 수정
Jun 28, 2024
f9a565b
chore: 기본 생성자 protected 변경
Jun 28, 2024
1b8bf73
fix: 정답 확인 비교 로직 수정
Jun 28, 2024
f116a69
chore: 원시타입 상수는 HigherCamelCase
Jun 28, 2024
ae34f09
refact: Stream.toList로 변경
Jun 28, 2024
d45e4de
refact: 일치하는 단어가 있을 경우 다른 위치는 NOT_EXIST 처리를 위한 클래스 추가
Jun 28, 2024
5a536b9
refact: HintLetter 추가로 리펙토링
Jun 28, 2024
1e02fe6
fix: HintLetter 추가로 정답 로직 변경
Jun 28, 2024
fe0cbb2
refact: 불필요한 메소드 삭제
Jun 28, 2024
b443f7b
refact: 불필요한 메소드 삭제
Jun 28, 2024
fa4d0ff
refact: 불필요한 엔터 삭제
Jun 28, 2024
62e8b94
fix: 입력 단어 유효성 오류 시 게임 종료하지 않고, 재시도
Jun 28, 2024
b056160
refact: 마지막 시도까지 실패 시 문구 노출 후 종료
Jun 28, 2024
42bcfe6
refactor: 기본 생성자 추가
Jun 28, 2024
486d28d
refactor: 상수 변경
Jun 28, 2024
83010de
refactor: 불필요한 공백 제거
Jun 28, 2024
bf9fbea
refactor: 클래스 내 멤버 배치 전체적으로 수정
Jun 28, 2024
922446c
refactor: Round에 넘겨줄 변수 상수처리
Jun 28, 2024
43345b3
test: 단어입력 테스트 작성
Jun 28, 2024
ae1a0b5
test: 단어입력 테스트 단위 쪼개서 작성
Jun 28, 2024
8100660
test: 정답 확인 테스트 작성
Jun 29, 2024
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
53 changes: 47 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# 미션 - 워들

## 🙆🏻‍♀️페어 구성🙆🏻
- 점프
- 얄뭉
---

## 🔍 진행 방식

- 미션은 **과제 진행 요구 사항**, **기능 요구 사항**, **프로그래밍 요구 사항** 세 가지로 구성되어 있다.
Expand All @@ -12,13 +17,16 @@

선풍적인 인기를 끌었던 영어 단어 맞추기 게임이다.

- 6x5 격자를 통해서 5글자 단어를 6번 만에 추측한다.
- 플레이어가 답안을 제출하면 프로그램이 정답과 제출된 단어의 각 알파벳 종류와 위치를 비교해 판별한다.
- 판별 결과는 흰색의 타일이 세 가지 색(초록색/노란색/회색) 중 하나로 바뀌면서 표현된다.
- 맞는 글자는 초록색, 위치가 틀리면 노란색, 없으면 회색
- [x] 게임 진행 메시지를 출력한다.
- [x] 격자를 통해서 5글자 단어를 6번 만에 추측한다.
- [x] 플레이어가 답안을 제출하면 프로그램이 정답과 제출된 단어의 각 알파벳 종류와 위치를 비교해 판별한다.
- [x] 입력 단어는 영단어야 한다.
- [x] 단어의 수는 5글자여야 한다.
- [x] 단어는 `words.txt`에 포함된 단어여야 한다.
- [x] 판별 결과는 흰색의 타일이 세 가지 색(초록색/노란색/회색) 중 하나로 바뀌면서 표현된다.
- 맞는 글자는 초록색(🟩), 위치가 틀리면 노란색(🟨), 없으면 회색(⬜)
- 두 개의 동일한 문자를 입력하고 그중 하나가 회색으로 표시되면 해당 문자 중 하나만 최종 단어에 나타난다.
- 정답과 답안은 `words.txt`에 존재하는 단어여야 한다.
- 정답은 매일 바뀌며 ((현재 날짜 - 2021년 6월 19일) % 배열의 크기) 번째의 단어이다.
- [x] 정답은 매일 바뀌며 ((현재 날짜 - 2021년 6월 19일) % 배열의 크기) 번째의 단어이다.

### 입출력 요구 사항

Expand Down Expand Up @@ -87,3 +95,36 @@ spill
- 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다.
- 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 분리해 구현한다.
- 힌트: MVC 패턴 기반으로 구현한 후, View와 Controller를 제외한 Model에 대한 단위 테스트 추가에 집중한다.
---
### 구현 목록
- 단어 불러오기
- 매일 바뀌는 정답단어 선택
- 단어 입력
- 입력시 단어목록에 있나 검증
- 비교 로직
- 같을때, 있을떄, 없을때 따른 비교결과
- 게임로직
- 6회 초과시 게임오버
- 정답시 게임클리어
- ui
- 비교 결과, 라운드 출력

---
### 리팩터링 포인트
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.

페어분과 주로 논의한 내용이 나눠진 클래스들을 합치는 것과 String 값 처리였는데 클래스의 분리는 충분하다고 판단했습니다! 😁
String의 경우 배열로 변경하려고 했으나 클래스가 하나 더 생길 수 있다는 점과 IntStream으로 처리해보자는 의견이 나와서 진행하지 않았습니다!

- [x] final 붙혀주기
- [x] Inputword 에 availableWords 상태로 가지고 있을 필요는 없다.
- [x] InputWord와 Answer를 합치기?
- [x] match 에 for 문안 함수로 발라내기
- [x] isEndGame 플래그 변수 네이밍 변경
- [x] 검증
- [x] 5글자 검증
- [x] 영단어 검증
- [x] 예외 try catch
- [x] GameManager에 있는 List 의 변수명 변경
- [x] HintView에 3depth 줄이기
- [x] InputWord의 마지막 테스트함수 이름
- [x] MatchResult에 inputChar가 쓰이지 않고 있어서 클래스 자체를 지우고 그냥 Hint만 쓰기
- ~~- 구조 변경~~
- ~~- GameManager의 의존성 줄여두기~~
- ~~-String 말고 char 배열로 쓸지? -> 좀 더 고민해보기~~
- ~~-분리된 view 부분 합칠지?~~
17 changes: 17 additions & 0 deletions src/main/java/WordleApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import config.FileConfig;
import controller.GameManager;
import infra.WordLoader;

import java.util.List;

public class WordleApplication {
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.

페어분 소스를 옮기다가 마지막 반영분을 누락했더라구요🤦🏻‍♀️
이번 리펙토링 기간에 기능을 몇가지 수정했는데요~

  1. 요구사항 미반영분
  2. validation 실패 시 종료하지 않고 진행
  3. 6번 실패 시 게임 종료 문구 노출

를 수정했습니다~

public static void main(String[] args) {

List<String> words = WordLoader.read(FileConfig.FILE_PATH);
GameManager gameManager = new GameManager(words);

gameManager.start();

}

}
5 changes: 5 additions & 0 deletions src/main/java/config/FileConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package config;

public class FileConfig {
Copy link

Choose a reason for hiding this comment

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

FileConfig 클래스도 WordLoader와 동일한 이유로 private 기본 생성자를 만들어두면 어떨까요 ?

Copy link
Author

Choose a reason for hiding this comment

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

반영했습니다~ 👍

public final static String FILE_PATH = "src/main/resources/words.txt";
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.

오모나 final과 static이 뒤집어져 있네요 🙈

}
79 changes: 79 additions & 0 deletions src/main/java/controller/GameManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
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 Round round;
private MatchResults matchResults;
private Word answer;

private List<String> availableWords;

private GuideTextView guideTextView;
private InputView inputView;
private HintView hintView;
private RoundView roundView;
private boolean isWinning = false;

protected GameManager() {
Copy link

Choose a reason for hiding this comment

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

GameManager 클래스에 기본 생성자는 어떤 용도로 쓰이나요 ?

Copy link
Author

Choose a reason for hiding this comment

The 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(6, 1);
Copy link

Choose a reason for hiding this comment

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

limit은 WORD의 MAX_LENGTH처럼 상수로 표현하면 좀 더 직관적일 것 같아요 !

Copy link
Author

Choose a reason for hiding this comment

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

상수를 어디에 선언하는 게 좋을까 고민했는데
GameManager가 관리자의 역할이니 가지고 있는게 맞다고 생각해 상수 선언했습니다!😀

this.matchResults = new MatchResults();
this.guideTextView = new GuideTextView();
this.inputView = new InputView();
this.hintView = new HintView();
this.roundView = new RoundView();
}


public void start() {
guideTextView.render();
while(!this.isWinning && round.isNotFinalRound()) {
startRound();
}
}

private void startRound() {
// 라운드 입력 view
String input = inputView.input();

Word inputWord;
try {
inputWord = Word.createInput(input, this.availableWords);
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
return;
}

checkAnswer(inputWord);

if(this.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);

this.isWinning = matchResultOfInput.isEndGame();
}

}
22 changes: 22 additions & 0 deletions src/main/java/domain/Hint.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package domain;

public enum Hint {
Copy link

Choose a reason for hiding this comment

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

enum 타입 정의 👍🏻

Copy link
Author

Choose a reason for hiding this comment

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

👍


CORRECT("🟩"), //\uD83D\\uDFE9
EXIST("🟨"), //\uD83D\\uDFE8
NOT_EXIST("⬜");

private final String tile;
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

@yeojiin yeojiin Jun 28, 2024

Choose a reason for hiding this comment

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

해당 부분은 주석 부분은 정리했는데 정확히 어디 부분이 안 맞는 지 알려주실 수 있을까요?

그리고 공유해주신 문서도 읽고, 더 찾아보면서 흥미로운 부분을 발견했는데요.
상수는 upperSnakeCase 로 작성하는게 맞다고 나오는데 (constant-names) 이 부분을 보면
final이나 static final 보다는 실제로 값이 변할 가능성이 있는지 없는지에 따라 upperSnakeCaselowerCamelCase 를 선택하는 것 같네요~

찾아보면서 java.jar 파일을 참고했는데 java.naming의 VersionHelper 클래스를 보니

VersionHelper

public final class VersionHelper {
    private static final VersionHelper helper = new VersionHelper();

    /**
     * Determines whether classes may be loaded from an arbitrary URL code base.
     */
    private static final boolean TRUST_URL_CODE_BASE;

...
}

와 같이 구현되어 있더라구요.
아마 원시값은 UPPER_SNAKE_CASE으로 유지하고, 원시값이 아닌 경우에는 lowerCamelCase를 사용하는게 아닌가 싶습니다.
따라서 FileConfig 부분을 FILE_PATH -> filePath -> FILE_PATH 로 두번의 수정을 했습니다.😂

코멘트 주신 덕분에 자세히 알아볼 수 있어서 재밌었습니다!

Copy link

Choose a reason for hiding this comment

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

후기까지 세세히 남겨주신 덕분에 저도 또 한 번 배워갑니다 🫡


Hint(String hint) {
this.tile = hint;
}

public String getTile() {
return tile;
}

public static boolean isCorrect(Hint hint) {
return Hint.CORRECT.equals(hint);
Copy link

Choose a reason for hiding this comment

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

enum 비교 관련해서 코멘트를 멋지게 달고 싶어서 서치하다가
흥미로운 글을 발견했는데요.
같이 보시면 좋을 것 같아 공유 드립니다 😆

Copy link
Author

Choose a reason for hiding this comment

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

오~ 정말 재밌는 글이네요! 습관적으로 equals를 사용했는데
역시 치이사님 덕분에 좋은 지식 얻어갑니다! 최고! 💯 👍

}
}
42 changes: 42 additions & 0 deletions src/main/java/domain/MatchResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package domain;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class MatchResult implements Iterable<Hint>{
Copy link

Choose a reason for hiding this comment

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

일급컬렉션 👍🏻
+) MatchResults 까지 👍🏻👍🏻

Copy link
Author

Choose a reason for hiding this comment

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

👍 👍

private final List<Hint> hints;

public MatchResult(List<Hint> hints) {
this.hints = hints;
}

public MatchResult() {
this.hints = new ArrayList<>();
}

@Override
public Iterator<Hint> iterator() {
return hints.iterator();
}

public boolean isEndGame() {
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) {
return this.hints.equals(((MatchResult) obj).hints);
Copy link

Choose a reason for hiding this comment

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

obj에 대한 타입 검증을 하지 않아도 될까요?
추가로, 이 라인에서 NPE가 발생할 수 있을 것 같아요 🤔

Copy link
Author

Choose a reason for hiding this comment

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

수정했습니다!

}
}


31 changes: 31 additions & 0 deletions src/main/java/domain/MatchResults.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package domain;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class MatchResults implements Iterable<MatchResult> {
Copy link

Choose a reason for hiding this comment

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

클래스 내 멤버 배치와 관련해서 궁금한 점이 있어요 🙋🏻‍♀️

저는 보통 상수 -> 일반 필드 -> 기본 생성자 -> 인자가 있는 생성자 -> public 메소드 -> private 메소드 -> 오버라이딩한 메소드 순으로 배치하곤 해요.

이 순서를 어떻게 정하느냐는 개인의 취향이지만,
정한 순서를 일관적으로 가져가는 게 가독성 측면에서 좋다고 생각합니다 !

얄뭉님은 어떻게 생각하시나요 ?
전체적으로 코드에 반영해보는 건 어떨까요 ?
(다른 클래스에도 해당되는 공통적인 코멘트지만 여기에 달아두겠습니다 ... 🙏🏻)

Copy link
Author

@yeojiin yeojiin Jun 28, 2024

Choose a reason for hiding this comment

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

좋은 코멘트 반영했습니다~~~ 👍 🙌🏻

private List<MatchResult> results;

public MatchResults(List<MatchResult> results) {
this.results = results;
}

public MatchResults() {
this.results = new ArrayList<>();
}


public List<MatchResult> getResults() {
return results;
}

@Override
public Iterator<MatchResult> iterator() {
return this.results.iterator();
}

public void add(MatchResult matchResultOfInput) {
this.results.add(matchResultOfInput);
}
}
31 changes: 31 additions & 0 deletions src/main/java/domain/Round.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package domain;

public class Round {
private int limit;
private int current;

public Round(int limit, int current) {
this.limit = limit;
this.current = current;
}

public void goNext() {
this.current++;
}

Boolean isLastRound() {
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.

메소드는 삭제하고 요구사항 미준수 부분 수정하면서 비슷한 메소드가 생겼네요 😅

return this.current >= this.limit;
}

public int getLimit() {
return limit;
}

public int getCurrent() {
return current;
}

public boolean isNotFinalRound() {
return current <= limit;
}
}
Loading