Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
68d651d
docs(readme): update project description
Jan 28, 2026
c5d5e9c
chore: set up initial project structure
Jan 28, 2026
f0f1a42
feat(game): add start message output
Jan 28, 2026
996b98e
feat(game) add random number generation
Jan 28, 2026
4c97560
feat(game) add GameController get answer
Jan 28, 2026
d289ad2
feat(game): add print enter usernumber
Feb 1, 2026
7eb46e6
refactor: reorganize domain (add Numbers class)
Feb 1, 2026
67d20be
refactor: add GameService, change GameController
Feb 1, 2026
327bac1
feat(game) add inputValidator - check input
Feb 1, 2026
2ffff42
feat(domain): add new domain model(JudgeCount)
Feb 1, 2026
3f98fc3
feat(game): add count feature (add JudgeService)
Feb 1, 2026
9a950e9
feat(add): add get user input (add InputView)
Feb 1, 2026
04d4bd6
refactor: inputview variable
Feb 1, 2026
0f7bb29
refactor: InputView delete repetition
Feb 1, 2026
c8a2119
feat(game): add playing game service (add GameService)
Feb 1, 2026
99883cb
feat(game): add print result message
Feb 1, 2026
745bd4c
feat(game) game 한 흐름 진행 구현
Feb 1, 2026
b27b6a0
fix(input): prompt print println 구분
Feb 1, 2026
0931c9a
feat(error): add error handling function at Numbers from
Feb 2, 2026
9221ebe
feat(domain): add new domain(Number)
Feb 2, 2026
2e5539c
feat(domain): Number domain apply
Feb 2, 2026
b2678ee
test: testcase 추가
Feb 2, 2026
bf29632
refactor(view): refactor input output view separate roles
Feb 2, 2026
7b9b633
refactor: add game domain
Feb 2, 2026
58f2b8d
refactor(domain): judgeservice 제거 numbers에 기능 추가
Feb 2, 2026
9a74fae
refactor: result 표시 outputview로 이동
Feb 2, 2026
ecd367a
refactor: restart 검증 gamecontroller로 이동
Feb 2, 2026
b18ee63
feat(game): 스트라이크 볼 순서 바꾸기
Feb 2, 2026
98fbea3
refactor: java 컨벤션 변수 이름 한글자 제거
Feb 2, 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
70 changes: 69 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,69 @@
# java-baseball-precourse
~~# java-baseball-precourse

## ⚾ 숫자 야구 게임 구현 기능 목록

### 1. 게임 실행
- 프로그램 시작 시 숫자 야구 게임을 실행한다.
- 사용자에게 게임 시작 안내 문구를 출력한다.

---

### 2. 숫자 야구 게임 정답 생성
- 컴퓨터는 1~9 사이의 서로 다른 숫자 3개를 랜덤으로 생성한다.
- 생성된 숫자는 게임이 끝날 때까지 정답으로 유지한다.

---

### 3. 사용자 입력 처리
- 사용자가 3자리 숫자를 입력한다.
- 입력은 한 번에 문자열 형태로 받는다.

---

### 4. 입력 검증
사용자의 입력이 올바른 형식인지 검증한다.

#### 4-2. 입력 길이가 3인지 확인
- 입력값이 정확히 3자리인지 검사한다.

#### 4-2. 숫자만 입력되었는지 확인
- 입력값이 1-9 사이의 숫자로만 구성되어 있는지 검사한다.

#### 4-3. 중복 숫가 존재하는 지 확인
- 입력값이 1-9 사이의 숫자로만 구성되어 있는지 검사한다.

#### 4-4. 예외 발생 시 출력
- 잘못된 입력이 들어오면 [ERROR]로 시작하는 메시지를 출력한다.

---

### 5. 판정 로직
입력값과 정답을 비교하여 결과를 판정한다.

#### 5-1. Strike 판정
- 숫자와 위치가 모두 일치하면 `strike`로 판단한다.

#### 5-2. Ball 판정
- 숫자는 포함되지만 위치가 다르면 `ball`로 판단한다.

