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

[4기][3주차] Wordle 과제 제출 - John #14

Open
wants to merge 47 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
7431331
docs: Define PairPrograming Rule
jhsong2580 Mar 24, 2023
cddc800
docs: 개발 전 program 설계
Mar 25, 2023
5a41792
Feat: 게임 결과 정보를 저장하는 객체 및 테스트 코드 구현
Mar 25, 2023
4b2c9fe
Feat: 게임 결과 정보를 저장하는 객체 및 테스트코드 구현
Mar 25, 2023
5200abd
Feat: FileUtils 기능 및 테스트코드 구현
jhsong2580 Mar 25, 2023
7f00a79
Feat: LocalDateTimeUtils 기능 및 테스트코드 구현
jhsong2580 Mar 25, 2023
5bf80e8
Feat: WordsGenerator 기능 및 테스트코드 구현
jhsong2580 Mar 25, 2023
6ae85d3
Feat: StringUtils 기능 및 테스트코드 구현
jhsong2580 Mar 25, 2023
3a257c3
Feat: InputManager 기능 및 테스트코드 구현
jhsong2580 Mar 26, 2023
d3232df
Feat: TileColor Enum 생성
jhsong2580 Mar 26, 2023
e12d323
fix: Result에서 관리하는 결과 배열 TileColor Enum List로 변경
jhsong2580 Mar 26, 2023
77e7800
feat: Word, AnswerWord 기능 및 테스트코드 구현
jhsong2580 Mar 26, 2023
6c630fa
feat: ViewManager 기능 구현 (색깔 네모 출력 구현 필요)
jhsong2580 Mar 26, 2023
ad73d76
test: ViewManager 테스트 구현 (색깔 네모 출력 구현 필요)
jhsong2580 Mar 26, 2023
0a3754d
feat: Result의 Match 정보 Getter 추가
jhsong2580 Mar 26, 2023
494ccc1
fix: WordsGenerator의 반환 타입을 AnswerWord로 변경
jhsong2580 Mar 26, 2023
5f6a416
fix: 단어 검증 로직 Word로 이동
jhsong2580 Mar 26, 2023
b5bdd40
rename: Word Package 이동
jhsong2580 Mar 26, 2023
a715736
docs
jhsong2580 Mar 26, 2023
e8d81be
Feat: 게임 결과에 따른 상자출력 기능 및 테스트 구현
jhsong2580 Mar 30, 2023
814e83a
refactor: 함수 의미를 명확하게 하기 위한 함수명 변경
jhsong2580 Mar 30, 2023
d8d5866
Feat: 게임 결과 출력 로직 및 테스트코드 구현
jhsong2580 Mar 30, 2023
fdc1f97
fix: Util Class 기능들에 대해서 STATIC Method로 변경
jhsong2580 Mar 30, 2023
0825f39
fix: 문자 검증 로직 WORD로 이동
jhsong2580 Mar 30, 2023
5eb81ea
feat: 시작 및 단어 입력 전 출력 기능 구현
jhsong2580 Mar 30, 2023
f9c5bf3
fix: 입력 에러시 재입력 받는 로직 제거
jhsong2580 Mar 30, 2023
317fb84
feat: 게임 컨트롤러 기능 및 테스트코드 구현
jhsong2580 Mar 30, 2023
87fd347
rename
jhsong2580 Mar 30, 2023
4940f8d
refactor: 필드 변수 FINAL추가
jhsong2580 Mar 30, 2023
3619c3a
test(refactor): Static Mocking 함수 및 Util성 함수 추출
jhsong2580 Mar 31, 2023
767ffff
docs: 기능 및 클래스 설명 정리
jhsong2580 Mar 31, 2023
0b8c2d9
feat: 문자 입력 가능 횟수를 의미하는 Coins 기능 및 테스트 구현
jhsong2580 Mar 31, 2023
ea030d0
refactor: 게임 가능 횟수를 의미하는 숫자를 Coins 객체로 감싼다
jhsong2580 Mar 31, 2023
1726d2b
feat: 입력 오류시 재입력 받는 InputViewManager 기능 및 테스트 구현
jhsong2580 Mar 31, 2023
3df7130
refactor: 문자 재입력 받는 책임을 InputViewManager로 이동
jhsong2580 Mar 31, 2023
f73b636
refactor: 게임 상태정보를 가지는 GameMachine Inner class 구현
jhsong2580 Mar 31, 2023
4431957
fix: 불필요한 Word 파일 삭제(Code with me 이슈로 커밋 누락)
jhsong2580 Apr 1, 2023
e903ca9
rename: InputViewManager -> InputManagerProxy
jhsong2580 Apr 1, 2023
a1f3738
refactor: static method class의 Singleton 패턴으로 변경
jhsong2580 Apr 15, 2023
a28a8ce
fix: 에러 메세지 영어로 수정
jhsong2580 Apr 15, 2023
8348de1
refactor: ViewManager Interface - 구현체 분리
jhsong2580 Apr 15, 2023
4aade01
refactor: WordGenerator Interface - 구현체 분리
jhsong2580 Apr 15, 2023
4076859
refactor: Inputmanager Interface - 구현체 분리
jhsong2580 Apr 15, 2023
6b830c9
rename
jhsong2580 Apr 15, 2023
d0fbdc1
feat: GameMachine 기능 분리
jhsong2580 Apr 16, 2023
a92bfe5
refactor: GameHost -> GameService 기능 이동
jhsong2580 Apr 16, 2023
a322978
docs
jhsong2580 Apr 16, 2023
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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# 미션 - 워들

