Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f18cd83
init project
dukcode Jan 28, 2026
f9e8854
docs: README 요구사항 정리
dukcode Jan 28, 2026
5e98d42
feat: BaseballNumber 생성자 구현
dukcode Jan 28, 2026
0d88641
refactor: BaseballNumber 숫자 범위 검증 메서드 분리
dukcode Jan 28, 2026
fb4ceb8
feat: 숫자 중복 관련 검증 구현
dukcode Jan 28, 2026
dbb7b03
feat: 스트라이크 갯수 계산 로직 구현
dukcode Jan 28, 2026
a2f5270
feat: 볼 갯수 계산 로직 구현
dukcode Jan 28, 2026
d592e85
feat: 리플레이 게임 시스템 구현
dukcode Feb 8, 2026
0fc6e62
feat: 턴제 게임 구현
dukcode Feb 8, 2026
3868a67
refactor: 패키지 구조를 도메인과 프레임워크로 분리
dukcode Feb 8, 2026
dfba8b3
feat: 야구 게임 핵심 로직 구현
dukcode Feb 8, 2026
abcb116
feat: 게임 설정을 관리하는 Config 클래스 추가
dukcode Feb 8, 2026
259b1d3
feat: 게임 생성을 위한 Factory 클래스 추가
dukcode Feb 8, 2026
1d18059
refactor: 패키지 이동에 따른 테스트 import 수정
dukcode Feb 8, 2026
7de3921
feat: GameFactory를 사용하여 야구 게임 실행
dukcode Feb 8, 2026
4b34605
test: BaseballGameTurnInput 검증 테스트 추가
dukcode Feb 8, 2026
dcce21d
test: BaseballGameService 게임 로직 테스트 추가
dukcode Feb 8, 2026
6338514
test: BaseballNumberTest 패키지 이동
dukcode Feb 8, 2026
ff8b0a0
refactor: BaseballGameConfig를 외부에서 주입받도록 기본 생성자 제거
dukcode Feb 8, 2026
5043dda
feat: 턴 입력 오류 시 재시도 구현
dukcode Feb 8, 2026
f1ba3ac
feat: 재시작 입력 오류 시 재시작 입력 재시도 구현
dukcode Feb 8, 2026
fd61587
refactor: BaseballGameService 기본 생성자 삭제
dukcode Feb 9, 2026
3b09d52
refactor: RandomBaseballNumberGenerator 기본 생성자 삭제
dukcode Feb 9, 2026
084c1a5
refactor: GameFactory 메서드 분리
dukcode Feb 9, 2026
970f613
refactor: 패키지명 오타 수정
dukcode Feb 9, 2026
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
10 changes: 10 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"permissions": {
"allow": [
"Bash(git add:*)",
"Bash(git rm:*)"
],
"deny": [],
"ask": []
}
}
74 changes: 73 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,73 @@
# java-baseball-precourse
# java-baseball-precourse

## 기능 요구사항

- [ ] 기본적으로 1부터 9까지 서로 다른 수로 이루어진 3자리의 수를 맞추는 게임이다.
- [ ] 같은 수가 같은 자리에 있으면 스트라이크, 다른 자리에 있으면 볼, 같은 수가 전혀 없으면 포볼 또는 낫싱이란 힌트를 얻고,
그 힌트를 이용해서 먼저 상대방(컴퓨터)의 수를 맞추면 승리한다.
- [예] 상대방(컴퓨터)의 수가 425일 때,
- 123을 제시한 경우 : 1스트라이크
- 456을 제시한 경우 : 1스트라이크 1볼
- 789를 제시한 경우 : 낫싱
- [ ] 위 숫자 야구게임에서 상대방의 역할을 컴퓨터가 한다. 컴퓨터는 1에서 9까지 서로 다른 임의의 수 3개를 선택한다.
게임 플레이어는 컴퓨터가 생각하고 있는 3개의 숫자를 입력하고, 컴퓨터는 입력한 숫자에 대한 결과를 출력한다.
- [ ] 이 같은 과정을 반복해 컴퓨터가 선택한 3개의 숫자를 모두 맞히면 게임이 종료된다.
- [ ] 게임을 종료한 후 게임을 다시 시작하거나 완전히 종료할 수 있다.
- [ ] 사용자가 잘못된 값을 입력할 경우 [ERROR]로 시작하는 에러 메시지를 출력하고 게임을 계속 진행할 수 있어야 한다.

---

## 프로그램 실행 결과

```
숫자를 입력해주세요 : 123
1스트라이크 1볼
숫자를 입력해주세요 : 145
1볼
숫자를 입력해주세요 : 671
2볼
숫자를 입력해주세요 : 216
1스트라이크
숫자를 입력해주세요 : 713
3스트라이크
3개의 숫자를 모두 맞히셨습니다! 게임 끝
게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.
1
숫자를 입력해주세요 : 123
1볼
```

---

## 프로그래밍 요구사항1 - 제약사항

