diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..58e81dfe4 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,35 @@ +# 페어 매칭 프로그램 + +## 개발 기능 목록 + +- [ ] 출력 기능 + - [ ] 기능 선택 안내 문구 + - [ ] 과정, 미션, 레벨 선택 안내 문구 + - [ ] 페어 매칭 결과 + - [ ] 페어 재매칭 시도 문구 + - [ ] 초기화 안내 문구 + +- [ ] 기능 선택 입력 기능 + - [ ] 한 개의 문자로 입력되는지 검증 + +- [ ] 기능 선택 검증 기능 + - [ ] 1, 2, 3, Q 문자 중 하나인지 검증 + +- [ ] 과정, 미션, 레벨 선택 입력 기능 + - [ ] ", "으로 구분되고, 과정은 3 ~ 5 글자, 레벨은 3 글자, 미션은 2 ~ 6글자로 입력 되는지 검증 + +- [ ] 과정, 미션, 레벨 선택 검증 기능 + - [ ] 정해진 과정, 미션, 레벨인지 검증 + +- [ ] 매칭 이력 확인 기능 + +- [ ] 페어 매칭 기능 + - [ ] 3회 초과 시도 시 에러 + - [ ] 동 레벨에서 중복 페어 금지 + - [ ] 홀수인 경어 마지막 크루는 마지막 페어에 포함 + +- [ ] 페어 조회 기능 + - [ ] 매칭 이력 없을 시 에러 + +- [ ] 페어 매칭 초기화 기능 + diff --git a/src/main/java/pairmatching/controller/PairMatchingController.java b/src/main/java/pairmatching/controller/PairMatchingController.java new file mode 100644 index 000000000..df39b622d --- /dev/null +++ b/src/main/java/pairmatching/controller/PairMatchingController.java @@ -0,0 +1,78 @@ +package pairmatching.controller; + +import pairmatching.domain.MatchingHistory; +import pairmatching.domain.MatchingMachine; +import pairmatching.domain.choice.Choice; +import pairmatching.domain.choice.ChoiceMaker; +import pairmatching.domain.choice.item.Course; +import pairmatching.domain.choice.item.Level; +import pairmatching.domain.choice.item.Mission; +import pairmatching.domain.command.UserCommand; +import pairmatching.domain.program.PairMatchingProgram; +import pairmatching.domain.command.Command; +import pairmatching.view.InputView; +import pairmatching.view.OutputView; + +import java.util.List; +import java.util.function.Supplier; + +public class PairMatchingController { + + private final InputView inputView; + private final OutputView outputView; + + public PairMatchingController(InputView inputView, OutputView outputView) { + this.inputView = inputView; + this.outputView = outputView; + } + + public void runPairMatchingProgram() { + executeUserCommand(); + + + } + + private void executeUserCommand() { + PairMatchingProgram program = new PairMatchingProgram(new MatchingHistory(), new MatchingMachine()); + UserCommand command = repeat(this::readUserCommand); + if (command.isCommandOf(Command.MATCHING)) { + Choice choice = repeat(this::readChoice); + if (program.hasMatched(choice)) { + outputView.printReMatchingGuide(); + inputView.readReMatchingCommand(); + } + } + if (command.isCommandOf(Command.CHECKING)) { + + } + if (command.isCommandOf(Command.RESETTING)) { + + } + if (command.isCommandOf(Command.QUITTING)) { + + } + } + + private Choice readChoice() { + ChoiceMaker choiceMaker = new ChoiceMaker(); + outputView.printChoiceGuideMessage(Course.namesOfValues(), Level.namesOfValues(), Mission.values()); + List items = inputView.readChoice(); + return choiceMaker.createChoice(items); + } + + private UserCommand readUserCommand() { + outputView.printCommandGuideMessage(Command.values()); + String command = inputView.readCommand(); + return new UserCommand(command); + } + + private T repeat(Supplier inputReader) { + while(true) { + try { + return inputReader.get(); + } catch (IllegalArgumentException e) { + outputView.print(e.getMessage()); + } + } + } +} diff --git a/src/main/java/pairmatching/domain/MatchingHistory.java b/src/main/java/pairmatching/domain/MatchingHistory.java new file mode 100644 index 000000000..582ee9479 --- /dev/null +++ b/src/main/java/pairmatching/domain/MatchingHistory.java @@ -0,0 +1,20 @@ +package pairmatching.domain; + +import pairmatching.domain.choice.Choice; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class MatchingHistory { + + private final Map> history; + + public MatchingHistory() { + this.history = new HashMap>(); + } + + public boolean hasMatchingOf(Choice choice) { + return history.containsKey(choice); + } +} diff --git a/src/main/java/pairmatching/domain/MatchingMachine.java b/src/main/java/pairmatching/domain/MatchingMachine.java new file mode 100644 index 000000000..300f13bf8 --- /dev/null +++ b/src/main/java/pairmatching/domain/MatchingMachine.java @@ -0,0 +1,5 @@ +package pairmatching.domain; + +public class MatchingMachine { + +} diff --git a/src/main/java/pairmatching/domain/choice/Choice.java b/src/main/java/pairmatching/domain/choice/Choice.java new file mode 100644 index 000000000..467007db8 --- /dev/null +++ b/src/main/java/pairmatching/domain/choice/Choice.java @@ -0,0 +1,15 @@ +package pairmatching.domain.choice; + +import pairmatching.domain.choice.item.Course; +import pairmatching.domain.choice.item.Mission; + +public class Choice { + + private final Course course; + private final Mission mission; + + public Choice(String courseName, String missionName) { + this.course = Course.valueOfCourse(courseName); + this.mission = Mission.valueOfMission(missionName); + } +} diff --git a/src/main/java/pairmatching/domain/choice/ChoiceMaker.java b/src/main/java/pairmatching/domain/choice/ChoiceMaker.java new file mode 100644 index 000000000..869abef7f --- /dev/null +++ b/src/main/java/pairmatching/domain/choice/ChoiceMaker.java @@ -0,0 +1,40 @@ +package pairmatching.domain.choice; + +import pairmatching.domain.choice.item.Course; +import pairmatching.domain.choice.item.Level; +import pairmatching.domain.choice.item.Mission; + +import java.util.List; + +public class ChoiceMaker { + + public Choice createChoice(List items) { + String courseName = items.get(0); + String levelName = items.get(1); + String missionName = items.get(2); + validate(courseName, levelName, missionName); + + return new Choice(courseName, missionName); + } + + private void validate(String courseName, String levelName, String missionName) { + if (!Course.contains(courseName)) { + throw new IllegalArgumentException("[ERROR] 해당하는 과정이 존재하지 않습니다."); + } + if (!Level.contains(levelName)) { + throw new IllegalArgumentException("[ERROR] 해당하는 레벨이 존재하지 않습니다."); + } + if (!Mission.contains(missionName)) { + throw new IllegalArgumentException("[ERROR] 해당하는 미션이 존재하지 않습니다."); + } + if (isValidLevel(levelName, missionName)) { + throw new IllegalArgumentException("[ERROR] 레벨과 미션이 일치하지 않습니다."); + } + } + + private boolean isValidLevel(String level, String missionName) { + Mission mission = Mission.valueOf(missionName); + String levelOfMission = mission.getLevelMessage(); + return level.equals(levelOfMission); + } +} diff --git a/src/main/java/pairmatching/domain/choice/item/Course.java b/src/main/java/pairmatching/domain/choice/item/Course.java new file mode 100644 index 000000000..93a33bd9a --- /dev/null +++ b/src/main/java/pairmatching/domain/choice/item/Course.java @@ -0,0 +1,38 @@ +package pairmatching.domain.choice.item; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public enum Course { + BACKEND("백엔드"), + FRONTEND("프론트엔드"); + + private final String courseName; + + Course(String courseName) { + this.courseName = courseName; + } + + public String getCourseName() { + return courseName; + } + + public static boolean contains(String name) { + return Arrays.stream(values()) + .anyMatch(value -> name.equals(value.getCourseName())); + } + + public static List namesOfValues() { + return Arrays.stream(values()) + .map(Course::getCourseName) + .collect(Collectors.toList()); + } + + public static Course valueOfCourse(String courseName) { + return Arrays.stream(values()) + .filter(value -> courseName.equals(value.getCourseName())) + .findAny() + .orElseThrow(() -> new IllegalStateException("[ERROR] 존재하지 않는 Course 입니다.")); + } +} diff --git a/src/main/java/pairmatching/domain/choice/item/Level.java b/src/main/java/pairmatching/domain/choice/item/Level.java new file mode 100644 index 000000000..f2ea52687 --- /dev/null +++ b/src/main/java/pairmatching/domain/choice/item/Level.java @@ -0,0 +1,34 @@ +package pairmatching.domain.choice.item; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public enum Level { + LEVEL1("레벨1"), + LEVEL2("레벨2"), + LEVEL3("레벨3"), + LEVEL4("레벨4"), + LEVEL5("레벨5"); + + private final String name; + + Level(String name) { + this.name = name; + } + + public static boolean contains(String level) { + return Arrays.stream(values()) + .anyMatch(value -> level.equals(value.getName())); + } + + public String getName() { + return name; + } + + public static List namesOfValues() { + return Arrays.stream(values()) + .map(Level::getName) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/pairmatching/domain/choice/item/Mission.java b/src/main/java/pairmatching/domain/choice/item/Mission.java new file mode 100644 index 000000000..ccb4d97f5 --- /dev/null +++ b/src/main/java/pairmatching/domain/choice/item/Mission.java @@ -0,0 +1,46 @@ +package pairmatching.domain.choice.item; + +import java.util.Arrays; + +public enum Mission { + CAR_RACING("자동차 경주", Level.LEVEL1), + LOTTO("로또", Level.LEVEL1), + NUMBER_BASEBALL("숫자야구게임", Level.LEVEL1), + SHOPPING_BASKET("장바구니", Level.LEVEL2), + PAYMENT("결제", Level.LEVEL2), + SUBWAY_MAP("지하철노선도", Level.LEVEL2), + IMPROVEMENT_OF_PERFORMANCE("성능개선", Level.LEVEL4), + PUBLISHING("배포", Level.LEVEL4); + + private final String name; + private final Level level; + + Mission(String name, Level level) { + this.name = name; + this.level = level; + } + + public static Mission valueOfMission(String missionName) { + return Arrays.stream(values()) + .filter(value -> missionName.equals(value.getName())) + .findAny() + .orElseThrow(() -> new IllegalStateException("[ERROR] 존재하지 않는 Mission 입니다.")); + } + + public String getName() { + return name; + } + + public Level getLevel() { + return level; + } + + public String getLevelMessage() { + return level.getName(); + } + + public static boolean contains(String name) { + return Arrays.stream(values()) + .anyMatch(value -> name.equals(value.getName())); + } +} diff --git a/src/main/java/pairmatching/domain/command/Command.java b/src/main/java/pairmatching/domain/command/Command.java new file mode 100644 index 000000000..8084a303b --- /dev/null +++ b/src/main/java/pairmatching/domain/command/Command.java @@ -0,0 +1,32 @@ +package pairmatching.domain.command; + +import java.util.Arrays; + +public enum Command { + MATCHING("1"), + CHECKING("2"), + RESETTING("3"), + QUITTING("Q"); + + private final String key; + + Command(String key) { + this.key = key; + } + + public String getKey() { + return key; + } + + public static Command valueOfCommand(String key) { + return Arrays.stream(values()) + .filter(value -> key.equals(value.getKey())) + .findAny() + .orElseThrow(() -> new IllegalStateException("[ERROR] 지원하는 명령어가 아닙니다.")); + } + + public static boolean contains(String command) { + return Arrays.stream(values()) + .anyMatch(value -> command.equals(value.getKey())); + } +} diff --git a/src/main/java/pairmatching/domain/command/UserCommand.java b/src/main/java/pairmatching/domain/command/UserCommand.java new file mode 100644 index 000000000..e29a3b9dd --- /dev/null +++ b/src/main/java/pairmatching/domain/command/UserCommand.java @@ -0,0 +1,21 @@ +package pairmatching.domain.command; + +public class UserCommand { + + private final Command command; + + public UserCommand(String command) { + validate(command); + this.command = Command.valueOfCommand(command); + } + + private void validate(String command) { + if (!Command.contains(command)) { + throw new IllegalArgumentException("[ERROR] 지원하는 기능 명령어가 아닙니다."); + } + } + + public boolean isCommandOf(Command command) { + return this.command.equals(command); + } +} diff --git a/src/main/java/pairmatching/domain/program/PairMatchingProgram.java b/src/main/java/pairmatching/domain/program/PairMatchingProgram.java new file mode 100644 index 000000000..c6ee549e1 --- /dev/null +++ b/src/main/java/pairmatching/domain/program/PairMatchingProgram.java @@ -0,0 +1,20 @@ +package pairmatching.domain.program; + +import pairmatching.domain.MatchingHistory; +import pairmatching.domain.MatchingMachine; +import pairmatching.domain.choice.Choice; + +public class PairMatchingProgram { + + private final MatchingHistory history; + private final MatchingMachine machine; + + public PairMatchingProgram(MatchingHistory history, MatchingMachine machine) { + this.history = history; + this.machine = machine; + } + + public boolean hasMatched(Choice choice) { + return history.hasMatchingOf(choice); + } +} diff --git a/src/main/java/pairmatching/view/InputView.java b/src/main/java/pairmatching/view/InputView.java new file mode 100644 index 000000000..668e8d02d --- /dev/null +++ b/src/main/java/pairmatching/view/InputView.java @@ -0,0 +1,41 @@ +package pairmatching.view; + +import camp.nextstep.edu.missionutils.Console; +import pairmatching.view.validatetool.ValidateTool; + +import java.util.Arrays; +import java.util.List; + +public class InputView { + + private static final String DELIMITER = ", "; + + public String readCommand() { + return readUsing(ValidateTool.COMMAND); + } + + public List readChoice() { + String input = readUsing(ValidateTool.CHOICE); + return Arrays.asList(input.split(DELIMITER)); + } + + public String readReMatchingCommand() { + return readUsing(ValidateTool.RE_MATCHING); + } + + private String readUsing(ValidateTool validateTool) { + String input = Console.readLine(); + validateBy(input, validateTool); + return input; + } + + private void validateBy(String input, ValidateTool validateTool) { + if (isInvalidFormat(input, validateTool)) { + throw new IllegalArgumentException(validateTool.getErrorMessage()); + } + } + + private boolean isInvalidFormat(String input, ValidateTool validateTool) { + return !input.matches(validateTool.getValidFormat()); + } +} diff --git a/src/main/java/pairmatching/view/OutputView.java b/src/main/java/pairmatching/view/OutputView.java new file mode 100644 index 000000000..690c79999 --- /dev/null +++ b/src/main/java/pairmatching/view/OutputView.java @@ -0,0 +1,72 @@ +package pairmatching.view; + +import pairmatching.domain.choice.item.Mission; +import pairmatching.domain.command.Command; +import pairmatching.view.message.CommandMessage; +import pairmatching.view.message.Message; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class OutputView { + + public void printCommandGuideMessage(Command[] commands) { + print(Message.COMMAND_GUIDE.getMessage()); + List commandMessages = makeCommandMessages(commands, CommandMessage.values()); + commandMessages.forEach(this::print); + } + + private List makeCommandMessages(Command[] commands, CommandMessage[] commandMessages) { + List messages = new ArrayList<>(); + for (int i = 0; i < commands.length; i++) { + CommandMessage message = commandMessages[i]; + Command command = commands[i]; + messages.add(message.getFormattedMessage(command.getKey())); + } + return messages; + } + + public void printChoiceGuideMessage(List courses, List levels, Mission[] missions) { + String message = makeChoiceMessage(courses, levels, missions); + print(message); + } + + private String makeChoiceMessage(List courses, List levels, Mission[] missions) { + List messages = new ArrayList<>(); + messages.add(Message.DIVIDING_LINE.getMessage()); + messages.add(makeChoiceContentMessage(courses, levels, missions)); + messages.add(Message.DIVIDING_LINE.getMessage()); + messages.add(Message.CHOICE_GUIDE.getMessage()); + messages.add(Message.CHOICE_EXAMPLE.getMessage()); + return String.join(Message.NEW_LINE.getMessage(), messages); + } + + private String makeChoiceContentMessage(List courses, List levels, Mission[] missions) { + List messages = new ArrayList<>(); + messages.add(Message.COURSE.getFormattedMessage(String.join(Message.DELIMITER.getMessage(), courses))); + messages.add(Message.MISSION.getMessage()); + for (String level : levels) { + String missionsOfLevel = makeMissionMessageByLevel(missions, level); + messages.add(Message.LEVEL.getFormattedMessage(level, missionsOfLevel)); + } + return String.join(Message.NEW_LINE.getMessage(), messages); + } + + private String makeMissionMessageByLevel(Mission[] missions, String level) { + return Arrays.stream(missions) + .filter(mission -> level.equals(mission.getLevelMessage())) + .map(Mission::getName) + .collect(Collectors.joining(Message.DELIMITER.getMessage())); + } + + public void printReMatchingGuide() { + print(Message.RE_MATCHING_GUIDE.getMessage()); + print(Message.RE_MATCHING_EXAMPLE.getMessage()); + } + + public void print(String message) { + System.out.print(message); + } +} diff --git a/src/main/java/pairmatching/view/message/CommandMessage.java b/src/main/java/pairmatching/view/message/CommandMessage.java new file mode 100644 index 000000000..bef201980 --- /dev/null +++ b/src/main/java/pairmatching/view/message/CommandMessage.java @@ -0,0 +1,22 @@ +package pairmatching.view.message; + +public enum CommandMessage { + MATCHING("%s. 페어 매칭\n"), + CHECKING("%s. 페어 조회\n"), + RESETTING("%s. 페어 초기화\n"), + QUITTING("%s. 종료\n"); + + private final String message; + + CommandMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public String getFormattedMessage(Object factor) { + return String.format(message, factor); + } +} diff --git a/src/main/java/pairmatching/view/message/Message.java b/src/main/java/pairmatching/view/message/Message.java new file mode 100644 index 000000000..ff8db542a --- /dev/null +++ b/src/main/java/pairmatching/view/message/Message.java @@ -0,0 +1,29 @@ +package pairmatching.view.message; + +public enum Message { + COMMAND_GUIDE("기능을 선택하세요.\n"), + DIVIDING_LINE("#############################################"), + COURSE("과정: %s"), + MISSION("미션:"), + LEVEL("- %s: %s"), + DELIMITER(" | "), + CHOICE_GUIDE("과정, 레벨, 미션을 선택하세요."), + CHOICE_EXAMPLE("ex) 백엔드, 레벨1, 자동차경주"), + NEW_LINE("\n"), + RE_MATCHING_GUIDE("매칭 정보가 있습니다. 다시 매칭하시겠습니까?"), + RE_MATCHING_EXAMPLE("네 | 아니오"); + + private final String message; + + Message(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public String getFormattedMessage(Object... factors) { + return String.format(message, factors); + } +} diff --git a/src/main/java/pairmatching/view/validatetool/ValidateTool.java b/src/main/java/pairmatching/view/validatetool/ValidateTool.java new file mode 100644 index 000000000..de7e5bf0c --- /dev/null +++ b/src/main/java/pairmatching/view/validatetool/ValidateTool.java @@ -0,0 +1,24 @@ +package pairmatching.view.validatetool; + +public enum ValidateTool { + + COMMAND("\\w", "[ERROR] 기능 명령어는 한 글자여야 합니다."), + CHOICE(".{1,10},.{3,5},.{1,10}", "[ERROR] 선택 입력 형식이 잘못되었습니다."), + RE_MATCHING("\\[가-힣]{1,3}", "[ERROR] 입력 형식이 잘못되었습니다."); + + private final String validFormat; + private final String errorMessage; + + ValidateTool(String validFormat, String errorMessage) { + this.validFormat = validFormat; + this.errorMessage = errorMessage; + } + + public String getValidFormat() { + return validFormat; + } + + public String getErrorMessage() { + return errorMessage; + } +}