diff --git a/docs/README.md b/docs/README.md index e69de29..285c976 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,219 @@ +# 페어 매칭 + + + +# 기능 목록 + +## 페어 매칭 기능 + +### 조건 +- 미션을 수행할 페어는 기본적으로 2명씩 매칭 +- 인원이 홀수인 경우 3인으로 구성 +- 같은 레벨에서 이미 페어를 맺은 크루와는 다시 페어 매칭 불가능 + +### 구현 +- 이름 목록 -> List +- 크루 목록 random으로 섞기 -> Randoms.shuffle 활용 +- 홀수인 경우 마지막 남은 크루는 마지막 페어에 포함. +- 같은 레벨에서 만난적이 있는 크루면 다시 크루 목록의 순서를 랜덤으로 섞어서 매칭 시도 +- 3회 시도까지 매칭이 되지 않거나 매칭을 할 수 있는 경우의 수가 없으면 에러 메시지 출력 + +### 재매칭 시도 +- 안내 문구를 출력 후 매칭 진행 +- 아니오를 선택할 경우 코스, 레벨, 미션을 다시 선택 + +## 페어 조회 기능 +- 과정,레벨, 미션을 선택하면 미션의 페어 정보 출력 +- 매칭 이력이 없으면 매칭 이력 없음을 안내 + + +# 입출력 요구 사항 + +## 파일 입출력 +- 주어진 .md 파일을 활용한다. +- 두 파일의 내용은 수정이 가능하다. 그러나 크루들의 이름은 중복될 수 없다. + +## 기능 선택 +- 기능의 종류를 선택한다. +- 1,2,3,Q 이외의 입력이 들어오면 예외를 처리한다. + +## 페어 매칭 + +### 입력 + +```text +기능을 선택하세요. +1. 페어 매칭 +2. 페어 조회 +3. 페어 초기화 +Q. 종료 +``` + +- 페어매칭 선택시 입력창 +```text + +############################################# +과정: 백엔드 | 프론트엔드 +미션: + - 레벨1: 자동차경주 | 로또 | 숫자야구게임 + - 레벨2: 장바구니 | 결제 | 지하철노선도 + - 레벨3: + - 레벨4: 성능개선 | 배포 + - 레벨5: +############################################ +과정, 레벨, 미션을 선택하세요. +ex) 백엔드, 레벨1, 자동차경주 +프론트엔드, 레벨1, 자동차경주 +``` +- **condition** +- 과정, 레벨, 미션을 선택하고 리스트에 없는 목록을 입력한 경우 예외처리를 한다. +- 3개만 입력 가능해야 함. + +- 매칭 정보가 있어서 아니오를 누른 경우 입력창 +```text +과정, 레벨, 미션을 선택하세요. +ex) 백엔드, 레벨1, 자동차경주 +``` + +- 매칭 정보가 있는 경우 확인 입력 창 +```text +매칭 정보가 있습니다. 다시 매칭하시겠습니까? +네 | 아니오 +``` +### 출력 +- 페어 매칭 결과 +```text + +페어 매칭 결과입니다. +다비 : 신디 +쉐리 : 덴버 +제키 : 로드 +라라 : 윌터 +니콜 : 이브 +린다 : 시저 +보노 : 제시 : 제키 +``` + +- 초기화후 출력 +```text +초기화 되었습니다. +``` + + + +## 프로그램 사용 예시 +```text +기능을 선택하세요. +1. 페어 매칭 +2. 페어 조회 +3. 페어 초기화 +Q. 종료 +1 + +############################################# +과정: 백엔드 | 프론트엔드 +미션: + - 레벨1: 자동차경주 | 로또 | 숫자야구게임 + - 레벨2: 장바구니 | 결제 | 지하철노선도 + - 레벨3: + - 레벨4: 성능개선 | 배포 + - 레벨5: +############################################ +과정, 레벨, 미션을 선택하세요. +ex) 백엔드, 레벨1, 자동차경주 +프론트엔드, 레벨1, 자동차경주 + +페어 매칭 결과입니다. +다비 : 신디 +쉐리 : 덴버 +제키 : 로드 +라라 : 윌터 +니콜 : 이브 +린다 : 시저 +보노 : 제시 : 제키 + +기능을 선택하세요. +1. 페어 매칭 +2. 페어 조회 +3. 페어 초기화 +Q. 종료 +1 + +############################################# +과정: 백엔드 | 프론트엔드 +미션: + - 레벨1: 자동차경주 | 로또 | 숫자야구게임 + - 레벨2: 장바구니 | 결제 | 지하철노선도 + - 레벨3: + - 레벨4: 성능개선 | 배포 + - 레벨5: +############################################ +과정, 레벨, 미션을 선택하세요. +ex) 백엔드, 레벨1, 자동차경주 +프론트엔드, 레벨1, 자동차경주 + +매칭 정보가 있습니다. 다시 매칭하시겠습니까? +네 | 아니오 +아니오 + +과정, 레벨, 미션을 선택하세요. +ex) 백엔드, 레벨1, 자동차경주 +프론트엔드, 레벨1, 자동차경주 +매칭 정보가 있습니다. 다시 매칭하시겠습니까? +네 | 아니오 +네 + +페어 매칭 결과입니다. +이브 : 윌터 +보노 : 제키 +신디 : 로드 +제시 : 린다 +시저 : 라라 +니콜 : 다비 +리사 : 덴버 : 제키 + +기능을 선택하세요. +1. 페어 매칭 +2. 페어 조회 +3. 페어 초기화 +Q. 종료 +2 + +############################################# +과정: 백엔드 | 프론트엔드 +미션: + - 레벨1: 자동차경주 | 로또 | 숫자야구게임 + - 레벨2: 장바구니 | 결제 | 지하철노선도 + - 레벨3: + - 레벨4: 성능개선 | 배포 + - 레벨5: +############################################ +과정, 레벨, 미션을 선택하세요. +ex) 백엔드, 레벨1, 자동차경주 +프론트엔드, 레벨1, 자동차경주 + +페어 매칭 결과입니다. +이브 : 윌터 +보노 : 제키 +신디 : 로드 +제시 : 린다 +시저 : 라라 +니콜 : 다비 +리사 : 덴버 : 제키 + +기능을 선택하세요. +1. 페어 매칭 +2. 페어 조회 +3. 페어 초기화 +Q. 종료 +3 + +초기화 되었습니다. + +기능을 선택하세요. +1. 페어 매칭 +2. 페어 조회 +3. 페어 초기화 +Q. 종료 +Q +``` \ No newline at end of file diff --git a/src/main/java/pairmatching/Application.java b/src/main/java/pairmatching/Application.java index c619a21..f6c383a 100644 --- a/src/main/java/pairmatching/Application.java +++ b/src/main/java/pairmatching/Application.java @@ -1,8 +1,10 @@ package pairmatching; +import pairmatching.luncher.PairMatchingGameLauncher; + public class Application { public static void main(String[] args) { - // TODO 구현 진행 - + PairMatchingGameLauncher pairMatchingGameLauncher = new PairMatchingGameLauncher(); + pairMatchingGameLauncher.play(); } } diff --git a/src/main/java/pairmatching/db/Database.java b/src/main/java/pairmatching/db/Database.java new file mode 100644 index 0000000..d8354b4 --- /dev/null +++ b/src/main/java/pairmatching/db/Database.java @@ -0,0 +1,58 @@ +package pairmatching.db; + +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import pairmatching.domain.type.MatchingInformation; +import pairmatching.domain.type.Pair; + +public class Database { + + private final Map> memory = new HashMap<>(); + + public void addAll(MatchingInformation matchingInformation, List pairs) { + memory.computeIfAbsent(matchingInformation.toString(), key -> new LinkedHashSet<>()) + .addAll(pairs); + } + + public boolean exist(MatchingInformation matchingInformation) { + return memory.containsKey(matchingInformation.toString()); + } + + public boolean existPair(String inputKey, Pair data) { + List keys = findKeys(inputKey); + return keys.stream() + .map(memory::get) + .anyMatch(pairs -> matchPairs(pairs, data)); + } + + public Set getAll(MatchingInformation matchingInformation) { + Set pairs = memory.get(matchingInformation.toString()); + if (pairs == null) { + return new LinkedHashSet<>(); + } + return pairs; + } + + public void clear() { + memory.clear(); + } + + public void removeKey(MatchingInformation matchingInformation) { + memory.remove(matchingInformation.toString()); + } + + private List findKeys(String inputKey) { + return memory.keySet().stream() + .filter(key -> key.contains(inputKey)) + .collect(Collectors.toList()); + } + + private boolean matchPairs(Set pairs, Pair value) { + return pairs.stream() + .anyMatch(pair -> pair.match(value)); + } +} diff --git a/src/main/java/pairmatching/domain/PairMatchingGame.java b/src/main/java/pairmatching/domain/PairMatchingGame.java new file mode 100644 index 0000000..4d634ec --- /dev/null +++ b/src/main/java/pairmatching/domain/PairMatchingGame.java @@ -0,0 +1,63 @@ +package pairmatching.domain; + +import java.util.List; +import java.util.Set; +import pairmatching.domain.checker.DefaultDuplicateChecker; +import pairmatching.domain.checker.DuplicateChecker; +import pairmatching.db.Database; +import pairmatching.domain.loader.CrewLoader; +import pairmatching.domain.maker.EvenPairMaker; +import pairmatching.domain.maker.OddPairMaker; +import pairmatching.domain.maker.PairMaker; +import pairmatching.domain.type.Crew; +import pairmatching.domain.type.MatchingInformation; +import pairmatching.domain.type.Pair; +import pairmatching.exception.EmptyMatchedCrewException; + +public class PairMatchingGame { + + private final Database database = new Database(); + private final CrewLoader crewLoader = new CrewLoader(); + private final DuplicateChecker duplicateChecker = new DefaultDuplicateChecker(database); + + public Set pairMatch(MatchingInformation matchingInformation) { + duplicateChecker.checkDuplicate(matchingInformation); + List pairs = makeCrewPairs(matchingInformation); + database.addAll(matchingInformation, pairs); + return database.getAll(matchingInformation); + } + + public Set reMatch(MatchingInformation matchingInformation) { + database.removeKey(matchingInformation); + List pairs = makeCrewPairs(matchingInformation); + database.addAll(matchingInformation, pairs); + return database.getAll(matchingInformation); + } + + public Set showPair(MatchingInformation matchingInformation) { + Set pairs = database.getAll(matchingInformation); + if (pairs.isEmpty()) { + throw new EmptyMatchedCrewException(); + } + return pairs; + } + + public void reset() { + database.clear(); + } + + private List makeCrewPairs(MatchingInformation matchingInformation) { + List names = crewLoader.getNames(matchingInformation.getCourse()); + PairMaker pairMaker = createPairMaker(names.size()); + List pairs = pairMaker.createPair(names); + duplicateChecker.checkDuplicatePair(matchingInformation.getLevel().toString(), pairs); + return pairs; + } + + private PairMaker createPairMaker(int number) { + if (number % 2 == 0) { + return new EvenPairMaker(); + } + return new OddPairMaker(); + } +} diff --git a/src/main/java/pairmatching/domain/checker/DefaultDuplicateChecker.java b/src/main/java/pairmatching/domain/checker/DefaultDuplicateChecker.java new file mode 100644 index 0000000..8343fd4 --- /dev/null +++ b/src/main/java/pairmatching/domain/checker/DefaultDuplicateChecker.java @@ -0,0 +1,32 @@ +package pairmatching.domain.checker; + +import java.util.List; +import pairmatching.db.Database; +import pairmatching.domain.type.MatchingInformation; +import pairmatching.domain.type.Pair; +import pairmatching.exception.DuplicateException; +import pairmatching.exception.DuplicatePairException; + +public class DefaultDuplicateChecker implements DuplicateChecker { + + private final Database database; + + public DefaultDuplicateChecker(Database database) { + this.database = database; + } + + @Override + public void checkDuplicate(MatchingInformation matchingInformation) { + if (database.exist(matchingInformation)) { + throw new DuplicateException(); + } + } + + @Override + public void checkDuplicatePair(String inputKey, List pairs) { + boolean duplicatePair = pairs.stream().anyMatch(pair -> database.existPair(inputKey, pair)); + if (duplicatePair) { + throw new DuplicatePairException(); + } + } +} diff --git a/src/main/java/pairmatching/domain/checker/DuplicateChecker.java b/src/main/java/pairmatching/domain/checker/DuplicateChecker.java new file mode 100644 index 0000000..60b9879 --- /dev/null +++ b/src/main/java/pairmatching/domain/checker/DuplicateChecker.java @@ -0,0 +1,14 @@ +package pairmatching.domain.checker; + + +import java.util.List; +import pairmatching.domain.type.MatchingInformation; +import pairmatching.domain.type.Pair; + + +public interface DuplicateChecker { + + void checkDuplicate(MatchingInformation matchingInformation); + + void checkDuplicatePair(String inputKey, List pairs); +} diff --git a/src/main/java/pairmatching/domain/loader/CrewLoader.java b/src/main/java/pairmatching/domain/loader/CrewLoader.java new file mode 100644 index 0000000..7b13db3 --- /dev/null +++ b/src/main/java/pairmatching/domain/loader/CrewLoader.java @@ -0,0 +1,42 @@ +package pairmatching.domain.loader; + +import camp.nextstep.edu.missionutils.Randoms; +import java.util.List; +import java.util.stream.Collectors; +import pairmatching.domain.type.Course; +import pairmatching.domain.type.Crew; +import pairmatching.domain.type.Name; +import pairmatching.util.io.FileNameLoader; +import pairmatching.util.io.NameLoader; + +public class CrewLoader { + + private static final String backendPath = "backend-crew.md"; + private static final String frontendPath = "fontend-crew.md"; + private static final String INVALID_COURSE_NAME = "정확한 Course 타입 입력을 해야 합니다."; + private final NameLoader nameLoader = new FileNameLoader(); + + public List getNames(Course course) { + if (course.equals(Course.BACKEND)) { + return makeBackCrews(); + } + if (course.equals(Course.FRONTEND)) { + return makeFrontCrews(); + } + throw new IllegalArgumentException(INVALID_COURSE_NAME); + } + + private List makeBackCrews() { + List names = nameLoader.loadNames(backendPath); + List temp = names.stream().map(Name::toString).collect(Collectors.toList()); + List shuffles = Randoms.shuffle(temp); + return shuffles.stream().map(Crew::makeBackendCrew).collect(Collectors.toList()); + } + + private List makeFrontCrews() { + List names = nameLoader.loadNames(frontendPath); + List temp = names.stream().map(Name::toString).collect(Collectors.toList()); + List shuffles = Randoms.shuffle(temp); + return shuffles.stream().map(Crew::makeFrontendCrew).collect(Collectors.toList()); + } +} diff --git a/src/main/java/pairmatching/domain/maker/EvenPairMaker.java b/src/main/java/pairmatching/domain/maker/EvenPairMaker.java new file mode 100644 index 0000000..51d524d --- /dev/null +++ b/src/main/java/pairmatching/domain/maker/EvenPairMaker.java @@ -0,0 +1,18 @@ +package pairmatching.domain.maker; + +import java.util.ArrayList; +import java.util.List; +import pairmatching.domain.type.Crew; +import pairmatching.domain.type.Pair; + +public class EvenPairMaker implements PairMaker { + + @Override + public List createPair(List names) { + List pairs = new ArrayList<>(); + for (int i = 0; i < names.size() - 1; i += 2) { + pairs.add(new Pair(names.get(i), names.get(i + 1))); + } + return pairs; + } +} diff --git a/src/main/java/pairmatching/domain/maker/OddPairMaker.java b/src/main/java/pairmatching/domain/maker/OddPairMaker.java new file mode 100644 index 0000000..6968ee7 --- /dev/null +++ b/src/main/java/pairmatching/domain/maker/OddPairMaker.java @@ -0,0 +1,20 @@ +package pairmatching.domain.maker; + +import java.util.ArrayList; +import java.util.List; +import pairmatching.domain.type.Crew; +import pairmatching.domain.type.Pair; + +public class OddPairMaker implements PairMaker { + + @Override + public List createPair(List names) { + List pairs = new ArrayList<>(); + int lastIndex = names.size() - 3; + for (int i = 0; i < lastIndex; i += 2) { + pairs.add(new Pair(names.get(i), names.get(i + 1))); + } + pairs.add(new Pair(names.get(lastIndex), names.get(lastIndex + 1), names.get(lastIndex + 2))); + return pairs; + } +} diff --git a/src/main/java/pairmatching/domain/maker/PairMaker.java b/src/main/java/pairmatching/domain/maker/PairMaker.java new file mode 100644 index 0000000..0e2502a --- /dev/null +++ b/src/main/java/pairmatching/domain/maker/PairMaker.java @@ -0,0 +1,10 @@ +package pairmatching.domain.maker; + +import java.util.List; +import pairmatching.domain.type.Crew; +import pairmatching.domain.type.Pair; + +public interface PairMaker { + + List createPair(List names); +} diff --git a/src/main/java/pairmatching/domain/type/Course.java b/src/main/java/pairmatching/domain/type/Course.java new file mode 100644 index 0000000..53faa2d --- /dev/null +++ b/src/main/java/pairmatching/domain/type/Course.java @@ -0,0 +1,27 @@ +package pairmatching.domain.type; + +import java.util.stream.Stream; + +public enum Course { + BACKEND("백엔드"), + FRONTEND("프론트엔드"); + + private static final String INVALID_COURSE_NAME = "존재하지 않는 과정을 입력하셨습니다. ex) 백엔드, 프론트엔드"; + private final String name; + + Course(String name) { + this.name = name; + } + + public static Course matchOf(String name) { + return Stream.of(Course.values()) + .filter(course -> name.equals(course.name)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException(INVALID_COURSE_NAME)); + } + + @Override + public String toString() { + return name; + } +} diff --git a/src/main/java/pairmatching/domain/type/Crew.java b/src/main/java/pairmatching/domain/type/Crew.java new file mode 100644 index 0000000..fc31bca --- /dev/null +++ b/src/main/java/pairmatching/domain/type/Crew.java @@ -0,0 +1,53 @@ +package pairmatching.domain.type; + +import java.util.Objects; + +public class Crew { + private final Course course; + private final Name name; + + public Crew(Course course, Name name) { + this.course = course; + this.name = name; + } + + public static Crew makeBackendCrew(String name) { + return new Crew(Course.BACKEND, new Name(name)); + } + + public static Crew makeFrontendCrew(String name) { + return new Crew(Course.FRONTEND, new Name(name)); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Crew crew = (Crew) o; + return course == crew.course && Objects.equals(name, crew.name); + } + + @Override + public int hashCode() { + return Objects.hash(course, name); + } + + public Course getCourse() { + return course; + } + public Name getName() { + return name; + } + + @Override + public String toString() { + return "Crew{" + + "course=" + course + + ", name=" + name + + '}'; + } +} diff --git a/src/main/java/pairmatching/domain/type/Feature.java b/src/main/java/pairmatching/domain/type/Feature.java new file mode 100644 index 0000000..ddd5f8d --- /dev/null +++ b/src/main/java/pairmatching/domain/type/Feature.java @@ -0,0 +1,34 @@ +package pairmatching.domain.type; + +import java.util.stream.Stream; + +public enum Feature { + PAIR_MATCHING("페어 매칭", "1"), + PAIR_SEARCH("페어 조회", "2"), + PAIR_INITIALIZE("페어 초기화", "3"), + QUIT("종료", "Q"); + + private static final String INVALID_FEATURE_KEY = "1,2,3,Q 이외에 다른 값을 입력할 수 없습니다."; + private final String name; + private final String keyType; + + Feature(String name, String keyType) { + this.name = name; + this.keyType = keyType; + } + + public static Feature matchOf(String keyType) { + return Stream.of(Feature.values()) + .filter(feature -> keyType.equals(feature.keyType)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException(INVALID_FEATURE_KEY)); + } + + public String getName() { + return name; + } + + public String getKeyType() { + return keyType; + } +} diff --git a/src/main/java/pairmatching/domain/type/Level.java b/src/main/java/pairmatching/domain/type/Level.java new file mode 100644 index 0000000..f809918 --- /dev/null +++ b/src/main/java/pairmatching/domain/type/Level.java @@ -0,0 +1,32 @@ +package pairmatching.domain.type; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public enum Level { + LEVEL1("레벨1"), + LEVEL2("레벨2"), + LEVEL3("레벨3"), + LEVEL4("레벨4"), + LEVEL5("레벨5"); + + private static final String INVALID_LEVEL_NAME = "레벨 입력이 옳바르지 않습니다."; + private final String name; + + Level(String name) { + this.name = name; + } + + public static Level matchOf(String name) { + return Stream.of(Level.values()) + .filter(level -> name.equals(level.name)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException(INVALID_LEVEL_NAME)); + } + + @Override + public String toString() { + return name; + } +} diff --git a/src/main/java/pairmatching/domain/type/MatchingInformation.java b/src/main/java/pairmatching/domain/type/MatchingInformation.java new file mode 100644 index 0000000..b566de0 --- /dev/null +++ b/src/main/java/pairmatching/domain/type/MatchingInformation.java @@ -0,0 +1,56 @@ +package pairmatching.domain.type; + +public class MatchingInformation { + + private static final String COMMA = ","; + private static final int INPUT_SIZE = 3; + private static final String ERROR_INPUT_SIZE = "입력은 3가지만 입력하세요."; + + private final Course course; + private final Level level; + private final Subject subject; + private final Mission mission; + + private MatchingInformation(Course course, Level level, Subject subject) { + this.course = course; + this.level = level; + this.subject = subject; + this.mission = Mission.matchOf(level, subject); + } + + public static MatchingInformation of(String line) { + String[] split = line.split(COMMA); + validateSplit(split); + Course course = Course.matchOf(split[0].trim()); + Level level = Level.matchOf(split[1].trim()); + Subject subject = Subject.matchOf(split[2].trim()); + return new MatchingInformation(course, level, subject); + } + + private static void validateSplit(String[] split) { + if (split.length > INPUT_SIZE) { + throw new IllegalArgumentException(ERROR_INPUT_SIZE); + } + } + + public Course getCourse() { + return course; + } + + public Level getLevel() { + return level; + } + + public Subject getSubject() { + return subject; + } + + public Mission getMission() { + return mission; + } + + @Override + public String toString() { + return course + "," + level + "," + subject; + } +} diff --git a/src/main/java/pairmatching/domain/type/Mission.java b/src/main/java/pairmatching/domain/type/Mission.java new file mode 100644 index 0000000..0667c81 --- /dev/null +++ b/src/main/java/pairmatching/domain/type/Mission.java @@ -0,0 +1,36 @@ +package pairmatching.domain.type; + + +import java.util.stream.Stream; + +public enum Mission { + RACING_CAR(Subject.RACING_CAR, Level.LEVEL1), + LOTTO(Subject.LOTTO, Level.LEVEL1), + BASE_BALL(Subject.BASE_BALL, Level.LEVEL1), + BASKET(Subject.BASKET, Level.LEVEL2), + CREDIT(Subject.CREDIT, Level.LEVEL2), + SUBWAY(Subject.SUBWAY, Level.LEVEL2), + IMPROVEMENT(Subject.IMPROVEMENT, Level.LEVEL4), + DISTRIBUTE(Subject.DISTRIBUTE, Level.LEVEL4); + + private static final String INVALID_MISSION_NAME = "레벨에 맞는 정확한 미션 이름을 입력해주세요."; + private final Subject subject; + private final Level level; + + Mission(Subject subject, Level level) { + this.subject = subject; + this.level = level; + } + + public static Mission matchOf(Level level, Subject subject) { + return Stream.of(Mission.values()) + .filter(mission -> subject.equals(mission.subject) && level.equals(mission.level)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException(INVALID_MISSION_NAME)); + } + + @Override + public String toString() { + return level + ":" + subject; + } +} diff --git a/src/main/java/pairmatching/domain/type/Name.java b/src/main/java/pairmatching/domain/type/Name.java new file mode 100644 index 0000000..f3b167c --- /dev/null +++ b/src/main/java/pairmatching/domain/type/Name.java @@ -0,0 +1,50 @@ +package pairmatching.domain.type; + +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Name { + + private static final String NAME_ERROR = "이름 똑바로 입력하세요."; + private final String name; + + public Name(String name) { + validateName(name); + this.name = name; + } + + private void validateName(String name) { + Matcher matcher = Pattern.compile("^[a-zA-Z가-힣]{1,10}$").matcher(name); + if (matcher.matches()) { + return; + } + throw new IllegalStateException(NAME_ERROR); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Name name1 = (Name) o; + return Objects.equals(name, name1.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return name; + } +} diff --git a/src/main/java/pairmatching/domain/type/Pair.java b/src/main/java/pairmatching/domain/type/Pair.java new file mode 100644 index 0000000..0d01a3e --- /dev/null +++ b/src/main/java/pairmatching/domain/type/Pair.java @@ -0,0 +1,76 @@ +package pairmatching.domain.type; + +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +public class Pair { + private final Crew first; + private final Crew second; + private final Crew third; + + public Pair(Crew first, Crew second) { + this.first = first; + this.second = second; + this.third = null; + } + + public Pair(Crew first, Crew second, Crew third) { + this.first = first; + this.second = second; + this.third = third; + } + + public boolean match(Pair other) { + Set existCrews = new HashSet<>(List.of(first, second)); + if (third != null) { + existCrews.add(third); + } + Set otherCrews = new HashSet<>(List.of(other.getFirst(), other.getSecond())); + if (other.getThird() != null) { + otherCrews.add(third); + } + existCrews.retainAll(otherCrews); + return existCrews.size() >= 2; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Pair pair = (Pair) o; + return Objects.equals(first, pair.first) && Objects.equals(second, + pair.second) && Objects.equals(third, pair.third); + } + + @Override + public int hashCode() { + return Objects.hash(first, second, third); + } + + public Crew getFirst() { + return first; + } + + public Crew getSecond() { + return second; + } + + public Crew getThird() { + return third; + } + + @Override + public String toString() { + String result = first + " : " + second; + if (third != null) { + result = result + " : " + third; + } + return result; + } +} diff --git a/src/main/java/pairmatching/domain/type/Subject.java b/src/main/java/pairmatching/domain/type/Subject.java new file mode 100644 index 0000000..0469869 --- /dev/null +++ b/src/main/java/pairmatching/domain/type/Subject.java @@ -0,0 +1,33 @@ +package pairmatching.domain.type; + +import java.util.stream.Stream; + +public enum Subject { + + RACING_CAR("자동차경주"), + LOTTO("로또"), + BASE_BALL("숫자야구게임"), + BASKET("장바구니"), + CREDIT("결제"), + SUBWAY("지하철노선도"), + IMPROVEMENT("성능개선"), + DISTRIBUTE("배포"); + + private static final String INVALID_SUBJECT_NAME = "잘못된 과목을 입력하셨습니다."; + private final String name; + + Subject(String name) { + this.name = name; + } + + public static Subject matchOf(String name) { + return Stream.of(Subject.values()) + .filter(subject -> name.equals(subject.name)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException(INVALID_SUBJECT_NAME)); + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/pairmatching/domain/type/YesOrNo.java b/src/main/java/pairmatching/domain/type/YesOrNo.java new file mode 100644 index 0000000..1f36496 --- /dev/null +++ b/src/main/java/pairmatching/domain/type/YesOrNo.java @@ -0,0 +1,22 @@ +package pairmatching.domain.type; + +import java.util.stream.Stream; + +public enum YesOrNo { + YES("네"), + NO("아니오"); + + private static final String INVALID_YES_OR_NO_NAME ="네 | 아니오 만 입력해주세요."; + private final String name; + + YesOrNo(String name) { + this.name = name; + } + + public static YesOrNo matchOf(String name) { + return Stream.of(YesOrNo.values()) + .filter(yesOrNo -> name.equals(yesOrNo.name)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException(INVALID_YES_OR_NO_NAME)); + } +} diff --git a/src/main/java/pairmatching/exception/BusinessException.java b/src/main/java/pairmatching/exception/BusinessException.java new file mode 100644 index 0000000..4f4ccee --- /dev/null +++ b/src/main/java/pairmatching/exception/BusinessException.java @@ -0,0 +1,8 @@ +package pairmatching.exception; + +public class BusinessException extends RuntimeException { + + public BusinessException(String message) { + super(message); + } +} diff --git a/src/main/java/pairmatching/exception/DuplicateException.java b/src/main/java/pairmatching/exception/DuplicateException.java new file mode 100644 index 0000000..27b5cb9 --- /dev/null +++ b/src/main/java/pairmatching/exception/DuplicateException.java @@ -0,0 +1,10 @@ +package pairmatching.exception; + +public class DuplicateException extends BusinessException { + + private static final String ERROR_MESSAGE = "중복된 값이 존재합니다."; + + public DuplicateException() { + super(ERROR_MESSAGE); + } +} diff --git a/src/main/java/pairmatching/exception/DuplicatePairException.java b/src/main/java/pairmatching/exception/DuplicatePairException.java new file mode 100644 index 0000000..954ab1c --- /dev/null +++ b/src/main/java/pairmatching/exception/DuplicatePairException.java @@ -0,0 +1,10 @@ +package pairmatching.exception; + +public class DuplicatePairException extends BusinessException { + + private static final String ERROR_MESSAGE = "페어 매칭시 중복이 있습니다"; + + public DuplicatePairException() { + super(ERROR_MESSAGE); + } +} diff --git a/src/main/java/pairmatching/exception/EmptyMatchedCrewException.java b/src/main/java/pairmatching/exception/EmptyMatchedCrewException.java new file mode 100644 index 0000000..74fff7e --- /dev/null +++ b/src/main/java/pairmatching/exception/EmptyMatchedCrewException.java @@ -0,0 +1,10 @@ +package pairmatching.exception; + +public class EmptyMatchedCrewException extends BusinessException { + + private static final String ERROR_MESSAGE = "매칭 이력이 없습니다."; + + public EmptyMatchedCrewException() { + super(ERROR_MESSAGE); + } +} diff --git a/src/main/java/pairmatching/exception/ExceedTryCountException.java b/src/main/java/pairmatching/exception/ExceedTryCountException.java new file mode 100644 index 0000000..39f9010 --- /dev/null +++ b/src/main/java/pairmatching/exception/ExceedTryCountException.java @@ -0,0 +1,10 @@ +package pairmatching.exception; + +public class ExceedTryCountException extends BusinessException { + + private static final String ERROR_MESSAGE = "%d 시도했지만 더 이상 매칭할 수 없습니다."; + + public ExceedTryCountException(int tryCount) { + super(String.format(ERROR_MESSAGE, tryCount)); + } +} diff --git a/src/main/java/pairmatching/luncher/PairMatchingGameLauncher.java b/src/main/java/pairmatching/luncher/PairMatchingGameLauncher.java new file mode 100644 index 0000000..b19c8c8 --- /dev/null +++ b/src/main/java/pairmatching/luncher/PairMatchingGameLauncher.java @@ -0,0 +1,27 @@ +package pairmatching.luncher; + +import pairmatching.luncher.controller.PairMatchingController; +import pairmatching.luncher.status.PairFeatureStatus; +import pairmatching.luncher.status.PairQuitStatus; +import pairmatching.luncher.status.PairStatus; +import pairmatching.exception.BusinessException; + +public class PairMatchingGameLauncher { + private final PairMatchingController pairMatchingController = new PairMatchingController(); + private PairStatus pairStatus = new PairFeatureStatus(); + + + public void play() { + while (pairStatus.isRunnable()) { + try { + pairStatus = pairStatus.next(pairMatchingController); + } catch(BusinessException e) { + pairMatchingController.printError(e.getMessage()); + pairStatus = new PairFeatureStatus(); + } catch (IllegalArgumentException | IllegalStateException e) { + pairMatchingController.printError(e.getMessage()); + pairStatus = new PairQuitStatus(); + } + } + } +} diff --git a/src/main/java/pairmatching/luncher/controller/PairMatchingController.java b/src/main/java/pairmatching/luncher/controller/PairMatchingController.java new file mode 100644 index 0000000..52ded32 --- /dev/null +++ b/src/main/java/pairmatching/luncher/controller/PairMatchingController.java @@ -0,0 +1,60 @@ +package pairmatching.luncher.controller; + +import java.util.Set; +import pairmatching.domain.PairMatchingGame; +import pairmatching.domain.type.Feature; +import pairmatching.domain.type.MatchingInformation; +import pairmatching.domain.type.Pair; +import pairmatching.domain.type.YesOrNo; +import pairmatching.view.InputView; +import pairmatching.view.OutputView; + +public class PairMatchingController { + + private final InputView inputView = new InputView(); + private final OutputView outputView = new OutputView(); + private final PairMatchingGame pairMatchingGame = new PairMatchingGame(); + private MatchingInformation cache; + public Feature inputFeature() { + return inputView.inputFeature(); + } + + public void requestPairMatch() { + MatchingInformation matchingInformation = inputView.inputMatchInformation(); + Set pairs = pairMatchingGame.pairMatch(matchingInformation); + outputView.showResult(pairs); + cache = matchingInformation; + } + + public void requestShowMatchedCrew() { + MatchingInformation matchingInformation = inputView.inputMatchInformation(); + Set pairs = pairMatchingGame.showPair(matchingInformation); + outputView.showResult(pairs); + } + + public void requestRePairMatch() { + MatchingInformation matchingInformation = inputView.inputReMatchInformation(); + Set pairs = pairMatchingGame.reMatch(matchingInformation); + outputView.showResult(pairs); + cache = matchingInformation; + } + + public void directMatch() { + Set pairs = pairMatchingGame.reMatch(cache); + outputView.showResult(pairs); + cache = null; + } + + public YesOrNo requestRetry() { + return inputView.inputRepeat(); + } + + public void requestReset() { + pairMatchingGame.reset(); + outputView.reset(); + } + + public void printError(String message) { + outputView.printError(message); + } +} diff --git a/src/main/java/pairmatching/luncher/status/PairFeatureStatus.java b/src/main/java/pairmatching/luncher/status/PairFeatureStatus.java new file mode 100644 index 0000000..3b1e844 --- /dev/null +++ b/src/main/java/pairmatching/luncher/status/PairFeatureStatus.java @@ -0,0 +1,32 @@ +package pairmatching.luncher.status; + +import pairmatching.luncher.controller.PairMatchingController; +import pairmatching.domain.type.Feature; +import pairmatching.luncher.status.match.PairMatchingStatus; + +public class PairFeatureStatus implements PairStatus { + + @Override + public PairStatus next(PairMatchingController context) { + Feature feature = context.inputFeature(); + return chooseStatus(feature); + } + + @Override + public boolean isRunnable() { + return true; + } + + private PairStatus chooseStatus(Feature feature) { + if (feature.equals(Feature.PAIR_MATCHING)) { + return new PairMatchingStatus(); + } + if (feature.equals(Feature.PAIR_SEARCH)) { + return new PairSearchStatus(); + } + if (feature.equals(Feature.PAIR_INITIALIZE)) { + return new PairResetStatus(); + } + return new PairQuitStatus(); + } +} diff --git a/src/main/java/pairmatching/luncher/status/PairQuitStatus.java b/src/main/java/pairmatching/luncher/status/PairQuitStatus.java new file mode 100644 index 0000000..2e31a1f --- /dev/null +++ b/src/main/java/pairmatching/luncher/status/PairQuitStatus.java @@ -0,0 +1,16 @@ +package pairmatching.luncher.status; + +import pairmatching.luncher.controller.PairMatchingController; + +public class PairQuitStatus implements PairStatus { + + @Override + public PairStatus next(PairMatchingController context) { + return null; + } + + @Override + public boolean isRunnable() { + return false; + } +} diff --git a/src/main/java/pairmatching/luncher/status/PairResetStatus.java b/src/main/java/pairmatching/luncher/status/PairResetStatus.java new file mode 100644 index 0000000..4fc866d --- /dev/null +++ b/src/main/java/pairmatching/luncher/status/PairResetStatus.java @@ -0,0 +1,17 @@ +package pairmatching.luncher.status; + +import pairmatching.luncher.controller.PairMatchingController; + +public class PairResetStatus implements PairStatus { + + @Override + public PairStatus next(PairMatchingController context) { + context.requestReset(); + return new PairFeatureStatus(); + } + + @Override + public boolean isRunnable() { + return false; + } +} diff --git a/src/main/java/pairmatching/luncher/status/PairRetryStatus.java b/src/main/java/pairmatching/luncher/status/PairRetryStatus.java new file mode 100644 index 0000000..7b42dc7 --- /dev/null +++ b/src/main/java/pairmatching/luncher/status/PairRetryStatus.java @@ -0,0 +1,23 @@ +package pairmatching.luncher.status; + +import pairmatching.domain.type.YesOrNo; +import pairmatching.luncher.controller.PairMatchingController; +import pairmatching.luncher.status.match.PairDirectMatchingStatus; +import pairmatching.luncher.status.match.PairReMatchingStatus; + +public class PairRetryStatus implements PairStatus { + + @Override + public PairStatus next(PairMatchingController context) { + YesOrNo yesOrNo = context.requestRetry(); + if (yesOrNo.equals(YesOrNo.YES)) { + return new PairDirectMatchingStatus(0); + } + return new PairReMatchingStatus(); + } + + @Override + public boolean isRunnable() { + return true; + } +} diff --git a/src/main/java/pairmatching/luncher/status/PairSearchStatus.java b/src/main/java/pairmatching/luncher/status/PairSearchStatus.java new file mode 100644 index 0000000..042de77 --- /dev/null +++ b/src/main/java/pairmatching/luncher/status/PairSearchStatus.java @@ -0,0 +1,22 @@ +package pairmatching.luncher.status; + +import pairmatching.luncher.controller.PairMatchingController; +import pairmatching.exception.EmptyMatchedCrewException; + +public class PairSearchStatus implements PairStatus { + + @Override + public PairStatus next(PairMatchingController context) { + try { + context.requestShowMatchedCrew(); + } catch(EmptyMatchedCrewException e) { + context.printError(e.getMessage()); + } + return new PairFeatureStatus(); + } + + @Override + public boolean isRunnable() { + return true; + } +} diff --git a/src/main/java/pairmatching/luncher/status/PairStatus.java b/src/main/java/pairmatching/luncher/status/PairStatus.java new file mode 100644 index 0000000..7e2b1ff --- /dev/null +++ b/src/main/java/pairmatching/luncher/status/PairStatus.java @@ -0,0 +1,10 @@ +package pairmatching.luncher.status; + +import pairmatching.luncher.controller.PairMatchingController; + +public interface PairStatus { + + PairStatus next(PairMatchingController context); + + boolean isRunnable(); +} diff --git a/src/main/java/pairmatching/luncher/status/match/PairDirectMatchingStatus.java b/src/main/java/pairmatching/luncher/status/match/PairDirectMatchingStatus.java new file mode 100644 index 0000000..568d68d --- /dev/null +++ b/src/main/java/pairmatching/luncher/status/match/PairDirectMatchingStatus.java @@ -0,0 +1,42 @@ +package pairmatching.luncher.status.match; + +import pairmatching.exception.DuplicateException; +import pairmatching.exception.DuplicatePairException; +import pairmatching.exception.ExceedTryCountException; +import pairmatching.luncher.controller.PairMatchingController; +import pairmatching.luncher.status.PairFeatureStatus; +import pairmatching.luncher.status.PairRetryStatus; +import pairmatching.luncher.status.PairStatus; + +public class PairDirectMatchingStatus implements PairStatus { + + private static final int LIMIT_MAX_TRY = 3; + private final int tryCount; + + public PairDirectMatchingStatus(int tryCount) { + validateTryCount(tryCount); + this.tryCount = tryCount; + } + @Override + public PairStatus next(PairMatchingController context) { + try { + context.directMatch(); + return new PairFeatureStatus(); + } catch (DuplicateException e) { + return new PairRetryStatus(); + } catch (DuplicatePairException e) { + return new PairDirectMatchingStatus(tryCount + 1); + } + } + + private void validateTryCount(int tryCount) { + if (tryCount >= LIMIT_MAX_TRY) { + throw new ExceedTryCountException(tryCount); + } + } + + @Override + public boolean isRunnable() { + return true; + } +} diff --git a/src/main/java/pairmatching/luncher/status/match/PairMatchingStatus.java b/src/main/java/pairmatching/luncher/status/match/PairMatchingStatus.java new file mode 100644 index 0000000..87be808 --- /dev/null +++ b/src/main/java/pairmatching/luncher/status/match/PairMatchingStatus.java @@ -0,0 +1,28 @@ +package pairmatching.luncher.status.match; + +import pairmatching.exception.DuplicateException; +import pairmatching.exception.DuplicatePairException; +import pairmatching.luncher.controller.PairMatchingController; +import pairmatching.luncher.status.PairFeatureStatus; +import pairmatching.luncher.status.PairRetryStatus; +import pairmatching.luncher.status.PairStatus; + +public class PairMatchingStatus implements PairStatus { + + @Override + public PairStatus next(PairMatchingController context) { + try { + context.requestPairMatch(); + return new PairFeatureStatus(); + } catch (DuplicateException e) { + return new PairRetryStatus(); + } catch (DuplicatePairException e) { + return new PairDirectMatchingStatus(1); + } + } + + @Override + public boolean isRunnable() { + return true; + } +} diff --git a/src/main/java/pairmatching/luncher/status/match/PairReMatchingStatus.java b/src/main/java/pairmatching/luncher/status/match/PairReMatchingStatus.java new file mode 100644 index 0000000..288b943 --- /dev/null +++ b/src/main/java/pairmatching/luncher/status/match/PairReMatchingStatus.java @@ -0,0 +1,28 @@ +package pairmatching.luncher.status.match; + +import pairmatching.exception.DuplicateException; +import pairmatching.exception.DuplicatePairException; +import pairmatching.luncher.controller.PairMatchingController; +import pairmatching.luncher.status.PairFeatureStatus; +import pairmatching.luncher.status.PairRetryStatus; +import pairmatching.luncher.status.PairStatus; + +public class PairReMatchingStatus implements PairStatus { + + @Override + public PairStatus next(PairMatchingController context) { + try { + context.requestRePairMatch(); + return new PairFeatureStatus(); + } catch (DuplicateException e) { + return new PairRetryStatus(); + } catch (DuplicatePairException e) { + return new PairDirectMatchingStatus(1); + } + } + + @Override + public boolean isRunnable() { + return true; + } +} diff --git a/src/main/java/pairmatching/util/io/ConsoleReader.java b/src/main/java/pairmatching/util/io/ConsoleReader.java new file mode 100644 index 0000000..1f39333 --- /dev/null +++ b/src/main/java/pairmatching/util/io/ConsoleReader.java @@ -0,0 +1,24 @@ +package pairmatching.util.io; + +import camp.nextstep.edu.missionutils.Console; +import pairmatching.domain.type.Feature; +import pairmatching.domain.type.MatchingInformation; +import pairmatching.domain.type.YesOrNo; + +public class ConsoleReader { + + public static Feature readFeature() { + String line = Console.readLine(); + return Feature.matchOf(line); + } + + public static MatchingInformation readInformation() { + String line = Console.readLine(); + return MatchingInformation.of(line); + } + + public static YesOrNo readYseOrNo() { + String line = Console.readLine(); + return YesOrNo.matchOf(line); + } +} diff --git a/src/main/java/pairmatching/util/io/FileNameLoader.java b/src/main/java/pairmatching/util/io/FileNameLoader.java new file mode 100644 index 0000000..0b90c30 --- /dev/null +++ b/src/main/java/pairmatching/util/io/FileNameLoader.java @@ -0,0 +1,33 @@ +package pairmatching.util.io; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import pairmatching.domain.type.Name; + + +public class FileNameLoader implements NameLoader { + + private static final String IO_ERROR = "파일을 읽는데 에러가 발생했습니다."; + + @Override + public List loadNames(String path) { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + URL url = classLoader.getResource(path); + if (url == null) { + return new ArrayList<>(); + } + List names; + try { + names = Files.readAllLines(Paths.get(url.toURI())); + } catch (IOException | URISyntaxException e) { + throw new IllegalStateException(IO_ERROR); + } + return names.stream().map(Name::new).collect(Collectors.toList()); + } +} diff --git a/src/main/java/pairmatching/util/io/NameLoader.java b/src/main/java/pairmatching/util/io/NameLoader.java new file mode 100644 index 0000000..fcacaf1 --- /dev/null +++ b/src/main/java/pairmatching/util/io/NameLoader.java @@ -0,0 +1,8 @@ +package pairmatching.util.io; + +import java.util.List; + +public interface NameLoader { + + List loadNames(String path); +} diff --git a/src/main/java/pairmatching/view/InputView.java b/src/main/java/pairmatching/view/InputView.java new file mode 100644 index 0000000..7b390e9 --- /dev/null +++ b/src/main/java/pairmatching/view/InputView.java @@ -0,0 +1,78 @@ +package pairmatching.view; + +import pairmatching.domain.type.Feature; +import pairmatching.domain.type.MatchingInformation; +import pairmatching.domain.type.YesOrNo; +import pairmatching.util.io.ConsoleReader; + +public class InputView extends View { + + private static final String REQUEST_FEATURE = "기능을 선택하세요.\n" + + "1. 페어 매칭\n" + + "2. 페어 조회\n" + + "3. 페어 초기화\n" + + "Q. 종료"; + + private static final String REQUEST_INFORMATION = "과정, 레벨, 미션을 선택하세요.\n" + + "ex) 백엔드, 레벨1, 자동차경주"; + + private static final String REQUEST_YES_OR_NO = "매칭 정보가 있습니다. 다시 매칭하시겠습니까?\n" + + "네 | 아니오"; + + private static final String SHOW_LIST = "#############################################\n" + + "과정: 백엔드 | 프론트엔드\n" + + "미션:\n" + + " - 레벨1: 자동차경주 | 로또 | 숫자야구게임\n" + + " - 레벨2: 장바구니 | 결제 | 지하철노선도\n" + + " - 레벨3: \n" + + " - 레벨4: 성능개선 | 배포\n" + + " - 레벨5: \n" + + "############################################\n"; + + public Feature inputFeature() { + while (true) { + try { + print(REQUEST_FEATURE); + return ConsoleReader.readFeature(); + } catch (IllegalArgumentException e) { + printError(e.getMessage()); + } + } + } + + public YesOrNo inputRepeat() { + while (true) { + try { + print(REQUEST_YES_OR_NO); + return ConsoleReader.readYseOrNo(); + } catch (IllegalArgumentException e) { + printError(e.getMessage()); + } + } + } + + public MatchingInformation inputMatchInformation() { + while (true) { + try { + printEmptyLine(); + print(SHOW_LIST); + print(REQUEST_INFORMATION); + return ConsoleReader.readInformation(); + } catch (IllegalArgumentException e) { + printError(e.getMessage()); + } + } + } + + public MatchingInformation inputReMatchInformation() { + while (true) { + try { + printEmptyLine(); + print(REQUEST_INFORMATION); + return ConsoleReader.readInformation(); + } catch (IllegalArgumentException e) { + printError(e.getMessage()); + } + } + } +} diff --git a/src/main/java/pairmatching/view/OutputView.java b/src/main/java/pairmatching/view/OutputView.java new file mode 100644 index 0000000..c4d1c2e --- /dev/null +++ b/src/main/java/pairmatching/view/OutputView.java @@ -0,0 +1,40 @@ +package pairmatching.view; + +import java.util.Set; +import pairmatching.domain.type.Crew; +import pairmatching.domain.type.Pair; + +public class OutputView extends View { + + private static final String RESULT_MATCHING = "페어 매칭 결과입니다."; + private static final String INITIALIZED = "초기화 되었습니다."; + + + public void showResult(Set pairs) { + print(RESULT_MATCHING); + for (Pair pair : pairs) { + print(makePairFormat(pair)); + } + printEmptyLine(); + } + + public void reset() { + print(INITIALIZED); + printEmptyLine(); + } + + @Override + public void printError(String message) { + super.printError(message); + } + + private String makePairFormat(Pair pair) { + Crew firstCrew = pair.getFirst(); + Crew secondCrew = pair.getSecond(); + Crew thirdCrew = pair.getThird(); + if (thirdCrew == null) { + return firstCrew.getName() + " : " + secondCrew.getName(); + } + return firstCrew.getName() + " : " + secondCrew.getName() + " : " + thirdCrew.getName(); + } +} diff --git a/src/main/java/pairmatching/view/View.java b/src/main/java/pairmatching/view/View.java new file mode 100644 index 0000000..42c8b47 --- /dev/null +++ b/src/main/java/pairmatching/view/View.java @@ -0,0 +1,18 @@ +package pairmatching.view; + +public class View { + + private static final String ERROR = "[ERROR]"; + + protected void print(String message) { + System.out.println(message); + } + + protected void printEmptyLine() { + System.out.println(); + } + + protected void printError(String message) { + System.out.println(ERROR + " " + message); + } +} diff --git a/src/test/java/pairmatching/code/CodeTest.java b/src/test/java/pairmatching/code/CodeTest.java index 9f779ab..162d986 100644 --- a/src/test/java/pairmatching/code/CodeTest.java +++ b/src/test/java/pairmatching/code/CodeTest.java @@ -10,7 +10,6 @@ public class CodeTest { @Test void parameterTest() { // given - // given // 메서드(public) 5개까지 제한 // 필드(클래스, 인스턴스 포함) 4개 제한 diff --git a/src/test/java/pairmatching/domain/checker/DuplicateCheckerTest.java b/src/test/java/pairmatching/domain/checker/DuplicateCheckerTest.java new file mode 100644 index 0000000..04ef8e2 --- /dev/null +++ b/src/test/java/pairmatching/domain/checker/DuplicateCheckerTest.java @@ -0,0 +1,48 @@ +package pairmatching.domain.checker; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import pairmatching.db.Database; +import pairmatching.domain.maker.OddPairMaker; +import pairmatching.domain.type.Crew; +import pairmatching.domain.type.Level; +import pairmatching.domain.type.MatchingInformation; +import pairmatching.domain.type.Pair; +import pairmatching.exception.DuplicatePairException; + +class DuplicateCheckerTest { + + private final Database database = new Database(); + private final DuplicateChecker duplicateChecker = new DefaultDuplicateChecker(database); + + + @Test + void test() { + // given + database.addAll(MatchingInformation.of("백엔드, 레벨1, 자동차경주"), makeExistPairs()); + + // expect + assertThatThrownBy( + () -> duplicateChecker.checkDuplicatePair(Level.LEVEL1.toString(), makeInputPairs())) + .isInstanceOf(DuplicatePairException.class); + } + + private List makeInputPairs() { + return makePairs("현규", "현석", "현자", "현빈", "현상", "현종", "빈석"); + } + + private List makeExistPairs() { + return makePairs("현규", "현승", "현자", "현석", "현빈", "현상", "현종"); + } + + private List makePairs(String... names) { + List crews = Stream.of(names) + .map(Crew::makeBackendCrew) + .collect(Collectors.toList()); + return new OddPairMaker().createPair(crews); + } +} \ No newline at end of file diff --git a/src/test/java/pairmatching/domain/loader/CrewLoaderTest.java b/src/test/java/pairmatching/domain/loader/CrewLoaderTest.java new file mode 100644 index 0000000..674ab42 --- /dev/null +++ b/src/test/java/pairmatching/domain/loader/CrewLoaderTest.java @@ -0,0 +1,35 @@ +package pairmatching.domain.loader; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import org.junit.jupiter.api.Test; +import pairmatching.domain.type.Course; +import pairmatching.domain.type.Crew; + +class CrewLoaderTest { + + @Test + void shouldReturnBackendCrewWhenInputBackendCourse() { + // given + CrewLoader crewLoader = new CrewLoader(); + + // when + List crews = crewLoader.getNames(Course.BACKEND); + + // then + crews.forEach(crew -> assertThat(crew.getCourse()).isEqualTo(Course.BACKEND)); + } + + @Test + void shouldReturnFrontendCrewWhenInputFrontendCourse() { + // given + CrewLoader crewLoader = new CrewLoader(); + + // when + List crews = crewLoader.getNames(Course.FRONTEND); + + // then + crews.forEach(crew -> assertThat(crew.getCourse()).isEqualTo(Course.FRONTEND)); + } +} \ No newline at end of file diff --git a/src/test/java/pairmatching/domain/maker/PairMakerTest.java b/src/test/java/pairmatching/domain/maker/PairMakerTest.java new file mode 100644 index 0000000..5f49684 --- /dev/null +++ b/src/test/java/pairmatching/domain/maker/PairMakerTest.java @@ -0,0 +1,43 @@ +package pairmatching.domain.maker; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import pairmatching.domain.type.Crew; +import pairmatching.domain.type.Pair; + +class PairMakerTest { + + @Test + void shouldReturnFinalPairLengthIs3WhenCrewsSizeAreOdd() { + //given + List crews = Stream.of("현승", "현규", "현자").map(Crew::makeBackendCrew).collect(Collectors.toList()); + PairMaker pairMaker = new OddPairMaker(); + + // when + List result = pairMaker.createPair(crews); + + // then + Pair lastPair = result.get(result.size() - 1); + assertThat(lastPair.getThird()).isNotNull(); + } + + @Test + void shouldReturnDoublePairWhenCrewsSizeIsEven() { + //given + List crews = Stream.of("현승", "현규", "현자", "현식").map(Crew::makeBackendCrew).collect(Collectors.toList()); + PairMaker pairMaker = new EvenPairMaker(); + + // when + List result = pairMaker.createPair(crews); + + // then + result.forEach((pair -> { + assertThat(pair.getFirst()).isNotNull(); + assertThat(pair.getSecond()).isNotNull(); + })); + } +} \ No newline at end of file