## 페어 프로그래밍 룰
- [룰](./readme/PairProgrammingRule.md)

## 기능 명세
- [기능 설명](./readme/FunctionDescription.md)
- [클래스 설명](./readme/ClassDescription.md)

## 🔍 진행 방식

- 미션은 **기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항** 세 가지로 구성되어 있다.
Expand Down
39 changes: 39 additions & 0 deletions readme/ClassDescription.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# 기능 설명

### controller
1. GameHost
- Service Layer을 통해 게임의 흐름을 통제한다

### service
1. InputManager
- 사용자의 입력을 Word 객체로 전달 받는다
2. InputViewManger
- ViewManger을 주입받는다.
- 단어 입력 전 / 후 출력을 관리하고, InputManager을 통해 단어를 받는다.
3. ViewManger
- 사용자에게 출력하는 모든 로직을 담당한다
4. WordsGenerator
- 날짜에 따라 오늘의 정답 단어를 AnswerWord로 추출한다

### domain
1. Word
- 입력받은 단어에 대해 저장 및 검증
2. AnswerWord
- Word를 상속받아, 다른 Word와의 비교 결과를 산출
3. Result
- 사용자의 입력 하나에 대한 게임 결과를 저장
4. TileColor
- Enum으로써, TileColor 별 표시 할 데이터를 저장
5. Coin
- 문자 입력 가능 횟수를 의미
- 최초 코인 개수를 통해 생성
- 문자 입력시 코인 개수 감소
- 코인이 남아있는지 여부 확인

### utils
1. FileUtils
- 파일 경로를 받아 Stream<String>을 반환
2. LocalDateTimeUtils
- 입력받은 2개의 날짜 차이를 반환
3. StringUtils
- 입력받은 문자에 대해 5개의 소문자로 구성된 문자인지 검증
21 changes: 21 additions & 0 deletions readme/FunctionDescription.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# 기능 명세

### 정답 단어 추출하기
1. 파일로부터 String데이터를 읽어와야 한다.
2. 파일로부터 읽어온 데이터중, (2021-6-19 과 오늘 날짜의 차이) % (파일로부터 읽어온 String 개수) 의 위치에 있는 단어를 정답 단어로 선출한다.

### 단어 입력받기
1. 사용자로부터 단어를 입력을 받는다. 단, 소문자 5개로 구성된 단어야만 한다.
2. 정상적이지 않은 입력이 발생했을때, 다시 입력을 받아야 한다.

### 정답 단어와 사용자가 입력한 단어 비교하기
1. 정답 단어와 사용자의 단어를 비교했을때, 위치까지 똑같은 Spelling과 위치는 다르나 정답 단어에 존재하는 Spelling 그리고 아예 관계없는 Spelling을 구분한 결과값이 있어야 한다.

### 게임 결과를 출력하기
1. 정답 단어와 사용자의 단어 비교 결과를 색깔 박스를 통해 표시를 해주어야 한다.
2. 결과 출력시, 이전 결과도 함께 출력 해 주어야 한다.
3. 유저의 게임 성공, 실패에 대한 결과 출력 및 실패시 엔 오늘의 단어도 같이 출력

