Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
50e9091
feat: add hello
mintjordy Jun 20, 2020
22e2d1f
refactor: upgrade gradle version.
mintjordy Jun 20, 2020
fbe75e9
refactor: insert source compatibility java8
mintjordy Jun 20, 2020
8df4903
docs: 구현 기능목록 추가
mintjordy Jun 28, 2020
54a3796
docs: 기능 구현 목록 수정
mintjordy Jun 28, 2020
6fd0635
feat: 숫자목록 중 하나의 숫자에 대한 객체 및 생성자 추가
mintjordy Jun 28, 2020
eca1b85
feat: 숫자목록에 해당하는 객체, 생성자 추가
mintjordy Jun 28, 2020
4ea7c93
feat: 두 숫자 목록 비교후 생성되는 게임 결과 객체 및 생성자 추가
mintjordy Jun 28, 2020
a9991e6
feat: 두 숫자 목록 비교 결과 계산 기능 구현
mintjordy Jun 28, 2020
c29176d
refactor: 하나의 숫자 값 객체 equals 메서드 추가
mintjordy Jun 28, 2020
d4de1f2
refactor: 수동 숫자목록 생성 기능 구현
mintjordy Jun 28, 2020
c449683
refactor: 자동 숫자목록 생성 기능 구현
mintjordy Jun 28, 2020
ee13756
feat: 게임 종료 여부 구분 기능 구현
mintjordy Jun 28, 2020
3620ee1
feat: input, output view, main 메소드 연결
mintjordy Jun 28, 2020
3d15b39
refactor: 값 타입 클래스 final 추가, result 객체 필드 count 객체로 포장
mintjordy Jun 28, 2020
dde588a
refactor: 매개변수, 지역변수 final 키워드 추가
mintjordy Jun 28, 2020
2b728bd
refactor: 게임 결과에 따라 스트라이크, 볼, 낫싱 출력하도록 변경
mintjordy Jun 28, 2020
913c743
refactor: 숫자 목록 생성기 factory -> generator로 변경
mintjordy Jun 28, 2020
a45e753
refactor: 예외메시지 출력 기능 output view 클래스 위임
mintjordy Jun 28, 2020
e048b8a
refactor: 매직넘버 추출
mintjordy Jun 28, 2020
35d601a
refactor: 필드명 변경
mintjordy Jun 28, 2020
86493d4
fix: 테스트 실패 구간 수정
mintjordy Jun 28, 2020
2e4083e
refactor: 변수, 클래스 rename
mintjordy Jun 28, 2020
4d86e55
refactor: 변수, 클래스 rename
mintjordy Jun 28, 2020
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
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# 숫자 야구 게임

## 프로그램 흐름

1. 컴퓨터는 세자리의 중복되지 않는 임의의 3자리 숫자를 생성한다.
2. 플레이어는 숫자를 수동으로 입력해 컴퓨터가 생성한 숫자를 맞힌다.
3. 컴퓨터는 플레이어가 입력한 값을 받아 게임 결과를 반환한다.
- 3스트라이크가 나오는 경우: 게임이 종료된다.
- 그 외의 경우: 2번부터 다시 수행한다.
4. 게임 종료 후, 게임 재시작 여부를 입력 한다.
- 재시작(1)을 요청하는 경우
- 1번부터 다시 시작한다.
- 게임 종료(2)를 요청하는 경우
- 프로그램을 종료한다.

---

## 기능 구현 목록
- 숫자목록은 3자리의 값을 자동/수동으로 생성한다.
- 수동 생성하는 경우, 콘솔에서 값을 직접 입력 받는다.
- 자동 생성하는 경우, 임의로 값을 생성한다.
- 생성되는 숫자는 1~9의 범위 값만 가질 수 있다.
- 1~9 외의 값을 포함하고 있으면 예외 발생
- 값이 3자리가 아니면 예외 발생
- 값들 중 중복 값이 포함되어 있으면 예외 발생
- 숫자 목록은 다른 숫자 목록을 입력 받아 스트라이크, 볼등의 게임 결과를 계산 할 수 있다.
- 계산한 결과는 게임의 결과(2스트라이크, 1스트라이크 1볼, 낫싱 등)을 알고 있다.
- 계산한 결과는 게임 종료 여부를 반환할 수 있다. (3스트라이크)


