Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
dc8a441
misc: README 수정
deunlee Jan 28, 2026
28352d4
feat: 컨트롤러 클래스 생성
deunlee Jan 28, 2026
599f43e
feat: OutputView에 메시지 추가
deunlee Jan 28, 2026
c14d895
feat: InputView 추가
deunlee Jan 28, 2026
5c6b541
feat: 랜덤 숫자 생성기 구현
deunlee Jan 28, 2026
376cde2
feat: 컨트롤러 시작 로직 구현
deunlee Jan 28, 2026
a5b9dbf
feat: 비교 결과 모델 추가
deunlee Jan 28, 2026
690d9b5
feat: 게임 진행 메서드 구현
deunlee Jan 28, 2026
4809bb2
feat: 비교 결과 메서드 추가
deunlee Jan 28, 2026
dc5a76e
feat: 볼/스트라이크 출력 구현
deunlee Jan 28, 2026
f4882df
feat: 게임 모델 추가
deunlee Jan 28, 2026
199d6e5
feat: 숫자 모델 추가
deunlee Jan 28, 2026
3925e81
feat: 사용자 입력과 비교하는 로직 추가
deunlee Jan 28, 2026
464c611
feat: 컨트롤러에서 사용자 입력 처리
deunlee Jan 28, 2026
b32e087
fix: 숫자 생성기 범위 수정
deunlee Jan 28, 2026
881d998
feat: 숫자 모델 구현
deunlee Jan 28, 2026
155bb19
misc: README 수정
deunlee Jan 28, 2026
ba27ec0
feat: 입력 뷰에 검증 로직 추가
deunlee Jan 28, 2026
1d766e1
feat: 입력 뷰 구현
deunlee Jan 28, 2026
94565f1
docs: 구현 및 테스트 요구사항 추가
deunlee Jan 28, 2026
4fabee6
misc: README 수정
deunlee Jan 28, 2026
3e3c2bf
test: 비교 결과 모델 테스트
deunlee Jan 28, 2026
30e7a82
chore: LICENSE 추가
deunlee Jan 28, 2026
b504f60
chore: README 수정
deunlee Feb 2, 2026
f87f911
test: 게임 테스트 추가
deunlee Feb 2, 2026
53d2cbd
test: 숫자 테스트 추가
deunlee 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
21 changes: 21 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Deun Lee

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
56 changes: 55 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,55 @@
# java-baseball-precourse
# Baseball-Game (Java)

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

## 프로그래밍 요구사항
- 자바 코드 컨벤션: https://naver.github.io/hackday-conventions-java/
- indent depth가 3이 넘지 않도록 구현 (예를 들어 while문 안에 if문이 있으면 들여쓰기는 2임)
- 자바 8에 추가된 Stream API를 사용하지 않고 구현 (단, 람다는 사용 가능)
- else, switch/case 예약어를 쓰지 않음 (if 조건절에서 값을 return하는 방식으로 구현)
- 함수(또는 메소드)의 길이가 15라인을 넘어가지 않도록 구현 (한 가지 일만 잘 하도록 구현)
- 도메인 로직에 단위 테스트를 구현 (단, UI(System.out, System.in, Scanner) 로직은 제외)
- 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 분리해 구현
- MVC 패턴 기반으로 구현한 후 View, Controller를 제외한 Model에 대한 단위 테스트를 추가
- JUnit5 기반 단위 테스트를 구현

