diff --git a/README.md b/README.md index 1bee39036..d308e6c42 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,39 @@ - 미션은 **기능 요구사항, 프로그래밍 요구사항, 과제 진행 요구사항** 세 가지로 구성되어 있다. - 세 개의 요구사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다. +## 유스케이스 +- 게임을 시작하라 + - 게임을 재시작하라 +- 게임을 종료하라 + +## 도메인 모델 +![img.png](img.png) + + +## 기능 목록 +- 게임 + - 게임을 시작하는 기능 + - 게임을 종료하는 기능 +- 심판 + - 플레이어와 상대방으로부터 숫자를 받아 스트라이크와 볼 개수를 판단하는 기능 +- 화면 + - 숫자 입력 메세지 출력 기능 + - 스트라이크와 볼 개수 혹은 낫싱을 출력하는 기능 + - 3개의 숫자를 모두 맞혔을 때 게임 종료 메세지와 게임 재시작 혹은 종료 여부를 입력하도록 출력하는 기능 +- 플레이어 + - 문자열 타입 숫자를 숫자형 리스트로 변환하는 기능 + - 심판의 요청에 따라 입력받은 유효한 숫자를 반환하는 기능 +- 유효성 검사기 + - 플레이어가 콘솔로 받은 입력이 숫자인지 판단하는 기능 + - 플레이어가 콘솔로 받은 입력에 중복된 수가 있는지 판단하는 기능 + - 플레이어가 콘솔로 받은 입력이 입력 숫자 범위 내에 존재하는지 판단하는 기능 + - 플레이어가 콘솔로 받은 입력의 길이가 올바른지 판단하는 기능 +- 상대방(컴퓨터) + - 랜덤하게 범위 내의 중복되지 않는 3자리 숫자를 생성하는 기능 + - 심판의 요청에 따라 자신의 숫자를 반환하는 기능 +- 판정 결과 + - 스트라이크 판정 개수가 최대 스트라이크 개수인지 판단 기능 + ## ✉️ 미션 제출 방법 diff --git a/img.png b/img.png new file mode 100644 index 000000000..6d3f4e22d Binary files /dev/null and b/img.png differ diff --git a/src/main/java/baseball/Application.java b/src/main/java/baseball/Application.java index 7f1901b8b..0148097c7 100644 --- a/src/main/java/baseball/Application.java +++ b/src/main/java/baseball/Application.java @@ -1,7 +1,25 @@ package baseball; +import camp.nextstep.edu.missionutils.Console; + +import static baseball.Message.SIGNAL_INVALID_ERROR; + public class Application { public static void main(String[] args) { - //TODO: 숫자 야구 게임 구현 + final String RESTART_SIGNAL = "1"; + final String CLOSE_SIGNAL = "2"; + + Game game = new Game(); + game.proceed(); + + String signal = Console.readLine(); + while (signal.equals(RESTART_SIGNAL)) { + game.proceed(); + signal = Console.readLine(); + } + + if (!signal.equals(CLOSE_SIGNAL)) { + throw new IllegalArgumentException(SIGNAL_INVALID_ERROR.getBody()); + } } } diff --git a/src/main/java/baseball/Game.java b/src/main/java/baseball/Game.java new file mode 100644 index 000000000..47f65a5b4 --- /dev/null +++ b/src/main/java/baseball/Game.java @@ -0,0 +1,46 @@ +package baseball; + +public class Game { + private Referee referee; + + private Player player; + + private Opponent computer; + + private boolean isRunning; + + + public void proceed() { + initializeProperty(); + + while (isRunning) { + run(); + } + + close(); + } + + private void initializeProperty() { + referee = new Referee(); + player = new Player(); + computer = new Opponent(); + isRunning = true; + } + + private void run() { + View.print(Message.INPUT_NUMBER_GUIDE); + JudgementResult judgementResult = referee.judge(player.getNumber(), computer.getNumber()); + View.print(judgementResult); + determineKeepRunning(judgementResult); + } + + private void determineKeepRunning(JudgementResult judgementResult) { + if (judgementResult.isMaxStrike()) { + isRunning = false; + } + } + + private void close() { + View.println(Message.RESTART_OR_CLOSE_GUIDE); + } +} diff --git a/src/main/java/baseball/JudgementResult.java b/src/main/java/baseball/JudgementResult.java new file mode 100644 index 000000000..011f1f131 --- /dev/null +++ b/src/main/java/baseball/JudgementResult.java @@ -0,0 +1,27 @@ +package baseball; + +public class JudgementResult { + private final int numberOfStrike; + + private final int numberOfBall; + + private final int MAX_STRIKE = 3; + + + public JudgementResult(int numberOfStrike, int numberOfBall) { + this.numberOfStrike = numberOfStrike; + this.numberOfBall = numberOfBall; + } + + public int getNumberOfStrike() { + return numberOfStrike; + } + + public int getNumberOfBall() { + return numberOfBall; + } + + public boolean isMaxStrike() { + return numberOfStrike == MAX_STRIKE; + } +} diff --git a/src/main/java/baseball/Message.java b/src/main/java/baseball/Message.java new file mode 100644 index 000000000..24efab492 --- /dev/null +++ b/src/main/java/baseball/Message.java @@ -0,0 +1,24 @@ +package baseball; + +public enum Message { + INPUT_NUMBER_GUIDE("숫자를 입력해주세요 : "), + MAX_STRIKE_AND_CLOSE_GUIDE("3개의 숫자를 모두 맞히셨습니다! 게임 종료"), + RESTART_OR_CLOSE_GUIDE("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."), + + + USER_NUMBER_NOT_NUMBER_ERROR("입력값에 숫자가 아닌 다른 값이 포함돼있습니다. 숫자만 입력해주세요."), + USER_NUMBER_DUPLICATE_ERROR("입력값에 중복이 포함돼있습니다. 중복되지 않는 숫자를 입력해주세요."), + USER_NUMBER_CONTAIN_ZERO_ERROR("입력값에 0은 포함될 수 없습니다. 1부터 9까지 숫자만 입력해주세요."), + USER_NUMBER_LENGTH_ERROR("입력값의 개수가 유효하지 않습니다. 3개의 숫자를 입력해주세요."), + SIGNAL_INVALID_ERROR("입력값에 1, 2가 아닌 다른 값이 포함돼있습니다. 1과 2중 하나를 선택해 입력해주세요."); + + private String body; + + Message(String body) { + this.body = body; + } + + public String getBody() { + return body; + } +} diff --git a/src/main/java/baseball/Opponent.java b/src/main/java/baseball/Opponent.java new file mode 100644 index 000000000..192c39e09 --- /dev/null +++ b/src/main/java/baseball/Opponent.java @@ -0,0 +1,32 @@ +package baseball; + +import camp.nextstep.edu.missionutils.Randoms; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +public class Opponent { + private List number = new ArrayList<>(); + + public List getNumber() { + if (number.isEmpty()) { + generateNumber(); + } + return number; + } + + private void generateNumber() { + HashSet digitsWithoutZero = new HashSet<>(); + while (digitsWithoutZero.size() < 3) { + digitsWithoutZero.add(getRandomDigitWithoutZero()); + } + number.addAll(digitsWithoutZero); + } + + private int getRandomDigitWithoutZero() { + final int startInclusive = 1; + final int endInclusive = 9; + + return Randoms.pickNumberInRange(startInclusive, endInclusive); + } +} diff --git a/src/main/java/baseball/Player.java b/src/main/java/baseball/Player.java new file mode 100644 index 000000000..e3c32a199 --- /dev/null +++ b/src/main/java/baseball/Player.java @@ -0,0 +1,24 @@ +package baseball; + +import camp.nextstep.edu.missionutils.Console; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class Player { + private List number; + + public List getNumber() { + String input = Console.readLine(); + Validator.validate(input); + number = convertStringToIntegerList(input); + return number; + } + + private List convertStringToIntegerList(String input) { + return Arrays + .stream(input.split("")) + .map(Integer::parseInt) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/java/baseball/Referee.java b/src/main/java/baseball/Referee.java new file mode 100644 index 000000000..36bc0a4a2 --- /dev/null +++ b/src/main/java/baseball/Referee.java @@ -0,0 +1,29 @@ +package baseball; + +import java.util.List; +import java.util.stream.IntStream; + +public class Referee { + public JudgementResult judge(List playerNumber, List opponentNumber) { + int numberOfStrike = countStrike(playerNumber, opponentNumber); + int numberOfBall = countBall(playerNumber, opponentNumber); + + return new JudgementResult(numberOfStrike, numberOfBall); + } + + private int countStrike(List playerNumber, List opponentNumber) { + return (int) IntStream + .rangeClosed(0, 2) + .filter(i -> playerNumber.get(i) == opponentNumber.get(i)) + .count(); + } + + private int countBall(List playerNumber, List opponentNumber) { + return (int) IntStream + .rangeClosed(0, 2) + .filter(playerNumberIndex -> { + int opponentNumberIndex = opponentNumber.indexOf(playerNumber.get(playerNumberIndex)); + return opponentNumberIndex != playerNumberIndex && opponentNumberIndex != -1;}) + .count(); + } +} diff --git a/src/main/java/baseball/Validator.java b/src/main/java/baseball/Validator.java new file mode 100644 index 000000000..cfc3f48d6 --- /dev/null +++ b/src/main/java/baseball/Validator.java @@ -0,0 +1,43 @@ +package baseball; + +import java.util.Arrays; + +import static baseball.Message.*; + +public class Validator { + public static void validate(String input) { + checkTypeConverting(input); + checkDuplication(input); + checkContainZero(input); + checkLength(input); + } + + private static void checkTypeConverting(String input) { + if (input == null || !input.matches("[0-9]+")) { + throw new IllegalArgumentException(USER_NUMBER_NOT_NUMBER_ERROR.getBody()); + } + } + + private static void checkDuplication(String input) { + long nonDuplicateWordCount = Arrays + .stream(input.split("")) + .distinct() + .count(); + + if (nonDuplicateWordCount != input.length()) { + throw new IllegalArgumentException(USER_NUMBER_DUPLICATE_ERROR.getBody()); + } + } + + private static void checkContainZero(String input) { + if (input.contains("0")) { + throw new IllegalArgumentException(USER_NUMBER_CONTAIN_ZERO_ERROR.getBody()); + } + } + + private static void checkLength(String input) { + if (input.length() != 3) { + throw new IllegalArgumentException(USER_NUMBER_LENGTH_ERROR.getBody()); + } + } +} diff --git a/src/main/java/baseball/View.java b/src/main/java/baseball/View.java new file mode 100644 index 000000000..6abb2c7ba --- /dev/null +++ b/src/main/java/baseball/View.java @@ -0,0 +1,39 @@ +package baseball; + +import static baseball.Message.*; + +public class View { + public static void print(Message message) { + System.out.print(message.getBody()); + } + + public static void println(Message message) { + System.out.println(message.getBody()); + } + + public static void print(JudgementResult result) { + String JudgementResultMessage = makeJudgementResultMessage(result); + System.out.println(JudgementResultMessage); + } + + private static String makeJudgementResultMessage(JudgementResult judgementResult) { + StringBuilder stringBuilder = new StringBuilder(); + + if (judgementResult.getNumberOfBall() > 0) { + stringBuilder.append(judgementResult.getNumberOfBall() + "볼 "); + } + if (judgementResult.getNumberOfStrike() > 0) { + stringBuilder.append(judgementResult.getNumberOfStrike() + "스트라이크 "); + } + + if (judgementResult.getNumberOfBall() == 0 && judgementResult.getNumberOfStrike() == 0) { + stringBuilder.append("낫싱"); + } + + if (judgementResult.isMaxStrike()) { + stringBuilder.append(MAX_STRIKE_AND_CLOSE_GUIDE.getBody()); + } + + return stringBuilder.toString(); + } +}