Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
71 changes: 70 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@

## 1차 구현 - 기능 목록 메모
0. 1차 구현 과정 정리: https://velog.io/@woply/야구-게임-TDD-리팩토링-1차-구현

1. 게임 준비하기: ReadyGame

Choose a reason for hiding this comment

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

전체적으로 클래스를 분리하셔서 깔끔하게 구현해주셨는데요 👍

아쉬운 점이 있다면 절차지향적인 코드가 주를 이루고 있다는 것입니다.
절차지향적인 코드로 작성되다보니 테스트 또한 객체 단위의 단위테스트 보다는 기능 테스트로 작성된 것 같아요!

적절한 객체를 도출해보는 방식으로 다시 구현해보면 좋은 인사이트가 있을 것 같아요.

- [ ] 사용자에게 3자리 수 입력받기: inputUserNumber
- [ ] 컴퓨터의 랜덤 수 3자리 생성하기: creatRandomNumber
- [ ] 입력 받은 숫자를 3개의 문자로 쪼갰을 때, 각각 1~9의 범위를 가지고 있으며, 총 3자리가 맞는지 검증: validateNumber

2. 결과 비교하기: Compare
- [ ] 동일한 숫자가 있는지 확인: checkSameNumber
- [ ] 같은 자리에 있는지 확인: checkSameLocation
- [ ] 결과 판단하기: refereeGame

3. 게임 진행하기: Play
- [ ] 순차적으로 게임을 진행: playGame
- [ ] 게임을 최초 실행하는: Main
---




## [NEXTSTEP 플레이그라운드의 미션 진행 과정](https://github.com/next-step/nextstep-docs/blob/master/playground/README.md)

---
## 학습 효과를 높이기 위해 추천하는 미션 진행 방법

---
1. 피드백 강의 전까지 미션 진행
1. 피드백 강의 전까지 미션 진행
> 피드백 강의 전까지 혼자 힘으로 미션 진행. 미션을 진행하면서 하나의 작업이 끝날 때 마다 add, commit
> 예를 들어 다음 숫자 야구 게임의 경우 0, 1, 2단계까지 구현을 완료한 후 push

Expand All @@ -24,3 +46,50 @@ git checkout main // 기본 브랜치가 main인 경우
git checkout -b 브랜치이름
ex) git checkout -b apply-feedback
```
---

# 요구사항 메모
> 1. 기능 요구 사항

- 기본적으로 1부터 9까지 서로 다른 수로 이루어진 3자리의 수를 맞추는 게임이다.
- 같은 수가 같은 자리에 있으면 스트라이크, 다른 자리에 있으면 볼, 같은 수가 전혀 없으면 포볼 또는 낫싱이란 힌트를 얻고, 그 힌트를 이용해서 먼저 상대방(컴퓨터)의 수를 맞추면 승리한다.
- e.g. 상대방(컴퓨터)의 수가 425일 때, 123을 제시한 경우 : 1스트라이크, 456을 제시한 경우 : 1볼 1스트라이크, 789를 제시한 경우 : 낫싱

- 위 숫자 야구 게임에서 상대방의 역할을 컴퓨터가 한다. 컴퓨터는 1에서 9까지 서로 다른 임의의 수 3개를 선택한다. 게 임 플레이어는 컴퓨터가 생각하고 있는 3개의 숫자를 입력하고, 컴퓨터는 입력한 숫자에 대한 결과를 출력한다.
- 이 같은 과정을 반복해 컴퓨터가 선택한 3개의 숫자를 모두 맞히면 게임이 종료된다.
- 게임을 종료한 후 게임을 다시 시작하거나 완전히 종료할 수 있다.


> 2. 프로그래밍 요구사항

- 자바 코드 컨벤션을 지키면서 프로그래밍한다.
- 기본적으로 Google Java Style Guide을 원칙으로 한다.
- 단, 들여쓰기는 '2 spaces'가 아닌 '4 spaces'로 한다.
- indent(인덴트, 들여쓰기) depth를 2가 넘지 않도록 구현한다. 1까지만 허용한다.
- 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
- 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다.
- else 예약어를 쓰지 않는다.
- 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
- else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다.
- 모든 로직에 단위 테스트를 구현한다. 단, UI(System.out, System.in) 로직은 제외
- 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다.
- UI 로직을 InputView, ResultView와 같은 클래스를 추가해 분리한다.
- 3항 연산자를 쓰지 않는다.
- 함수(또는 메소드)가 한 가지 일만 하도록 최대한 작게 만들어라.

> - 기능 목록 및 commit 로그 요구사항
- 기능을 구현하기 전에 README.md 파일에 구현할 기능 목록을 정리해 추가한다.
- git의 commit 단위는 앞 단계에서 README.md 파일에 정리한 기능 목록 단위로 추가한다.
- 참고문서: AngularJS Commit Message Conventions

> - AngularJS Commit Message Conventions 중
- commit message 종류를 다음과 같이 구분
```
feat (feature)
fix (bug fix)
docs (documentation)
style (formatting, missing semi colons, …)
refactor
test (when adding missing tests)
chore (maintain)
Comment on lines +88 to +94

