diff --git a/README.md b/README.md index 8d7e8aee..ef523fcd 100644 --- a/README.md +++ b/README.md @@ -1 +1,75 @@ -# java-baseball-precourse \ No newline at end of file +# java-baseball-precourse +## 기능 목록 + +- 사용자로부터 3개의 숫자 입력 받기 +- 컴퓨터 플레이어의 경우 1~9까지의 중복되지 않은 랜덤 숫자 생성 +- 사용자와 컴퓨터의 숫자 비교 하여 볼과 스트라이크 판단 +- 반복하여 사용자의 입력을 받아 스트라이크와 볼 비교 +- 자리와 숫자까지 맞으면 스트라이크 +- 숫자만 맞으면 볼 +- 아무것도 맞지 않으면 낫싱 +- 3스트라이크면 게임 종료하기 +- 숫자는 3대만 들어오게 함 +- 숫자는 10이상의 숫자는 들어오지 않게 함 + +## 과제 요구 사항 + +* 미션은 숫자 야구 저장소를 포크하고 클론하는 것으로 시작한다. +* 기능을 구현하기 전 README.md 에 구현할 기능 목록을 정리해 추가한다. +* Git의 커밋 단위는 앞 단계에서 README.md 에 정리한 기능 목록 단위로 추가한다. +* AngularJS Git Commit Message Conventions을 참고해 커밋 메시지를 작성한다. + +## 기능 요구 사항 + +* 기본적으로 1부터 9까지 서로 다른 수로 이루어진 3자리의 수를 맞추는 게임이다# + * 같은 수가 같은 자리에 있으면 스트라이크, 다른 자리에 있으면 볼, 같은 수가 전혀 없으면 낫싱이란 힌트를 얻고, 그 힌트를 이용해서 먼저 상대방(컴퓨터)의 수를 맞추면 승리한다. + ```textmate + * e.g. + * 상대방(컴퓨터)의 수가 425일 때, + * 123을 제시한 경우: 1스트라이크 + * 456을 제시한 경우: 1볼 1스트라이크 + * 789를 제시한 경우: 낫싱 + * ``` + 위 숫자 야구 게임에서 상대방의 역할을 컴퓨터가 한다. 컴퓨터는 1에서 9까지 서로 다른 임의의 수 3개를 선택한다. 게임 플레이어는 컴퓨터가 생각하고 있는 + 3개의 숫자를 입력하고, 컴퓨터는 입력한 숫자에 대한 결과를 출력한다. + 이 같은 과정을 반복해 컴퓨터가 선택한 3개의 숫자를 모두 맞히면 게임이 종료된다. + 게임을 종료한 후 게임을 다시 시작하거나 완전히 종료할 수 있다. + 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException 을 발생시킨 후 애플리케이션은 종료되어야 한다. + +## 실행 결과 + +숫자를 입력해 주세요 : 123 +1볼 1스트라이크 +숫자를 입력해 주세요 : 145 +1볼 +숫자를 입력해 주세요 : 671 +2볼 +숫자를 입력해 주세요 : 216 +1스트라이크 +숫자를 입력해 주세요 : 713 +3스트라이크 +3개의 숫자를 모두 맞히셨습니다! 게임 종료 +게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요. +1 +숫자를 입력해 주세요 : 123 +1볼 +… + +## 프로그래밍 요구 사항 1 + +JDK 17 버전에서 실행 가능해야 한다. +프로그램 실행의 시작점은 Application 의 main() 이다. +build.gradle 파일은 변경할 수 없으며, 제공된 라이브러리 이외의 외부 라이브러리는 사용하지 않는다. +프로그램 종료 시 System.exit() 를 호출하지 않는다. +프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 등의 이름을 바꾸거나 이동하지 않는다. + +## 프로그래밍 요구 사항 2 + +자바 코드 컨벤션을 지키면서 프로그래밍한다. +기본적으로 Google Java Style Guide을 원칙으로 한다. +단, 들여쓰기는 '2 spaces'가 아닌 '4 spaces'로 한다. +indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. +예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. +힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다. +3항 연산자를 쓰지 않는다. +함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라. \ No newline at end of file diff --git a/src/main/java/Application.java b/src/main/java/Application.java new file mode 100644 index 00000000..9815edc7 --- /dev/null +++ b/src/main/java/Application.java @@ -0,0 +1,21 @@ +import model.Computer; +import model.User; +import controller.GameController; +import view.UserInterface; + +public class Application { + + public static void main(String[] args) { + User user = new User(); + Computer computer = new Computer(); + + UserInterface userInterface = new UserInterface(); + GameController gameController = new GameController(user, computer); + + UserInterface.printStartingGame(); + gameController.startGame(); + UserInterface.printEndingGame(); + + userInterface.closeScanner(); + } +} diff --git a/src/main/java/controller/GameController.java b/src/main/java/controller/GameController.java new file mode 100644 index 00000000..a79d5293 --- /dev/null +++ b/src/main/java/controller/GameController.java @@ -0,0 +1,43 @@ +package controller; + +import model.BallCountStatus; +import model.Computer; +import model.User; + +import java.util.*; + +public class GameController { + private User user; + private Computer computer; + private InputController inputController; + private ScoreController scoreController; + private static final String ALL_STRIKE_MESSAGE = "3스트라이크"; + + + public GameController(User user, Computer computer) { + this.user = user; + this.computer = computer; + this.inputController = new InputController(); + this.scoreController = new ScoreController(); + } + + public void startGame() { + setComputerNumbers(); + String result = ""; + while (!result.equals(ALL_STRIKE_MESSAGE)) { + setUserNumbers(); + result = scoreController.calculateScore(user, computer); + System.out.println(result); + } + System.out.println("승리!"); + } + + public void setUserNumbers() { + user.setNumberList(inputController.generateUserNum()); + } + + public void setComputerNumbers() { + computer.setNumberSet(inputController.generateComputerNum()); + } + +} diff --git a/src/main/java/controller/InputController.java b/src/main/java/controller/InputController.java new file mode 100644 index 00000000..fdb5bd60 --- /dev/null +++ b/src/main/java/controller/InputController.java @@ -0,0 +1,33 @@ +package controller; + +import java.util.*; + +public class InputController { + public ArrayList generateUserNum() { + ArrayList userNumber = new ArrayList<>(); + Scanner scanner = new Scanner(System.in); + System.out.print("숫자를 입력하세요: "); + for (int i = 0; i < 3; i++) { + if (scanner.hasNextInt()) { + userNumber.add(scanner.nextInt()); + } else { + scanner.next(); + i = 0; + } + } + if(userNumber.stream().anyMatch(x->x>9)){ + System.out.println("한자리 수만 입력할 수 있습니다. 다시 입력해주세요"); + userNumber.clear(); + } + return userNumber; + } + + public Set generateComputerNum() { + Set numbers = new LinkedHashSet<>(); + Random random = new Random(); + while (numbers.size() < 3) { + numbers.add(random.nextInt(1, 10)); + } + return numbers; + } +} diff --git a/src/main/java/controller/ScoreController.java b/src/main/java/controller/ScoreController.java new file mode 100644 index 00000000..7eeec651 --- /dev/null +++ b/src/main/java/controller/ScoreController.java @@ -0,0 +1,73 @@ +package controller; + +import model.BallCountStatus; +import model.Computer; +import model.User; + +import java.util.ArrayList; + +public class ScoreController { + private static final String ALL_STRIKE_MESSAGE = "3스트라이크"; + private static final int MAX_SCORE = 3; + private static final String NO_SCORE = "낫싱"; + + public String calculateScore(User user, Computer computer) { + if (user.getNumberList().isEmpty()) return ""; + ArrayList ballCount = new ArrayList<>(); + + compareScoreStrike(ballCount, user, computer); + + if (ballCount.size() == MAX_SCORE) { + return ALL_STRIKE_MESSAGE; + } + + compareScoreBall(ballCount, user, computer); + + return convertScoreListToString(ballCount); + } + + protected void compareScoreBall( + ArrayList score, User user, Computer computer) { + for (Integer userDatum : user.getNumberList()) { + if (computer.getNumberSet().stream().anyMatch(userDatum::equals) + && score.size() <= MAX_SCORE) { + score.add(BallCountStatus.Ball); + } + } + } + + protected void compareScoreStrike( + ArrayList score, User user, Computer computer) { + ArrayList computerNumberList = new ArrayList<>(computer.getNumberSet()); + for (int i = 0; i < user.getNumberList().size(); i++) { + if (computerNumberList.get(i).intValue() == user.getNumberList().get(i).intValue()) { + score.add(BallCountStatus.Strike); + } + } + } + // + protected String convertScoreListToString(ArrayList score) { + if (score.isEmpty()) { + return NO_SCORE; + } else { + long ballNumber = getScoreCount(score, BallCountStatus.Ball); + long strikeNumber = getScoreCount(score, BallCountStatus.Strike); + ballNumber -= strikeNumber; + String ballResult = (ballNumber) + "볼"; + String strikeResult = strikeNumber + "스트라이크"; + StringBuilder result = new StringBuilder(); + if (ballNumber > 0) { + result.append(ballResult); + } + if (strikeNumber > 0) { + if (ballNumber > 0) result.append(" "); + result.append(strikeResult); + } + return result.toString(); + } + } + + private long getScoreCount(ArrayList score, BallCountStatus status) { + return score.stream().filter(x -> x == status).count(); + } +} diff --git a/src/main/java/model/BallCountStatus.java b/src/main/java/model/BallCountStatus.java new file mode 100644 index 00000000..b2f87b8c --- /dev/null +++ b/src/main/java/model/BallCountStatus.java @@ -0,0 +1,5 @@ +package model; + +public enum BallCountStatus { + Ball, Strike +} diff --git a/src/main/java/model/Computer.java b/src/main/java/model/Computer.java new file mode 100644 index 00000000..75ac499b --- /dev/null +++ b/src/main/java/model/Computer.java @@ -0,0 +1,20 @@ +package model; + +import java.util.*; + +public class Computer { + private Set numberSet; + + public Computer() { + numberSet = new LinkedHashSet<>(); + } + + public Computer setNumberSet(Set numberSet) { + this.numberSet = numberSet; + return this; + } + + public Set getNumberSet() { + return numberSet; + } +} diff --git a/src/main/java/model/User.java b/src/main/java/model/User.java new file mode 100644 index 00000000..13c22549 --- /dev/null +++ b/src/main/java/model/User.java @@ -0,0 +1,21 @@ +package model; + +import java.util.ArrayList; +import java.util.List; + +public class User { + private List numberList; + + public User() { + numberList = new ArrayList<>(); + } + + public User setNumberList(List numberList) { + this.numberList = numberList; + return this; + } + + public List getNumberList() { + return numberList; + } +} diff --git a/src/main/java/view/UserInterface.java b/src/main/java/view/UserInterface.java new file mode 100644 index 00000000..0b82297f --- /dev/null +++ b/src/main/java/view/UserInterface.java @@ -0,0 +1,26 @@ +package view; + +import java.util.Scanner; + +public class UserInterface { + private static final String THREE_STRIKE = "3스트라이크"; + private Scanner scanner; + + public UserInterface() { + this.scanner = new Scanner(System.in); + } + + public static void printStartingGame() { + System.out.println("=============== 숫자 야구 ================"); + } + + public static void printEndingGame() { + System.out.println("=============== 게임 종료 ================"); + } + + public void closeScanner() { + if (scanner != null) { + scanner.close(); + } + } +} diff --git a/src/test/java/controller/GameControllerTest.java b/src/test/java/controller/GameControllerTest.java new file mode 100644 index 00000000..cf74da7c --- /dev/null +++ b/src/test/java/controller/GameControllerTest.java @@ -0,0 +1,50 @@ +package controller; + +import model.Computer; +import model.User; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +class GameControllerTest { + User user = new User(); + Computer computer = new Computer(); + GameController gameController = new GameController(user, computer); + + @Test + void setUserNumbers() { + // Given + ArrayList expectedNumbers = new ArrayList<>(Arrays.asList(1, 2, 3)); + GameController gameController = new GameController(user, computer); + + // When + String input = "1 2 3"; + System.setIn(new ByteArrayInputStream(input.getBytes())); + gameController.setUserNumbers(); + + // Then + Assertions.assertThat(user.getNumberList()).isEqualTo(expectedNumbers); + } + + @Test + void setComputerNumbers() { + // Given + Set expectedNumbers = new LinkedHashSet<>(); + expectedNumbers.add(1); + expectedNumbers.add(2); + expectedNumbers.add(3); + + // When + gameController.setComputerNumbers(); + + // Then + Assertions.assertThat(computer.getNumberSet().size()).isEqualTo(expectedNumbers.size()); + } +} diff --git a/src/test/java/controller/InputControllerTest.java b/src/test/java/controller/InputControllerTest.java new file mode 100644 index 00000000..e63391ab --- /dev/null +++ b/src/test/java/controller/InputControllerTest.java @@ -0,0 +1,48 @@ +package controller; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.util.ArrayList; +import java.util.Set; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +class InputControllerTest { + + InputController inputController = new InputController(); + + @Test // [MethodName][StateUnderTest][ExpectedBehavior] + void testGeneratedUserNum() { + String input = "1 2 3"; + // String을 setIn을 통해 입력시킨다.new ByteArrayInputStream(input.getBytes())는 input 문자열을 바이트 배열로 + // 변환한 후, 이를 사용하여 새로운 ByteArrayInputStream 객체를 생성합니다. ByteArrayInputStream은 InputStream의 하위 + // 클래스로, 바이트 배열에서 데이터를 읽는 데 사용됩니다. + System.setIn(new ByteArrayInputStream(input.getBytes())); + + ArrayList result = inputController.generateUserNum(); + + assertThat(result.size()).isEqualTo(3); + assertThat(result.get(0)).isEqualTo(1); + assertThat(result.get(1)).isEqualTo(2); + assertThat(result.get(2)).isEqualTo(3); + } + + @Test + void testGenerateComputerNumSize() { + Set result = inputController.generateComputerNum(); + assertThat(result.size()).isEqualTo(3); + } + + @Test + void testInputNumberGreaterThan9() { + String input = "10 2 1"; + System.setIn(new ByteArrayInputStream(input.getBytes())); + + ArrayList result = inputController.generateUserNum(); + + assertThat(result.size()).isEqualTo(0); + } +} diff --git a/src/test/java/controller/ScoreControllerTest.java b/src/test/java/controller/ScoreControllerTest.java new file mode 100644 index 00000000..e29db138 --- /dev/null +++ b/src/test/java/controller/ScoreControllerTest.java @@ -0,0 +1,141 @@ +package controller; + +import model.BallCountStatus; +import model.Computer; +import model.User; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.sql.Array; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +class ScoreControllerTest { + ScoreController scoreController = new ScoreController(); + InputController inputController = new InputController(); + + + @Test + void compareScoreBall() { + ArrayList inputStatus = new ArrayList<>(); // empty score board + User user = new User(); + + Computer computer = new Computer(); + Set computerNumbers = new LinkedHashSet<>(); + computerNumbers.add(1); + computerNumbers.add(2); + computerNumbers.add(3); + computer.setNumberSet(computerNumbers); + + String expectedUserNum = "3 1 2"; + System.setIn(new ByteArrayInputStream(expectedUserNum.getBytes())); + user.setNumberList(inputController.generateUserNum()); + + scoreController.compareScoreBall(inputStatus, user, computer); + + assertThat(inputStatus.size()).isEqualTo(3); + for (BallCountStatus status : inputStatus) { + assertThat(status).isEqualTo(BallCountStatus.Ball); + } + } + + @Test + void compareScoreStrike() { + ArrayList inputStatus = new ArrayList<>(); // empty score board + User user = new User(); + + Computer computer = new Computer(); + Set computerNumbers = new LinkedHashSet<>(); + computerNumbers.add(1); + computerNumbers.add(2); + computerNumbers.add(3); + computer.setNumberSet(computerNumbers); + + String expectedUserNum = "1 2 3"; + System.setIn(new ByteArrayInputStream(expectedUserNum.getBytes())); + user.setNumberList(inputController.generateUserNum()); + + scoreController.compareScoreStrike(inputStatus, user, computer); + + assertThat(inputStatus.size()).isEqualTo(3); + for (BallCountStatus status : inputStatus) { + assertThat(status).isEqualTo(BallCountStatus.Strike); + } + } + + @Test + @DisplayName("OneBall OneStrike") + void testConvertScoreListToStringOneBallOneStrike() { + // given + String expected = "1볼 1스트라이크"; + ArrayList myScore = new ArrayList<>(); + myScore.add(BallCountStatus.Ball); + myScore.add(BallCountStatus.Strike); + myScore.add(BallCountStatus.Ball); + ScoreController scoreController = new ScoreController(); + + // when + String result = scoreController.convertScoreListToString(myScore); + + // then + assertThat(result).isEqualTo(expected); + } + + @Test + @DisplayName("ThreeStrike") + void testConvertScoreListToStringThreeStrike() { + // given + String expected = "3스트라이크"; + ArrayList myScore = new ArrayList<>(); + myScore.add(BallCountStatus.Strike); + myScore.add(BallCountStatus.Strike); + myScore.add(BallCountStatus.Strike); + ScoreController scoreController = new ScoreController(); + + // when + String result = scoreController.convertScoreListToString(myScore); + + // then + assertThat(result).isEqualTo(expected); + } + @Test + @DisplayName("ThreeBall") + void testConvertScoreListToStringThreeBall() { + // given + String expected = "3볼"; + ArrayList myScore = new ArrayList<>(); + myScore.add(BallCountStatus.Ball); + myScore.add(BallCountStatus.Ball); + myScore.add(BallCountStatus.Ball); + ScoreController scoreController = new ScoreController(); + + // when + String result = scoreController.convertScoreListToString(myScore); + + // then + assertThat(result).isEqualTo(expected); + } + @Test + @DisplayName("Nothing") + void testConvertScoreListToStringNothing() { + // given + String expected = "낫싱"; + ArrayList myScore = new ArrayList<>(); + ScoreController scoreController = new ScoreController(); + + // when + String result = scoreController.convertScoreListToString(myScore); + + // then + assertThat(result).isEqualTo(expected); + } + + +}