#### 5-3. Nothing 판정
- 일치하는 숫자가 하나도 없다면 `낫싱`을 출력한다.

---

### 6. 결과 출력
- 판정 결과에 따라 `볼`, `스트라이크`, `낫싱`을 출력한다.
- 3 strike가 되면 게임 종료 문구를 출력한다.

---

### 7. 재시작 및 종료
게임이 종료된 후 사용자의 선택을 입력받는다.

#### 7-1. 게임 재시작
- 사용자가 `1`을 입력하면 새로운 게임을 시작한다.

#### 7-2. 게임 종료
- 사용자가 `2`를 입력하면 프로그램을 종료한다.~~


5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
id 'java'
id 'application'
}

group = 'camp.nextstep.edu'
Expand All @@ -23,3 +24,7 @@ dependencies {
test {
useJUnitPlatform()
}

application {
mainClass = 'Application'
}
17 changes: 17 additions & 0 deletions src/main/java/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import controller.GameController;
import service.GameService;
import view.InputView;
import view.OutputView;
import service.RandomNumberGenerator;

public class Application {
public static void main(String[] args) {
InputView inputView = new InputView();
OutputView outputView = new OutputView();
GameService gameService = new GameService(
new RandomNumberGenerator()
);

new GameController(inputView, outputView, gameService).run();
}
}
57 changes: 57 additions & 0 deletions src/main/java/controller/GameController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package controller;

import domain.JudgeCount;
import service.GameService;
import util.Validator;
import view.InputView;
import view.OutputView;

public class GameController {
private final InputView inputView;
private final OutputView outputView;
private final GameService gameService;

public GameController(InputView inputView, OutputView outputView, GameService gameService) {
this.inputView = inputView;
this.outputView = outputView;
this.gameService = gameService;
}

public void run() {
outputView.printStartMessage();
while (true) {
playGame();
if (shouldStop()) {
return;
}
}
}

private void playGame() {
gameService.startNewGame();
playUntilWin();
}

private void playUntilWin() {
while (!gameService.isGameOver()) {
try {
JudgeCount count = gameService.play(inputView.readGuess());
outputView.printResult(count);
} catch (IllegalArgumentException exception) {
outputView.printError(exception.getMessage());
}
}
outputView.printGameEndMessage();
}

private boolean shouldStop() {
while (true) {
String input = inputView.readRestartCommand();
String error = Validator.restartError(input);
if (error == null) {
return "2".equals(input);
}
outputView.printError(error);
}
}
}
20 changes: 20 additions & 0 deletions src/main/java/domain/Game.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package domain;

public class Game {
private final Numbers answer;
private JudgeCount lastCount;

public Game(Numbers answer) {
this.answer = answer;
}

public JudgeCount play(String guessInput) {
JudgeCount count = answer.judge(Numbers.from(guessInput));
lastCount = count;
return count;
}

public boolean isOver() {
return lastCount != null && lastCount.isThreeStrikes();
}
}
8 changes: 8 additions & 0 deletions src/main/java/domain/JudgeCount.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package domain;

public record JudgeCount(int ball, int strike) {

public boolean isThreeStrikes() {
return strike == 3;
}
}
25 changes: 25 additions & 0 deletions src/main/java/domain/Number.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package domain;

import util.Validator;

public class Number {
private final int value;

private Number(int value) {
this.value = value;
}

public static Number fromChar(char digitChar) {
String error = Validator.numberError(digitChar);
if (error != null) throw new IllegalArgumentException(error);
return new Number(digitChar - '0');
}

public int value() {
return value;
}

public boolean same(int other) {
return value == other;
}
}
64 changes: 64 additions & 0 deletions src/main/java/domain/Numbers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package domain;

import util.Validator;

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

public class Numbers {
private static final int LENGTH = 3;
private final List<Number> numbers;

private Numbers(List<Number> numbers) {
this.numbers = numbers;
}

public static Numbers from(String input) {
String error = Validator.guessError(input);
if (error != null) {
throw new IllegalArgumentException(error);
}
return new Numbers(parse(input));
}

private static List<Number> parse(String input) {
List<Number> parsedNumbers = new ArrayList<>(LENGTH);
for (char digitChar : input.toCharArray()) {
parsedNumbers.add(Number.fromChar(digitChar));
}
return parsedNumbers;
}

public int get(int index) {
return numbers.get(index).value();
}

public boolean contains(int number) {
for (Number numberItem : numbers) {
if (numberItem.same(number)) {
return true;
}
}
return false;
}

public int size() {
return numbers.size();
}

public JudgeCount judge(Numbers guess) {
int ball = 0;
int strike = 0;
for (int index = 0; index < size(); index++) {
int guessDigit = guess.get(index);
if (guessDigit == get(index)) {
strike++;
continue;
}
if (contains(guessDigit)) {
ball++;
}
}
return new JudgeCount(ball, strike);
}
}
27 changes: 27 additions & 0 deletions src/main/java/service/GameService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package service;

import domain.Game;
import domain.JudgeCount;
import domain.Numbers;

public class GameService {
private final RandomNumberGenerator randomNumberGenerator;
private Game currentGame;

public GameService(RandomNumberGenerator randomNumberGenerator) {
this.randomNumberGenerator = randomNumberGenerator;
}

public void startNewGame() {
Numbers answer = randomNumberGenerator.numberGenerate();
currentGame = new Game(answer);
}

public JudgeCount play(String guessInput) {
return currentGame.play(guessInput);
}

public boolean isGameOver() {
return currentGame.isOver();
}
}
26 changes: 26 additions & 0 deletions src/main/java/service/RandomNumberGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package service;

import domain.Numbers;

import java.util.concurrent.ThreadLocalRandom;

public class RandomNumberGenerator {
public Numbers numberGenerate() {
int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9};
shuffle(numbers);
return Numbers.from("" + numbers[0] + numbers[1] + numbers[2]);
}

private void shuffle(int[] numbers) {
for (int currentIndex = numbers.length - 1; currentIndex > 0; currentIndex--) {
int swapIndex = ThreadLocalRandom.current().nextInt(currentIndex + 1);
swap(numbers, currentIndex, swapIndex);
}
}

private void swap(int[] numbers, int firstIndex, int secondIndex) {
int temp = numbers[firstIndex];
numbers[firstIndex] = numbers[secondIndex];
numbers[secondIndex] = temp;
}
}
54 changes: 54 additions & 0 deletions src/main/java/util/Validator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package util;

