Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,23 @@

## 과제 제출 과정
* [과제 제출 방법](https://github.com/next-step/nextstep-docs/tree/master/ent-precourse)

## 기능 요구사항 분석
- 임의의 정답 생성
- 컴퓨터는 게임 시작 시 1부터 9까지 서로 다른 수로 이루어진 3자리 수를 임의로 생성한다.
- 정답 입력
- 사용자는 1부터 9까지 서로 다른 수로 이루어진 3자리 수를 입력할 수 있다.
- 정답을 맞출 때까지 사용자는 반복해서 정답을 입력한다.
- 정답 비교
- 사용자가 입력을 수행할 때마다 컴퓨터는 임의로 생성된 수와 비교하여 결과를 출력한다.
- 같은 수가 같은 자리에 있으면 **스트라이크**
- 같은 수가 다른 자리에 있으면 **볼**
- 같은 수가 전혀 없으면 **낫싱**
- 출력의 우선순위는 스트라이크가 볼보다 높다. ex) 1 스트라이크 1볼
- 게임 재시작 or 종료
- 사용자가 정답을 맞출 경우 게임을 재시작하거나 완전히 종료할 수 있다.

## 고려사항
- 정답의 길이가 항상 3일까? -> 3자리 이상의 숫자 야구 가능
- 항상 정답을 임의의 수로만 생성할까? -> 직접 입력으로 변경 가능
- 항상 서로 다른 수로만 이루어질까? -> 규칙이기 때문에 바뀌지 않음
3 changes: 3 additions & 0 deletions src/main/java/AnswerMaker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
public interface AnswerMaker {
String makeAnswer(int length);
}
76 changes: 76 additions & 0 deletions src/main/java/GamePlayer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import UI.InputUI;

public class GamePlayer {

private final int length;
private String answer;
private String userInput;

public GamePlayer(int length) {
this.length = length;
}

public void start() {
boolean isRestart = false;
while (!isRestart) {
playGame();
String userInput = InputUI.getRestartFlag();
isRestart = userInput.equals("2");
System.out.println();
}
System.out.println("게임을 완전히 종료합니다.");
}

private void playGame() {
System.out.println("게임을 시작합니다.");
// 정답 생성
AnswerMaker answerMaker = new RandomAnswerMaker();
answer = answerMaker.makeAnswer(length);

boolean isCorrect = false;
while (!isCorrect) {
// 정답 입력
userInput = InputUI.getAnswerByUser(length);
isCorrect = compareInputWithAnswer();
}
}

private boolean compareInputWithAnswer() {
int strikeCnt = 0;
int ballCnt = 0;
for (int i = 0; i < userInput.length(); i++) {
strikeCnt += isStrike(i);
ballCnt += isBall(i);
}

printResult(strikeCnt, ballCnt);
if (strikeCnt == length) return true;
return false;
}

private int isStrike(int idx) {
if (answer.indexOf(userInput.charAt(idx)) == idx)
return 1;
return 0;
}

private int isBall(int idx) {
if (answer.indexOf(userInput.charAt(idx)) != -1 && answer.indexOf(userInput.charAt(idx)) != idx)
return 1;
return 0;
}

private void printResult(int strikeCnt, int ballCnt) {
if (strikeCnt > 0)
System.out.print(strikeCnt + "스트라이크 ");
if (ballCnt > 0)
System.out.print(ballCnt + "볼");
if (strikeCnt == 0 && ballCnt == 0)
System.out.print("낫싱");
System.out.println();

if (strikeCnt == length) {
System.out.println(length + "개의 숫자를 모두 맞히셨습니다! 게임 종료");
}
}
}
6 changes: 6 additions & 0 deletions src/main/java/GameStarter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
public class GameStarter {
public static void main(String[] args) {
GamePlayer gamePlayer = new GamePlayer(3);
gamePlayer.start();
}
}
18 changes: 18 additions & 0 deletions src/main/java/RandomAnswerMaker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class RandomAnswerMaker implements AnswerMaker {

@Override
public String makeAnswer(int length) {
List<String> numbers = new ArrayList<>();
for (int i = 1; i < 10; i++) {
numbers.add(String.valueOf(i));
}
Collections.shuffle(numbers);

return String.join("", numbers.subList(0, length));
}

}
45 changes: 45 additions & 0 deletions src/main/java/UI/InputUI.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package UI;

import java.util.Scanner;