### 게임 횟수 제한하기
1. 사용자가 최대 6번까지 입력 받을 수 있게 제한. 6번 이내에 클리어 시 게임 종료.

36 changes: 36 additions & 0 deletions readme/PairProgrammingRule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# 페어프로그래밍 룰

### 네비게이터
1. 오류를 너무 빨리 체크하지 말자
2. 높은 추상화 수준의 지시를 하자(큰 그림을 제안하자)
3. 자신의 키보드를 사용하자

### 드라이버
1. 천천히 코딩하자. 네비게이터와 항상 말을 하면서 코딩하자
2. 네비게이터가 말이 없다면 동기화가 안된것. 동기화를 다시 하고 드라이빙하자
3. 휴식하자.
4. 경청하자. 네비게이터가 제안할때는 키보드에서 손을 떼자.


### 네비게이터 & 드라이버
1. 시작하기 전에 모든 알림을 끄자. (필요한 어플만 켜놓자)
2. 자주 역할을 전환하자. (시간을 정해두고)
3. 항상 설계를 합의하고 시작하자.
---
- [codeStyle](https://github.com/google/styleguide/blob/gh-pages/intellij-java-google-style.xml)
- 페어그래밍 툴 : Intellij Code With Me
- 각자 20분 코딩 & 10분회고 & 10분 휴식
- 회고 10분이 넘어가면 다시 처음부터 회고
- 자바 코드 컨벤션을 지키면서 프로그래밍한다.
- 기본적으로 Google Java Style Guide을 원칙으로 한다.
- 단, 들여쓰기는 '2 spaces'가 아닌 '4 spaces'로 한다.
- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
- 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
- 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다.
- 3항 연산자를 쓰지 않는다.
- 함수(또는 메서드)의 길이가 10라인을 넘어가지 않도록 구현한다.
- 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다.
- else 예약어를 쓰지 않는다.
- 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
- else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다.

22 changes: 22 additions & 0 deletions src/main/java/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import com.wodle.controller.GameHost;
import com.wodle.service.InputManager;
import com.wodle.service.InputMangerProxy;
import com.wodle.service.ViewManager;
import com.wodle.service.WordsGenerator;

public class Application {

public static void main(String[] args) {
ViewManager viewManager = new ViewManager();
InputManager inputMangerProxy = new InputMangerProxy(viewManager);
WordsGenerator wordsGenerator = new WordsGenerator();
GameHost host = new GameHost(
inputMangerProxy,
viewManager,
wordsGenerator
);

host.play();
}

}
72 changes: 72 additions & 0 deletions src/main/java/com/wodle/controller/GameHost.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.wodle.controller;

import com.wodle.domain.AnswerWord;
import com.wodle.domain.Coins;
import com.wodle.domain.Result;
import com.wodle.domain.Word;
import com.wodle.service.InputManager;
import com.wodle.service.ViewManager;
import com.wodle.service.WordsGenerator;

public class GameHost {

private static final int START_COIN = 6;
private final InputManager inputManagerProxy;

private final ViewManager viewManager;

private final WordsGenerator wordsGenerator;

public GameHost(InputManager inputManagerProxy, ViewManager viewManager,
WordsGenerator wordsGenerator) {
this.inputManagerProxy = inputManagerProxy;
this.viewManager = viewManager;
this.wordsGenerator = wordsGenerator;
}

Choose a reason for hiding this comment

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

게임 호스트가 인풋과 뷰, 워드 제네레이터까지 가지고 있다면

추후 추가 사항이 생겨 게임 호스트의 코드를 수정하게 되면 다음의 이유가 될 것입니다.

  • 가지고 있는 코인 수정
  • 인풋 매니저를 다른 매니저로 변경
  • 뷰 매니저를 다른 매니저로 변경
  • 워드 제너레이터를 다른 제너레이터로 변경

GameHost 는 코인을 가지고 있는 역할과 게임 실행을 담당하는 역할 2가지를 겸하고 있다고 생각이 드는데

지금도 너무 좋은 코드이지만 역할을 분리해보는 것도 더 좋지 않을까 아쉬움이 듭니다ㅎ

Choose a reason for hiding this comment

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

코드 재사용성을 위해 InputManager , ViewManager , WordsGenerator 를 인터페이스로 관리해보는 건 어떨까요??

Copy link
Author

Choose a reason for hiding this comment

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

사이에 interface를 두어서 Class간 결합을 느슨하게 하라는 말씀이시군요!

InputManager, WordGenerator, ViewManager을 추상화 하였습니다.


public void play() {
GameMachine machine = init();

viewManager.printGameStart();

while (machine.isGameNotEnd()) {
machine.useCoin();
Word inputWord = inputManagerProxy.inputWord();
Result wordCompareResult = machine.compareWord(inputWord);
viewManager.printCompareResult(wordCompareResult);

machine.isGameEnd = wordCompareResult.isGameEnd();
}

viewManager.printResult(machine.isGameEnd, machine.word);
}

private GameMachine init(){
AnswerWord word = wordsGenerator.getTodayWord();
return new GameMachine(word, START_COIN);
}

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.

GameMachine

  • AnswerWord를 초기화 한다
  • 사용자의 입력에 대해 정답과 비교하여, 결과를 출력한다.

User

  • Coin을 가지고, Coin을 사용한다.

위 두가지로 분리를 하였습니다.

GameService가 위 두 Domain을 통해 게임을 진행합니다.


위처럼 설계 분리하는걸 의미하는게 맞으실까요 ? ?


private static class GameMachine {
private final AnswerWord word;
private final Coins coins;
private boolean isGameEnd = false;

public GameMachine(AnswerWord word, int startCoin){
this.word = word;
this.coins = new Coins(startCoin);
}

public boolean isGameNotEnd (){
return !coins.isEmpty() && !isGameEnd;
}

public void useCoin(){
coins.use();
}

public Result compareWord(Word inputWord){
return this.word.compare(inputWord);
}
}

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

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

public class AnswerWord extends Word {

private final boolean[] alphabetExistInfoStore = new boolean[26];

Choose a reason for hiding this comment

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

이렇게 정답 체크를 해결하셨군요ㅎㅎ
좋은 방법 배워갑니다ㅎ


public AnswerWord(String word) {
super(word);

Choose a reason for hiding this comment

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

super 클래스인 Word 에서는 생성자에서

    private void validate(String word) {
        if (!matchesFiveSmallAlphabet(word)) {
            throw new IllegalArgumentException("user input require 5 small alphabet");
        }
    }

위와 같은 메서드를 호출하는데 단순히 StringUtils 라는 클래스의 static 메서드를 호출합니다.

전체적으로 Word 라는 클래스를 사용하는 이유는 추상화된 타입을 사용하기 위함으로 생각되어집니다.

생성자에서 메서드를 호출하는 클래스를 상속하면 객체 생성시 super에서 호출되는 메서드들을 제어하기 힘들어 지고 이 경우로 인해 뜻하지 않는 문제가 발생한다고 알고 있습니다.

ex) 누군가가 Word의 validate 메서드를 수정 -> Word 를 상속한 클래스에서 원하지 않은 동작을 할 수도 있음
-> 사이드 이펙트 발생

이 경우에 Word 를 클래스로 만들어 상속하기보다

interface Word {
 char[] getWordArray();
}

위와 같이 인터페이스로 사용해보는 건 어떨까요??
부족하지만... 제 개인적인 의견입니다ㅠ

Copy link
Author

Choose a reason for hiding this comment

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

AnswerWord가 Word를 상속받는 이유는 Word가 제공하는 검증 기능과 AnswerWord가 가지고있는 정답 추론 기능을 함께 사용하기 위함입니다.

그리고 말씀하신 것처럼 Word의 생성자에 validate()를 제거해야한다면...
Word에서 제공하는 단어 검증 기능을 Validator Class를 만들어서 분리를 해야할까요?

Choose a reason for hiding this comment

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

validate 메서드가 private 메서드라 괜찮아 보이긴하지만..
이 부분에 대해서는 많은 고민이 필요할 듯 합니다.
Composition 을 이용해 상속대신 AnswerWordword 를 포함하는 구조로 변경하는 방법도 있을 수 있고,
다른 다양한 방법이 있을 것 같습니다!

ㅠ경험이 부족해 어떤 방법이 최선의 방법인지는 잘 모르겠습니다ㅠ

Arrays.fill(alphabetExistInfoStore, false);

char[] words = word.toCharArray();

for (char c : words) {
alphabetExistInfoStore[c - 'a'] = true;
}
}

public Result compare(Word target) {
char[] sourceWordArray = this.getWordArray();
char[] targetWordArray = target.getWordArray();
List<TileColor> matchResult = new LinkedList<>();

for (int i = 0; i < sourceWordArray.length; i++) {
matchResult.add(match(sourceWordArray, targetWordArray, i));
}

return new Result(matchResult);
}

private TileColor match(char[] source, char[] target, int index) {
if (source[index] == target[index]) {
return TileColor.GREEN;
}

if (alphabetExistInfoStore[target[index] - 'a']) {
return TileColor.YELLOW;
}

return TileColor.GREY;
}
}
29 changes: 29 additions & 0 deletions src/main/java/com/wodle/domain/Coins.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.wodle.domain;

public class Coins {

private int coin;

public Coins(int availableCoin) {
validate(availableCoin);

this.coin = availableCoin;
}

public void use() {
int tempCoin = this.coin - 1;
validate(tempCoin);

Choose a reason for hiding this comment

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

이 부분은 정말 궁금하네요ㅎ

int 변수를 생성하신 이유가 this.coin - 1 의 값을 매번 계산하는 것 보다 변수에 계산된 값을 저장하는 게 더 성능상 이점이 있어서인가요?? => 순수한 질문입니다!

Copy link
Author

Choose a reason for hiding this comment

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

만약 this.coin이 0일때, this.coin = this.coin -1; validate(this.coin);
으로 하게 된다면 에러는 발생을 하나 coin값은 -1로 변경된 상태로 존재하게 됩니다.

그래서 this.coin을 변경하기 전에 검증 용도로 임시 변수를 만들어 둔 것 입니다!

this.coin = tempCoin;
}

private void validate(int availableCoin) {
if (availableCoin < 0) {
throw new IllegalArgumentException("Coin can be set under 0");
}
}


public boolean isEmpty() {
return this.coin == 0;
}
}
24 changes: 24 additions & 0 deletions src/main/java/com/wodle/domain/Result.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.wodle.domain;

import com.wodle.domain.TileColor;
import java.util.Collections;
import java.util.List;

public class Result {

private final long matchCount;
private final List<TileColor> matchStatus;

public Result(List<TileColor> matchStatus) {
this.matchStatus = matchStatus;
this.matchCount = matchStatus.stream()
.filter(tileColor -> tileColor == TileColor.GREEN)
.count();
}

public boolean isGameEnd() {
return matchCount == matchStatus.size();
}

public List<TileColor> getMatchStatus(){ return Collections.unmodifiableList(matchStatus) ;}
}
17 changes: 17 additions & 0 deletions src/main/java/com/wodle/domain/TileColor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.wodle.domain;

public enum TileColor {
GREEN("\uD83D\uDFE9"),
YELLOW("\uD83D\uDFE8"),
GREY("⬜");

private final String print;

TileColor(String print) {
this.print = print;
}

public String getPrint() {
return print;
}
}
28 changes: 28 additions & 0 deletions src/main/java/com/wodle/domain/Word.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.wodle.domain;


import static com.wodle.utils.StringUtils.matchesFiveSmallAlphabet;

public class Word {

private final String word;

public Word(String word) {
validate(word);
this.word = word;
}

private void validate(String word) {
if (!matchesFiveSmallAlphabet(word)) {
throw new IllegalArgumentException("user input require 5 small alphabet");
}
}

protected char[] getWordArray() {
return word.toCharArray();
}

public String getWord() {
return word;
}
}
19 changes: 19 additions & 0 deletions src/main/java/com/wodle/service/InputManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.wodle.service;

import camp.nextstep.edu.missionutils.Console;
import com.wodle.domain.Word;
import java.util.NoSuchElementException;

public class InputManager {

public Word inputWord() {
try {
String userInput = Console.readLine();
return new Word(userInput);
} catch (IllegalArgumentException e) {
throw e;
} catch (NoSuchElementException e) {
throw new NoSuchElementException("No such line");
}
}
}
Loading