diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..a55e7a17 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/src/main/java/kr/ladder/domain/InputValidation.java b/src/main/java/kr/ladder/domain/InputValidation.java index a088608d..fea1ffd9 100644 --- a/src/main/java/kr/ladder/domain/InputValidation.java +++ b/src/main/java/kr/ladder/domain/InputValidation.java @@ -3,9 +3,17 @@ import kr.ladder.view.InputView; import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; public class InputValidation { public static final String ERROR_MASSAGE_PLAYER_NAME = "정해진 형식에 맞지 않습니다. 다시 입력해주세요"; + public static final String ERROR_MESSAGE_LADDER_HEIGHT = "다시 입력해주세요. 최소 사다리 높이는 1입니다."; + public static final String ERROR_MESSAGE_LADDER_TYPE = "정수를 입력해 주세요."; + public static final String ERROR_MASSAGE_PRIZES_NUMBER = "경품을 다시 입력해주세요. 인원 수와 같은 수가 들어와야 합니다."; + private static final String ERROR_MASSAGE_NO_PLAYER_NAME = "해당 참가자가 없습니다."; public static final int MIN_NAME_LENGTH = 1; public static final int MAX_NAME_LENGTH = 5; private final InputView inputView; @@ -21,12 +29,13 @@ public InputValidation(){ - 각 이름중에 null이 있는 경우 3. 쉼표로 끝난 경우 4. 영어, 한글이 아닌 문자가 들어온 경우 + 5. 같은 이름을 가진 플레이어가 있을 경우 -> player 입력을 다시 받는다 // 🤔InputView에서 요청해서 다시 받는게 맞나?? return 위에서 검증을 마친(정상적인) 플레이어 이름 배열 */ - public String[] inspectPlayers(String[] players) throws IOException { - return inspectPlayersNameLength(players); + public String[] inspectPlayers() throws IOException { + return inspectPlayersNameLength(inputView.getPlayer()); } private String[] inspectPlayersNameLength(String[] players) throws IOException { @@ -37,16 +46,16 @@ private String[] inspectPlayersNameLength(String[] players) throws IOException { return players; } - private boolean correctLength(String[] players){ + private boolean correctLength(String[] words){ int errors = 0; // 이름이 1~5글자 사이에 있지 않으면 count - for (String playerName : players) { - errors += countOverScopeName(playerName); + for (String word : words) { + errors += countOverScopeName(word); } return (errors == 0); } - private int countOverScopeName(String playerName){ - if (playerName.length() >= MIN_NAME_LENGTH && playerName.length() <= MAX_NAME_LENGTH){ + private int countOverScopeName(String word){ + if (word.length() >= MIN_NAME_LENGTH && word.length() <= MAX_NAME_LENGTH){ return 0; } return 1; @@ -55,14 +64,85 @@ private int countOverScopeName(String playerName){ /* 사다리 높이 검사() while 정상적인 사다리 높이가 아니라면 1. 사다리 높이가 0 이하 ✅ - 2. int가 아닌 경우 + 2. int가 아닌 경우 ✅ -> 입력을 다시 받는다 return 정상적인 사다리 높이 */ - public int inspectLadderHeight(int ladderHeight) throws IOException { - if (ladderHeight < 1) { + public int inspectLadderHeight() throws IOException { + String ladderHeight = inputView.getLadderHeight(); + while (!isInteger(ladderHeight) || !isAvailableHeight(Integer.parseInt(ladderHeight))) { ladderHeight = inputView.getLadderHeight(); } - return ladderHeight; + return Integer.parseInt(ladderHeight); + } + + private boolean isAvailableHeight(int ladderHeight){ + if (ladderHeight < 1) { + System.out.println(ERROR_MESSAGE_LADDER_HEIGHT); + return false; + } + return true; + } + + private boolean isInteger(String ladderHeight) { + try { + Integer.parseInt(ladderHeight); + return true; + } catch (NumberFormatException e) { + System.out.println(ERROR_MESSAGE_LADDER_TYPE); + return false; + } + } + + /* + 상품 목록 검사 + while 정상적인 경품 목록이 아니라면 + 1. 1~5 글자 사이가 아닌 경우 ✅(사람 이름 검사 로직 재사용) + 2. 플레이어 숫자와 동일한 갯수가 아닐 경우 + -> 경품 입력을 다시 받는다. + return 정상적인 경품 목록 + */ + + public String[] inspectPrizes(int playersNumber) throws IOException { + String[] prizes = inputView.getPrizes(); + while (!correctPrizeLength(prizes) || !sameNumberAsPlayers(prizes.length, playersNumber)) { + prizes = inputView.getPrizes(); + } + return prizes; + } + + private boolean correctPrizeLength(String[] prizes) throws IOException { + if (!correctLength(prizes)) {// 각 이름이 1~5 글자 사이가 아니면 + System.out.println(ERROR_MASSAGE_PLAYER_NAME); + return false; + } + return true; + } + + private boolean sameNumberAsPlayers(int prizesNumber, int playersNumber){ + if (prizesNumber != playersNumber) { + System.out.println(ERROR_MASSAGE_PRIZES_NUMBER); + return false; + } + return true; + } + + /* + 결과를 보고 싶은 사람 검사 + players에 있는 사람만 입력받을 수 있도록 한다. + */ + public String inspectCommand(String[] players) throws IOException { + String command = inputView.getCommand(); + + if (command.equals("all") || command.equals("춘식이")) { // 모두 출력하거나 실행 종료하는 로직은 다른 클래스에 위임한다. + return command; + } + Optional matchedPlayer = Arrays.stream(players).filter(p -> p.equals(command)).findFirst(); + while (matchedPlayer.isEmpty()){ + System.out.println(ERROR_MASSAGE_NO_PLAYER_NAME); + String newCommand = inputView.getCommand(); + matchedPlayer = Arrays.stream(players).filter(p -> p.equals(newCommand)).findFirst(); + } + return command; } } diff --git a/src/main/java/kr/ladder/domain/Ladder.java b/src/main/java/kr/ladder/domain/Ladder.java index f2c0209d..f4388418 100644 --- a/src/main/java/kr/ladder/domain/Ladder.java +++ b/src/main/java/kr/ladder/domain/Ladder.java @@ -1,7 +1,9 @@ package kr.ladder.domain; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class Ladder { private final List ladder; @@ -14,14 +16,15 @@ public void make(int playerNumber, int ladderHeight) { // 1. 사다리를 만든다 makeLadderLine(playerNumber, ladderHeight); // 2. 유효성 검사를 한다 (세로줄 공백인지 확인하고, 사다리 재입력 받음) - while (!available()){ + while (!available() && ladderHeight != 1){ + ladder.clear(); makeLadderLine(playerNumber, ladderHeight); } // 3. OutputView에 넘겨서 바로 출력 가능하게 만들어준다. // generate(); } - - private void makeLadderLine(int playerNumber, int ladderHeight){ + + private void makeLadderLine(int playerNumber, int ladderHeight) { for (int i = 0; i < ladderHeight; i++) { // 행 넣기 LadderLine ladderLine = new LadderLine(); ladderLine.make(playerNumber); @@ -30,7 +33,7 @@ private void makeLadderLine(int playerNumber, int ladderHeight){ } private boolean available(){ - int[] countBlanks = new int[ladder.size()]; + int[] countBlanks = new int[ladder.get(0).size()]; for (LadderLine ladderLine : ladder){ countBlank(countBlanks, ladderLine); } @@ -54,37 +57,47 @@ private boolean isFilled(int[] countBlanks){ } return true; } - -// 욕심 시작... -// private void rebuildColumn(int ladderHeight){ -// int[] columnFalses = new int[ladder.size()]; -// for (LadderLine ladderLine : ladder) { -// List booleans = ladderLine.ladderLine; -// for (int i = 0; i < booleans.size(); i++) { -// if (!booleans.get(i)){ -// columnFalses[i]++; -// } -// } -// } -// -// for (int i = 0; i < columnFalses.length; i++) { -// if (columnFalses[i] == ladderHeight) { -// changeLadderColumn(i); -// } -// } -// } - - // 하드 코딩해서 마지막 열에서 추가하는 것으로 생각중.. -// private void changeLadderColumn(int falseIndex){ -// -// } public String generate(){ StringBuilder sb = new StringBuilder(); for (LadderLine ladderLine : ladder) { + sb.append(" "); // player name과 맞추기 위해서 sb.append(ladderLine.generate()).append("\n"); } return sb.toString(); } + public List play(){ + List mappingIndex = new ArrayList<>(); + int playerNum = ladder.get(0).size()+1; + for (int i = 0; i < playerNum; i++) { // player 수 + int nowIndex = i; + for (int j = 0; j < ladder.size(); j++) { // 사다리 높이 + nowIndex += compareRight(nowIndex, playerNum, j) + compareLeft(nowIndex, j); + } + mappingIndex.add(nowIndex); + } + return mappingIndex; + } + + private int compareRight(int nowIndex, int playerNum, int height) { + if (nowIndex == playerNum-1) { // 오른쪽 끝 + return 0; + } + if (ladder.get(height).get(nowIndex)) { // 오른쪽 발판 있으면 + return 1; + } + return 0; + } + + private int compareLeft(int nowIndex, int height){ + if (nowIndex == 0) { // 왼쪽 끝 + return 0; + } + if (ladder.get(height).get(nowIndex-1)) { // 왼쪽 발판 있으면 + return -1; + } + return 0; + } + } diff --git a/src/main/java/kr/ladder/domain/LadderGameController.java b/src/main/java/kr/ladder/domain/LadderGameController.java index 42a71ca1..82b822e9 100644 --- a/src/main/java/kr/ladder/domain/LadderGameController.java +++ b/src/main/java/kr/ladder/domain/LadderGameController.java @@ -7,27 +7,37 @@ import java.io.IOException; public class LadderGameController { - private final InputView inputView; - - private final Ladder ladder; - - private final OutputView outputView; - private final InputValidation validation; - - public LadderGameController() { - this.inputView = new InputView(); - this.ladder = new Ladder(); - this.outputView = new OutputView(); - this.validation = new InputValidation(); + private final Ladder ladder; + private final InputValidation validation; + private final ResultRepository resultRepository; + + public LadderGameController() { + this.ladder = new Ladder(); + this.validation = new InputValidation(); + this.resultRepository = new ResultRepository(); + } + + public void run() throws IOException { + // 입력 + String[] players = validation.inspectPlayers(); + String[] prizes = validation.inspectPrizes(players.length); + int ladderHeight = validation.inspectLadderHeight(); + // 사다리 만들기 + ladder.make(players.length, ladderHeight); + // 플레이어와 경품 맵핑하기 + resultRepository.mapping(ladder.play(), players, prizes, ladder.generate()); + // 결과를 보고 싶은 사람 입력 받기 + String command = validation.inspectCommand(players); + // 출력 + while (!command.equals("춘식이")) { + resultRepository.printPrize(command); + command = validation.inspectCommand(players); } + // 종료 + exit(); + } - public void run() throws IOException { - // 입력 - String[] players = validation.inspectPlayers(inputView.getPlayer()); - int ladderHeight = validation.inspectLadderHeight(inputView.getLadderHeight()); - // 사다리 만들기 - ladder.make(players.length, ladderHeight); - // 출력 - outputView.printPlayersAndLadder(players, ladder.generate()); - } + private void exit() { + resultRepository.exit(); + } } diff --git a/src/main/java/kr/ladder/domain/ResultRepository.java b/src/main/java/kr/ladder/domain/ResultRepository.java new file mode 100644 index 00000000..a8f3c99b --- /dev/null +++ b/src/main/java/kr/ladder/domain/ResultRepository.java @@ -0,0 +1,46 @@ +package kr.ladder.domain; + +import kr.ladder.view.OutputView; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ResultRepository { + private final Map result; + private final OutputView outputView; + + public ResultRepository(){ + this.result = new HashMap<>(); + this.outputView = new OutputView(); + } + + public void mapping(List mappingIndex, String[] players, String[] prizes, String generatedLadder){ + // player index, prize index + for (int i = 0; i < mappingIndex.size(); i++) { + result.put(players[i], prizes[mappingIndex.get(i)]); + } + outputView.printAll(players, generatedLadder, prizes); + } + + public void printPrize(String command){ + if (command.equals("all")) { + outputView.print(resultToString()); + } + if (!command.equals("all")) { + outputView.print(result.get(command)); + } + } + + private String resultToString() { + StringBuilder sb = new StringBuilder(); + for (String key : result.keySet()) { + sb.append(key).append(" : ").append(result.get(key)).append("\n"); + } + return sb.toString(); + } + + public void exit() { + outputView.printExitMassage(); + } +} diff --git a/src/main/java/kr/ladder/view/InputView.java b/src/main/java/kr/ladder/view/InputView.java index 1bf04892..58f73e6e 100644 --- a/src/main/java/kr/ladder/view/InputView.java +++ b/src/main/java/kr/ladder/view/InputView.java @@ -6,7 +6,9 @@ public class InputView { public static final String ASKING_LADDER_HEIGHT = "최대 사다리 높이는 몇 개인가요?"; - public static final String ASKING_PLAYER_NAMES = "참여할 사람 이름을 입력하세요. 각 이름은 최대 5글자까지 입니다. (이름은 쉼표(,)로 구분하세요)"; + public static final String ASKING_PLAYER_NAME = "참여할 사람 이름을 입력하세요. 각 이름은 최대 5글자까지 입니다. (이름은 쉼표(,)로 구분하세요)"; + public static final String ASKING_PRIZE_NAME = "경품 목록을 입력하세요. (경품은 쉼표(,)로 구분하세요)"; + private static final String ASKING_RESULT_PLAYER = "결과를 보고 싶은 사람은?"; BufferedReader br; public InputView() { @@ -14,12 +16,22 @@ public InputView() { } public String[] getPlayer() throws IOException { - System.out.println(ASKING_PLAYER_NAMES); + System.out.println(ASKING_PLAYER_NAME); return br.readLine().split(","); } - public int getLadderHeight() throws IOException { - System.out.println(ASKING_LADDER_HEIGHT); - return Integer.parseInt(br.readLine()); + public String getLadderHeight() throws IOException { + System.out.println("\n" + ASKING_LADDER_HEIGHT); + return br.readLine(); + } + + public String[] getPrizes() throws IOException { + System.out.println("\n" + ASKING_PRIZE_NAME); + return br.readLine().split(","); + } + + public String getCommand() throws IOException { + System.out.println("\n" + ASKING_RESULT_PLAYER); + return br.readLine(); } } diff --git a/src/main/java/kr/ladder/view/OutputView.java b/src/main/java/kr/ladder/view/OutputView.java index 502198a1..c15c7c9d 100644 --- a/src/main/java/kr/ladder/view/OutputView.java +++ b/src/main/java/kr/ladder/view/OutputView.java @@ -2,33 +2,56 @@ public class OutputView { private static final int DEFAULT_WIDTH = 6; + private static final String EXIT_MASSAGE = "게임을 종료합니다."; + private static final String RESULT_MESSAGE = "실행 결과"; StringBuilder sb; public OutputView(){ sb = new StringBuilder(); } - public void printPlayersAndLadder(String[] players, String ladder){ - printPlayersName(players); + public void printAll(String[] players, String ladder, String[] prizes) { + sb.append("\n").append("사다리 결과").append("\n").append("\n"); + printName(players); printLadder(ladder); + printName(prizes); + sb.deleteCharAt(sb.length() - 1); // 마지막 개행 제거 System.out.println(sb); sb.setLength(0); // stringBuilder 초기화 (리셋) } - // 이 부분은 OutputView 의 일이 아니지 않나 고민중... - private void printPlayersName(String[] players){ - for (String player : players) { - sb.append(player).append(insertBlank(player.length())); + private void printName(String[] words){ + for (String word : words) { + sb.append(insertBlankAroundName(word)); } sb.append("\n"); } - public String insertBlank(int playerNameLength){ - return " ".repeat(DEFAULT_WIDTH - playerNameLength); + // TODO: 테스트를 위해 private -> public로 변경했는데 더 나은 방법이 없을까? + public String insertBlankAroundName(String word){ + return insertFront(word.length()) + word + insertBack(word.length()); } + private String insertFront(int word){ // 글자수가 홀수면 (이름 뒤에 들어가는 공백 수 - 1) 개가 들어가야 한다. + return " ".repeat((int)Math.floor((double)(DEFAULT_WIDTH - word) / 2)); + } + + private String insertBack(int word){ // 글자수가 홀수면 (이름 앞에 들어가는 공백 수 + 1) 개가 들어가야 한다. + return " ".repeat((int)Math.ceil((double)(DEFAULT_WIDTH - word) / 2)); + } + + private void printLadder(String ladder){ sb.append(ladder); } + + public void printExitMassage() { + System.out.println(EXIT_MASSAGE); + } + + public void print(String str) { + System.out.println("\n" + RESULT_MESSAGE); + System.out.println(str); + } } diff --git a/src/main/java/kr/ladder/Test.java b/src/test/java/ladder/InputTest.java similarity index 92% rename from src/main/java/kr/ladder/Test.java rename to src/test/java/ladder/InputTest.java index b3bf4391..53a52b27 100644 --- a/src/main/java/kr/ladder/Test.java +++ b/src/test/java/ladder/InputTest.java @@ -1,10 +1,10 @@ -package kr.ladder; +package ladder; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; -public class Test { +public class InputTest { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String name = br.readLine(); diff --git a/src/test/java/ladder/OutputTest.java b/src/test/java/ladder/OutputTest.java new file mode 100644 index 00000000..e192a253 --- /dev/null +++ b/src/test/java/ladder/OutputTest.java @@ -0,0 +1,30 @@ +package ladder; + +import kr.ladder.view.OutputView; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class OutputTest { + + @Test + @DisplayName("플레이어 이름 글자수에 따라 공백이 적절하게 들어가는지 확인.") + void testPlayerName(){ + /* + 공백을 포함한 이름 글자 수는 6개이다. (사다리와 간격을 맞추기 위해) + 글자수가 홀수면 (ex) 이지안) + 1. 이름 앞에 들어가는 공백 수 = 이름 뒤에 들어가는 공백 수 - 1 (ex) 1) + 2. 이름 뒤에 들어가는 공백 수 = 이름 앞에 들어가는 공백 수 + 1 (ex) 2) + */ + //given + OutputView outputView = new OutputView(); + SoftAssertions softAssertions = new SoftAssertions(); + + //when + String playerName = "이지안"; + String playerNameWithBlanks = outputView.insertBlankAroundName(playerName); + + //then + softAssertions.assertThat(playerNameWithBlanks).isEqualTo(" 이지안 "); + } +} diff --git a/src/test/java/ladder/PlayerTest.java b/src/test/java/ladder/PlayerTest.java deleted file mode 100644 index 56a13f6d..00000000 --- a/src/test/java/ladder/PlayerTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package ladder; - -import kr.ladder.view.OutputView; -import org.assertj.core.api.SoftAssertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -public class PlayerTest { - - @Test - @DisplayName("플레이어 이름을 출력할 때 5글자를 정확히 출력하는지 확인") - void testPlayerName(){ - //given - OutputView outputView = new OutputView(); - SoftAssertions softAssertions = new SoftAssertions(); - - //when - String playerName = "하이이이이이"; - int blankRepeatNumber = outputView.insertBlank(playerName.length()).length(); - - System.out.println(blankRepeatNumber); - //then - // 🤔넣는 공백이 0은 아니어야 하는데(=이름이 6글자라는 소리) 왜 테스트에 안걸릴까? - softAssertions.assertThat(blankRepeatNumber).isPositive().isGreaterThan(1).isLessThan(5).isNotEqualTo(0); - } - -}