public class InputUI {
private static final Scanner sc = new Scanner(System.in);

public static String getAnswerByUser(int length) {
System.out.print("서로 다른 수로 이루어진 세자리 수를 입력해주세요 : ");
String userInput = sc.next();
while (userInput.length() != length || !isValidInput(userInput)) {
System.out.print("\n올바르지 않은 입력입니다.\n" +
"서로 다른 수로 이루어진 세자리 수를 입력해주세요 : ");
userInput = sc.next();
}

return userInput;
}

public static String getRestartFlag() {
System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.");
String userInput = sc.next();
while (!userInput.equals("1") && !userInput.equals("2")) {
System.out.println("올바르지 않은 입력입니다.\n" +
"게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.");
userInput = sc.next();
}
return userInput;
}

private static boolean isValidInput(String input) {
StringBuilder sb = new StringBuilder();
for (char c : input.toCharArray()) {
appendUniqueChar(sb, c);
}

return input.equals(sb.toString());
}

private static void appendUniqueChar(StringBuilder sb, char c) {
if (!sb.toString().contains(String.valueOf(c)) && Character.isDigit(c)) {
sb.append(c);
}
}
}
36 changes: 36 additions & 0 deletions src/test/java/AnswerMakerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class AnswerMakerTest {
private final AnswerMaker answerMaker;

public AnswerMakerTest() {
this.answerMaker = new RandomAnswerMaker();
}

@Test
@DisplayName("생성된 수는 모두 다른 수로 이루어져있는가?")
public void checkDuplicationInAnswer() {
String answer = answerMaker.makeAnswer(3);

assertThat(checkDuplication(answer)).isTrue();
}

@Test
@DisplayName("생성된 수는 세자리 수인가?")
public void checkAnswerSize() {
String answer = answerMaker.makeAnswer(3);

assertThat(answer.length()).isEqualTo(3);
}

private boolean checkDuplication(String str) {
if (str.charAt(0) == str.charAt(1)) return false;
if (str.charAt(1) == str.charAt(2)) return false;
if (str.charAt(0) == str.charAt(2)) return false;
return true;
}

}
82 changes: 82 additions & 0 deletions src/test/java/GamePlayerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import static org.assertj.core.api.Assertions.assertThat;

class GamePlayerTest {

private final ByteArrayOutputStream out = new ByteArrayOutputStream();

private final GamePlayer gamePlayer;

public GamePlayerTest() {
this.gamePlayer = new GamePlayer(3);
}

@Test
@DisplayName("정답 여부가 맞게 결정되는가?")
void test1() throws NoSuchMethodException, NoSuchFieldException, IllegalAccessException, InvocationTargetException {
// private 메서드 접근 허용
Method testMethod = GamePlayer.class.getDeclaredMethod("compareInputWithAnswer");
Field answerField = GamePlayer.class.getDeclaredField("answer");
Field userInputField = GamePlayer.class.getDeclaredField("userInput");
testMethod.setAccessible(true);
answerField.setAccessible(true);
userInputField.setAccessible(true);

answerField.set(gamePlayer, "123");
userInputField.set(gamePlayer, "321");

boolean actual = (boolean) testMethod.invoke(gamePlayer);

assertThat(actual).isFalse();
}

@Test
@DisplayName("힌트가 맞게 출력되는가?")
void test2() throws NoSuchMethodException, NoSuchFieldException, IllegalAccessException, InvocationTargetException {
// private 메서드 접근 허용
Method testMethod = GamePlayer.class.getDeclaredMethod("compareInputWithAnswer");
Field answerField = GamePlayer.class.getDeclaredField("answer");
Field userInputField = GamePlayer.class.getDeclaredField("userInput");
testMethod.setAccessible(true);
answerField.setAccessible(true);
userInputField.setAccessible(true);

System.setOut(new PrintStream(out));

answerField.set(gamePlayer, "123");
userInputField.set(gamePlayer, "321");

testMethod.invoke(gamePlayer);

assertThat(out.toString().strip()).isEqualTo("1스트라이크 2볼");
}

@Test
@DisplayName("일치하는 수가 없는 경우 '낫싱'이 출력되는가?")
void test3() throws NoSuchMethodException, NoSuchFieldException, IllegalAccessException, InvocationTargetException {
// private 메서드 접근 허용
Method testMethod = GamePlayer.class.getDeclaredMethod("compareInputWithAnswer");
Field answerField = GamePlayer.class.getDeclaredField("answer");
Field userInputField = GamePlayer.class.getDeclaredField("userInput");
testMethod.setAccessible(true);
answerField.setAccessible(true);
userInputField.setAccessible(true);

System.setOut(new PrintStream(out));

answerField.set(gamePlayer, "123");
userInputField.set(gamePlayer, "456");

testMethod.invoke(gamePlayer);

assertThat(out.toString().strip()).isEqualTo("낫싱");
}
}
53 changes: 53 additions & 0 deletions src/test/java/UI/InputUITest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package UI;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayInputStream;
import java.io.InputStream;

import static org.assertj.core.api.Assertions.assertThat;

public class InputUITest {

@Test
@DisplayName("사용자가 입력한 답은 숫자로만 이루어져 있는가?")
void checkUserInputIsNum() {
inputStringToStream("123");
String userInput = InputUI.getAnswerByUser(3);

for (int i = 0; i < userInput.length(); i++) {
assertThat(userInput.charAt(i)).isBetween('1', '9');
}
}

@Test
@DisplayName("사용자가 입력한 답은 세자리 수인가?")
void checkUserInput() {
inputStringToStream("456");
String userInput = InputUI.getAnswerByUser(3);

assertThat(userInput.length()).isEqualTo(3);
}

@Test
@DisplayName("사용자가 입력한 답은 서로 다른 수로 이루어져 있는가?")
void test() {
inputStringToStream("789");
String userInput = InputUI.getAnswerByUser(3);

assertThat(checkDuplication(userInput)).isTrue();
}

private void inputStringToStream(String input) {
InputStream in = new ByteArrayInputStream(input.getBytes());
System.setIn(in);
}

private boolean checkDuplication(String str) {
if (str.charAt(0) == str.charAt(1)) return false;
if (str.charAt(1) == str.charAt(2)) return false;
if (str.charAt(0) == str.charAt(2)) return false;
return true;
}
}