diff --git a/README.md b/README.md index 8fe711203..9d0a61176 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,32 @@ + +## 1차 구현 - 기능 목록 메모 +0. 1차 구현 과정 정리: https://velog.io/@woply/야구-게임-TDD-리팩토링-1차-구현 + +1. 게임 준비하기: ReadyGame +- [ ] 사용자에게 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 @@ -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) +``` \ No newline at end of file diff --git a/src/main/java/Compare.java b/src/main/java/Compare.java new file mode 100644 index 000000000..87ea3f7ff --- /dev/null +++ b/src/main/java/Compare.java @@ -0,0 +1,51 @@ +import java.util.Collections; +import java.util.List; + +public class Compare { + + public int checkSameNumber(List userNumberList, List computerNumberList) { + + int sameNumberCount = 0; + for (int i = 0; i < userNumberList.size(); i++) { + sameNumberCount += countSameNumber(userNumberList, computerNumberList.get(i)); + } + return sameNumberCount; + } + + private int countSameNumber(List userNumberList, int compareNumber) { + return Collections.frequency(userNumberList, compareNumber); + } + + public int checkSameLocation(List userNumberList, List 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+" 볼"; + } +} diff --git a/src/main/java/Play.java b/src/main/java/Play.java new file mode 100644 index 000000000..c7f798ea7 --- /dev/null +++ b/src/main/java/Play.java @@ -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("숫자 야구를 시작합니다."); + List computerNumber = readyGame.creatRandomNumber(); + + + while (gameState) { + System.out.println("숫자를 입력하세요.\n>"); + List 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 retryUserNumber(List userNumber) { + + ReadyGame readyGame = new ReadyGame(); + List retryUserNumber = userNumber; + + while (!(readyGame.validateNumber(retryUserNumber))) { + System.out.println("잘못된 숫자입니다. 숫자를 다시 입력하세요.\n>"); + List 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(); + + } +} diff --git a/src/main/java/ReadyGame.java b/src/main/java/ReadyGame.java new file mode 100644 index 000000000..742a5c85d --- /dev/null +++ b/src/main/java/ReadyGame.java @@ -0,0 +1,62 @@ +import java.util.*; + +public class ReadyGame { + + public String inputUserNumber() { + String userNumber; + Scanner scanner; + + scanner = new Scanner(System.in); + userNumber = scanner.nextLine(); + + return userNumber; + } + + public List creatRandomNumber() { + + List computerNumber = new ArrayList<>(); + Random random = new Random(); + + for (int i = 0; i < 3; i++) { + int randomNumber = random.nextInt(9); + if (!(computerNumber.contains(randomNumber + 1))) { + computerNumber.add(randomNumber + 1); + continue; + } + i--; + } + return computerNumber; + } + + public List splitNumber(String userNumber) { + + List userNumberList = new ArrayList<>(); + String[] splitUserNumber = userNumber.split(""); + for (String eachNumber : splitUserNumber) { + userNumberList.add(Integer.parseInt(eachNumber)); + } + return userNumberList; + } + + public boolean validateNumber(List userNumberList) { + boolean verificationResult = false; + + if (!(userNumberList.size() == 3)) { + return verificationResult; + } + + for (Integer number : userNumberList) { + if (!(0 < number && number < 10)) { + return verificationResult; + } + + if (!(Collections.frequency(userNumberList, number) == 1)) { + return verificationResult; + } + } + return true; + } + + + +} diff --git a/src/test/java/CompareTest.java b/src/test/java/CompareTest.java new file mode 100644 index 000000000..2921ff767 --- /dev/null +++ b/src/test/java/CompareTest.java @@ -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개가 주어진다 + List userNumberList = Arrays.asList(1, 3, 5); + List 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 userNumberList = Arrays.asList(1, 2, 5); + List computerNumberList = Arrays.asList(1, 5, 2); + + // when + // 두 List를 비교하여 동일한 위치에 동일한 수를 가진 숫자가 몇 개인지 확인하는 메서드를 실행한다 + int sameLocationNumberAmount = compare.checkSameLocation(userNumberList, computerNumberList); + + // then + // 일치하는 숫자가 정확한지 확인한다. + assertThat(sameLocationNumberAmount).isEqualTo(2); + } + + @Test + void 게임_결과_확인() throws Exception { + + // given + // '동일한 숫자의 갯수'와 '위치가 동일한 숫자의 갯수'가 주어진다. + + int sameNumberAmount = 3; + int sameLocationNumberAmount = 1; + + // when + // 볼과 스트라이크로 환산하는 메서드를 통해 결과를 받는다. + String gameResult = compare.refereeGame(sameNumberAmount, sameLocationNumberAmount); + + // then + // 게임 결과가 정확한지 확인한다. + assertThat(gameResult).isEqualTo("1 스트라이크 2 볼"); + //assertThat(gameResult).isEqualTo("낫싱"); + } + + +} \ No newline at end of file diff --git a/src/test/java/ReadyGameTest.java b/src/test/java/ReadyGameTest.java new file mode 100644 index 000000000..b02134151 --- /dev/null +++ b/src/test/java/ReadyGameTest.java @@ -0,0 +1,64 @@ +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class ReadyGameTest { + + ReadyGame readyGame = new ReadyGame(); + + @Test + void inputUserNumberTest() { + + String userNumber = "123";// Sacanner를 통해 사용자가 3자리 수를 입력했다고 가정 + assertThat(userNumber.length()).isEqualTo(3); + + } + + @Test + void creatRandomNumberTest() { + + List computerNumber = readyGame.creatRandomNumber(); + SoftAssertions softly = new SoftAssertions(); + + assertThat(computerNumber.size()).isEqualTo(3); // 3자리 숫자 확인 + + for (Integer number : computerNumber) { + assertThat(number > 0 && number < 10).isEqualTo(true); // 각 숫자의 범위가 1~10에 해당하는지 확인 + assertThat(Collections.frequency(computerNumber, number)).isEqualTo(1); // 해당 값이 1개만 존재 + } + } + + @Test + void splitNumberTest() { + String userNumber = "456"; + List userNumberList = readyGame.splitNumber(userNumber); + + assertThat(userNumberList.toString()).isEqualTo("[4, 5, 6]"); + + } + + + @Test + void validateNumberTest() { + List userNumberList = Arrays.asList(1, 2, 3); + boolean validateResult = readyGame.validateNumber(userNumberList); + + assertThat(validateResult).isEqualTo(true); // 테스트 1: 프로덕트 코드에 기대하는 결과가 true인지 확인 + + assertThat(userNumberList.size()).isEqualTo(3); // 테스트 2: 3자리 숫자 확인 + + for (Integer number : userNumberList) { + assertThat(number > 0 && number < 10).isEqualTo(true); // 각 숫자의 범위가 1~10에 해당하는지 확인 + assertThat(Collections.frequency(userNumberList, number)).isEqualTo(1); // 해당 값이 1개만 존재 + } + } + + + + +} \ No newline at end of file