- [ ] 자바 코드 컨벤션을 지키면서 프로그래밍한다.
- https://naver.github.io/hackday-conventions-java/
- [ ] indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
- 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
- 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다.
- [ ] 자바 8에 추가된 stream api를 사용하지 않고 구현해야 한다. 단, 람다는 사용 가능하다.
- [ ] else 예약어를 쓰지 않는다.
- 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
- else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다.
- [ ] 함수(또는 메소드)의 길이가 15라인을 넘어가지 않도록 구현한다.
- 함수(또는 메소드)가 한 가지 일만 잘 하도록 구현한다.

---

## 프로그래밍 요구사항2 - 단위 테스트

- [ ] 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다.
- [ ] 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 분리해 구현한다.
- [ ] 힌트는 MVC 패턴 기반으로 구현한 후 View, Controller를 제외한 Model에 대한 단위 테스트를 추가하는 것에 집중한다.
- [ ] JUnit5와 AssertJ 사용법에 익숙하지 않은 개발자는 첨부한 "학습테스트를 통해 JUnit 학습하기.pdf" 문서를 참고해 사용법을 학습한 후 JUnit5 기반 단위 테스트를 구현한다.

---

## 과제 진행 요구사항

- [ ] 미션은 https://github.com/next-step/java-baseball-precourse 저장소를 fork/clone해 시작한다.
- [ ] 기능을 구현하기 전에 java-baseball-precourse/README.md 파일에 구현할 기능 목록을 정리해 추가한다.
- [ ] git의 commit 단위는 앞 단계에서 README.md 파일에 정리한 기능 목록 단위 또는 의미 있는 단위로 Commit한다.
- [ ] AngularJS Commit Message Conventions 참고해 commit log를 남기려고 노력해 본다.
- [ ] 과제 진행 및 제출 방법은 프리코스 과제 제출 문서를 참고한다.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ plugins {
id 'java'
}

group = 'camp.nextstep.edu'
group = 'com.kakao.onboarding.precourse.dukeyun'
version = '1.0-SNAPSHOT'

