diff --git a/README.md b/README.md index 8dc2333686..d85d89b6b1 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,16 @@ ## controller - [x] 사다리 게임에 참여할 사람 이름을 입력받는다. +- [x] 사다리게임 실행 결과를 입력받는다. - [x] 최대 사다리 높이를 입력받는다. - [x] 전체 플레이어 이름을 출력한다. - [x] 사다리 생성 결과를 출력한다. +- [x] 사다리 게임 실행 결과를 출력한다. +- [x] 사다리 게임 진행을 완료한다. +- [x] 사다리 게임 결과를 각 참여자에게 저장한다. +- [x] 결과를 보고 싶은 사람을 입력받는다. +- [x] 참가자 개인의 결과를 출력한다. +- [x] 모든 참가자의 결과를 출력하고 프로그램을 종료한다. ## model - [x] 사다리 높이에 맞게 라인을 생성한다. @@ -32,24 +39,42 @@ - [x] 사다리 게임 전체 참여자 정보를 생성한다. - [x] 사다리 게임 전체 참여자 명단을 반환한다. + - [x] 사다리 라인에 따라 참여자 현재 위치를 바꾼다. + - [x] 사다리 타기가 완료된 참여자의 위치에 맞는 결과를 저장한다. + - [x] 참여자의 이름을 검색하여 해당 참여자의 최종 결과를 반환한다. + - [x] 존재하지 않는 이름을 검색한 경우 예외 처리한다. + - [x] 사다리 게임 참여자 정보를 저장한다. - - [x] 참여자의 이름 정보를 조회할 수 있다. + - [x] 참여자의 이름 정보가 일치하는지 확인할 수 있다. + - [x] 참여자의 개인 결과를 저장한다. + - [x] 참여자의 현재 위치와 주어진 위치 값의 동일 여부를 알 수 있다. + - [x] 참여자 간 위치를 변경한다. - [x] 사람 이름은 최대 5글자까지 부여할 수 있다. - [x] 사람 이름은 문자로만 이루어져야 한다. - [x] 사람 이름 간 비교가 가능해야 한다. +- [x] 사다리 게임 전체 결과를 관리한다. + - [x] 사다리 게임 결과 값의 수는 전체 사람 수와 동일해야 한다. + - [x] 사다리 게임 결과는 쉼표를 기준으로 입력받는다. + +- [x] 사다리 게임 결과는 최대 5글자까지 부여할 수 있다. + - [x] 참여자 이름은 쉼표를 기준으로 입력받는다. +- [x] 참여자 이름 중복 여부를 검사한다. - [x] 사다리 길이는 1이상의 자연수만 가능하다. -- [x] 사다리 발판 모양을 생성한다. +- [x] 게임 참여자의 위치는 정수로 표현한다. + - [x] 주어진 위치 값과의 동일 여부를 알 수 있다. + ## view ### 입력 - [x] 게임 참여자 이름을 입력할 수 있다. - [x] 최대 사다리 높이를 입력받는다. +- [x] 게임 결과를 입력할 수 있다. ### 출력 @@ -57,19 +82,17 @@ - [x] 출력 메세지 - [x] 참여할 사람 이름 입력 메세지 - [x] 최대 사다리 높이 입력 메세지 + - [x] 실행 결과 입력 메세지 + - [x] 사다리 결과 메세지 + - [x] 결과 출력 대상 입력 메세지 + - [x] 실행 결과 확인 메세지 - [x] 실행 결과 출력 - [x] 전체 플레이어 이름 출력 - [x] 사다리 출력 + - [x] 사다리 발판 유/무에 따른 모양을 생성한다. + - [x] 게임 결과 출력 + - [x] 단일 플레이어 결과 출력 + - [x] 모든 플레이어 결과 출력 ## Todo -- 매개변수 final화 하기 -- (출력값을 위한 Enum 사용 고려?) - - (true, 사다리 있는 칸 출력값) - - (false, 사다리 없는 칸 출력값) -- 같은 클래스의 일급 컬렉션 비교 기능 추가하기 -- toString 추가 -- makeAllPlayerTest 테스트 결과 출력 부분 - - 테스트 케이스 추가 - - 혹은 parameterizedTest 어노테이션 삭제 -- 플레이어 이름 제한 길이에 따른 step 길이 변화 기능 추가하기 diff --git a/src/main/java/Application.java b/src/main/java/Application.java index 81fad24458..e4eee64ad0 100644 --- a/src/main/java/Application.java +++ b/src/main/java/Application.java @@ -5,9 +5,8 @@ import java.util.Scanner; public class Application { - public static void main(String[] args) { - LadderGameController ladderGameController = new LadderGameController( new InputView(new Scanner(System.in)),new OutputView()); + LadderGameController ladderGameController = new LadderGameController(new InputView(new Scanner(System.in)), new OutputView()); ladderGameController.run(); } } diff --git a/src/main/java/controller/LadderGameController.java b/src/main/java/controller/LadderGameController.java index 2c65a19b80..1c8c6fdb1e 100644 --- a/src/main/java/controller/LadderGameController.java +++ b/src/main/java/controller/LadderGameController.java @@ -1,12 +1,16 @@ package controller; -import model.Ladder; -import model.LadderHeight; -import model.Names; -import model.Players; +import model.domain.Ladder; +import model.domain.LadderGame; +import model.vo.LadderHeight; +import model.vo.Name; +import model.domain.Players; +import model.vo.Result; import view.InputView; import view.OutputView; +import java.util.List; + public class LadderGameController { public InputView inputView; public OutputView outputView; @@ -18,12 +22,51 @@ public LadderGameController(InputView inputView, OutputView outputView) { public void run() { Players players = new Players(setPlayerNames()); + List results = setResults(players.size()); Ladder ladder = new Ladder(players, setLadderHeight()); + showCreateLadderResult(players, results, ladder); + LadderGame ladderGame = new LadderGame(players, ladder, results); + showGameResult(ladderGame.getPlayersAfterPlay()); + } + + private void showCreateLadderResult(Players players, List results, Ladder ladder) { + outputView.printMakeLadderResultMessage(); outputView.printAllPlayerNames(players.getAllPlayerNames()); outputView.printLadder(ladder); + outputView.printAllResults(results); + } + + private void showGameResult(Players players) { + Name name = getDesirousResultName(); + repeatShowResultUntilInputAll(players, name); + showAllResults(players); + } + + private void repeatShowResultUntilInputAll(Players players, Name name) { + Name all = new Name("all"); + while (!name.isSame(all)) { + outputView.printResultHeaderMessage(); + outputView.printResult(players.getResultOf(name)); + name = getDesirousResultName(); + } + } + + private void showAllResults(Players players) { + outputView.printResultHeaderMessage(); + players.getAllPlayerNames().forEach(name -> outputView.printNameAndResult(name, players.getResultOf(name))); + } + + private Name getDesirousResultName() { + outputView.printDesirousResultNameMessage(); + return inputView.readDesirousResultName(); + } + + private List setResults(int playerCount) { + outputView.printResultsMessage(); + return inputView.readResults(playerCount); } - private Names setPlayerNames() { + private List setPlayerNames() { outputView.printPlayerNamesMessage(); return inputView.readPlayerNames(); } diff --git a/src/main/java/model/LadderStep.java b/src/main/java/model/LadderStep.java deleted file mode 100644 index 81c4d28f93..0000000000 --- a/src/main/java/model/LadderStep.java +++ /dev/null @@ -1,18 +0,0 @@ -package model; - -public enum LadderStep { - //첫 STEP 길이는 플레이어 이름 길이 제한, 나머지 STEP 길이는 플레이어 이름 길이 제한 + 1로 설정한다. - FIRST_STEP(String.format("%5s", " |")), - EMPTY_STEP(String.format("%6s", " |")), - EXIST_STEP(String.format("%6s", "-----|")); - - private final String step; - - LadderStep(String step) { - this.step = step; - } - - public String getStep() { - return step; - } -} diff --git a/src/main/java/model/Names.java b/src/main/java/model/Names.java deleted file mode 100644 index a49c427712..0000000000 --- a/src/main/java/model/Names.java +++ /dev/null @@ -1,36 +0,0 @@ -package model; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -public class Names { - private static final String NAMES_DELIMITER = ","; - - private final List names = new ArrayList<>(); - - public Names(String inputNames) { - splitInputNames(inputNames).stream() - .map(String::strip) - .forEach(name -> names.add(new Name(name))); - } - - public Name getName(int index) { - return names.get(index); - } - - public List getValues() { - return names.stream() - .map(Name::getValue) - .collect(Collectors.toList()); - } - - public int size() { - return names.size(); - } - - private List splitInputNames(String inputNames) { - return Arrays.asList(inputNames.split(NAMES_DELIMITER)); - } -} diff --git a/src/main/java/model/Player.java b/src/main/java/model/Player.java deleted file mode 100644 index 70333b4f82..0000000000 --- a/src/main/java/model/Player.java +++ /dev/null @@ -1,13 +0,0 @@ -package model; - -public class Player { - private final Name name; - - public Player(final Name name) { - this.name = name; - } - - public String getName() { - return name.getValue(); - } -} diff --git a/src/main/java/model/Players.java b/src/main/java/model/Players.java deleted file mode 100644 index abc5328602..0000000000 --- a/src/main/java/model/Players.java +++ /dev/null @@ -1,26 +0,0 @@ -package model; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -public class Players { - private final List players = new ArrayList<>(); - - public Players(Names names) { - IntStream.range(0, names.size()).forEach(index -> - players.add(new Player(names.getName(index))) - ); - } - - public int size() { - return players.size(); - } - - public List getAllPlayerNames() { - return players.stream() - .map(Player::getName) - .collect(Collectors.toList()); - } -} diff --git a/src/main/java/model/Ladder.java b/src/main/java/model/domain/Ladder.java similarity index 78% rename from src/main/java/model/Ladder.java rename to src/main/java/model/domain/Ladder.java index 3f2cc569d8..6ba3f8835a 100644 --- a/src/main/java/model/Ladder.java +++ b/src/main/java/model/domain/Ladder.java @@ -1,4 +1,6 @@ -package model; +package model.domain; + +import model.vo.LadderHeight; import java.util.ArrayList; import java.util.List; @@ -20,6 +22,7 @@ public int getHeight() { } private void makeLadderLines(int playersSize, int height) { - IntStream.range(0, height).forEach(index -> lines.add(new Line(new RandomPointGenerator(), playersSize))); + PointGenerator pointGenerator = new RandomPointGenerator(); + IntStream.range(0, height).forEach(index -> lines.add(new Line(pointGenerator, playersSize))); } } diff --git a/src/main/java/model/domain/LadderGame.java b/src/main/java/model/domain/LadderGame.java new file mode 100644 index 0000000000..b20cdbbc2f --- /dev/null +++ b/src/main/java/model/domain/LadderGame.java @@ -0,0 +1,33 @@ +package model.domain; + +import model.vo.Result; + +import java.util.List; +import java.util.stream.IntStream; + +public class LadderGame { + private Players players; + private Ladder ladder; + private List results; + + public LadderGame(Players players, Ladder ladder, List results) { + this.players = players; + this.ladder = ladder; + this.results = results; + } + + public Players getPlayersAfterPlay() { + play(); + return players; + } + + private void play() { + IntStream.range(0, ladder.getHeight()) + .forEach(index -> playOneLine(players, ladder.getLine(index))); + players.saveAllResults(results); + } + + private void playOneLine(Players players, Line line) { + players.moveAllPlayersByLinePoints(line.getPoints()); + } +} diff --git a/src/main/java/model/Line.java b/src/main/java/model/domain/Line.java similarity index 97% rename from src/main/java/model/Line.java rename to src/main/java/model/domain/Line.java index 590ee680be..a8e07f33fa 100644 --- a/src/main/java/model/Line.java +++ b/src/main/java/model/domain/Line.java @@ -1,4 +1,4 @@ -package model; +package model.domain; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/model/domain/Player.java b/src/main/java/model/domain/Player.java new file mode 100644 index 0000000000..993e115633 --- /dev/null +++ b/src/main/java/model/domain/Player.java @@ -0,0 +1,42 @@ +package model.domain; + +import model.vo.Name; +import model.vo.Position; +import model.vo.Result; + +public class Player { + private final Name name; + private Result result; + private Position position; + + public Player(final Name name, final Position position) { + this.name = name; + this.position = position; + } + + public Name getName() { + return name; + } + + public Result getResult() { + return result; + } + + public void saveResult(final Result result) { + this.result = result; + } + + public boolean isSameName(Name other) { + return this.name.isSame(other); + } + + public boolean isSamePosition(Position other) { + return this.position.isSame(other); + } + + public void changePositionWith(Player otherPlayer) { + Position temporaryPosition = otherPlayer.position; + otherPlayer.position = this.position; + this.position = temporaryPosition; + } +} diff --git a/src/main/java/model/domain/Players.java b/src/main/java/model/domain/Players.java new file mode 100644 index 0000000000..e137c3a304 --- /dev/null +++ b/src/main/java/model/domain/Players.java @@ -0,0 +1,89 @@ +package model.domain; + +import model.vo.Name; +import model.vo.Position; +import model.vo.Result; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class Players { + private static final String NO_PLAYER_NAME_ERROR = "[ERROR] 해당 이름의 플레이어는 존재하지 않습니다."; + private static final String NO_PLAYER_AT_POSITION_ERROR = "[ERROR] 해당 포지션의 플레이어는 존재하지 않습니다."; + private final List players = new ArrayList<>(); + + public Players(List names) { + IntStream.range(0, names.size()) + .mapToObj(index -> new Player(names.get(index), new Position(index))) + .forEach(players::add); + } + + public int size() { + return players.size(); + } + + public List getAllPlayerNames() { + return players.stream() + .map(Player::getName) + .collect(Collectors.toList()); + } + + public List getAllNamesOrderedByPosition() { + return IntStream.range(0, players.size()) + .mapToObj(index -> findNameBy(new Position(index))) + .collect(Collectors.toList()); + } + + public void moveAllPlayersByLinePoints(List points) { + IntStream.range(0, points.size()) + .forEach(index -> changePlayerPositionsAtPoint(index, points.get(index))); + } + + public void saveAllResults(List results) { + List playersOrderedByPosition = findPlayersOrderedByPosition(); + IntStream.range(0, results.size()) + .forEach(index -> saveResult(playersOrderedByPosition.get(index), results.get(index))); + } + + public Result getResultOf(Name name) { + return players.stream() + .filter(player -> player.isSameName(name)) + .map(Player::getResult) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(NO_PLAYER_NAME_ERROR)); + } + + private void saveResult(Player player, Result result) { + player.saveResult(result); + } + + private List findPlayersOrderedByPosition() { + return IntStream.range(0, players.size()) + .mapToObj(Position::new) + .map(this::findPlayerBy) + .collect(Collectors.toList()); + } + + private void changePlayerPositionsAtPoint(int index, boolean point) { + if (point) { + findPlayerBy(new Position(index)).changePositionWith(findPlayerBy(new Position(index + 1))); + } + } + + private Name findNameBy(Position position) { + return players.stream() + .filter(player -> player.isSamePosition(position)) + .map(Player::getName) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(NO_PLAYER_AT_POSITION_ERROR)); + } + + private Player findPlayerBy(Position position) { + return players.stream() + .filter(player -> player.isSamePosition(position)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(NO_PLAYER_AT_POSITION_ERROR)); + } +} diff --git a/src/main/java/model/PointGenerator.java b/src/main/java/model/domain/PointGenerator.java similarity index 78% rename from src/main/java/model/PointGenerator.java rename to src/main/java/model/domain/PointGenerator.java index 58102a4758..6a801cee65 100644 --- a/src/main/java/model/PointGenerator.java +++ b/src/main/java/model/domain/PointGenerator.java @@ -1,8 +1,6 @@ -package model; +package model.domain; @FunctionalInterface public interface PointGenerator { - boolean generate(); - } diff --git a/src/main/java/model/RandomPointGenerator.java b/src/main/java/model/domain/RandomPointGenerator.java similarity index 91% rename from src/main/java/model/RandomPointGenerator.java rename to src/main/java/model/domain/RandomPointGenerator.java index 46b448563c..c5f4f4adc3 100644 --- a/src/main/java/model/RandomPointGenerator.java +++ b/src/main/java/model/domain/RandomPointGenerator.java @@ -1,4 +1,4 @@ -package model; +package model.domain; import java.util.Random; diff --git a/src/main/java/model/LadderHeight.java b/src/main/java/model/vo/LadderHeight.java similarity index 97% rename from src/main/java/model/LadderHeight.java rename to src/main/java/model/vo/LadderHeight.java index d1f6d76066..b1645345f0 100644 --- a/src/main/java/model/LadderHeight.java +++ b/src/main/java/model/vo/LadderHeight.java @@ -1,4 +1,4 @@ -package model; +package model.vo; public class LadderHeight { private static final String MINIMUM_LADDER_HEIGHT_ERROR = "[ERROR] 사다리 높이는 최소 1 이상의 값을 입력해야 합니다."; diff --git a/src/main/java/model/Name.java b/src/main/java/model/vo/Name.java similarity index 66% rename from src/main/java/model/Name.java rename to src/main/java/model/vo/Name.java index 4e014ee77f..980c08d8ea 100644 --- a/src/main/java/model/Name.java +++ b/src/main/java/model/vo/Name.java @@ -1,4 +1,4 @@ -package model; +package model.vo; import java.util.Objects; import java.util.regex.Matcher; @@ -13,42 +13,42 @@ public class Name { private final String name; public Name(String name) { - validateNameLength(name); - validateNameHasOnlyCharacters(name); + validateLength(name); + validateHasOnlyCharacters(name); this.name = name; } - public String getValue() { + public String getName() { return name; } - @Override - public int hashCode() { - return Objects.hash(name); - } - - @Override - public boolean equals(Object object) { - if (this == object) { - return true; - } - if (object == null || getClass() != object.getClass()) { - return false; - } - Name otherName = (Name) object; - return Objects.equals(name, otherName.getValue()); + public boolean isSame(Name other) { + return this.name.equals(other.name); } - private void validateNameLength(String name) { + private void validateLength(String name) { if (name.length() > MAXIMUM_NAME_LENGTH) { throw new IllegalArgumentException(String.format(MAXIMUM_NAME_LENGTH_ERROR, MAXIMUM_NAME_LENGTH)); } } - private void validateNameHasOnlyCharacters(String name) { + private void validateHasOnlyCharacters(String name) { Matcher matcher = pattern.matcher(name); if (!matcher.matches()) { throw new IllegalArgumentException(NAME_HAS_NON_ALPHABETIC_ERROR); } } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + if (other == null || getClass() != other.getClass()) return false; + Name otherName = (Name) other; + return Objects.equals(name, otherName.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } } diff --git a/src/main/java/model/vo/Position.java b/src/main/java/model/vo/Position.java new file mode 100644 index 0000000000..c1d691e22a --- /dev/null +++ b/src/main/java/model/vo/Position.java @@ -0,0 +1,13 @@ +package model.vo; + +public class Position { + private final int position; + + public Position(int position) { + this.position = position; + } + + public boolean isSame(Position other) { + return this.position == other.position; + } +} diff --git a/src/main/java/model/vo/Result.java b/src/main/java/model/vo/Result.java new file mode 100644 index 0000000000..2bedfe93b5 --- /dev/null +++ b/src/main/java/model/vo/Result.java @@ -0,0 +1,23 @@ +package model.vo; + +public class Result { + private static final int MAXIMUM_RESULT_LENGTH = 5; + private static final String MAXIMUM_RESULT_LENGTH_ERROR = "[ERROR] 게임 결과 입력값 길이는 %d 이하로만 가능합니다."; + + private final String result; + + public Result(String result) { + validateResultLength(result); + this.result = result; + } + + public String getResult() { + return result; + } + + private void validateResultLength(String result) { + if (result.length() > MAXIMUM_RESULT_LENGTH) { + throw new IllegalArgumentException(String.format(MAXIMUM_RESULT_LENGTH_ERROR, MAXIMUM_RESULT_LENGTH)); + } + } +} diff --git a/src/main/java/techcourse/jcf/mission/SimpleArrayList.java b/src/main/java/techcourse/jcf/mission/SimpleArrayList.java new file mode 100644 index 0000000000..932fdc8b74 --- /dev/null +++ b/src/main/java/techcourse/jcf/mission/SimpleArrayList.java @@ -0,0 +1,207 @@ +package techcourse.jcf.mission; + +import java.util.Arrays; +import java.util.Objects; + +public class SimpleArrayList implements SimpleList { + private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + private static final int DEFAULT_CAPACITY = 10; + private static final String[] EMPTY_ELEMENTDATA = {}; + private static final String[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; + + private String[] elementData; + private int size; + /* + * 리스트가 구조적으로 사이즈 변경이 이뤄진 횟수, iterator 순회 과정에서 변경됨이 확인되면 fast fail 하도록 할때 사용 가능 + * 보통 add, remove 메서드에서 사용 + * abstract List에 선언되어 있는데 하위 클래스에서 fast fail을 사용하지 않는다면 무시해도 된다고 함. + */ + private int modCount = 0; + + public SimpleArrayList(int initialCapacity) { + if (initialCapacity > 0) { + this.elementData = new String[initialCapacity]; + } else if (initialCapacity == 0) { + this.elementData = EMPTY_ELEMENTDATA; + } else { + throw new IllegalArgumentException("Illegal Capacity: " + + initialCapacity); + } + } + + public SimpleArrayList() { + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; + } + + @Override + public boolean add(String value) { + modCount++; + if (size == elementData.length) { + elementData = grow(); + } + elementData[size] = value; + size++; + return true; + } + + @Override + public void add(int index, String value) { + rangeCheckForAdd(index); //index 값 검증 후 이상 시 예외처리 + modCount++; + final int s; + String[] elementData; + if ((s = size) == (elementData = this.elementData).length) + elementData = grow(); + System.arraycopy(elementData, index, + elementData, index + 1, + s - index); + elementData[index] = value; + size = s + 1; + } + + private void rangeCheckForAdd(int index) { + if (index > size || index < 0) + throw new IndexOutOfBoundsException("index Error" + index); + } + + @Override + public String set(int index, String value) { + Objects.checkIndex(index, size); + String oldValue = elementData(index); + elementData[index] = value; + return oldValue; + } + + @Override + public String get(int index) { + Objects.checkIndex(index, size); + return elementData(index); + } + + //추후 제네릭 추가되야 할 듯 + private String elementData(int index) { + return elementData[index]; + } + + @Override + public boolean contains(String value) { + return indexOf(value) >= 0; + } + + @Override + public int indexOf(String value) { + return indexOfRange(value, 0, size); + } + + private int indexOfRange(String value, int start, int end) { + String[] es = elementData; + if (value == null) { + for (int i = start; i < end; i++) { + if (es[i] == null) { + return i; + } + } + } else { + for (int i = start; i < end; i++) { + if (value.equals(es[i])) { + return i; + } + } + } + return -1; + } + + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + @Override + public boolean remove(String value) { + final String[] es = elementData; + final int size = this.size(); + int i = 0; + found: + { + if (value == null) { + for (; i < size; i++) + if (es[i] == null) + break found; + } else { + for (; i < size; i++) + if (value.equals(es[i])) + break found; + } + return false; + } + fastRemove(es, i); + return false; + } + + private void fastRemove(String[] es, int i) { + modCount++; + final int newSize; + if ((newSize = size - 1) > i) + System.arraycopy(es, i+1, es, i, newSize - i); + es[size = newSize] = null; + + } + + @Override + public String remove(int index) { + Objects.checkIndex(index, size); + final String[] es = elementData; + + // 제네릭 미션 진행 시 여기에 추가 코드 들어가야 할 듯 + String oldValue = es[index]; + fastRemove(es, index); + + return oldValue; + } + + @Override + public void clear() { + modCount++; + final String[] es = elementData; + for (int to = size, i = size = 0; i < to; i++) { + es[i] = null; + } + } + + + private String[] grow() { + return grow(size + 1); + } + + private String[] grow(int minCapacity) { + return elementData = Arrays.copyOf(elementData, newCapacity(minCapacity)); + } + + private int newCapacity(int minCapacity) { + int oldCapacity = elementData.length; + int newCapacity = oldCapacity + (oldCapacity >> 1); + if (newCapacity - minCapacity <= 0) { + if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) + return Math.max(DEFAULT_CAPACITY, minCapacity); + if (minCapacity < 0) //overflow + throw new OutOfMemoryError(); + return minCapacity; + } + return (newCapacity - MAX_ARRAY_SIZE <= 0) + ? newCapacity + : hugeCapacity(minCapacity); + } + + private int hugeCapacity(int minCapacity) { + if (minCapacity < 0) // overflow + throw new OutOfMemoryError(); + return (minCapacity > MAX_ARRAY_SIZE) + ? Integer.MAX_VALUE + : MAX_ARRAY_SIZE; // 실제로는 사용되지 않는 값...? + } +} diff --git a/src/main/java/techcourse/jcf/mission/SimpleLinkedList.java b/src/main/java/techcourse/jcf/mission/SimpleLinkedList.java new file mode 100644 index 0000000000..8934cc5b09 --- /dev/null +++ b/src/main/java/techcourse/jcf/mission/SimpleLinkedList.java @@ -0,0 +1,200 @@ +package techcourse.jcf.mission; + +public class SimpleLinkedList implements SimpleList { + private int size = 0; + private Node first; + private Node last; + + SimpleLinkedList() { + } + + @Override + public boolean add(String value) { + linkLast(value); + return true; + } + + private void linkLast(String value) { + final Node l = last; + final Node newNode = new Node<>(l, value, null); + last = newNode; + if (l == null) + first = newNode; + else + l.next = newNode; + size++; + } + + @Override + public void add(int index, String value) { + checkPositionIndex(index); + + if (index == size) + linkLast(value); + else + linkBefore(value, node(index)); + } + + private void linkBefore(String value, Node succ) { + final Node pred = succ.prev; + final Node newNode = new Node<>(pred, value, succ); + succ.prev = newNode; + if (pred == null) + first = newNode; + else + pred.next = newNode; + size++; + } + + private Node node(int index) { + Node x = first; + for (int i = 0; i < index; i++) + x = x.next; + return x; + } + + private void checkPositionIndex(int index) { + if (!isPositionIndex(index)) + throw new IndexOutOfBoundsException("리스트 범위를 벗어난 인덱스입니다."); + } + + private boolean isPositionIndex(int index) { + return index >= 0 && index <= size; + } + + @Override + public String set(int index, String value) { + checkElementIndex(index); + Node x = node(index); + String oldValue = x.item; + x.item = value; + return oldValue; + } + + @Override + public String get(int index) { + checkElementIndex(index); + return node(index).item; + } + + private void checkElementIndex(int index) { + if (!isElementIndex(index)) + throw new IndexOutOfBoundsException("리스트 범위에 해당하지 않는 index 입니다."); + } + + private boolean isElementIndex(int index) { + return index >= 0 && index < size; + } + + @Override + public boolean contains(String value) { + return indexOf(value) >= 0; + } + + @Override + public int indexOf(String value) { + int index = 0; + if (value == null) { + for (Node x = first; x != null; x = x.next) { + if (x.item == null) + return index; + index++; + } + } else { + for (Node x = first; x != null; x = x.next) { + if (value.equals(x.item)) + return index; + index++; + } + } + return -1; + } + + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + @Override + public boolean remove(String value) { + if(value == null) { + for (Node x = first; x != null; x = x.next) { + if (x.item == null) { + unlink(x); + return true; + } + } + } else { + for (Node x = first; x != null; x = x.next) { + if (value.equals(x.item)) { + unlink(x); + return true; + } + } + } + return false; + } + + @Override + public String remove(int index) { + checkElementIndex(index); + return unlink(node(index)); + } + + private String unlink(Node x) { + final String element = x.item; + final Node next = x.next; + final Node prev = x.prev; + + if(prev == null) { + first = next; + } else { + prev.next = next; + x.prev = null; + } + + if(next == null) { + last = prev; + } else { + next.prev = prev; + x.next = null; + } + + x.item = null; + size--; + return element; + } + + @Override + public void clear() { + // clear를 수행하면서 노드 간 모든 링크들을 끊어주는 것은 불필요하다. + // 하지만 버려진 노드들이 한 세대 이상 남아있게 된다면 generational GC를 도와주게 된다. + // 하지만 해당 노드들에 접근 가능한 Iterator가 있다 하더라도 메모리를 해제를 보장한다. + for (Node x = first; x != null; ) { + Node next = x.next; + x.item = null; + x.next = null; + x.prev = null; + x = null; + } + first = last = null; + size = 0; + } + + private static class Node { + String item; + Node next; + Node prev; + + Node(Node prev, String element, Node next) { + this.item = element; + this.next = next; + this.prev = prev; + } + } +} diff --git a/src/main/java/techcourse/jcf/mission/SimpleList.java b/src/main/java/techcourse/jcf/mission/SimpleList.java new file mode 100644 index 0000000000..939f066257 --- /dev/null +++ b/src/main/java/techcourse/jcf/mission/SimpleList.java @@ -0,0 +1,27 @@ +package techcourse.jcf.mission; + +public interface SimpleList { + + boolean add(String value); + + void add(int index, String value); + + String set(int index, String value); + + String get(int index); + + boolean contains(String value); + + int indexOf(String value); + + int size(); + + boolean isEmpty(); + + boolean remove(String value); + + String remove(int index); + + void clear(); +} + diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 928d178943..db4a05fe6e 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -1,22 +1,78 @@ package view; -import model.LadderHeight; -import model.Names; +import model.vo.LadderHeight; +import model.vo.Name; +import model.vo.Result; +import java.util.Arrays; +import java.util.List; import java.util.Scanner; +import java.util.stream.Collectors; public class InputView { - private Scanner scanner; + private static final String DUPLICATED_NAME_ERROR = "[ERROR] 참가자 이름은 중복될 수 없습니다."; + private static final String WRONG_SIZE_RESULTS_ERROR = "[ERROR] 사다리 게임 결과 값의 개수는 전체 사람의 수와 동일해야 합니다."; + private static final String INPUT_DELIMITER = ","; + + private final Scanner scanner; public InputView(Scanner scanner) { this.scanner = scanner; } - public Names readPlayerNames() { - return new Names(scanner.nextLine()); + public List readPlayerNames() { + return createNames(scanner.nextLine()); } public LadderHeight readLadderHeight() { - return new LadderHeight(scanner.nextInt()); + return new LadderHeight(Integer.parseInt(scanner.nextLine())); + } + + public List readResults(int playerCount) { + return createResults(playerCount, scanner.nextLine()); + } + + public Name readDesirousResultName() { + return new Name(scanner.nextLine()); + } + + private List createNames(String inputNames) { + List names = splitByDelimiter(inputNames); + validateDuplicatedNames(names); + return names.stream() + .map(Name::new) + .collect(Collectors.toList()); + } + + private List createResults(int playersSize, String inputResults) { + List results = splitByDelimiter(inputResults); + validateResultsSize(playersSize, results); + return results.stream() + .map(Result::new) + .collect(Collectors.toList()); + } + + private List splitByDelimiter(String inputNames) { + return Arrays.stream(inputNames.split(INPUT_DELIMITER)) + .map(String::strip) + .collect(Collectors.toList()); + } + + private void validateDuplicatedNames(List names) { + if (names.size() != getDistinctCount(names)) { + throw new IllegalArgumentException(DUPLICATED_NAME_ERROR); + } + } + + private void validateResultsSize(int playersSize, List results) { + if (results.size() != playersSize) { + throw new IllegalArgumentException(WRONG_SIZE_RESULTS_ERROR); + } + } + + private int getDistinctCount(List names) { + return (int) names.stream() + .distinct() + .count(); } } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index b6fd4099f4..03cab26a1e 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,9 +1,9 @@ package view; -import model.Ladder; -import model.LadderStep; -import model.Line; -import model.Players; +import model.domain.Ladder; +import model.domain.Line; +import model.vo.Name; +import model.vo.Result; import java.util.List; import java.util.stream.Collectors; @@ -11,10 +11,18 @@ public class OutputView { private static final int STARTING_INDEX_OF_RIGHT_FORMATTING = 1; - private static final String LEFT_FORMATTING_TEMPLATE = "%-5s "; - private static final String RIGHT_FORMATTING_TEMPLATE = "%5s "; + private static final String LEFT_FORMATTING_TEMPLATE = "%-5s"; + private static final String RIGHT_FORMATTING_TEMPLATE = "%6s"; + private static final String FIRST_STEP = String.format("%5s", " |"); + private static final String EMPTY_STEP = String.format("%6s", " |"); + private static final String EXIST_STEP = String.format("%6s", "-----|"); private static final String PLAYER_NAME_MESSAGE = "참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)"; private static final String LADDER_HEIGHT_MESSAGE = "최대 사다리 높이는 몇 개인가요?"; + private static final String RESULTS_MESSAGE = "실행 결과를 입력하세요. (결과는 쉼표(,)로 구분하세요)"; + private static final String DESIROUS_RESULT_NAME_MESSAGE = "\n결과를 보고 싶은 사람은?"; + private static final String RESULT_HEADER = "\n실행 결과"; + private static final String LADDER_RESULT_MESSAGE = "\n사다리 결과\n"; + private static final String NAME_RESULT_DELIMITER = " : "; public void printPlayerNamesMessage() { System.out.println(PLAYER_NAME_MESSAGE); @@ -24,9 +32,26 @@ public void printLadderHeightMessage() { System.out.println(LADDER_HEIGHT_MESSAGE); } - public void printAllPlayerNames(List allPlayerNames) { - System.out.println(makeLeftFormattingFirstName(allPlayerNames) + - makeRightFormattingNamesFromSecond(allPlayerNames)); + public void printResultsMessage() { + System.out.println(RESULTS_MESSAGE); + } + + public void printResultHeaderMessage() { + System.out.println(RESULT_HEADER); + } + + public void printDesirousResultNameMessage() { + System.out.println(DESIROUS_RESULT_NAME_MESSAGE); + } + + public void printMakeLadderResultMessage() { + System.out.println(LADDER_RESULT_MESSAGE); + } + + public void printAllPlayerNames(List allPlayerNames) { + List names = unwrapNames(allPlayerNames); + System.out.println(makeLeftFormattingFirstWord(names) + + makeRightFormattingWordsFromSecond(names)); } public void printLadder(Ladder ladder) { @@ -34,27 +59,50 @@ public void printLadder(Ladder ladder) { .forEach(index -> printLadderLine(ladder.getLine(index))); } + public void printAllResults(List allResults) { + List results = unwrapResults(allResults); + System.out.println(makeLeftFormattingFirstWord(results) + makeRightFormattingWordsFromSecond(results)); + } + + public void printResult(Result result) { + System.out.println(result.getResult()); + } + + private List unwrapResults(List results) { + return results.stream().map(Result::getResult).collect(Collectors.toList()); + } + + private List unwrapNames(List names) { + return names.stream() + .map(Name::getName) + .collect(Collectors.toList()); + } + private void printLadderLine(Line line) { - StringBuilder stringBuilder = new StringBuilder(LadderStep.FIRST_STEP.getStep()); + StringBuilder stringBuilder = new StringBuilder(FIRST_STEP); line.getPoints().forEach(point -> stringBuilder.append(makeNextStep(point))); System.out.println(stringBuilder); } private String makeNextStep(Boolean point) { - String nextStep = LadderStep.EMPTY_STEP.getStep(); if (point) { - nextStep = LadderStep.EXIST_STEP.getStep(); + return EXIST_STEP; } - return nextStep; + + return EMPTY_STEP; } - private String makeLeftFormattingFirstName(List allPlayerNames) { - return String.format(LEFT_FORMATTING_TEMPLATE, allPlayerNames.get(0)); + private String makeLeftFormattingFirstWord(List words) { + return String.format(LEFT_FORMATTING_TEMPLATE, words.get(0)); } - private String makeRightFormattingNamesFromSecond(List allPlayerNames) { - return allPlayerNames.subList(STARTING_INDEX_OF_RIGHT_FORMATTING, allPlayerNames.size()).stream() + private String makeRightFormattingWordsFromSecond(List words) { + return words.subList(STARTING_INDEX_OF_RIGHT_FORMATTING, words.size()).stream() .map(name -> String.format(RIGHT_FORMATTING_TEMPLATE, name)) - .collect(Collectors.joining(" ")); + .collect(Collectors.joining("")); + } + + public void printNameAndResult(Name name, Result result) { + System.out.println(name.getName() + NAME_RESULT_DELIMITER + result.getResult()); } } diff --git a/src/test/java/model/LadderStepTest.java b/src/test/java/model/LadderStepTest.java deleted file mode 100644 index bc240cefaa..0000000000 --- a/src/test/java/model/LadderStepTest.java +++ /dev/null @@ -1,16 +0,0 @@ -package model; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -public class LadderStepTest { - @Test - @DisplayName("사다리 발판 모양을 생성 기능 테스트") - void createLadderStepTest() { - assertThat(LadderStep.FIRST_STEP.getStep()).isEqualTo(String.format("%5s", " |")); - assertThat(LadderStep.EMPTY_STEP.getStep()).isEqualTo(String.format("%6s", " |")); - assertThat(LadderStep.EXIST_STEP.getStep()).isEqualTo(String.format("%6s", "-----|")); - } -} diff --git a/src/test/java/model/NamesTest.java b/src/test/java/model/NamesTest.java deleted file mode 100644 index 03667e1a32..0000000000 --- a/src/test/java/model/NamesTest.java +++ /dev/null @@ -1,41 +0,0 @@ -package model; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatNoException; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.util.List; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -public class NamesTest { - private static Stream provideNamesInputValues() { - return Stream.of( - Arguments.of("hiiro", List.of("hiiro")), - Arguments.of("pobi, neo, conan, ocean, hiiro", - List.of("pobi", "neo", "conan", "ocean", "hiiro")) - ); - } - - @Test - @DisplayName("Names 객체 생성 성공 테스트") - void createNamesTest() { - assertThatNoException().isThrownBy(() -> new Names("pobi, neo, conan")); - } - - @ParameterizedTest - @MethodSource("provideNamesInputValues") - @DisplayName("참여자 이름은 쉼표를 기준으로 입력받는 기능 테스트") - void splitNamesByCommas(String inputValue, List expectedResult) { - //given - Names names = new Names(inputValue); - - //then - assertThat(names.getValues()).isEqualTo(expectedResult); - } -} diff --git a/src/test/java/model/PlayerTest.java b/src/test/java/model/PlayerTest.java deleted file mode 100644 index caf549d728..0000000000 --- a/src/test/java/model/PlayerTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package model; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.assertj.core.api.Assertions; - -public class PlayerTest { - @Test - @DisplayName("Player 객체 생성 성공 테스트") - void createPlayerTest() { - Assertions.assertThatNoException().isThrownBy(() -> new Player(new Name("pobi"))); - } - - @Test - @DisplayName("참여자의 이름 정보를 조회할 수 있는 기능 테스트") - void getPlayerNameTest() { - //given - Player player = new Player(new Name("pobi")); - - //when - String result = player.getName(); - - //then - assertThat(result).isEqualTo("pobi"); - } -} diff --git a/src/test/java/model/PlayersTest.java b/src/test/java/model/PlayersTest.java deleted file mode 100644 index 0c21507356..0000000000 --- a/src/test/java/model/PlayersTest.java +++ /dev/null @@ -1,30 +0,0 @@ -package model; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatNoException; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import java.util.List; - -public class PlayersTest { - @Test - @DisplayName("Players 객체 생성 성공 테스트") - void createPlayersTest() { - Names names = new Names("pobi, neo, hiiro"); - assertThatNoException().isThrownBy(() -> new Players(names)); - } - - @Test - @DisplayName("사다리 게임 전체 참여자 명단을 반환하는 기능 테스트") - void getAllPlayerNamesTest() { - //given - Players players = new Players(new Names("pobi, neo, hiiro")); - - //when - List result = players.getAllPlayerNames(); - - //then - assertThat(result).isEqualTo(List.of("pobi", "neo", "hiiro")); - } -} diff --git a/src/test/java/model/LadderTest.java b/src/test/java/model/domain/LadderTest.java similarity index 56% rename from src/test/java/model/LadderTest.java rename to src/test/java/model/domain/LadderTest.java index c7c146235d..25ffc62215 100644 --- a/src/test/java/model/LadderTest.java +++ b/src/test/java/model/domain/LadderTest.java @@ -1,33 +1,38 @@ -package model; +package model.domain; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatNoException; +import model.vo.LadderHeight; +import model.vo.Name; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import java.util.List; + public class LadderTest { + List names; + Players players; + LadderHeight ladderHeight; + + @BeforeEach + void beforeTest() { + names = List.of(new Name("pobi"), new Name("neo"), new Name("hiiro")); + players = new Players(names); + ladderHeight = new LadderHeight(5); + } + @Test @DisplayName("Ladder 객체 생성 성공 테스트") void createLadderTest() { - //given - Names names = new Names("pobi, neo, hiiro"); - Players players = new Players(names); - LadderHeight ladderHeight = new LadderHeight(5); - - //then - assertThatNoException().isThrownBy(() -> { - new Ladder(players, ladderHeight); - }); + assertThatNoException().isThrownBy(() -> new Ladder(players, ladderHeight)); } @Test @DisplayName("사다리 높이에 맞게 라인을 생성하는 기능 테스트 ") void makeLinesByLadderHeightTest() { //given - Names names = new Names("pobi, neo, hiiro"); - Players players = new Players(names); - LadderHeight ladderHeight = new LadderHeight(5); Ladder ladder = new Ladder(players, ladderHeight); //then diff --git a/src/test/java/model/LineTest.java b/src/test/java/model/domain/LineTest.java similarity index 98% rename from src/test/java/model/LineTest.java rename to src/test/java/model/domain/LineTest.java index cfd721adc1..4e2d0f7888 100644 --- a/src/test/java/model/LineTest.java +++ b/src/test/java/model/domain/LineTest.java @@ -1,4 +1,4 @@ -package model; +package model.domain; import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; diff --git a/src/test/java/model/domain/PlayerTest.java b/src/test/java/model/domain/PlayerTest.java new file mode 100644 index 0000000000..dde20f908c --- /dev/null +++ b/src/test/java/model/domain/PlayerTest.java @@ -0,0 +1,81 @@ +package model.domain; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import model.vo.Name; +import model.vo.Position; +import model.vo.Result; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +/** + * 사다리 게임 참여자 정보를 가지는 클래스. + * 원시타입 데이터의 getter는 테스트하지 않는다. + */ +public class PlayerTest { + @Test + @DisplayName("Player 객체 생성 성공 테스트") + void createPlayerTest() { + Assertions.assertThatNoException().isThrownBy(() -> new Player(new Name("pobi"), new Position(0))); + } + + @Test + @DisplayName("참여자의 이름 정보가 일치하는지 확인할 수 있는 기능 테스트") + void isPlayerNameTest() { + //given + Player player = new Player(new Name("pobi"), new Position(0)); + + //when + boolean result = player.isSameName(new Name("pobi")); + + //then + assertThat(result).isEqualTo(true); + } + + @Test + @DisplayName("사다리 게임의 개인 결과를 저장하는 기능 테스트") + void savePlayerResultTest() { + //given + Player player = new Player(new Name("pobi"), new Position(0)); + Result playerResult = new Result("5000"); + + //when + player.saveResult(playerResult); + Result result = player.getResult(); + + //then + assertThat(result.getResult()).isEqualTo("5000"); + } + + @ParameterizedTest + @CsvSource(value = {"0:0:true", "0:1:false"}, delimiter = ':') + @DisplayName("참여자의 현재 위치와 주어진 위치 값의 동일 여부 확인 기능 테스트") + void savePlayerPositionTest(int playerPosition, int inputNumber, boolean expected) { + //given + Player player = new Player(new Name("hiiro"), new Position(playerPosition)); + + //when + boolean result = player.isSamePosition(new Position(inputNumber)); + + //then + assertThat(result).isEqualTo(expected); + } + + @Test + @DisplayName("참여자 간 위치를 변경하는 기능 테스트") + void changePlayerPositionTest() { + //given + Player hiiro = new Player(new Name("hiiro"), new Position(0)); + Player ocean = new Player(new Name("ocean"), new Position(1)); + + //when + hiiro.changePositionWith(ocean); + + //then + assertThat(hiiro.isSamePosition(new Position(1))).isTrue(); + assertThat(ocean.isSamePosition(new Position(0))).isTrue(); + } +} diff --git a/src/test/java/model/domain/PlayersTest.java b/src/test/java/model/domain/PlayersTest.java new file mode 100644 index 0000000000..21c55d92fc --- /dev/null +++ b/src/test/java/model/domain/PlayersTest.java @@ -0,0 +1,106 @@ +package model.domain; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatNoException; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +import model.vo.Name; +import model.vo.Result; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.stream.Collectors; + +public class PlayersTest { + private static final String NO_PLAYER_NAME_ERROR = "[ERROR] 해당 이름의 플레이어는 존재하지 않습니다."; + + @Test + @DisplayName("Players 객체 생성 성공 테스트") + void createPlayersTest() { + assertThatNoException().isThrownBy(() -> new Players(List.of(new Name("pobi"), new Name("neo"), new Name("hiiro")))); + } + + @Test + @DisplayName("사다리 게임 전체 참여자 명단을 반환하는 기능 테스트") + void getAllPlayerNamesTest() { + //given + Players players = new Players(List.of(new Name("pobi"), new Name("neo"), new Name("hiiro"))); + + //when + List names = players.getAllPlayerNames(); + List result = names.stream() + .map(Name::getName) + .collect(Collectors.toList()); + + //then + assertThat(result).isEqualTo(List.of("pobi", "neo", "hiiro")); + } + + @Test + @DisplayName("사다리 라인에 따라 참여자 현재 위치를 바꾸는 기능 테스트") + void moveAllPlayersByLinePointsTest() { + //given + Players players = new Players(List.of(new Name("pobi"), new Name("neo"), new Name("hiiro"), new Name("ocean"))); + List> givenLines = List.of(List.of(true, false, true), List.of(false, true, false), + List.of(true, false, false), List.of(true, false, true)); + + //when + givenLines.forEach(players::moveAllPlayersByLinePoints); + + List names = players.getAllNamesOrderedByPosition(); + List result = names.stream() + .map(Name::getName) + .collect(Collectors.toList()); + + //then + assertThat(result).isEqualTo(List.of("neo", "ocean", "hiiro", "pobi")); + } + + @Test + @Disabled("참여자 이름을 검색하여 최종결과 반환 기능 테스트 내용과 중복되므로 비활성화한다.") + @DisplayName("사다리 타기가 완료된 참여자의 위치에 맞는 결과 저장 기능 테스트") + void saveResultByPositionTest() {} + + @Test + @DisplayName("참여자 이름을 검색하여 최종 결과 반환 기능 테스트") + void getResultByNameTest() { + //given + Players players = new Players(List.of(new Name("pobi"), new Name("neo"), new Name("hiiro"), new Name("ocean"))); + List> givenLines = List.of(List.of(true, false, true), List.of(false, true, false), + List.of(true, false, false), List.of(true, false, true)); + List results = List.of(new Result("꽝"), new Result("5000"), new Result("꽝"), new Result("3000")); + + //when + givenLines.forEach(players::moveAllPlayersByLinePoints); + players.saveAllResults(results); + + Result pobiResult = players.getResultOf(new Name("pobi")); + Result neoResult = players.getResultOf(new Name("neo")); + Result hiiroResult = players.getResultOf(new Name("hiiro")); + Result oceanResult = players.getResultOf(new Name("ocean")); + + //then + assertThat(pobiResult.getResult()).isEqualTo("3000"); + assertThat(neoResult.getResult()).isEqualTo("꽝"); + assertThat(hiiroResult.getResult()).isEqualTo("꽝"); + assertThat(oceanResult.getResult()).isEqualTo("5000"); + } + + @Test + @DisplayName("존재하지 않는 이름을 검색한 경우 예외 처리 기능 테스트") + void throwExceptionNoNameErrorTest() { + //given + Players players = new Players(List.of(new Name("pobi"), new Name("neo"), new Name("hiiro"), new Name("ocean"))); + List results = List.of(new Result("꽝"), new Result("5000"), new Result("꽝"), new Result("3000")); + + //when + players.saveAllResults(results); + + //then + assertThatThrownBy(() -> players.getResultOf(new Name("kevin"))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(NO_PLAYER_NAME_ERROR); + } +} diff --git a/src/test/java/model/LadderHeightTest.java b/src/test/java/model/vo/LadderHeightTest.java similarity index 84% rename from src/test/java/model/LadderHeightTest.java rename to src/test/java/model/vo/LadderHeightTest.java index 1196f49cd7..59b0a946e3 100644 --- a/src/test/java/model/LadderHeightTest.java +++ b/src/test/java/model/vo/LadderHeightTest.java @@ -1,12 +1,15 @@ -package model; +package model.vo; import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +/** + * 사다리 높이 값을 Wrapping하는 클래스. + * 원시타입 데이터의 getter는 테스트하지 않는다. + */ public class LadderHeightTest { private static final String MINIMUM_LADDER_HEIGHT_ERROR = "[ERROR] 사다리 높이는 최소 1 이상의 값을 입력해야 합니다."; @@ -23,9 +26,4 @@ void validateLadderHeightTest() { .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining(MINIMUM_LADDER_HEIGHT_ERROR); } - - @Test - @Disabled("단순 getter 메서드는 테스트하지 않는다.") - void getHeight() { - } } diff --git a/src/test/java/model/NameTest.java b/src/test/java/model/vo/NameTest.java similarity index 71% rename from src/test/java/model/NameTest.java rename to src/test/java/model/vo/NameTest.java index 98e27a1270..b14736ecda 100644 --- a/src/test/java/model/NameTest.java +++ b/src/test/java/model/vo/NameTest.java @@ -1,15 +1,19 @@ -package model; +package model.vo; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; +/** + * 플레이어 이름을 Wrapping하는 클래스. + * 원시타입 데이터의 getter는 테스트하지 않는다. + */ public class NameTest { private static final int MAXIMUM_NAME_LENGTH = 5; private static final String MAXIMUM_NAME_LENGTH_ERROR = "[ERROR] 사람 최대 이름 길이는 %d 이하로만 가능합니다."; @@ -32,25 +36,24 @@ void limitPlayerNameLengthTest() { @ParameterizedTest @ValueSource(strings = {"1234", " ", "@#$@", "abs@#"}) @DisplayName("사람 이름은 문자로만 이루어져 있는지 확인하는 기능 테스트") - void validateNameHasOnlyCharacters(String inputName) { + void validateNameHasOnlyCharactersTest(String inputName) { assertThatThrownBy(() -> new Name(inputName)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining(NAME_HAS_NON_ALPHABETIC_ERROR); } - @Test - @Disabled("단순 getter 메서드는 테스트하지 않는다.") - void getValueTest() { - } - - @Test - @DisplayName("사람 이름 간 비교하는 기능 테스트") - void compareNameTest() { + @ParameterizedTest + @CsvSource(value = {"pobi:pobi:true", "pobi:neo:false"}, delimiter = ':') + @DisplayName("사람 이름 간 비교 기능 테스트") + void isSameTest(String name1, String name2, boolean answer) { //given - Name name = new Name("pobi"); + Name name = new Name(name1); + Name otherName = new Name(name2); + + //when + boolean result = name.isSame(otherName); //then - assertThat(name).isEqualTo(new Name("pobi")); - assertThat(name).isNotEqualTo(new Name("neo")); + assertThat(result).isEqualTo(answer); } } diff --git a/src/test/java/model/vo/PositionTest.java b/src/test/java/model/vo/PositionTest.java new file mode 100644 index 0000000000..ea7b14b454 --- /dev/null +++ b/src/test/java/model/vo/PositionTest.java @@ -0,0 +1,36 @@ +package model.vo; + +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +/** + * 플레이어 위치를 Wrapping하는 클래스. + * 원시타입 데이터의 getter는 테스트하지 않는다. + */ +public class PositionTest { + @Test + @DisplayName("Position 객체 생성 성공 테스트") + void createPositionTest() { + assertThatNoException().isThrownBy(() -> new Position(1)); + } + + @ParameterizedTest + @CsvSource(value = {"0:false", "1:true"}, delimiter = ':') + @DisplayName("주어진 위치 값과의 동일 여부를 알 수 있는 기능 테스트") + void isSamePositionValueTest(int testCase, boolean expected) { + //given + Position existingPosition = new Position(1); + Position comparePosition = new Position(testCase); + + //when + boolean result = existingPosition.isSame(comparePosition); + + //then + assertThat(result).isEqualTo(expected); + } +} diff --git a/src/test/java/model/vo/ResultTest.java b/src/test/java/model/vo/ResultTest.java new file mode 100644 index 0000000000..4bc6ada297 --- /dev/null +++ b/src/test/java/model/vo/ResultTest.java @@ -0,0 +1,30 @@ +package model.vo; + +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * 사다리 게임 결과 값을 Wrapping하는 클래스. + * 원시타입 데이터의 getter는 테스트하지 않는다. + */ +public class ResultTest { + private static final int MAXIMUM_RESULT_LENGTH = 5; + private static final String MAXIMUM_RESULT_LENGTH_ERROR = "[ERROR] 게임 결과 입력값 길이는 %d 이하로만 가능합니다."; + + @Test + @DisplayName("Result 객체 생성 성공 테스트") + void createResultTest() { + assertThatNoException().isThrownBy(() -> new Result("10000")); + } + + @Test + @DisplayName("결과값 길이 제한으로 인한 Result 객체 생성 실패 테스트") + void limitResultLengthTest() { + assertThatThrownBy(() -> new Result("5가 넘는 길이 결과")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(String.format(MAXIMUM_RESULT_LENGTH_ERROR, MAXIMUM_RESULT_LENGTH)); + } +} diff --git a/src/test/java/techcourse/jcf/mission/ListStudy.java b/src/test/java/techcourse/jcf/mission/ListStudy.java new file mode 100644 index 0000000000..bde9ef04a3 --- /dev/null +++ b/src/test/java/techcourse/jcf/mission/ListStudy.java @@ -0,0 +1,42 @@ +package techcourse.jcf.mission; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.LinkedList; + +public class ListStudy { + @Test + public void arrayList() { + ArrayList values = new ArrayList<>(); + values.add("first"); + values.add("second"); + + assertThat(values.add("third")).isTrue(); // 세 번째 값을 추가한다. + assertThat(values.size()).isEqualTo(3); // list의 크기를 구한다. + assertThat(values.get(0)).isEqualTo("first"); // 첫 번째 값을 찾는다. + assertThat(values.contains("first")).isTrue(); // "first" 값이 포함되어 있는지를 확인한다. + assertThat(values.remove(0)).isEqualTo("first"); // 첫 번째 값을 삭제한다. + assertThat(values.size()).isEqualTo(2); // 값이 삭제 됐는지 확인한다. + + // TODO values에 담긴 모든 값을 출력한다. + } + + @Test + public void linkedList() { + LinkedList values = new LinkedList<>(); + values.add("first"); + values.add("second"); + + assertThat(values.add("third")).isTrue(); // 세 번째 값을 추가한다. + assertThat(values.size()).isEqualTo(3); // list의 크기를 구한다. + assertThat(values.get(0)).isEqualTo("first"); // 첫 번째 값을 찾는다. + assertThat(values.contains("first")).isTrue(); // "first" 값이 포함되어 있는지를 확인한다. + assertThat(values.remove(0)).isEqualTo("first"); // 첫 번째 값을 삭제한다. + assertThat(values.size()).isEqualTo(2); // 값이 삭제 됐는지 확인한다. + + // TODO values에 담긴 모든 값을 출력한다. + } +} diff --git a/src/test/java/techcourse/jcf/mission/SimpleArrayListTest.java b/src/test/java/techcourse/jcf/mission/SimpleArrayListTest.java new file mode 100644 index 0000000000..c9d6806ea2 --- /dev/null +++ b/src/test/java/techcourse/jcf/mission/SimpleArrayListTest.java @@ -0,0 +1,82 @@ +package techcourse.jcf.mission; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + + +public class SimpleArrayListTest { + static SimpleArrayList values; + + @BeforeEach + void beforeTest() { + values = new SimpleArrayList(); + values.add("first"); + values.add("second"); + } + + @Test + void add() { + assertThat(values.add("third")).isTrue(); + } + + @Test + void testAdd() { + values.add(1, "firstPointFive"); + assertThat(values.get(1)).isEqualTo("firstPointFive"); + } + + @Test + void set() { + values.set(0, "modified First"); + assertThat(values.get(0)).isEqualTo("modified First"); + } + + @Test + void get() { + assertThat(values.get(1)).isEqualTo("second"); + } + + @Test + void contains() { + assertThat(values.contains("first")).isTrue(); + assertThat(values.contains("fourth")).isFalse(); + } + + @Test + void indexOf() { + assertThat(values.indexOf("second")).isEqualTo(1); + } + + @Test + void size() { + values.add("third"); + assertThat(values.size()).isEqualTo(3); + } + + @Test + void isEmpty() { + SimpleArrayList newValues = new SimpleArrayList(); + assertThat(newValues.isEmpty()).isTrue(); + } + + @Test + void remove() { + values.remove("first"); + assertThat(values.get(0)).isEqualTo("second"); + } + + @Test + void testRemove() { + values.add("third"); + values.remove(1); + assertThat(values.get(1)).isEqualTo("third"); + } + + @Test + void clear() { + values.clear(); + assertThat(values.size()).isEqualTo(0); + } +} diff --git a/src/test/java/techcourse/jcf/mission/SimpleLinkedListTest.java b/src/test/java/techcourse/jcf/mission/SimpleLinkedListTest.java new file mode 100644 index 0000000000..58fc617974 --- /dev/null +++ b/src/test/java/techcourse/jcf/mission/SimpleLinkedListTest.java @@ -0,0 +1,81 @@ +package techcourse.jcf.mission; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class SimpleLinkedListTest { + static SimpleLinkedList values; + + @BeforeEach + void beforeTest() { + values = new SimpleLinkedList(); + values.add("first"); + values.add("second"); + } + + @Test + void add() { + assertThat(values.add("third")).isTrue(); + } + + @Test + void testAdd() { + values.add(1, "firstPointFive"); + assertThat(values.get(1)).isEqualTo("firstPointFive"); + } + + @Test + void set() { + values.set(0, "modified First"); + assertThat(values.get(0)).isEqualTo("modified First"); + } + + @Test + void get() { + assertThat(values.get(1)).isEqualTo("second"); + } + + @Test + void contains() { + assertThat(values.contains("first")).isTrue(); + assertThat(values.contains("fourth")).isFalse(); + } + + @Test + void indexOf() { + assertThat(values.indexOf("second")).isEqualTo(1); + } + + @Test + void size() { + values.add("third"); + assertThat(values.size()).isEqualTo(3); + } + + @Test + void isEmpty() { + SimpleArrayList newValues = new SimpleArrayList(); + assertThat(newValues.isEmpty()).isTrue(); + } + + @Test + void remove() { + values.remove("first"); + assertThat(values.get(0)).isEqualTo("second"); + } + + @Test + void testRemove() { + values.add("third"); + values.remove(1); + assertThat(values.get(1)).isEqualTo("third"); + } + + @Test + void clear() { + values.clear(); + assertThat(values.size()).isEqualTo(0); + } +}