## 과제 진행 요구사항
- [next-step/java-baseball-precourse](https://github.com/next-step/java-baseball-precourse) 저장소를 fork/clone해 시작
- 기능을 구현하기 전에 README.md 파일에 구현할 기능 목록을 정리해 추가
- commit 단위는 README.md 파일에 정리한 기능 목록 단위 또는 의미있는 단위 함
- [AngularJS Commit Message Conventions](https://gist.github.com/stephenparish/9941e89d80e2bc58a153) 참고해 commit log를 남기려고 노력
- 과제 마감 시간: 2026.02.09 10시

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

## 라이선스
- MIT License
Binary file added docs/프리코스_숫자야구게임.pdf
Binary file not shown.
Binary file not shown.
Empty file removed src/main/java/.gitkeep
Empty file.
10 changes: 10 additions & 0 deletions src/main/java/com/deunlee/baseball/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.deunlee.baseball;

import com.deunlee.baseball.controller.GameController;

public class Application {
public static void main(String[] args) {
GameController controller = new GameController();
controller.run();
}
}
63 changes: 63 additions & 0 deletions src/main/java/com/deunlee/baseball/controller/GameController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.deunlee.baseball.controller;

import com.deunlee.baseball.model.BaseballGame;
import com.deunlee.baseball.model.BaseballNumberGenerator;
import com.deunlee.baseball.model.ComparisonResult;
import com.deunlee.baseball.model.Numbers;
import com.deunlee.baseball.view.TextInputView;
import com.deunlee.baseball.view.TextOutputView;

public class GameController {
private final TextInputView inputView;
private final TextOutputView outputView;

public GameController() {
this.inputView = new TextInputView();
this.outputView = new TextOutputView();
}

public void run() {
outputView.printStartMessage();
boolean isContinue = true;
while (isContinue) {
playGame();
isContinue = shouldRestart();
}
}

private void playGame() {
final BaseballGame game = new BaseballGame(new BaseballNumberGenerator());
ComparisonResult result = null;
while (result == null || !result.isWin()) {
result = processGuess(game);
}
outputView.printWinMessage();
}

private boolean shouldRestart() {
outputView.printRestartPrompt();
try {
return inputView.readRestartDecision();
} catch (IllegalArgumentException e) {
handleError(e);
return shouldRestart(); // 잘못된 값을 입력하면 다시 물어봄
}
}

private ComparisonResult processGuess(BaseballGame game) {
try {
outputView.printGuessPrompt();
final Numbers playerNumbers = new Numbers(inputView.readPlayerGuess());
final ComparisonResult result = game.checkResult(playerNumbers);
outputView.printResult(result);
return result;
} catch (IllegalArgumentException e) {
handleError(e);
return null;
}
}

private void handleError(IllegalArgumentException e) {
outputView.printError(e.getMessage());
}
}
38 changes: 38 additions & 0 deletions src/main/java/com/deunlee/baseball/model/BaseballGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.deunlee.baseball.model;

public class BaseballGame {
private final Numbers answer;

public BaseballGame(IRandomNumberGenerator generator) {
this.answer = new Numbers(generator.generateUniqueNumbers());
}

public ComparisonResult checkResult(final Numbers playerNumbers) {
int strikes = countStrikes(answer, playerNumbers);
int balls = countBalls(answer, playerNumbers);
return new ComparisonResult(strikes, balls);
}

private int countStrikes(final Numbers answer, final Numbers guess) {
int strikes = 0;
for (int i = 0; i < 3; i++) {
if (answer.getDigitAt(i) == guess.getDigitAt(i)) {
strikes++;
}
}
return strikes;
}

private int countBalls(final Numbers answer, final Numbers guess) {
int balls = 0;
for (int i = 0; i < 3; i++) {
if (answer.getDigitAt(i) == guess.getDigitAt(i)) {
continue; // 스트라이크는 제외
}
if (answer.contains(guess.getDigitAt(i))) {
balls++;
}
}
return balls;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.deunlee.baseball.model;

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

public class BaseballNumberGenerator implements IRandomNumberGenerator {
private static final int REQUIRED_SIZE = 3;

private final Random random = new Random();

@Override
public List<Integer> generateUniqueNumbers() {
List<Integer> numbers = new ArrayList<>();
while (numbers.size() < REQUIRED_SIZE) {
int number = random.nextInt(9) + 1; // (1 ~ 9)
if (!numbers.contains(number)) {
numbers.add(number);
}
}
return numbers;
}
}
21 changes: 21 additions & 0 deletions src/main/java/com/deunlee/baseball/model/ComparisonResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.deunlee.baseball.model;

public class ComparisonResult {
private static final int WIN_STRIKES = 3;
private static final int NOTHING = 0;

private final int strikes;
private final int balls;

public ComparisonResult(int strikes, int balls) {
this.strikes = strikes;
this.balls = balls;
}

public int getStrikes() { return strikes; }
public int getBalls() { return balls; }
public boolean isWin() { return strikes == 3; }
public boolean isNothing() { return strikes == 0 && balls == 0; }
public boolean hasOnlyBalls() { return strikes == 0 && balls > 0; }
public boolean hasOnlyStrikes() { return balls == 0 && strikes > 0; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.deunlee.baseball.model;

import java.util.List;

public interface IRandomNumberGenerator {
List<Integer> generateUniqueNumbers();
}
52 changes: 52 additions & 0 deletions src/main/java/com/deunlee/baseball/model/Numbers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.deunlee.baseball.model;

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

public class Numbers {
private static final int REQUIRED_SIZE = 3;
private static final int MIN_DIGIT = 1;
private static final int MAX_DIGIT = 9;

private final List<Integer> digits;

public Numbers(List<Integer> digits) {
validateSize(digits);
validateRange(digits);
validateUnique(digits);
this.digits = new ArrayList<>(digits);
}

private void validateSize(final List<Integer> digits) {
if (digits.size() != REQUIRED_SIZE) {
throw new IllegalArgumentException("숫자는 " + REQUIRED_SIZE + "개여야 합니다.");
}
}

private void validateRange(final List<Integer> digits) {
for (int digit : digits) {
if (digit < MIN_DIGIT || digit > MAX_DIGIT) {
throw new IllegalArgumentException("숫자는 " + MIN_DIGIT + "부터 " + MAX_DIGIT + " 사이여야 합니다.");
}
}
}

private void validateUnique(final List<Integer> digits) {
if (new HashSet<>(digits).size() != digits.size()) {
throw new IllegalArgumentException("숫자는 중복될 수 없습니다.");
}
}

public int getDigitAt(int position) {
return digits.get(position);
}

public boolean contains(int digit) {
return digits.contains(digit);
}

public int size() {
return digits.size();
}
}
60 changes: 60 additions & 0 deletions src/main/java/com/deunlee/baseball/view/TextInputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.deunlee.baseball.view;

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

public class TextInputView {
private static final int REQUIRED_LENGTH = 3;
private static final int RESTART_DECISION = 1;

public List<Integer> readPlayerGuess() {
final String input = readLine();
validateLength(input, REQUIRED_LENGTH);
return convertToDigits(input);
}

public boolean readRestartDecision() {
final String input = readLine();
validateLength(input, 1);
int decision = convertToDigit(input.charAt(0));
validateRestartDecision(decision);
return decision == RESTART_DECISION;
}

private String readLine() {
Scanner scanner = new Scanner(System.in);
return scanner.nextLine();
}

private int convertToDigit(char c) {
validateDigit(c);
return c - '0';
}

private List<Integer> convertToDigits(final String input) {
List<Integer> digits = new ArrayList<>();
for (char c : input.toCharArray()) {
digits.add(convertToDigit(c));
}
return digits;
}

private void validateLength(final String input, int length) {
if (input.length() != length) {
throw new IllegalArgumentException(length + "자리 숫자를 입력해야 합니다.");
}
}

private void validateDigit(final char c) {
if (!Character.isDigit(c)) {
throw new IllegalArgumentException("숫자만 입력해야 합니다.");
}
}

private void validateRestartDecision(int decision) {
if (decision != 1 && decision != 2) {
throw new IllegalArgumentException("1 또는 2를 입력해야 합니다.");
}
}
}
Loading