
public class InputView {
private final BufferedReader br;
public InputView() {
this.br = new BufferedReader(new InputStreamReader(System.in));
}
public String getInput() throws IOException {
System.out.println("숫자를 입력 해 주세요");
String input = br.readLine();
if (!isProperInput(input)) {
getInput();
}
return input;
}
public boolean wantContinue() throws IOException {
String input = br.readLine();
return input.equals("1");
}
public boolean isProperInput(String s) {
if (s.length() != 3) {
System.out.println("3자리 숫자를 입력해주세요");
return false;
}
try {
Integer.parseInt(s);
} catch (NumberFormatException e) {
System.out.println("숫자만 입력해주세요");
return false;
}
return true;
}
}
public class NumberProvider {
public String makeRandomAnswer() {
List<Integer> digits = new ArrayList<>();
for (int i = 0; i < 10; i++) {
digits.add(i);
}
Collections.shuffle(digits);
int firstDigit = digits.get(0);
int secondDigit = digits.get(1);
int thirdDigit = digits.get(2);
int answer = firstDigit * 100 + secondDigit * 10 + thirdDigit;
return String.valueOf(answer);
}
}
사용자와의 게임을 위해서 임의의 숫자를 만들고, String 타입으로 리턴하는 책임
임의의 숫자 만들기 + 숫자를 받아서 리턴하기 의 책임을 나누지 않은 이유는...너무 간단하니까 그러긴 했는데
그래도 나누는 게 좋겠다.
public class NumberProvider {
private int makeInteger() {
List<Integer> digits = new ArrayList<>();
for (int i = 0; i < 10; i++) {
digits.add(i);
}
Collections.shuffle(digits);
int firstDigit = digits.get(0);
int secondDigit = digits.get(1);
int thirdDigit = digits.get(2);
return firstDigit * 100 + secondDigit * 10 + thirdDigit;
}
public String makeRandomAnswer(){
return String.valueOf(this.makeInteger());
}
}
이 녀석은(Checker) 입력받은 숫자가 정답(생성된 난수)랑 일치하는지 여부를 판별한다. 즉, 낫싱(아예 틀림) 볼(숫자는 맞는데 순서가 틀림) 개수, 스트라이크(숫자, 순서 둘 다 맞음) 개수, 정답(모든 숫자와 순서가 동일함) 을 판별한다.
비슷한 책임을 가지는 녀석이 여러개로 나뉘어서, 상속으로 구현했다 (조금 후회하긴 한다)
abstract class Checker {
public Checker() {
}
}
class StrikeChecker extends Checker{
private int checkStrike(String aInput, String aAnswer){
return aInput.equals(aAnswer) ? 1 : 0;
}
public int countStrike(String input, String answer){
String[] inputs = input.split("");
String[] answers = answer.split("");
int strikeCount = 0;
for (int i = 0; i < inputs.length; i++) {
strikeCount += checkStrike(inputs[i], answers[i]);
}
return strikeCount;
}
}
class BallChecker extends Checker {
private int checkBall(String s, String answer){
return answer.contains(s) ? 1 : 0;
}
public int countBall(String input, String answer, int strikeCount){
String[] given = input.split("");
Set<String> set = new HashSet<>(Arrays.asList(given));
int count = 0;
for (String s : set) {
count += checkBall(s, answer);
}
if (strikeCount != 0) count -= strikeCount;
return count;
}
}
가장 아쉬운 녀석. 아쉬움이 남음
public class GameChecker {
private NothingChecker nothingChecker;
private CorrectChecker correctChecker;
private StrikeChecker strikeChecker;
private BallChecker ballChecker;
private void init() throws IOException {
this.ballChecker = new BallChecker();
this.correctChecker = new CorrectChecker();
this.nothingChecker = new NothingChecker();
this.strikeChecker = new StrikeChecker();
}
public String check(String input, String answer) throws IOException {
init();
if (this.correctChecker.isCorrect(input, answer)) return "정답";
int strikeCount = strikeChecker.countStrike(input, answer);
int ballCount = ballChecker.countBall(input, answer, strikeCount);
if (nothingChecker.isNothing(strikeCount, ballCount)) return "낫싱";
String ball = ballCount + "볼";
if (strikeCount == 0 && ballCount != 0) return ball;
String strike = strikeCount + "스트라이크";
if (ballCount == 0 && strikeCount != 0) return strike;
return strike + ball;
}
}
체커를 의존 주입 형식으로 받는다면, nothingChecker 등을 protected로 해둔 의미가 없어진다...(밖에서 꺼내가지 못하도록 막아두는 차원에서 checker의 하위 패키지에서만 사용 가능하게 했다)
그러나, init() 이라는 매서드를 통해서 체크할 때마다 초기화 하는것도 이상하다.
이 부분은...그냥 게임체커 인스턴스가 생성 될 때 (생성자가 호출 될 때) 함께 초기화하는것으로 해야할 듯 하다...
package baseboll.checker;
import java.io.IOException;
public class GameChecker {
private NothingChecker nothingChecker;
private CorrectChecker correctChecker;
private StrikeChecker strikeChecker;
private BallChecker ballChecker;
public GameChecker() {
init();
}
private void init() {
this.ballChecker = new BallChecker();
this.correctChecker = new CorrectChecker();
this.nothingChecker = new NothingChecker();
this.strikeChecker = new StrikeChecker();
}
public String check(String input, String answer){
if (this.correctChecker.isCorrect(input, answer)) return "정답";
int strikeCount = strikeChecker.countStrike(input, answer);
int ballCount = ballChecker.countBall(input, answer, strikeCount);
if (nothingChecker.isNothing(strikeCount, ballCount)) return "낫싱";
String ball = ballCount + "볼";
if (strikeCount == 0 && ballCount != 0) return ball;
String strike = strikeCount + "스트라이크";
if (ballCount == 0 && strikeCount != 0) return strike;
return strike + ball;
}
}
public class OutputView {
public void nothing(){
System.out.println("낫싱");
}
public void gameEnd(){
System.out.println("3개의 숫자를 모두 맞히셨습니다! 게임 종료" + "\n" + "게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.");
}
public void sendOutput(String checkResult){
if (checkResult.equals("정답")) {
gameEnd();
return;
}
if (checkResult.equals("낫싱")) {
nothing();
return;
}
if (checkResult.equals("")) return;
System.out.println(checkResult);
}
}
정말 출력만 해주는 녀석
사실상 제일 깔끔한것 아닐까?...
public class GameController {
private final InputView inputView;
private final OutputView outputView;
private final GameChecker gameChecker;
private final NumberProvider numberProvider;
public GameController(InputView inputView, OutputView outputView, GameChecker gameChecker, NumberProvider numberProvider) {
this.inputView = inputView;
this.outputView = outputView;
this.gameChecker = gameChecker;
this.numberProvider = numberProvider;
}
private boolean correct(String input, String answer) {
return input.equals(answer);
}
public void game() throws IOException {
String answer = numberProvider.makeRandomAnswer();
System.out.println(answer);
String input = "";
while (!correct(input, answer)){
input = inputView.getInput(); // 인펏부터 받는다
String checked = gameChecker.check(input, answer);
outputView.sendOutput(checked);
}
if (inputView.wantContinue()) game();
}
}
여기서도 protected 로 숨긴 폐해가 나온다 (correct가 저기 왜있음?)
어쨌든, 위의 모든 요소들을 의존 주입 받아서 사용자에게 game() 이라는 매서드만 노출해주는 녀석
어려워!!!

잘 나오는 모습.(답이 먼저 나오는 것은 테스트 용이성을 위해...)
테스트코드도 작성중인데, 어느 정도 범위로 해야할지 모르겠다!