java {
Expand Down
Empty file removed src/main/java/.gitkeep
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.kakao.onboarding.precourse.dukeyun.baseball;

import com.kakao.onboarding.precourse.dukeyun.baseball.framework.gamesystem.GameSystem;

public class App {

public static void main(String[] args) {
GameSystem baseballGameSystem = GameFactory.createBaseballGameSystem();
baseballGameSystem.run();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.kakao.onboarding.precourse.dukeyun.baseball;

import com.kakao.onboarding.precourse.dukeyun.baseball.baseball.BaseballGameConfig;
import com.kakao.onboarding.precourse.dukeyun.baseball.baseball.BaseballGameConsoleView;
import com.kakao.onboarding.precourse.dukeyun.baseball.baseball.BaseballGameService;
import com.kakao.onboarding.precourse.dukeyun.baseball.baseball.RandomBaseballNumberGenerator;
import com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.Game;
import com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.turn.TurnBaseGame;
import com.kakao.onboarding.precourse.dukeyun.baseball.framework.gamesystem.GameSystem;
import com.kakao.onboarding.precourse.dukeyun.baseball.framework.gamesystem.ReplayableGameSystem;
import com.kakao.onboarding.precourse.dukeyun.baseball.framework.gamesystem.ReplayableGameSystemConsoleView;

public class GameFactory {
private GameFactory() {
}

public static BaseballGameConfig baseballGameConfig() {
return new BaseballGameConfig(3, 1, 9);
}

public static GameSystem createBaseballGameSystem() {
return createBaseballGameSystem(baseballGameConfig());
}

public static GameSystem createBaseballGameSystem(BaseballGameConfig config) {
return new ReplayableGameSystem(new ReplayableGameSystemConsoleView(), createBaseballGame(config));
}

private static Game createBaseballGame(BaseballGameConfig config) {
return new TurnBaseGame(
new BaseballGameConsoleView(config),
new BaseballGameService(config, new RandomBaseballNumberGenerator(config))
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.kakao.onboarding.precourse.dukeyun.baseball.baseball;

public class BaseballGameConfig {

private static final int DEFAULT_LENGTH = 3;
private static final int DEFAULT_MIN_RANGE = 1;
private static final int DEFAULT_MAX_RANGE = 9;

private final int length;
private final int minRange;
private final int maxRange;

public BaseballGameConfig(int length, int minRange, int maxRange) {
validateConfig(length, minRange, maxRange);
this.length = length;
this.minRange = minRange;
this.maxRange = maxRange;
}

public static BaseballGameConfig ofDefault() {
return new BaseballGameConfig(DEFAULT_LENGTH, DEFAULT_MIN_RANGE, DEFAULT_MAX_RANGE);
}

private void validateConfig(int length, int minRange, int maxRange) {
if (length <= 0) {
throw new RuntimeException("길이는 양수여야 합니다.");
}
if (minRange >= maxRange) {
throw new RuntimeException("최소값은 최대값보다 작아야 합니다.");
}
int availableNumbers = maxRange - minRange + 1;
if (length > availableNumbers) {
throw new RuntimeException(
String.format("길이(%d)가 사용 가능한 숫자의 개수(%d)보다 큽니다.", length, availableNumbers));
}
}

public int getLength() {
return length;
}

public int getMinRange() {
return minRange;
}

public int getMaxRange() {
return maxRange;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.kakao.onboarding.precourse.dukeyun.baseball.baseball;

import com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.turn.TurnBaseGameView;
import com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.turn.TurnInput;
import com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.turn.TurnResult;
import com.kakao.onboarding.precourse.dukeyun.baseball.utils.Console;

public class BaseballGameConsoleView implements TurnBaseGameView {

private static final String REQUEST_INPUT_MESSAGE = "숫자를 입력해주세요 : ";
private static final String CLEAR_MESSAGE_FORMAT = "%d개의 숫자를 모두 맞히셨습니다! 게임 끝";

private static final String BALL_UNIT = "볼";
private static final String STRIKE_UNIT = "스트라이크";
private static final String NOTHING = "낫싱";

private final BaseballGameConfig config;

public BaseballGameConsoleView(BaseballGameConfig config) {
this.config = config;
}

private static void printNumStrike(int numStrike) {
System.out.printf("%d%s", numStrike, STRIKE_UNIT);
}

private static void printNumBall(int numBall) {
System.out.printf("%d%s ", numBall, BALL_UNIT);
}

private static void printNewLine() {
System.out.println();
}

private static void printNothing() {
System.out.print(NOTHING);
}

@Override
public void printStart() {
}

@Override
public TurnInput requestTurnInput() {
System.out.print(REQUEST_INPUT_MESSAGE);
return new BaseballGameTurnInput(config, Console.readLine());
}

@Override
public void printClear() {
System.out.printf(CLEAR_MESSAGE_FORMAT + "%n", config.getLength());
}

@Override
public void printResult(TurnResult turnResult) {
BaseballGameTurnResult baseballGameTurnResult = (BaseballGameTurnResult)turnResult;

int numBall = baseballGameTurnResult.getNumBall();
int numStrike = baseballGameTurnResult.getNumStrike();

if (numBall == 0 && numStrike == 0) {
printNothing();
printNewLine();
return;
}

if (numBall != 0) {
printNumBall(numBall);
}

if (numStrike != 0) {
printNumStrike(numStrike);
}

printNewLine();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.kakao.onboarding.precourse.dukeyun.baseball.baseball;

import com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.turn.TurnBaseGameService;
import com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.turn.TurnInput;
import com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.turn.TurnResult;

public class BaseballGameService implements TurnBaseGameService {

private final BaseballGameConfig config;
private final BaseballNumberGenerator baseballNumberGenerator;
private BaseballNumber answer;

public BaseballGameService(BaseballGameConfig config, BaseballNumberGenerator baseballNumberGenerator) {
this.config = config;
this.baseballNumberGenerator = baseballNumberGenerator;
}

@Override
public void init() {
this.answer = baseballNumberGenerator.generate();
}

@Override
public TurnResult playTurn(TurnInput turnInput) {
BaseballGameTurnInput baseballGameTurnInput = (BaseballGameTurnInput)turnInput;
BaseballNumber baseballNumber = baseballGameTurnInput.getBaseballNumber();

return calculateResult(baseballNumber);
}

private TurnResult calculateResult(BaseballNumber baseballNumber) {
int numStrike = answer.countStrike(baseballNumber);
int numBall = answer.countBall(baseballNumber);

return new BaseballGameTurnResult(isClear(numStrike), numStrike, numBall);
}

private boolean isClear(int numStrike) {
return numStrike == config.getLength();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.kakao.onboarding.precourse.dukeyun.baseball.baseball;

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

import com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.turn.TurnInput;

public class BaseballGameTurnInput implements TurnInput {

private final BaseballNumber baseballNumber;

public BaseballGameTurnInput(BaseballGameConfig config, String input) {
valid(input);
this.baseballNumber = createBaseballNumber(config, input);
}

private BaseballNumber createBaseballNumber(BaseballGameConfig config, String input) {
List<Integer> numbers = new ArrayList<>();
for (char ch : input.toCharArray()) {
numbers.add(ch - '0');
}
return new BaseballNumber(config, numbers);
}

private void valid(String input) {
for (char ch : input.toCharArray()) {
validIsNumber(ch);
}
}

private void validIsNumber(char ch) {
if (!Character.isDigit(ch)) {
throw new IllegalArgumentException("입력은 숫자여야 합니다.");
}
}

public BaseballNumber getBaseballNumber() {
return baseballNumber;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.kakao.onboarding.precourse.dukeyun.baseball.baseball;

import com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.turn.TurnResult;

public class BaseballGameTurnResult implements TurnResult {

private final boolean clear;
private final int numStrike;
private final int numBall;

public BaseballGameTurnResult(boolean clear, int numStrike, int numBall) {
this.clear = clear;
this.numStrike = numStrike;
this.numBall = numBall;
}

@Override
public boolean isGameCleared() {
return clear;
}

public int getNumStrike() {
return numStrike;
}

public int getNumBall() {
return numBall;
}
}
Loading