2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ plugins {

group 'org.example'
version '1.0-SNAPSHOT'
sourceCompatibility = '8'

repositories {
mavenCentral()
}

dependencies {
testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.6.0'
testCompile("org.junit.jupiter:junit-jupiter-params:5.4.2")
testCompile group: 'org.assertj', name: 'assertj-core', version: '3.15.0'
}
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#Sat Jun 20 13:56:54 KST 2020
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
Expand Down
54 changes: 54 additions & 0 deletions src/main/java/baseball/BaseBallMainApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package baseball;

import baseball.domain.AutoNumberBallsGenerator;
import baseball.domain.ManualNumberBallsGenerator;
import baseball.domain.NumberBalls;
import baseball.domain.NumberBallsGenerator;
import baseball.domain.WhetherReplay;
import baseball.domain.Result;
import baseball.view.InputView;
import baseball.view.OutputView;

public class BaseBallMainApplication {
public static void main(String[] args) {
final NumberBallsGenerator autoGenerator = new AutoNumberBallsGenerator();
do {
final NumberBalls computerBalls = autoGenerator.generateBalls();
inputNumberWhileAllStrike(computerBalls);
OutputView.printWinningMessage();
} while (inputWhetherReplay().isReplay());
}

private static void inputNumberWhileAllStrike(final NumberBalls computerBalls) {
while (true) {
final NumberBalls playerBalls = inputPlayerBalls();
final Result computeResult = computerBalls.compute(playerBalls);
OutputView.printCompareResult(computeResult);
if (computeResult.isAllStrikes()) {
break;
}
}
}

private static NumberBalls inputPlayerBalls() {
while (true) {
try {
final NumberBallsGenerator manualBallsGenerator = new ManualNumberBallsGenerator(
InputView.inputBaseBallNumbers());
return manualBallsGenerator.generateBalls();
} catch (IllegalArgumentException e) {
OutputView.printErrorMessage(e);
}
}
}

private static WhetherReplay inputWhetherReplay() {
while (true) {
try {
return WhetherReplay.of(InputView.inputWhetherReplay());
} catch (IllegalArgumentException e) {
OutputView.printErrorMessage(e);
}
}
}
}
27 changes: 27 additions & 0 deletions src/main/java/baseball/domain/AutoNumberBallsGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package baseball.domain;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class AutoNumberBallsGenerator implements NumberBallsGenerator {
private static final int BALLS_FIRST_INDEX = 0;

private final List<NumberBall> allDistinctNumbers = new ArrayList<>();

public AutoNumberBallsGenerator() {
init();
}

private void init() {
for (int i = NumberBall.MIN_VALUE; i < NumberBall.MAX_VALUE; i++) {
allDistinctNumbers.add(NumberBall.of(i));
}
}

@Override
public NumberBalls generateBalls() {
Collections.shuffle(allDistinctNumbers);
return new NumberBalls(allDistinctNumbers.subList(BALLS_FIRST_INDEX, NumberBalls.BALL_NUMBER_LENGTH));
}
}
67 changes: 67 additions & 0 deletions src/main/java/baseball/domain/Count.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package baseball.domain;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public final class Count {
private static final int MIN_COUNT_VALUE = 0;
private static final int MAX_COUNT_VALUE = NumberBalls.BALL_NUMBER_LENGTH;
private static final Map<Integer, Count> COUNT_CACHE;

private static final String INVALID_COUNT_VALUE_BOUNDARY_EXCEPTION_MESSAGE = "볼, 스트라이크수를 계산할 수 있는 유효한 값의 범위가 아닙니다.";

static {
COUNT_CACHE = new HashMap<>();
for (int i = MIN_COUNT_VALUE; i <= MAX_COUNT_VALUE; i++) {
COUNT_CACHE.put(i, new Count(i));
}
}

private final int value;

private Count(final int value) {
verifyCountValue(value);
this.value = value;
}

private void verifyCountValue(final int value) {
if (value < MIN_COUNT_VALUE || value > MAX_COUNT_VALUE) {
throw new IllegalArgumentException(INVALID_COUNT_VALUE_BOUNDARY_EXCEPTION_MESSAGE);
}
}

public static Count of(final int value) {
if (COUNT_CACHE.containsKey(value)) {
return COUNT_CACHE.get(value);
}
throw new IllegalArgumentException(INVALID_COUNT_VALUE_BOUNDARY_EXCEPTION_MESSAGE);
}

public boolean isSameValue(final int countValue) {
return this.value == countValue;
}

public boolean isSumOfValueGreaterThanMax(final Count otherCount) {
return (this.value + otherCount.value) > NumberBalls.BALL_NUMBER_LENGTH;
}

public int getValue() {
return value;
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Count count1 = (Count)o;
return value == count1.value;
}

@Override
public int hashCode() {
return Objects.hash(value);
}
}
45 changes: 45 additions & 0 deletions src/main/java/baseball/domain/ManualNumberBallsGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package baseball.domain;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class ManualNumberBallsGenerator implements NumberBallsGenerator {
private static final int EXTRACT_COMPLETE_CRITERIA = 0;
private static final int EXTRACT_BALL_NUMBER_DIVIDER = 10;

private static final String NEGATIVE_NUMBER_EXCEPTION_MESSAGE = "음수는 허용되지 않습니다.";

private final int number;

public ManualNumberBallsGenerator(final int number) {
validateNumber(number);
this.number = number;
}

private void validateNumber(final int number) {
if (number < EXTRACT_COMPLETE_CRITERIA) {
throw new IllegalArgumentException(NEGATIVE_NUMBER_EXCEPTION_MESSAGE);
}
}

@Override
public NumberBalls generateBalls() {
final List<NumberBall> result = new ArrayList<>();
final Stack<NumberBall> numberBalls = extractNumberBalls();
while (!numberBalls.empty()) {
result.add(numberBalls.pop());
}
return new NumberBalls(result);
}

private Stack<NumberBall> extractNumberBalls() {
final Stack<NumberBall> numberBallStack = new Stack<>();
int num = number;
while (num > EXTRACT_COMPLETE_CRITERIA) {
numberBallStack.push(NumberBall.of(num % EXTRACT_BALL_NUMBER_DIVIDER));
num /= EXTRACT_BALL_NUMBER_DIVIDER;
}
return numberBallStack;
}
}
54 changes: 54 additions & 0 deletions src/main/java/baseball/domain/NumberBall.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package baseball.domain;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public final class NumberBall {
static final int MIN_VALUE = 1;
static final int MAX_VALUE = 9;
private static final Map<Integer, NumberBall> BALL_CACHE = new HashMap<>();

private static final String INVALID_BALL_NUMBER_BOUNDARY_EXCEPTION_MESSAGE = "주어진 범위 바깥의 볼은 사용할 수 없어요.";

static {
for (int i = MIN_VALUE; i <= MAX_VALUE; i++) {
BALL_CACHE.put(i, new NumberBall(i));
}
}

private final int value;

private NumberBall(final int value) {
verifyNumberBall(value);
this.value = value;
}

private void verifyNumberBall(final int value) {
if (value < MIN_VALUE || value > MAX_VALUE) {
throw new IllegalArgumentException(INVALID_BALL_NUMBER_BOUNDARY_EXCEPTION_MESSAGE);
}
}

public static NumberBall of(final int value) {
if (BALL_CACHE.containsKey(value)) {
return BALL_CACHE.get(value);
}
throw new IllegalArgumentException(INVALID_BALL_NUMBER_BOUNDARY_EXCEPTION_MESSAGE);
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
NumberBall that = (NumberBall)o;
return value == that.value;
}

@Override
public int hashCode() {
return Objects.hash(value);
}
}
92 changes: 92 additions & 0 deletions src/main/java/baseball/domain/NumberBalls.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package baseball.domain;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

public final class NumberBalls {
static final int BALL_NUMBER_LENGTH = 3;
private static final int DEFAULT_STRIKE_COUNT_NUMBER = 0;

private static final String INVALID_BALLS_ARGUMENT_EXCEPTION_MESSAGE = "입력 값이 유효하지 않습니다.";
private static final String DUPLICATE_NUMBER_CONTAINS_EXCEPTION_MESSAGE = "중복된 값은 들어갈 수 없습니다.";
private static final String NULL_ARGUMENT_EXCEPTION_MESSAGE = "Null객체는 허용되지 않습니다.";

private final List<NumberBall> balls;

public NumberBalls(final List<NumberBall> balls) {
verifyBalls(balls);
this.balls = Collections.unmodifiableList(new ArrayList<>(balls));
}

private void verifyBalls(final List<NumberBall> balls) {
verifyNonNull(balls);
verifyNoContainsDuplicateBall(balls);
verifyLength(balls);
}

private void verifyNonNull(final List<NumberBall> balls) {
if (Objects.isNull(balls)) {
throw new IllegalArgumentException(NULL_ARGUMENT_EXCEPTION_MESSAGE);
}
}

private void verifyNoContainsDuplicateBall(final List<NumberBall> balls) {
final Set<NumberBall> numberBalls = new HashSet<>(balls);
if (balls.size() != numberBalls.size()) {
throw new IllegalArgumentException(DUPLICATE_NUMBER_CONTAINS_EXCEPTION_MESSAGE);
}
}

private void verifyLength(final List<NumberBall> balls) {
if (balls.size() != BALL_NUMBER_LENGTH) {
throw new IllegalArgumentException(INVALID_BALLS_ARGUMENT_EXCEPTION_MESSAGE);
}
}

public Result compute(final NumberBalls other) {
final int strike = computeStrike(other);
final int ball = computeBall(other, strike);
return Result.of(strike, ball);
}

private int computeStrike(final NumberBalls other) {
int strike = DEFAULT_STRIKE_COUNT_NUMBER;
for (int i = 0; i < this.balls.size(); i++) {
if (this.balls.get(i).equals(other.balls.get(i))) {
strike++;
}
}
return strike;
}

private int computeBall(final NumberBalls other, final int strike) {
final int ballCandidateCount = computeCountOfBallCandidate(other);
return ballCandidateCount - strike;
}

private int computeCountOfBallCandidate(final NumberBalls other) {
final List<NumberBall> otherBalls = other.balls;
final Set<NumberBall> distinctNumbers = new HashSet<>(this.balls);
distinctNumbers.addAll(otherBalls);
return this.balls.size() + otherBalls.size() - distinctNumbers.size();
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
NumberBalls that = (NumberBalls)o;
return Objects.equals(balls, that.balls);
}

@Override
public int hashCode() {
return Objects.hash(balls);
}
}
Loading