From a99b1c4b8ce31ebdf2c18b4050d6c1c2a9eeb3a5 Mon Sep 17 00:00:00 2001 From: DoomchitYJ Date: Wed, 11 Dec 2024 23:56:26 +0900 Subject: [PATCH 1/2] =?UTF-8?q?24.12.11=20=EC=B5=9C=EC=A2=85=20=EC=BD=94?= =?UTF-8?q?=ED=85=8C=20=EB=8C=80=EB=B9=84=20=ED=92=80=EC=9D=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/README.md | 112 ++++++++++++++++++ src/main/java/pairmatching/Application.java | 5 +- src/main/java/pairmatching/config/Config.java | 7 ++ .../java/pairmatching/constant/Constant.java | 6 + .../pairmatching/controller/Controller.java | 72 +++++++++++ src/main/java/pairmatching/domain/Course.java | 38 ++++++ .../pairmatching/domain/CrewNameList.java | 26 ++++ src/main/java/pairmatching/domain/Level.java | 33 ++++++ .../java/pairmatching/domain/Mission.java | 44 +++++++ src/main/java/pairmatching/domain/Pair.java | 46 +++++++ .../java/pairmatching/domain/PairMatcher.java | 11 ++ src/main/java/pairmatching/domain/Pairs.java | 28 +++++ .../exception/ExceptionMessage.java | 26 ++++ .../exception/PairmatchingException.java | 7 ++ .../repository/CrewNameRepository.java | 23 ++++ .../service/PairCheckService.java | 55 +++++++++ .../service/PairMatchingService.java | 89 ++++++++++++++ .../service/PairResetService.java | 19 +++ .../java/pairmatching/view/ErrorPrinter.java | 7 ++ .../java/pairmatching/view/InputView.java | 28 +++++ .../java/pairmatching/view/OutputView.java | 82 +++++++++++++ 21 files changed, 763 insertions(+), 1 deletion(-) create mode 100644 src/main/README.md create mode 100644 src/main/java/pairmatching/config/Config.java create mode 100644 src/main/java/pairmatching/constant/Constant.java create mode 100644 src/main/java/pairmatching/controller/Controller.java create mode 100644 src/main/java/pairmatching/domain/Course.java create mode 100644 src/main/java/pairmatching/domain/CrewNameList.java create mode 100644 src/main/java/pairmatching/domain/Level.java create mode 100644 src/main/java/pairmatching/domain/Mission.java create mode 100644 src/main/java/pairmatching/domain/Pair.java create mode 100644 src/main/java/pairmatching/domain/PairMatcher.java create mode 100644 src/main/java/pairmatching/domain/Pairs.java create mode 100644 src/main/java/pairmatching/exception/ExceptionMessage.java create mode 100644 src/main/java/pairmatching/exception/PairmatchingException.java create mode 100644 src/main/java/pairmatching/repository/CrewNameRepository.java create mode 100644 src/main/java/pairmatching/service/PairCheckService.java create mode 100644 src/main/java/pairmatching/service/PairMatchingService.java create mode 100644 src/main/java/pairmatching/service/PairResetService.java create mode 100644 src/main/java/pairmatching/view/ErrorPrinter.java create mode 100644 src/main/java/pairmatching/view/InputView.java create mode 100644 src/main/java/pairmatching/view/OutputView.java diff --git a/src/main/README.md b/src/main/README.md new file mode 100644 index 000000000..ec0f231f0 --- /dev/null +++ b/src/main/README.md @@ -0,0 +1,112 @@ +# 예상 시나리오 +1. 기능 선택 보기를 출력한다. +2. 기능 선택 입력을 받는다. + 1) 1을 입력받은 경우 (페어 매칭) + 1) 과정, 미션을 출력한다. + 2) 과정, 미션, 레벨을 입력 받는다. + * 이미 매칭 결과가 있을 경우, 재매칭 여부를 묻는다. + * 네 -> 2-i-c로 가서 재매칭 결과를 출력한다. + * 아니오 -> 2-i-b로 돌아간다. + * 올바르지 않은 값을 입력한 경우, 에러 메시지를 출력하고 다시 입력 받는다. + 3) 매칭 결과를 출력한다. + 4) 1로 돌아간다. + 2) 2를 입력한다. (페어 조회) + 1) 과정, 미션을 출력한다. + 2) 과정, 미션, 레벨을 입력 받는다. + * 매칭 결과가 있을 경우, 기존에 존재하는 해당 매칭 결과를 출력한다. + * 없을 경우, 없음을 고지하고 1로 돌아간다. + * 올바르지 않은 값을 입력한 경우, 에러 메시지를 출력하고 다시 입력 받는다. + 3) 3을 입력한다. (페어 초기화) + 1) 초기화되었음을 출력한다. + 4) Q를 입력한다. (종료) + 1) 프로그램을 종료한다. + 5) 이 외의 값을 입력한다. + 1) 에러 메시지를 출력하고 다시 입력 받는다. + +### 예외 처리 +1. 기능 선택 시, 주어진 1, 2, 3, Q 외 값을 입력한다. +2. 과정, 미션, 레벨 입력 시, 주어진 보기 외 값을 입력한다. + +# 구현 기능 목록 + +### 기능 선택 보기를 입력 받는 기능 + +- [x] "기능을 선택하세요.\n1. 페어 매칭\n2. 페어 조회\n3. 페어 초기화\nQ. 종료"를 출력한다. +- [x] 값을 입력 받는다. +- [x] 입력값을 검증한다. + - [x] 1, 2, 3, Q 중에 하나인가? + +### 크루원 목록 생성 기능 + +- [x] backend-crews.md 파일을 읽는다. +- [x] 내용을 List 변수에 담는다. +- [x] frontend-crews.md 파일을 읽는다. +- [x] 내용을 List 변수에 담는다. + +### 페어 매칭 기능 + +- [x] "#############################################\n + 과정: [과정 목록]\n + 미션:\n + \- 레벨1: [미션 목록]\n + \- 레벨2: [미션 목록]\n + \- 레벨3: [미션 목록]\n + \- 레벨4: [미션 목록]\n + \- 레벨5: [미션 목록]\n + ############################################\n + 과정, 레벨, 미션을 선택하세요.\n + ex) 백엔드, 레벨1, 자동차경주"를 출력한다. +- [x] 과정, 레벨, 미션을 입력 받는다. +- [ ] 입력값을 검증한다. + - [ ] 과정, 레벨, 미션으로 파싱한다. + - [ ] 과정이 존재하는가? + - [ ] 레벨이 존재하는가? + - [ ] 미션이 존재하는가? +- [ ] 기존 매칭 결과가 있을 경우, + - [ ] "매칭 정보가 있습니다. 다시 매칭하시겠습니까?\n 네 | 아니오"를 출력한다. + - [ ] 값을 입력 받는다. + - [ ] "네" -> 아래의 "새로운 매칭 결과를 출력하는 경우"로 넘어간다. + - [ ] "아니오" -> "과정, 레벨, 미션 입력"으로 돌아간다. +- [ ] 새로운 매칭 결과를 출력하는 경우, + - [ ] 각 과정에 맞는 크루원 이름을 셔플한다. + - [ ] 결과를 저장한다. +- [ ] 매칭 결과를 출력한다. + - [ ] "페어 매칭 결과입니다."를 출력한다. + - [ ] 한 줄에 두 명씩 묶어 "[이름] : [이름]" 형태로 출력한다. + - [ ] 크루원이 홀수일 경우, 마지막 3명을 한 페어로 묶어 출력한다. +- [ ] 기능 선택으로 돌아간다. + +### 페어 조회 기능 + +- [ ] "#############################################\n + 과정: [과정 목록]\n + 미션:\n + \- 레벨1: [미션 목록]\n + \- 레벨2: [미션 목록]\n + \- 레벨3: [미션 목록]\n + \- 레벨4: [미션 목록]\n + \- 레벨5: [미션 목록]\n + ############################################\n + 과정, 레벨, 미션을 선택하세요.\n + ex) 백엔드, 레벨1, 자동차경주"를 출력한다. +- [ ] 과정, 레벨, 미션을 입력 받는다. +- [ ] 입력값을 검증한다. + - [ ] 과정, 레벨, 미션으로 파싱한다. + - [ ] 과정이 존재하는가? + - [ ] 레벨이 존재하는가? + - [ ] 미션이 존재하는가? +- [ ] 기존 매칭 결과가 존재하는 경우, + - [ ] 매칭 결과를 출력한다. +- [ ] 매칭 결과가 존재하지 않을 경우, + - [ ] "매칭 결과가 존재하지 않습니다."를 출력한다. +- [ ] 기능 선택으로 돌아간다. + +### 페어 초기화 기능 + +- [ ] 매칭 결과를 모두 삭제한다. +- [ ] "초기화 되었습니다."를 출력한다. +- [ ] 기능 선택으로 돌아간다. + +### 종료 기능 + +- [ ] Q를 입력받을 경우, 프로그램을 종료한다. \ No newline at end of file diff --git a/src/main/java/pairmatching/Application.java b/src/main/java/pairmatching/Application.java index 6f56e741c..c1700364b 100644 --- a/src/main/java/pairmatching/Application.java +++ b/src/main/java/pairmatching/Application.java @@ -1,7 +1,10 @@ package pairmatching; +import pairmatching.controller.Controller; + public class Application { public static void main(String[] args) { - // TODO 구현 진행 + Controller controller = new Controller(); + controller.run(); } } diff --git a/src/main/java/pairmatching/config/Config.java b/src/main/java/pairmatching/config/Config.java new file mode 100644 index 000000000..47c54e10e --- /dev/null +++ b/src/main/java/pairmatching/config/Config.java @@ -0,0 +1,7 @@ +package pairmatching.config; + +public class Config { + + public static final String BACKEND_CREW_FILE = "src/main/resources/backend-crew.md"; + public static final String FRONTEND_CREW_FILE = "src/main/resources/backend-crew.md"; +} diff --git a/src/main/java/pairmatching/constant/Constant.java b/src/main/java/pairmatching/constant/Constant.java new file mode 100644 index 000000000..805c8062f --- /dev/null +++ b/src/main/java/pairmatching/constant/Constant.java @@ -0,0 +1,6 @@ +package pairmatching.constant; + +public class Constant { + + public static final int MAX_TRY = 5; +} diff --git a/src/main/java/pairmatching/controller/Controller.java b/src/main/java/pairmatching/controller/Controller.java new file mode 100644 index 000000000..2876aa679 --- /dev/null +++ b/src/main/java/pairmatching/controller/Controller.java @@ -0,0 +1,72 @@ +package pairmatching.controller; + +import static pairmatching.constant.Constant.MAX_TRY; +import static pairmatching.exception.ExceptionMessage.MAX_TRY_ERROR; +import static pairmatching.exception.ExceptionMessage.NO_FUNCTION_ERROR; +import static pairmatching.view.ErrorPrinter.printError; + +import pairmatching.domain.Pairs; +import pairmatching.exception.PairmatchingException; +import pairmatching.service.PairCheckService; +import pairmatching.service.PairMatchingService; +import pairmatching.service.PairResetService; +import pairmatching.view.InputView; + +public class Controller { + + private static Pairs pairs = new Pairs(); + + private static final String PAIR_MATCHING_FUNC = "1"; + private static final String PAIR_CHECK_FUNC = "2"; + private static final String PAIR_RESET_FUNC = "3"; + private static final String PAIR_QUIT_FUNC = "Q"; + + public void run() { + boolean running = true; + while (running) { + String functionInput = readFunction(); + if (functionInput.equals(PAIR_MATCHING_FUNC)) { + PairMatchingService pairMatchingService = new PairMatchingService(pairs); + pairMatchingService.operate(); + continue; + } + if (functionInput.equals(PAIR_CHECK_FUNC)) { + PairCheckService pairCheckService = new PairCheckService(pairs); + pairCheckService.operate(); + continue; + } + if (functionInput.equals(PAIR_RESET_FUNC)) { + PairResetService pairResetService = new PairResetService(pairs); + pairs = pairResetService.operate(); + continue; + } + if (functionInput.equals(PAIR_QUIT_FUNC)) { + running = false; + } + } + } + + private String readFunction() { + for (int i = 1; i <= MAX_TRY; i++) { + try { + String input = InputView.readFunction(); + if (isValidInput(input)) { + return input; + } + } catch (IllegalArgumentException e) { + printError(e.getMessage()); + } + } + throw new PairmatchingException(MAX_TRY_ERROR); + } + + private boolean isValidInput(String input) { + if (input.equals(PAIR_MATCHING_FUNC) + || input.equals(PAIR_CHECK_FUNC) + || input.equals(PAIR_RESET_FUNC) + || input.equals(PAIR_QUIT_FUNC)) { + return true; + } + throw new PairmatchingException(NO_FUNCTION_ERROR); + } +} diff --git a/src/main/java/pairmatching/domain/Course.java b/src/main/java/pairmatching/domain/Course.java new file mode 100644 index 000000000..a5e4fccff --- /dev/null +++ b/src/main/java/pairmatching/domain/Course.java @@ -0,0 +1,38 @@ +package pairmatching.domain; + +import static pairmatching.config.Config.BACKEND_CREW_FILE; +import static pairmatching.config.Config.FRONTEND_CREW_FILE; +import static pairmatching.exception.ExceptionMessage.NO_COURSE; + +import pairmatching.exception.PairmatchingException; + +public enum Course { + + BACKEND("백엔드", BACKEND_CREW_FILE), + FRONTEND("프론트엔드", FRONTEND_CREW_FILE); + + private final String name; + private final String filePath; + + Course(final String name, final String filePath) { + this.name = name; + this.filePath = filePath; + } + + public String getName() { + return name; + } + + public String getFilePath() { + return filePath; + } + + public static Course fromName(String name) { + for (Course course : values()) { + if (course.getName().equals(name)) { + return course; + } + } + throw new PairmatchingException(NO_COURSE); + } +} diff --git a/src/main/java/pairmatching/domain/CrewNameList.java b/src/main/java/pairmatching/domain/CrewNameList.java new file mode 100644 index 000000000..12a556d67 --- /dev/null +++ b/src/main/java/pairmatching/domain/CrewNameList.java @@ -0,0 +1,26 @@ +package pairmatching.domain; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import pairmatching.repository.CrewNameRepository; + +public class CrewNameList { + + private static Map> crewNames; + + public static List getNames(Course course) { + if (crewNames == null) { + initializeCrewNames(); + } + return crewNames.get(course); + } + + private static void initializeCrewNames() { + crewNames = new HashMap<>(); + CrewNameRepository crewNameRepository = new CrewNameRepository(); + for (Course course : Course.values()) { + crewNames.put(course, crewNameRepository.loadCrew(course)); + } + } +} diff --git a/src/main/java/pairmatching/domain/Level.java b/src/main/java/pairmatching/domain/Level.java new file mode 100644 index 000000000..7aab53c93 --- /dev/null +++ b/src/main/java/pairmatching/domain/Level.java @@ -0,0 +1,33 @@ +package pairmatching.domain; + +import static pairmatching.exception.ExceptionMessage.NO_LEVEL; + +import pairmatching.exception.PairmatchingException; + +public enum Level { + + LEVEL1("레벨1"), + LEVEL2("레벨2"), + LEVEL3("레벨3"), + LEVEL4("레벨4"), + LEVEL5("레벨5"); + + private final String name; + + Level(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static Level fromName(String name) { + for (Level level : values()) { + if (level.getName().equals(name)) { + return level; + } + } + throw new PairmatchingException(NO_LEVEL); + } +} diff --git a/src/main/java/pairmatching/domain/Mission.java b/src/main/java/pairmatching/domain/Mission.java new file mode 100644 index 000000000..34339a149 --- /dev/null +++ b/src/main/java/pairmatching/domain/Mission.java @@ -0,0 +1,44 @@ +package pairmatching.domain; + +import static pairmatching.domain.Level.LEVEL1; +import static pairmatching.domain.Level.LEVEL2; +import static pairmatching.domain.Level.LEVEL4; +import static pairmatching.exception.ExceptionMessage.NO_MISSION; + +import pairmatching.exception.PairmatchingException; + +public enum Mission { + + RACINGCAR("자동차경주", LEVEL1), + LOTTO("로또", LEVEL1), + BASEBALL("숫자야구게임", LEVEL1), + CART("장바구니", LEVEL2), + PAYMENT("결제", LEVEL2), + SUBWAY("지하철노선도", LEVEL2), + IMPROVEMENT("성능개선", LEVEL4), + DEPLOY("배포", LEVEL4); + + private final String name; + private final Level level; + + Mission(String name, Level level) { + this.name = name; + this.level = level; + } + + public String getName() { + return name; + } + public Level getLevel() { + return level; + } + + public static Mission fromName(String name) { + for (Mission mission : values()) { + if (mission.getName().equals(name)) { + return mission; + } + } + throw new PairmatchingException(NO_MISSION); + } +} diff --git a/src/main/java/pairmatching/domain/Pair.java b/src/main/java/pairmatching/domain/Pair.java new file mode 100644 index 000000000..7d2025204 --- /dev/null +++ b/src/main/java/pairmatching/domain/Pair.java @@ -0,0 +1,46 @@ +package pairmatching.domain; + +import java.util.List; +import java.util.Objects; + +public class Pair { + + private final Course course; + private final Level level; + private final Mission mission; + private static List crews; + + public Pair(Course course, Level level, Mission mission) { + this.course = course; + this.level = level; + this.mission = mission; + + crews = PairMatcher.matchPair(CrewNameList.getNames(course)); + } + + public Course getCourse() { + return course; + } + public Level getLevel() { + return level; + } + public Mission getMission() { + return mission; + } + public List getCrews() { + return crews; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Pair)) return false; + Pair pair = (Pair) o; + return course == pair.course && level == pair.level && mission == pair.mission; + } + + @Override + public int hashCode() { + return Objects.hash(course, level, mission); + } +} diff --git a/src/main/java/pairmatching/domain/PairMatcher.java b/src/main/java/pairmatching/domain/PairMatcher.java new file mode 100644 index 000000000..e73b18424 --- /dev/null +++ b/src/main/java/pairmatching/domain/PairMatcher.java @@ -0,0 +1,11 @@ +package pairmatching.domain; + +import camp.nextstep.edu.missionutils.Randoms; +import java.util.List; + +public class PairMatcher { + + public static List matchPair(List names) { + return Randoms.shuffle(names); + } +} diff --git a/src/main/java/pairmatching/domain/Pairs.java b/src/main/java/pairmatching/domain/Pairs.java new file mode 100644 index 000000000..69cd3309e --- /dev/null +++ b/src/main/java/pairmatching/domain/Pairs.java @@ -0,0 +1,28 @@ +package pairmatching.domain; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class Pairs { + + private List pairs = new ArrayList<>(); + + public Optional findByCourseLevelMission(final Course course, final Level level, final Mission mission) { + return pairs.stream() + .filter(pair -> pair.getCourse() == course && pair.getLevel() == level && pair.getMission() == mission) + .findFirst(); + } + + public void save(final Pair pair) { + Optional existingPair = findByCourseLevelMission(pair.getCourse(), pair.getLevel(), pair.getMission()); + if (existingPair.isPresent()) { + pairs.remove(existingPair.get()); + } + pairs.add(pair); + } + + public void reset() { + pairs.clear(); + } +} diff --git a/src/main/java/pairmatching/exception/ExceptionMessage.java b/src/main/java/pairmatching/exception/ExceptionMessage.java new file mode 100644 index 000000000..e0bad5a1f --- /dev/null +++ b/src/main/java/pairmatching/exception/ExceptionMessage.java @@ -0,0 +1,26 @@ +package pairmatching.exception; + +public enum ExceptionMessage { + + FILE_PATH_ERROR("파일의 경로를 찾을 수 없습니다."), + MAX_TRY_ERROR("최대 입력 가능 횟수를 초과했습니다."), + NO_COURSE("해당하는 과정이 없습니다."), + NO_LEVEL("해당하는 레벨이 없습니다."), + NO_MISSION("해당하는 미션이 없습니다."), + REPLY_ERROR("네 | 아니오 중 입력해주세요."), + NO_MATCHING_ERROR("매칭 이력이 없습니다."), + LEVEL_MISSION_ERROR("레벨과 미션을 재확인해주세요."), + NO_FUNCTION_ERROR("보기의 기능 중 선택해주세요."); + + private static final String ERROR_PREFIX = "[ERROR] "; + + private final String message; + + ExceptionMessage(final String message) { + this.message = message; + } + + public String getMessage() { + return ERROR_PREFIX + message; + } +} diff --git a/src/main/java/pairmatching/exception/PairmatchingException.java b/src/main/java/pairmatching/exception/PairmatchingException.java new file mode 100644 index 000000000..cf3407ede --- /dev/null +++ b/src/main/java/pairmatching/exception/PairmatchingException.java @@ -0,0 +1,7 @@ +package pairmatching.exception; + +public class PairmatchingException extends IllegalArgumentException { + public PairmatchingException(final ExceptionMessage exceptionMessage) { + super(exceptionMessage.getMessage()); + } +} diff --git a/src/main/java/pairmatching/repository/CrewNameRepository.java b/src/main/java/pairmatching/repository/CrewNameRepository.java new file mode 100644 index 000000000..373f5b806 --- /dev/null +++ b/src/main/java/pairmatching/repository/CrewNameRepository.java @@ -0,0 +1,23 @@ +package pairmatching.repository; + +import static pairmatching.exception.ExceptionMessage.FILE_PATH_ERROR; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import pairmatching.domain.Course; +import pairmatching.exception.PairmatchingException; + +public class CrewNameRepository { + + public List loadCrew(Course course) { + try { + Path filePath = Paths.get(course.getFilePath()); + return Files.readAllLines(filePath); + } catch (IOException e) { + throw new PairmatchingException(FILE_PATH_ERROR); + } + } +} diff --git a/src/main/java/pairmatching/service/PairCheckService.java b/src/main/java/pairmatching/service/PairCheckService.java new file mode 100644 index 000000000..a156babf2 --- /dev/null +++ b/src/main/java/pairmatching/service/PairCheckService.java @@ -0,0 +1,55 @@ +package pairmatching.service; + +import static pairmatching.constant.Constant.MAX_TRY; +import static pairmatching.exception.ExceptionMessage.MAX_TRY_ERROR; +import static pairmatching.exception.ExceptionMessage.NO_MATCHING_ERROR; +import static pairmatching.view.ErrorPrinter.printError; + +import java.util.Optional; +import pairmatching.domain.Course; +import pairmatching.domain.Level; +import pairmatching.domain.Mission; +import pairmatching.domain.Pair; +import pairmatching.domain.Pairs; +import pairmatching.exception.PairmatchingException; +import pairmatching.view.InputView; +import pairmatching.view.OutputView; + +public class PairCheckService { + + private static final String DELIMITER = ","; + private static final int COURSE_INDEX = 0; + private static final int LEVEL_INDEX = 1; + private static final int MISSION_INDEX = 2; + + private Pairs pairs; + + public PairCheckService(Pairs pairs) { + this.pairs = pairs; + } + + public void operate() { + OutputView.showMission(); + showResult(); + } + + private void showResult() { + for (int i = 1; i <= MAX_TRY; i++) { + try { + String[] input = InputView.readMission().split(DELIMITER); + Course course = Course.fromName(input[COURSE_INDEX].trim()); + Level level = Level.fromName(input[LEVEL_INDEX].trim()); + Mission mission = Mission.fromName(input[MISSION_INDEX].trim()); + Optional pair = pairs.findByCourseLevelMission(course, level, mission); + if (pair.isPresent()) { + OutputView.showPair(pair.get()); + return; + } + throw new PairmatchingException(NO_MATCHING_ERROR); + } catch (IllegalArgumentException e) { + printError(e.getMessage()); + } + } + throw new PairmatchingException(MAX_TRY_ERROR); + } +} diff --git a/src/main/java/pairmatching/service/PairMatchingService.java b/src/main/java/pairmatching/service/PairMatchingService.java new file mode 100644 index 000000000..9aa14df57 --- /dev/null +++ b/src/main/java/pairmatching/service/PairMatchingService.java @@ -0,0 +1,89 @@ +package pairmatching.service; + +import static pairmatching.constant.Constant.MAX_TRY; +import static pairmatching.exception.ExceptionMessage.LEVEL_MISSION_ERROR; +import static pairmatching.exception.ExceptionMessage.MAX_TRY_ERROR; +import static pairmatching.exception.ExceptionMessage.REPLY_ERROR; +import static pairmatching.view.ErrorPrinter.printError; + +import java.util.Optional; +import pairmatching.domain.Course; +import pairmatching.domain.Level; +import pairmatching.domain.Mission; +import pairmatching.domain.Pair; +import pairmatching.domain.Pairs; +import pairmatching.exception.PairmatchingException; +import pairmatching.view.InputView; +import pairmatching.view.OutputView; + +public class PairMatchingService { + + private static final String DELIMITER = ","; + private static final int COURSE_INDEX = 0; + private static final int LEVEL_INDEX = 1; + private static final int MISSION_INDEX = 2; + + private static final String REPLY_YES = "네"; + private static final String REPLY_NO = "아니오"; + + private Pairs pairs; + + public PairMatchingService(Pairs pairs) { + this.pairs = pairs; + } + + public void operate() { + OutputView.showMission(); + Pair pair = readCourseLevelMission(); + OutputView.showPair(pair); + } + + private Pair readCourseLevelMission() { + for (int i = 1; i <= MAX_TRY; i++) { + try { + String[] input = InputView.readMission().split(DELIMITER); + Course course = Course.fromName(input[COURSE_INDEX].trim()); + Level level = Level.fromName(input[LEVEL_INDEX].trim()); + Mission mission = Mission.fromName(input[MISSION_INDEX].trim()); + checkLevelMission(level, mission); + Optional pair = pairs.findByCourseLevelMission(course, level, mission); + if (pair.isPresent()) { + String reply = readReply(); + if (!reply.equals(REPLY_YES)) { + return readCourseLevelMission(); + } + } + Pair newPair = new Pair(course, level, mission); + pairs.save(newPair); + return newPair; + } catch (IllegalArgumentException e) { + printError(e.getMessage()); + } + } + throw new PairmatchingException(MAX_TRY_ERROR); + } + + private void checkLevelMission(Level level, Mission mission) { + if (mission.getLevel() != level) { + throw new PairmatchingException(LEVEL_MISSION_ERROR); + } + } + private String readReply() { + for (int i = 1; i <= MAX_TRY; i++) { + try { + String reply = InputView.readReply(); + validateReply(reply); + return reply; + } catch (IllegalArgumentException e) { + printError(e.getMessage()); + } + } + throw new PairmatchingException(MAX_TRY_ERROR); + } + + private void validateReply(String reply) { + if (!reply.equals(REPLY_YES) && !reply.equals(REPLY_NO)) { + throw new PairmatchingException(REPLY_ERROR); + } + } +} diff --git a/src/main/java/pairmatching/service/PairResetService.java b/src/main/java/pairmatching/service/PairResetService.java new file mode 100644 index 000000000..9118094c2 --- /dev/null +++ b/src/main/java/pairmatching/service/PairResetService.java @@ -0,0 +1,19 @@ +package pairmatching.service; + +import pairmatching.domain.Pairs; +import pairmatching.view.OutputView; + +public class PairResetService { + + private Pairs pairs; + + public PairResetService(Pairs pairs) { + this.pairs = pairs; + } + + public Pairs operate() { + pairs.reset(); + OutputView.noticeReset(); + return pairs; + } +} diff --git a/src/main/java/pairmatching/view/ErrorPrinter.java b/src/main/java/pairmatching/view/ErrorPrinter.java new file mode 100644 index 000000000..50a75bfcc --- /dev/null +++ b/src/main/java/pairmatching/view/ErrorPrinter.java @@ -0,0 +1,7 @@ +package pairmatching.view; + +public class ErrorPrinter { + public static void printError(String message) { + System.out.println(message); + } +} diff --git a/src/main/java/pairmatching/view/InputView.java b/src/main/java/pairmatching/view/InputView.java new file mode 100644 index 000000000..6949be410 --- /dev/null +++ b/src/main/java/pairmatching/view/InputView.java @@ -0,0 +1,28 @@ +package pairmatching.view; + +import camp.nextstep.edu.missionutils.Console; + +public class InputView { + + private static final String FUNCTION_VIEW = "기능을 선택하세요.\n1. 페어 매칭\n2. 페어 조회\n3. 페어 초기화\nQ. 종료"; + + private static final String MISSION_VIEW = "과정, 레벨, 미션을 선택하세요.\n" + + "ex) 백엔드, 레벨1, 자동차경주"; + + private static final String REPLY_VIEW = "매칭 정보가 있습니다. 다시 매칭하시겠습니까?\n네 | 아니오"; + + public static String readFunction() { + System.out.println(FUNCTION_VIEW); + return Console.readLine().trim(); + } + + public static String readMission() { + System.out.println(MISSION_VIEW); + return Console.readLine().trim(); + } + + public static String readReply() { + System.out.println(REPLY_VIEW); + return Console.readLine().trim(); + } +} diff --git a/src/main/java/pairmatching/view/OutputView.java b/src/main/java/pairmatching/view/OutputView.java new file mode 100644 index 000000000..8fd76ff48 --- /dev/null +++ b/src/main/java/pairmatching/view/OutputView.java @@ -0,0 +1,82 @@ +package pairmatching.view; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import pairmatching.domain.Course; +import pairmatching.domain.Level; +import pairmatching.domain.Mission; +import pairmatching.domain.Pair; + +public class OutputView { + + private static final String LINE = "#############################################"; + private static final String COURSE_VIEW = " 과정: %s\n"; + private static final String MISSION_VIEW = " 미션:"; + private static final String LEVEL_VIEW = " - %s: %s\n"; + + private static final String DELIMITER = " | "; + + private static final String PAIR_START_VIEW = "페어 매칭 결과입니다."; + private static final String PAIR_DELIMITER = " : "; + + private static final String RESET_VIEW = "초기화 되었습니다."; + + public static void showMission() { + List course = Arrays.stream(Course.values()) + .map(Course::getName) + .collect(Collectors.toList()); + List level = Arrays.stream(Level.values()) + .map(Level::getName) + .collect(Collectors.toList()); + + Map> groupedMission = Arrays.stream(Mission.values()) + .collect(Collectors.groupingBy(Mission::getLevel, + Collectors.mapping(Mission::getName, Collectors.toList()))); + List> mission = Arrays.stream(Level.values()) + .map(eachLevel -> groupedMission.getOrDefault(eachLevel, Collections.emptyList())) + .collect(Collectors.toList()); + + System.out.println(LINE); + String courses = String.join(DELIMITER, course); + System.out.printf(COURSE_VIEW, courses); + List missions = makeFormattedMissions(mission); + System.out.println(MISSION_VIEW); + for (int idx = 0; idx < level.size(); idx++) { + System.out.printf(LEVEL_VIEW, level.get(idx), missions.get(idx)); + } + System.out.println(LINE); + } + + public static void showPair(final Pair pair) { + System.out.println(PAIR_START_VIEW); + List crewNames = pair.getCrews(); + if (crewNames.size() % 2 == 1) { + for (int i = 0; i < crewNames.size()-3; i+=2) { + System.out.println(String.join(PAIR_DELIMITER, crewNames.get(i), crewNames.get(i+1))); + } + System.out.println(String.join(PAIR_DELIMITER, + crewNames.get(crewNames.size()-3), + crewNames.get(crewNames.size()-2), + crewNames.get(crewNames.size()-1))); + System.out.println(); + return; + } + for (int i = 0; i < crewNames.size()-1; i+=2) { + System.out.println(String.join(PAIR_DELIMITER, crewNames.get(i), crewNames.get(i+1))); + } + System.out.println(); + } + + private static List makeFormattedMissions(List> mission) { + return mission.stream() + .map(line -> String.join(DELIMITER, line)) + .collect(Collectors.toList()); + } + + public static void noticeReset() { + System.out.println(RESET_VIEW); + } +} From 930034a549086268b78e169a46c002dbd1046467 Mon Sep 17 00:00:00 2001 From: DoomchitYJ Date: Thu, 12 Dec 2024 00:08:46 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix(PairCheckService):=20=EB=A7=A4=EC=B9=AD?= =?UTF-8?q?=20=EC=9D=B4=EB=A0=A5=EC=9D=B4=20=EC=97=86=EC=9D=84=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0,=20=EA=B8=B0=EB=8A=A5=20=EC=84=A0=ED=83=9D=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=8F=8C=EC=95=84=EA=B0=80=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - showResult(): 기존 매칭 이력이 없을 경우, 다시 과정, 레벨, 미션 입력으로 돌아가는 구조에서 기능 선택 입력으로 돌아가도록 수정함 --- src/main/README.md | 68 +++++++++---------- .../pairmatching/controller/Controller.java | 8 ++- .../service/PairCheckService.java | 16 +++-- 3 files changed, 50 insertions(+), 42 deletions(-) diff --git a/src/main/README.md b/src/main/README.md index ec0f231f0..028ff4585 100644 --- a/src/main/README.md +++ b/src/main/README.md @@ -57,28 +57,28 @@ 과정, 레벨, 미션을 선택하세요.\n ex) 백엔드, 레벨1, 자동차경주"를 출력한다. - [x] 과정, 레벨, 미션을 입력 받는다. -- [ ] 입력값을 검증한다. - - [ ] 과정, 레벨, 미션으로 파싱한다. - - [ ] 과정이 존재하는가? - - [ ] 레벨이 존재하는가? - - [ ] 미션이 존재하는가? -- [ ] 기존 매칭 결과가 있을 경우, - - [ ] "매칭 정보가 있습니다. 다시 매칭하시겠습니까?\n 네 | 아니오"를 출력한다. - - [ ] 값을 입력 받는다. - - [ ] "네" -> 아래의 "새로운 매칭 결과를 출력하는 경우"로 넘어간다. - - [ ] "아니오" -> "과정, 레벨, 미션 입력"으로 돌아간다. -- [ ] 새로운 매칭 결과를 출력하는 경우, - - [ ] 각 과정에 맞는 크루원 이름을 셔플한다. - - [ ] 결과를 저장한다. -- [ ] 매칭 결과를 출력한다. - - [ ] "페어 매칭 결과입니다."를 출력한다. - - [ ] 한 줄에 두 명씩 묶어 "[이름] : [이름]" 형태로 출력한다. - - [ ] 크루원이 홀수일 경우, 마지막 3명을 한 페어로 묶어 출력한다. -- [ ] 기능 선택으로 돌아간다. +- [x] 입력값을 검증한다. + - [x] 과정, 레벨, 미션으로 파싱한다. + - [x] 과정이 존재하는가? + - [x] 레벨이 존재하는가? + - [x] 미션이 존재하는가? +- [x] 기존 매칭 결과가 있을 경우, + - [x] "매칭 정보가 있습니다. 다시 매칭하시겠습니까?\n 네 | 아니오"를 출력한다. + - [x] 값을 입력 받는다. + - [x] "네" -> 아래의 "새로운 매칭 결과를 출력하는 경우"로 넘어간다. + - [x] "아니오" -> "과정, 레벨, 미션 입력"으로 돌아간다. +- [x] 새로운 매칭 결과를 출력하는 경우, + - [x] 각 과정에 맞는 크루원 이름을 셔플한다. + - [x] 결과를 저장한다. +- [x] 매칭 결과를 출력한다. + - [x] "페어 매칭 결과입니다."를 출력한다. + - [x] 한 줄에 두 명씩 묶어 "[이름] : [이름]" 형태로 출력한다. + - [x] 크루원이 홀수일 경우, 마지막 3명을 한 페어로 묶어 출력한다. +- [x] 기능 선택으로 돌아간다. ### 페어 조회 기능 -- [ ] "#############################################\n +- [x] "#############################################\n 과정: [과정 목록]\n 미션:\n \- 레벨1: [미션 목록]\n @@ -89,24 +89,24 @@ ############################################\n 과정, 레벨, 미션을 선택하세요.\n ex) 백엔드, 레벨1, 자동차경주"를 출력한다. -- [ ] 과정, 레벨, 미션을 입력 받는다. -- [ ] 입력값을 검증한다. - - [ ] 과정, 레벨, 미션으로 파싱한다. - - [ ] 과정이 존재하는가? - - [ ] 레벨이 존재하는가? - - [ ] 미션이 존재하는가? -- [ ] 기존 매칭 결과가 존재하는 경우, - - [ ] 매칭 결과를 출력한다. -- [ ] 매칭 결과가 존재하지 않을 경우, - - [ ] "매칭 결과가 존재하지 않습니다."를 출력한다. -- [ ] 기능 선택으로 돌아간다. +- [x] 과정, 레벨, 미션을 입력 받는다. +- [x] 입력값을 검증한다. + - [x] 과정, 레벨, 미션으로 파싱한다. + - [x] 과정이 존재하는가? + - [x] 레벨이 존재하는가? + - [x] 미션이 존재하는가? +- [x] 기존 매칭 결과가 존재하는 경우, + - [x] 매칭 결과를 출력한다. +- [x] 매칭 결과가 존재하지 않을 경우, + - [x] "매칭 이력이 없습니다."를 출력한다. +- [x] 기능 선택으로 돌아간다. ### 페어 초기화 기능 -- [ ] 매칭 결과를 모두 삭제한다. -- [ ] "초기화 되었습니다."를 출력한다. -- [ ] 기능 선택으로 돌아간다. +- [x] 매칭 결과를 모두 삭제한다. +- [x] "초기화 되었습니다."를 출력한다. +- [x] 기능 선택으로 돌아간다. ### 종료 기능 -- [ ] Q를 입력받을 경우, 프로그램을 종료한다. \ No newline at end of file +- [x] Q를 입력받을 경우, 프로그램을 종료한다. \ No newline at end of file diff --git a/src/main/java/pairmatching/controller/Controller.java b/src/main/java/pairmatching/controller/Controller.java index 2876aa679..8cf08a271 100644 --- a/src/main/java/pairmatching/controller/Controller.java +++ b/src/main/java/pairmatching/controller/Controller.java @@ -32,8 +32,12 @@ public void run() { } if (functionInput.equals(PAIR_CHECK_FUNC)) { PairCheckService pairCheckService = new PairCheckService(pairs); - pairCheckService.operate(); - continue; + try { + pairCheckService.operate(); + continue; + } catch (PairmatchingException e) { + printError(e.getMessage()); + } } if (functionInput.equals(PAIR_RESET_FUNC)) { PairResetService pairResetService = new PairResetService(pairs); diff --git a/src/main/java/pairmatching/service/PairCheckService.java b/src/main/java/pairmatching/service/PairCheckService.java index a156babf2..841138bec 100644 --- a/src/main/java/pairmatching/service/PairCheckService.java +++ b/src/main/java/pairmatching/service/PairCheckService.java @@ -34,18 +34,22 @@ public void operate() { } private void showResult() { + Optional pair = findPair(); + if (pair.isPresent()) { + OutputView.showPair(pair.get()); + return; + } + throw new PairmatchingException(NO_MATCHING_ERROR); + } + + private Optional findPair() { for (int i = 1; i <= MAX_TRY; i++) { try { String[] input = InputView.readMission().split(DELIMITER); Course course = Course.fromName(input[COURSE_INDEX].trim()); Level level = Level.fromName(input[LEVEL_INDEX].trim()); Mission mission = Mission.fromName(input[MISSION_INDEX].trim()); - Optional pair = pairs.findByCourseLevelMission(course, level, mission); - if (pair.isPresent()) { - OutputView.showPair(pair.get()); - return; - } - throw new PairmatchingException(NO_MATCHING_ERROR); + return pairs.findByCourseLevelMission(course, level, mission); } catch (IllegalArgumentException e) { printError(e.getMessage()); }