import java.util.HashSet;
import java.util.Set;

public class Validator {
private static final String ERROR_PREFIX = "[ERROR] ";
private static final int INPUT_LENGTH = 3;
private static final String RESTART_CODE = "1";
private static final String EXIT_CODE = "2";

public static String guessError(String input) {
if (input.length() != INPUT_LENGTH) {
return errorMessage(INPUT_LENGTH + "자여야 합니다.");
}
return validateCharacters(input);
}

private static String validateCharacters(String input) {
Set<Character> uniqueChars = new HashSet<>();

for (char digitChar : input.toCharArray()) {
String error = numberError(digitChar);
if (error != null) {
return error;
}
if (!uniqueChars.add(digitChar)) {
return errorMessage("중복된 숫자는 사용할 수 없습니다.");
}
}
return null;
}

public static String numberError(char digitChar) {
if (!Character.isDigit(digitChar)) {
return errorMessage("숫자만 입력해야 합니다.");
}
if (digitChar == '0') {
return errorMessage("0은 사용할 수 없습니다.");
}
return null;
}

public static String restartError(String input) {
if (RESTART_CODE.equals(input) || EXIT_CODE.equals(input)) {
return null;
}
return errorMessage(String.format("%s(재시작), %s(종료)만 입력 가능합니다.", RESTART_CODE, EXIT_CODE));
}

private static String errorMessage(String message) {
return ERROR_PREFIX + message;
}
}
Loading