Choose a reason for hiding this comment

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

AngularJS의 커밋 컨벤션을 찾아보셨네요 👍

하나의 커밋에 모든 작업을 하기보다는 작은 작업 단위의 커밋으로 나누어서 구현해보면 좋을 것 같습니다 :)

```
51 changes: 51 additions & 0 deletions src/main/java/Compare.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import java.util.Collections;
import java.util.List;

public class Compare {

public int checkSameNumber(List<Integer> userNumberList, List<Integer> computerNumberList) {

int sameNumberCount = 0;
for (int i = 0; i < userNumberList.size(); i++) {
sameNumberCount += countSameNumber(userNumberList, computerNumberList.get(i));
}
return sameNumberCount;
}

private int countSameNumber(List<Integer> userNumberList, int compareNumber) {
return Collections.frequency(userNumberList, compareNumber);
}

public int checkSameLocation(List<Integer> userNumberList, List<Integer> computerNumberList) {

int sameLocationNumberCount = 0;

for (int i = 0; i < userNumberList.size(); i++) {
sameLocationNumberCount += CountSameLocationNumber((userNumberList.get(i)), computerNumberList.get(i));
}
return sameLocationNumberCount;
}

private int CountSameLocationNumber(int userNumber, int computerNumber) {
if (userNumber == computerNumber) {
return 1;
}
return 0;
}

public String refereeGame(int sameNumberAmount, int sameLocationNumberAmount) {

int ball = sameNumberAmount - sameLocationNumberAmount;
int strike = sameLocationNumberAmount;

if (ball ==0 && strike ==0) {
return "낫싱";
}

if (ball ==3 && strike ==3) {
return "아웃!";
}

return strike + " 스트라이크 "+ball+" 볼";
}
}
85 changes: 85 additions & 0 deletions src/main/java/Play.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import java.util.List;
import java.util.Scanner;

public class Play {

public void playGame() {

ReadyGame readyGame = new ReadyGame();
Compare compare = new Compare();
boolean gameState = true;


// 1. 게임 준비
System.out.println("숫자 야구를 시작합니다.");

Choose a reason for hiding this comment

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

  • 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다.
  • UI 로직을 InputView, ResultView와 같은 클래스를 추가해 분리한다.

프로그래밍 요구사항 중에 위와 같은 요구사항이 있었는데요. 이 부분을 어떻게 구현할지 생각해보시면 좋을 것 같습니다.

List<Integer> computerNumber = readyGame.creatRandomNumber();


while (gameState) {
System.out.println("숫자를 입력하세요.\n>");
List<Integer> userNumber = readyGame.splitNumber(readyGame.inputUserNumber());

// 사용자의 입력 숫자가 잘못된 경우 재입력 받는다.
retryUserNumber(userNumber);

// 2. 게임 진행
int sameNumberCount = compare.checkSameNumber(userNumber, computerNumber);
int sameLocationNumberCount = compare.checkSameLocation(userNumber, computerNumber);

// 게임의 결과를 확인한다
String gameResult = compare.refereeGame(sameNumberCount, sameLocationNumberCount);
System.out.println(gameResult);

// 숫자를 모두 맞혔을 경우, 사용자의 입력을 반복하는 while 값을 false로 변경한다.
gameState = stopGameLoop(sameNumberCount, sameLocationNumberCount);

// 숫자를 모두 맞혔을 경우, 게임 재개 여부를 확인한다.
if (askRegame(sameNumberCount, sameLocationNumberCount) == 1) {
playGame();
}
}
}

private List<Integer> retryUserNumber(List<Integer> userNumber) {

ReadyGame readyGame = new ReadyGame();
List<Integer> retryUserNumber = userNumber;

while (!(readyGame.validateNumber(retryUserNumber))) {
System.out.println("잘못된 숫자입니다. 숫자를 다시 입력하세요.\n>");
List<Integer> tempUserNumber = readyGame.splitNumber(readyGame.inputUserNumber());
retryUserNumber = tempUserNumber;
}
return retryUserNumber;

}

private int askRegame(int sameNumberCount, int sameLocationNumberCount) {

int userChoose = 2;

if (sameNumberCount == 3 && sameLocationNumberCount == 3) {
System.out.println("숫자를 모두 맞히셨습니다! 게임 종료");
System.out.println("게임을 새로 시작하려면 1, 종료하려면 아무 숫자나 입력해주세요.");
Scanner scanner = new Scanner(System.in);
userChoose = scanner.nextInt();

}
return userChoose;
}

private boolean stopGameLoop(int sameNumberCount, int sameLocationNumberCount) {
boolean gameState = true;
if (sameNumberCount == 3 && sameLocationNumberCount == 3) {
return false;
}
return gameState;
}


public static void main(String[] args) {
Play play = new Play();
play.playGame();

}
}
62 changes: 62 additions & 0 deletions src/main/java/ReadyGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import java.util.*;

public class ReadyGame {

Choose a reason for hiding this comment

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

ReadyGame 클래스의 인스턴스는 어떤 상태(내부 필드)도 가지지 않기 때문에 이 클래스의 모든 객체들은 동일한 객체이겠네요 !

너무 많은 상태를 가지는 것은 좋은 설계가 아니지만, 아무것도 가지지 않는 방식 또한 바람직한 설계는 아닙니다.

적절한 상태를 가진 객체를 설계해보면 좋을 것 같아요.

다른 클래스들에도 함께 적용되는 피드백일 것 같습니다 :)


public String inputUserNumber() {
String userNumber;
Scanner scanner;

scanner = new Scanner(System.in);
userNumber = scanner.nextLine();
Comment on lines +6 to +10

Choose a reason for hiding this comment

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

지역 변수의 초기화는 아래와 같은 방식으로 해주시면 좋을 것 같아요 :)

Suggested change
String userNumber;
Scanner scanner;
scanner = new Scanner(System.in);
userNumber = scanner.nextLine();
String scanner = new Scanner(System.in);
Scanner userNumber = scanner.nextLine();


return userNumber;
}

public List<Integer> creatRandomNumber() {

List<Integer> computerNumber = new ArrayList<>();
Random random = new Random();

for (int i = 0; i < 3; i++) {
int randomNumber = random.nextInt(9);

Choose a reason for hiding this comment

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

임의의 숫자를 사용하는 매직넘버는소스 코드를 읽기 어렵게 만드는데요 !

이 부분을 어떻게 개선할 수 있을까요? 관련 링크 남겨놓겠습니다 :)

https://www.slipp.net/questions/356

if (!(computerNumber.contains(randomNumber + 1))) {
computerNumber.add(randomNumber + 1);
continue;
}
i--;
}
return computerNumber;
}

public List<Integer> splitNumber(String userNumber) {

List<Integer> userNumberList = new ArrayList<>();
String[] splitUserNumber = userNumber.split("");
for (String eachNumber : splitUserNumber) {
userNumberList.add(Integer.parseInt(eachNumber));
}
return userNumberList;
}

public boolean validateNumber(List<Integer> userNumberList) {
boolean verificationResult = false;

if (!(userNumberList.size() == 3)) {
return verificationResult;
}

for (Integer number : userNumberList) {
if (!(0 < number && number < 10)) {

Choose a reason for hiding this comment

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

indent(인덴트, 들여쓰기) depth를 2가 넘지 않도록 구현한다. 1까지만 허용한다.

위 프로그래밍의 요구사항을 만족하도록 개선해보면 좋을 것 같아요.
depth를 줄이는 가장 쉬운 방법은 메서드로 분리하는 것입니다.

return verificationResult;
}

if (!(Collections.frequency(userNumberList, number) == 1)) {
return verificationResult;
}
}
return true;
}



Comment on lines +59 to +61

Choose a reason for hiding this comment

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

의미 없는 공백은 지워주세요 :)

}
Comment on lines +61 to +62

Choose a reason for hiding this comment

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

파일 마지막에 엔터(개행문자)를 넣어주세요 :)

이유는 리뷰를 진행할 때 깃허브에서 경고메시지를 지우고 혹시 모르는 파일 읽기 오류에 대비하기 위함입니다.

좀 더 알고싶으시면 참고 링크를 보셔도 재밌을 것 같네요 :)

Intellij 를 사용하실 경우엔Preferences -> Editor -> General -> Ensure line feed at file end on save 를 체크해주시면파일 저장 시 마지막에 개행문자를 자동으로 넣어줍니다!

https://minz.dev/19https://stackoverflow.com/questions/729692/why-should-text-files-end-with-a-newline

65 changes: 65 additions & 0 deletions src/test/java/CompareTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import org.junit.jupiter.api.Test;

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

import static org.assertj.core.api.Assertions.assertThat;

class CompareTest {

Compare compare = new Compare();

@Test
void 동일한_숫자_갯수_확인() throws Exception {
// given
// 숫자 list 2개가 주어진다

Choose a reason for hiding this comment

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

테스트를 given when then 패턴으로 잘 짜주셨네요!

다만 잘 짜여진 테스트에서 주석은 필요하지 않을 것 같습니다.

List<Integer> userNumberList = Arrays.asList(1, 3, 5);
List<Integer> computerNumberList = Arrays.asList(5, 2, 3);

// when
// 일치하는 숫자를 확인해주는 메서드를 실행한다
int sameNumberAmount = compare.checkSameNumber(userNumberList, computerNumberList);

// then
// 일치하는 숫자를 확인한다.
assertThat(sameNumberAmount).isEqualTo(2);
}

@Test
void 동일한_숫자의위치_갯수_확인() throws Exception {

// given
// 3개의 숫자로 구성된 List 객체 2개가 주어진다.
List<Integer> userNumberList = Arrays.asList(1, 2, 5);
List<Integer> computerNumberList = Arrays.asList(1, 5, 2);

// when
// 두 List를 비교하여 동일한 위치에 동일한 수를 가진 숫자가 몇 개인지 확인하는 메서드를 실행한다
int sameLocationNumberAmount = compare.checkSameLocation(userNumberList, computerNumberList);

// then
// 일치하는 숫자가 정확한지 확인한다.
assertThat(sameLocationNumberAmount).isEqualTo(2);
}

@Test
void 게임_결과_확인() throws Exception {

// given
// '동일한 숫자의 갯수'와 '위치가 동일한 숫자의 갯수'가 주어진다.

Choose a reason for hiding this comment

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

하나의 라인도 일관성 있게 통일해주세요 :)

int sameNumberAmount = 3;
int sameLocationNumberAmount = 1;

// when
// 볼과 스트라이크로 환산하는 메서드를 통해 결과를 받는다.
String gameResult = compare.refereeGame(sameNumberAmount, sameLocationNumberAmount);

// then
// 게임 결과가 정확한지 확인한다.
assertThat(gameResult).isEqualTo("1 스트라이크 2 볼");
//assertThat(gameResult).isEqualTo("낫싱");
}


}
Loading