diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000..65629b5d
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,13 @@
+## 작업 요약
+
+- 요구사항에 맞게 작업할 내용을 간략하게 정리합니다.
+
+## 작업 내용
+
+- [ ] 작업 1
+- [ ] 작업 2
+
+
+
+## 참고 자료
+- 기술 문서나 테스트 결과 등 작업에 관련된 참고 자료를 정리합니다.
diff --git a/README.md b/README.md
index b722ac00..656aac1b 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,17 @@
# 미션 - 워들
+## 👥 페어 멤버
+| ALEX |HOLDEN |
+| --- | --- |
+[](https://github.com/giibeom)| [](https://github.com/seung-00) |
+
+* [Alex 님의 repository](https://github.com/giibeom/study-java-wordle) 에서 페어 프로그래밍을 진행했습니다.
+ * 이 repository 는 해당 repository 를 미러링했습니다.
+* 진행 방식은 해당 [repository wiki](https://github.com/giibeom/study-java-wordle/wiki) 에 정리되어 있습니다.
+
+
+---
+
## 🔍 진행 방식
- 미션은 **기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항** 세 가지로 구성되어 있다.
diff --git a/src/main/java/woowaapplication/pair/game/Application.java b/src/main/java/woowaapplication/pair/game/Application.java
new file mode 100644
index 00000000..3415cdf0
--- /dev/null
+++ b/src/main/java/woowaapplication/pair/game/Application.java
@@ -0,0 +1,11 @@
+package woowaapplication.pair.game;
+
+import woowaapplication.pair.game.wordle.WordleGame;
+
+public class Application {
+
+ public static void main(String[] args) {
+ WordleGame wordleGame = new WordleGame();
+ wordleGame.start();
+ }
+}
diff --git a/src/main/java/woowaapplication/pair/game/util/FileReader.java b/src/main/java/woowaapplication/pair/game/util/FileReader.java
new file mode 100644
index 00000000..d2798493
--- /dev/null
+++ b/src/main/java/woowaapplication/pair/game/util/FileReader.java
@@ -0,0 +1,20 @@
+package woowaapplication.pair.game.util;
+
+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.List;
+import woowaapplication.pair.game.wordle.exception.ReadFileException;
+
+public class FileReader {
+
+ public static List readLinesFromFile(URL fileUrl) {
+ try {
+ return Files.readAllLines(Paths.get(fileUrl.toURI()));
+ } catch (IOException | URISyntaxException e) {
+ throw new ReadFileException();
+ }
+ }
+}
diff --git a/src/main/java/woowaapplication/pair/game/util/KeywordValidator.java b/src/main/java/woowaapplication/pair/game/util/KeywordValidator.java
new file mode 100644
index 00000000..7b230a2f
--- /dev/null
+++ b/src/main/java/woowaapplication/pair/game/util/KeywordValidator.java
@@ -0,0 +1,28 @@
+package woowaapplication.pair.game.util;
+
+import woowaapplication.pair.game.wordle.exception.InvalidInputKeywordException;
+
+public class KeywordValidator {
+
+ public static void validate(String keyword, int lengthLimit) {
+ if (keyword == null) {
+ throw new InvalidInputKeywordException();
+ }
+
+ if (!isAlphabetic(keyword)) {
+ throw new InvalidInputKeywordException();
+ }
+
+ if (!isLengthValid(keyword, lengthLimit)) {
+ throw new InvalidInputKeywordException();
+ }
+ }
+
+ private static boolean isAlphabetic(String keyword) {
+ return keyword.matches("[a-zA-Z]+");
+ }
+
+ private static boolean isLengthValid(String keyword, int length) {
+ return keyword.length() == length;
+ }
+}
diff --git a/src/main/java/woowaapplication/pair/game/wordle/WordleGame.java b/src/main/java/woowaapplication/pair/game/wordle/WordleGame.java
new file mode 100644
index 00000000..647f64c5
--- /dev/null
+++ b/src/main/java/woowaapplication/pair/game/wordle/WordleGame.java
@@ -0,0 +1,88 @@
+package woowaapplication.pair.game.wordle;
+
+import woowaapplication.pair.game.util.KeywordValidator;
+import woowaapplication.pair.game.wordle.domain.Coin;
+import woowaapplication.pair.game.wordle.domain.WordleBlock;
+import woowaapplication.pair.game.wordle.dto.GameResultDto;
+import woowaapplication.pair.game.wordle.exception.InvalidAnswerKeywordException;
+import woowaapplication.pair.game.wordle.exception.InvalidInputKeywordException;
+import woowaapplication.pair.game.wordle.exception.OutOfChanceException;
+import woowaapplication.pair.game.wordle.exception.ReadFileException;
+
+public class WordleGame {
+ public static final int TOTAL_CHANCE = 6;
+
+ public static final int KEYWORD_LENGTH = 5;
+
+ private String answerKeyword;
+
+private final WordleGameStorage wordleGameStorage;
+
+ private final WordleGameIO wordleGameIO;
+
+ public WordleGame() {
+ Coin coin = Coin.of(this.TOTAL_CHANCE);
+ this.wordleGameStorage = WordleGameStorage.of(coin);
+ this.wordleGameIO = WordleGameIO.of();
+ }
+
+ public static WordleGame of() {
+ return new WordleGame();
+ }
+
+ public void start() {
+ ready();
+
+ while (!wordleGameStorage.isGameEnd()) {
+ try {
+ run();
+ } catch (InvalidInputKeywordException e) {
+ System.out.println(e.getMessage());
+ } catch (InvalidAnswerKeywordException e) {
+ System.out.println(e.getMessage());
+ break;
+ } catch (ReadFileException e) {
+ System.out.println(e.getMessage());
+ break;
+ } catch (OutOfChanceException e) {
+ System.out.println(e.getMessage());
+ break;
+ }
+ }
+
+ terminate();
+ }
+
+ private void run() {
+ String inputKeyword = WordleGameIO.printInputKeyword();
+
+ KeywordValidator.validate(inputKeyword, KEYWORD_LENGTH);
+
+ GameResultDto gameResultDto = playRound(inputKeyword, answerKeyword);
+
+ wordleGameIO.printResult(gameResultDto);
+ }
+
+ public GameResultDto playRound(String inputKeyword, String answerKeyword) {
+ WordleBlock[] wordleBlocks = WordleBlock.from(inputKeyword, answerKeyword);
+
+ wordleGameStorage.submit(wordleBlocks);
+
+ return GameResultDto.of(
+ wordleGameStorage.convertHistoryToEmoji(),
+ TOTAL_CHANCE,
+ wordleGameStorage.getRestChance(),
+ wordleGameStorage.isGameClear()
+ );
+ }
+
+ private void ready() {
+ WordleGameIO.printReady();
+
+ answerKeyword = WordleGameAnswerGenerator.getAnswerKeyword();
+ }
+
+ private void terminate() {
+ WordleGameIO.printTerminate();
+ }
+}
diff --git a/src/main/java/woowaapplication/pair/game/wordle/WordleGameAnswerGenerator.java b/src/main/java/woowaapplication/pair/game/wordle/WordleGameAnswerGenerator.java
new file mode 100644
index 00000000..8441e39f
--- /dev/null
+++ b/src/main/java/woowaapplication/pair/game/wordle/WordleGameAnswerGenerator.java
@@ -0,0 +1,52 @@
+package woowaapplication.pair.game.wordle;
+
+import java.net.URL;
+import java.time.LocalDate;
+import java.time.Period;
+import java.util.List;
+
+import woowaapplication.pair.game.util.FileReader;
+import woowaapplication.pair.game.wordle.exception.InvalidAnswerKeywordException;
+
+public class WordleGameAnswerGenerator {
+
+ public static final String WORDS_FILE_NAME = "words.txt";
+
+ private static final LocalDate standardDate = LocalDate.of(2021, 6, 19);
+
+ public static String getAnswerKeyword(LocalDate comparisonDate) {
+ List keywords = readKeywordsFromFile();
+
+ if (keywords.isEmpty()) {
+ throw new InvalidAnswerKeywordException();
+ }
+
+ int index = findAnswerKeywordIndex(keywords, comparisonDate);
+
+ return keywords.get(index);
+ }
+
+ public static String getAnswerKeyword() {
+ List keywords = readKeywordsFromFile();
+ LocalDate comparisonDate = LocalDate.now();
+
+ if (keywords.isEmpty()) {
+ throw new InvalidAnswerKeywordException();
+ }
+
+ int index = findAnswerKeywordIndex(keywords, comparisonDate);
+
+ return keywords.get(index);
+ }
+
+ public static List readKeywordsFromFile() {
+ URL resource = WordleGameAnswerGenerator.class.getClassLoader().getResource(WORDS_FILE_NAME);
+ return FileReader.readLinesFromFile(resource);
+ }
+
+ public static int findAnswerKeywordIndex(List keywords, LocalDate comparisonDate) {
+ Period period = Period.between(standardDate, comparisonDate);
+
+ return (period.getDays() % keywords.size()) - 1;
+ }
+}
diff --git a/src/main/java/woowaapplication/pair/game/wordle/WordleGameIO.java b/src/main/java/woowaapplication/pair/game/wordle/WordleGameIO.java
new file mode 100644
index 00000000..d892d4a6
--- /dev/null
+++ b/src/main/java/woowaapplication/pair/game/wordle/WordleGameIO.java
@@ -0,0 +1,41 @@
+package woowaapplication.pair.game.wordle;
+
+import java.util.Scanner;
+
+import woowaapplication.pair.game.wordle.dto.GameResultDto;
+
+public class WordleGameIO {
+
+
+ public WordleGameIO() {
+ }
+
+ public static void printReady() {
+ System.out.println("WORDLE을 6번 만에 맞춰 보세요.");
+ System.out.println("시도의 결과는 타일의 색 변화로 나타납니다.");
+
+ }
+
+ public static String printInputKeyword() {
+ System.out.println("5글자의 단어를 입력해주세요.");
+
+ Scanner sc = new Scanner(System.in);
+ return sc.nextLine();
+ }
+
+ public static void printTerminate() {
+ System.out.println("게임이 종료되었습니다.");
+ }
+
+ public void printResult(GameResultDto gameResultDto) {
+ System.out.println(gameResultDto.getHistory());
+
+ if (gameResultDto.isClear()) {
+ System.out.println(gameResultDto.getChance());
+ }
+ }
+
+ public static WordleGameIO of() {
+ return new WordleGameIO();
+ }
+}
diff --git a/src/main/java/woowaapplication/pair/game/wordle/WordleGameStorage.java b/src/main/java/woowaapplication/pair/game/wordle/WordleGameStorage.java
new file mode 100644
index 00000000..e0fc45f9
--- /dev/null
+++ b/src/main/java/woowaapplication/pair/game/wordle/WordleGameStorage.java
@@ -0,0 +1,53 @@
+package woowaapplication.pair.game.wordle;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import woowaapplication.pair.game.wordle.domain.Coin;
+import woowaapplication.pair.game.wordle.domain.WordleBlock;
+
+public class WordleGameStorage {
+
+ private final List wordleBlocksHistory = new ArrayList<>();
+ private final Coin coin;
+
+ public WordleGameStorage(Coin coin) {
+ this.coin = coin;
+ }
+
+ public int getRestChance() {
+ return coin.getRestChance();
+ }
+
+ private boolean isGameOver() {
+ return coin.isOutOfChance();
+ }
+
+ public boolean isGameClear() {
+ if (wordleBlocksHistory.isEmpty()){
+ return false;
+ }
+ return WordleBlock.isAllCorrect(wordleBlocksHistory.get(wordleBlocksHistory.size() - 1));
+ }
+
+ public boolean isGameEnd() {
+ return isGameOver() || isGameClear();
+ }
+
+ public void submit(WordleBlock[] wordleBlocks) {
+ wordleBlocksHistory.add(wordleBlocks);
+
+ coin.decreaseChance();
+ }
+
+ public List convertHistoryToEmoji() {
+ return wordleBlocksHistory.stream()
+ .map(WordleBlock::toEmojiList)
+ .collect(Collectors.toList());
+ }
+
+ public static WordleGameStorage of(Coin coin) {
+ return new WordleGameStorage(coin);
+ }
+}
diff --git a/src/main/java/woowaapplication/pair/game/wordle/domain/Coin.java b/src/main/java/woowaapplication/pair/game/wordle/domain/Coin.java
new file mode 100644
index 00000000..f42bbc8f
--- /dev/null
+++ b/src/main/java/woowaapplication/pair/game/wordle/domain/Coin.java
@@ -0,0 +1,32 @@
+package woowaapplication.pair.game.wordle.domain;
+
+import woowaapplication.pair.game.wordle.exception.OutOfChanceException;
+
+public class Coin {
+
+ private int restChance;
+
+ public Coin(int restChance) {
+ this.restChance = restChance;
+ }
+
+ public void decreaseChance() {
+ if (restChance < 1) {
+ throw new OutOfChanceException();
+ }
+
+ restChance -= 1;
+ }
+
+ public int getRestChance() {
+ return restChance;
+ }
+
+ public boolean isOutOfChance() {
+ return restChance < 1;
+ }
+
+ public static Coin of(int restChance) {
+ return new Coin(restChance);
+ }
+}
diff --git a/src/main/java/woowaapplication/pair/game/wordle/domain/WordleBlock.java b/src/main/java/woowaapplication/pair/game/wordle/domain/WordleBlock.java
new file mode 100644
index 00000000..866aa35e
--- /dev/null
+++ b/src/main/java/woowaapplication/pair/game/wordle/domain/WordleBlock.java
@@ -0,0 +1,70 @@
+package woowaapplication.pair.game.wordle.domain;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import woowaapplication.pair.game.wordle.WordleGame;
+
+public enum WordleBlock {
+ CORRECT( "🟩"),
+ EXIST_BUT_WRONG_SPOT( "🟨"),
+ WRONG( "⬜"),
+ ;
+
+ private final String emoji;
+
+ WordleBlock(String emoji) {
+ this.emoji = emoji;
+ }
+
+ public String getEmoji() {
+ return emoji;
+ }
+
+ public static boolean isAllCorrect(WordleBlock[] wordleBlocks) {
+ return Arrays.stream(wordleBlocks)
+ .allMatch(block -> block == WordleBlock.CORRECT);
+ }
+
+ public static WordleBlock[] from(String inputKeyword, String answerKeyword) {
+ WordleBlock[] resultBlocks = new WordleBlock[WordleGame.KEYWORD_LENGTH];
+ Set answerLetters = createAnswerLetters(answerKeyword);
+
+ for (int index = 0; index < inputKeyword.length(); index++) {
+ char inputLetter = inputKeyword.charAt(index);
+ char answerLetter = answerKeyword.charAt(index);
+
+ WordleBlock block = compareLetters(inputLetter, answerLetter, answerLetters);
+
+ resultBlocks[index] = block;
+ }
+
+ return resultBlocks;
+ }
+
+ private static HashSet createAnswerLetters(String answerKeyword) {
+ return answerKeyword.chars()
+ .mapToObj(letter -> (char) letter)
+ .collect(Collectors.toCollection(HashSet::new));
+ }
+
+ private static WordleBlock compareLetters(char inputLetter, char answerLetter, Set answerLetters) {
+ if (answerLetter == inputLetter) {
+ return WordleBlock.CORRECT;
+ }
+
+ if (answerLetters.contains(inputLetter)) {
+ return WordleBlock.EXIST_BUT_WRONG_SPOT;
+ }
+
+ return WordleBlock.WRONG;
+ }
+
+ public static String[] toEmojiList(WordleBlock[] wordleBlocks) {
+ return Arrays.stream(wordleBlocks)
+ .map(WordleBlock::getEmoji)
+ .toArray(String[]::new);
+ }
+}
diff --git a/src/main/java/woowaapplication/pair/game/wordle/dto/GameResultDto.java b/src/main/java/woowaapplication/pair/game/wordle/dto/GameResultDto.java
new file mode 100644
index 00000000..8e5ae76c
--- /dev/null
+++ b/src/main/java/woowaapplication/pair/game/wordle/dto/GameResultDto.java
@@ -0,0 +1,39 @@
+package woowaapplication.pair.game.wordle.dto;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class GameResultDto {
+ private final String history;
+ private final String chance;
+
+ private final boolean isClear;
+
+ public GameResultDto(String history, String chance, boolean isClear) {
+ this.history = history;
+ this.chance = chance;
+ this.isClear = isClear;
+ }
+
+ public static GameResultDto of(List scores, int totalChance, int restChance, boolean isClear) {
+ String chance = isClear? (totalChance - restChance) + "/" + totalChance : null;
+
+ String history = scores.stream()
+ .map(array -> java.lang.String.join(" ", array))
+ .collect(Collectors.joining("\n"));
+
+ return new GameResultDto(history, chance, isClear);
+ }
+
+ public String getHistory() {
+ return history;
+ }
+
+ public String getChance() {
+ return chance;
+ }
+
+ public boolean isClear() {
+ return isClear;
+ }
+}
diff --git a/src/main/java/woowaapplication/pair/game/wordle/exception/InvalidAnswerKeywordException.java b/src/main/java/woowaapplication/pair/game/wordle/exception/InvalidAnswerKeywordException.java
new file mode 100644
index 00000000..94b1f833
--- /dev/null
+++ b/src/main/java/woowaapplication/pair/game/wordle/exception/InvalidAnswerKeywordException.java
@@ -0,0 +1,10 @@
+package woowaapplication.pair.game.wordle.exception;
+
+public class InvalidAnswerKeywordException extends RuntimeException {
+
+ private static final String MESSAGE = "정답 키워드가 유효하지 않습니다.";
+
+ public InvalidAnswerKeywordException() {
+ super(MESSAGE);
+ }
+}
diff --git a/src/main/java/woowaapplication/pair/game/wordle/exception/InvalidInputKeywordException.java b/src/main/java/woowaapplication/pair/game/wordle/exception/InvalidInputKeywordException.java
new file mode 100644
index 00000000..2208bc38
--- /dev/null
+++ b/src/main/java/woowaapplication/pair/game/wordle/exception/InvalidInputKeywordException.java
@@ -0,0 +1,10 @@
+package woowaapplication.pair.game.wordle.exception;
+
+public class InvalidInputKeywordException extends RuntimeException {
+
+ private static final String MESSAGE = "입력한 키워드가 유효하지 않습니다.";
+
+ public InvalidInputKeywordException() {
+ super(MESSAGE);
+ }
+}
diff --git a/src/main/java/woowaapplication/pair/game/wordle/exception/OutOfChanceException.java b/src/main/java/woowaapplication/pair/game/wordle/exception/OutOfChanceException.java
new file mode 100644
index 00000000..a2b2b06e
--- /dev/null
+++ b/src/main/java/woowaapplication/pair/game/wordle/exception/OutOfChanceException.java
@@ -0,0 +1,10 @@
+package woowaapplication.pair.game.wordle.exception;
+
+public class OutOfChanceException extends RuntimeException {
+
+ private static final String MESSAGE = "남은 기회가 없습니다.";
+
+ public OutOfChanceException() {
+ super(MESSAGE);
+ }
+}
diff --git a/src/main/java/woowaapplication/pair/game/wordle/exception/ReadFileException.java b/src/main/java/woowaapplication/pair/game/wordle/exception/ReadFileException.java
new file mode 100644
index 00000000..0e9250e8
--- /dev/null
+++ b/src/main/java/woowaapplication/pair/game/wordle/exception/ReadFileException.java
@@ -0,0 +1,9 @@
+package woowaapplication.pair.game.wordle.exception;
+
+public class ReadFileException extends RuntimeException {
+ private static final String MESSAGE = "파일 데이터 읽기를 실패하였습니다.";
+
+ public ReadFileException() {
+ super(MESSAGE);
+ }
+}
diff --git a/src/test/java/acceptance/WordleGameAcceptanceTest.java b/src/test/java/acceptance/WordleGameAcceptanceTest.java
new file mode 100644
index 00000000..56b72319
--- /dev/null
+++ b/src/test/java/acceptance/WordleGameAcceptanceTest.java
@@ -0,0 +1,102 @@
+package acceptance;
+
+import static acceptance.support.WordGameSupporter.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static woowaapplication.pair.game.wordle.WordleGame.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import woowaapplication.pair.game.wordle.WordleGame;
+import woowaapplication.pair.game.wordle.domain.Coin;
+import woowaapplication.pair.game.wordle.dto.GameResultDto;
+
+@DisplayName("워들 인수 테스트")
+public class WordleGameAcceptanceTest {
+ private String 정답_키워드 = "jason";
+
+ private final String 오답_키워드 = "jxosn";
+
+ private final String 정답_기록 = "🟩 🟩 🟩 🟩 🟩";
+
+ private final String 오답_기록 = "🟩 ⬜ 🟨 🟨 🟩";
+
+ private Coin 코인;
+ private WordleGame 워들_게임;
+
+ @BeforeEach
+ void setUp() {
+ 코인 = Coin.of(TOTAL_CHANCE);
+ 워들_게임 = of();
+ }
+
+
+ @Nested
+ @DisplayNameGeneration(ReplaceUnderscores.class)
+ class 정답_케이스 {
+
+ @Nested
+ @DisplayName("정답 키워드를 입력하면")
+ class Context_with_corrected_keyword {
+
+ @Test
+ @DisplayName("남은 시도 횟수가 그대로 반환되고, 5개의 네모칸이 모두 초록색으로 반환된다")
+ void it_returns_remaining_chance_and_answer() {
+ GameResultDto 게임_결과 = 워들_게임.playRound(정답_키워드, 정답_키워드);
+
+ assertAll(
+ () -> 정답_기록이_올바르다(게임_결과),
+ () -> 시도_횟수가_올바르다(게임_결과, TOTAL_CHANCE - 1)
+ );
+ }
+ }
+ }
+
+ @Nested
+ @DisplayNameGeneration(ReplaceUnderscores.class)
+ class 오답_케이스 {
+
+ @Nested
+ @DisplayName("오답 키워드를 입력하면")
+ class Context_with_input_incorrect_keyword {
+ @Test
+ @DisplayName("올바른 알파벳인 자리는 초록색, 위치가 틀린 자리는 노란색, 오답은 흰색으로 표시한다")
+ void it_returns_answer_and_decrease_rest_chance() {
+ int 기존_남은_시도_횟수 = 코인.getRestChance();
+ GameResultDto 게임_결과 = 워들_게임.playRound(오답_키워드, 정답_키워드);
+
+ assertAll(
+ () -> 게임_기록이_올바르다(게임_결과, 오답_기록)
+ );
+ }
+ }
+ }
+
+ @Nested
+ @DisplayNameGeneration(ReplaceUnderscores.class)
+ class 오답_후_정답_케이스 {
+
+ @Nested
+ @DisplayName("오답 키워드 입력 후, 정답 키워드를 입력하면")
+ class Context_with_input_incorrect_keyword {
+ @Test
+ @DisplayName("남은 시도 횟수가 2 감소되고, 오답과 정답을 포함한 게임 기록이 반환된다")
+ void it_returns_answer_and_decrease_rest_chance() {
+ int 기존_남은_시도_횟수 = 코인.getRestChance();
+
+ 워들_게임.playRound(오답_키워드, 정답_키워드);
+
+ GameResultDto 게임_결과 = 워들_게임.playRound(정답_키워드, 정답_키워드);
+
+ assertAll(
+ () -> 게임_기록이_올바르다(게임_결과, 오답_기록+"\n"+정답_기록),
+ () -> 시도_횟수가_올바르다(게임_결과, 기존_남은_시도_횟수 - 2)
+ );
+ }
+ }
+ }
+}
diff --git a/src/test/java/acceptance/support/WordGameSupporter.java b/src/test/java/acceptance/support/WordGameSupporter.java
new file mode 100644
index 00000000..7192be74
--- /dev/null
+++ b/src/test/java/acceptance/support/WordGameSupporter.java
@@ -0,0 +1,27 @@
+package acceptance.support;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import woowaapplication.pair.game.wordle.WordleGame;
+import woowaapplication.pair.game.wordle.dto.GameResultDto;
+
+public class WordGameSupporter {
+
+ private static String 정답_기록 = "🟩 🟩 🟩 🟩 🟩";
+
+ private static int 전체_시도_횟수 = WordleGame.TOTAL_CHANCE;
+
+ public static void 정답_기록이_올바르다(GameResultDto 게임_결과) {
+ assertThat(게임_결과.getHistory()).isEqualTo(정답_기록);
+ }
+
+ public static void 게임_기록이_올바르다(GameResultDto 게임_결과, String 예상_결과) {
+ assertThat(게임_결과.getHistory()).isEqualTo(예상_결과);
+ }
+
+ public static void 시도_횟수가_올바르다(GameResultDto 게임_결과, int 예상_시도_횟수) {
+ String 올바른_시도_횟수 = (전체_시도_횟수 - 예상_시도_횟수) + "/" + 전체_시도_횟수;
+
+ assertThat(게임_결과.getChance()).isEqualTo(올바른_시도_횟수);
+ }
+}
diff --git a/src/test/java/unit/WordleGameAnswerGeneratorTest.java b/src/test/java/unit/WordleGameAnswerGeneratorTest.java
new file mode 100644
index 00000000..264eaa46
--- /dev/null
+++ b/src/test/java/unit/WordleGameAnswerGeneratorTest.java
@@ -0,0 +1,37 @@
+package unit;
+
+import java.time.LocalDate;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import woowaapplication.pair.game.wordle.WordleGameAnswerGenerator;
+
+public class WordleGameAnswerGeneratorTest {
+ private String 오늘의_정답_키워드 = "jason";
+
+ private LocalDate 비교_날짜 = LocalDate.of(2021, 6, 24);
+
+ @Nested
+ @DisplayNameGeneration(ReplaceUnderscores.class)
+ class 정답_생성_테스트 {
+
+ @Nested
+ @DisplayName("오늘의 날짜가 2021년 6월 24일이면")
+ class Context_with_corrected_keyword {
+ @Test
+ @DisplayName("오늘의 정답 키워드는 jason이다")
+ void it_returns_remaining_chance_and_answer() {
+ String actual오늘의_정답_키워드 = WordleGameAnswerGenerator.getAnswerKeyword(비교_날짜);
+
+ assertThat(오늘의_정답_키워드).isEqualTo(actual오늘의_정답_키워드);
+ }
+ }
+ }
+
+}
diff --git a/src/test/java/unit/WordleGameStorageTest.java b/src/test/java/unit/WordleGameStorageTest.java
new file mode 100644
index 00000000..cc0c43d7
--- /dev/null
+++ b/src/test/java/unit/WordleGameStorageTest.java
@@ -0,0 +1,63 @@
+package unit;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static woowaapplication.pair.game.wordle.domain.WordleBlock.CORRECT;
+import static woowaapplication.pair.game.wordle.domain.WordleBlock.EXIST_BUT_WRONG_SPOT;
+import static woowaapplication.pair.game.wordle.domain.WordleBlock.WRONG;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import woowaapplication.pair.game.wordle.WordleGame;
+import woowaapplication.pair.game.wordle.domain.Coin;
+import woowaapplication.pair.game.wordle.domain.WordleBlock;
+import woowaapplication.pair.game.wordle.WordleGameStorage;
+
+@DisplayName("워들 게임 저장소 테스트")
+class WordleGameStorageTest {
+ private WordleGameStorage wordleGameStorage;
+
+ @BeforeEach
+ void setUp() {
+ Coin coin = Coin.of(WordleGame.TOTAL_CHANCE);
+ wordleGameStorage = WordleGameStorage.of(coin);
+ }
+
+ @Nested
+ @DisplayNameGeneration(ReplaceUnderscores.class)
+ class 정답_확인_기능 {
+
+ @Nested
+ @DisplayName("정답으로 구성된 워들 블럭들이 주어진다면")
+ class Context_with_clear_wordle_blocks {
+ private WordleBlock[] 워들_블럭들 = {CORRECT, CORRECT, CORRECT, CORRECT, CORRECT};
+
+ @Test
+ @DisplayName("게임 엔드 판단한다")
+ void it_clear_game() {
+ wordleGameStorage.submit(워들_블럭들);
+
+ assertThat(wordleGameStorage.isGameEnd()).isTrue();
+ }
+ }
+
+ @Nested
+ @DisplayName("정답이 아닌 것이 존재하는 워들 블럭들이 주어진다면")
+ class Context_with_not_clear_wordle_blocks {
+ private WordleBlock[] 워들_블럭들 = {CORRECT, WRONG, EXIST_BUT_WRONG_SPOT, CORRECT, CORRECT};
+
+ @Test
+ @DisplayName("게임 클리어 실패로 판단한다")
+ void it_clear_game() {
+ wordleGameStorage.submit(워들_블럭들);
+
+ assertThat(wordleGameStorage.isGameEnd()).isFalse();
+ }
+ }
+ }
+
+}
diff --git a/src/test/java/unit/domain/WordleBlockTest.java b/src/test/java/unit/domain/WordleBlockTest.java
new file mode 100644
index 00000000..2f91cdc9
--- /dev/null
+++ b/src/test/java/unit/domain/WordleBlockTest.java
@@ -0,0 +1,67 @@
+package unit.domain;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static woowaapplication.pair.game.wordle.domain.WordleBlock.CORRECT;
+import static woowaapplication.pair.game.wordle.domain.WordleBlock.EXIST_BUT_WRONG_SPOT;
+import static woowaapplication.pair.game.wordle.domain.WordleBlock.WRONG;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import woowaapplication.pair.game.wordle.domain.WordleBlock;
+
+class WordleBlockTest {
+
+ @Nested
+ @DisplayNameGeneration(ReplaceUnderscores.class)
+ class 입력_키워드와_정답_키워드로_워들블럭_리스트를_만드는_기능 {
+ private String 정답_키워드 = "jason";
+
+ @Nested
+ @DisplayName("입력 키워드가 정답일 경우")
+ class Context_with_correct_input_keyword {
+ private String 입력_키워드 = "jason";
+
+ @Test
+ @DisplayName("CORRECT로 구성된 워들 블럭들을 반환환다")
+ void it_returns_correct_wordle_blocks() {
+ WordleBlock[] 워들_블럭들 = WordleBlock.from(입력_키워드, 정답_키워드);
+
+ assertThat(워들_블럭들).containsOnly(CORRECT);
+ }
+ }
+
+ @Nested
+ @DisplayName("입력 키워드가 한 글자도 못맞춘 경우")
+ class Context_with_wrong_input_keyword {
+ private String 입력_키워드 = "xxxxx";
+
+ @Test
+ @DisplayName("WRONG으로 구성된 워들 블럭들을 반환환다")
+ void it_returns_correct_wordle_blocks() {
+ WordleBlock[] 워들_블럭들 = WordleBlock.from(입력_키워드, 정답_키워드);
+
+ assertThat(워들_블럭들).containsOnly(WRONG);
+ }
+ }
+
+ @Nested
+ @DisplayName("입력 키워드의 첫번째 글자는 정답이고,"
+ + "나머지 글자들은 정답 키워드에 존재하는 글자이지만 다른 위치에 있는 경우")
+ class Context_with_exist_but_wrong_spot_input_keyword {
+
+ private String 입력_키워드 = "jjjjj";
+
+ @Test
+ @DisplayName("첫번째는 CORRECT, 나머지는 EXIST_BUT_WRONG_SPOT으로 구성된 워들 블럭들을 반환환다")
+ void it_returns_correct_wordle_blocks() {
+ WordleBlock[] 워들_블럭들 = WordleBlock.from(입력_키워드, 정답_키워드);
+
+ assertThat(워들_블럭들).containsExactly(CORRECT, EXIST_BUT_WRONG_SPOT, EXIST_BUT_WRONG_SPOT,
+ EXIST_BUT_WRONG_SPOT, EXIST_BUT_WRONG_SPOT);
+ }
+ }
+ }
+}
diff --git a/src/test/java/unit/util/KeywordValidatorTest.java b/src/test/java/unit/util/KeywordValidatorTest.java
new file mode 100644
index 00000000..d6aee3d1
--- /dev/null
+++ b/src/test/java/unit/util/KeywordValidatorTest.java
@@ -0,0 +1,49 @@
+package unit.util;
+
+import static org.assertj.core.api.Assertions.*;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import woowaapplication.pair.game.util.KeywordValidator;
+import woowaapplication.pair.game.wordle.exception.InvalidInputKeywordException;
+
+class KeywordValidatorTest {
+
+ @Nested
+ @DisplayNameGeneration(ReplaceUnderscores.class)
+ class 입력_검증_예외_케이스 {
+ private final int LENGTH_LIMIT = 5;
+
+ @Nested
+ @DisplayName("알파벳이 아닌 입력이 들어오면")
+ class Context_with_not_string_input {
+
+ private final String 알파벳이_아닌_키워드 = "12345";
+
+ @Test
+ @DisplayName("시도가 무효된다")
+ void it_makes_chance_canceled() {
+ assertThatThrownBy(() -> KeywordValidator.validate(알파벳이_아닌_키워드, LENGTH_LIMIT)).isInstanceOf(
+ InvalidInputKeywordException.class);
+ }
+ }
+
+ @Nested
+ @DisplayName("글자수 제한에 맞지 않는 입력이 들어오면")
+ class Context_with_over_limit_input {
+
+ private final String 글자수_제한에_맞지_않는_키워드 = "alexholden";
+
+ @Test
+ @DisplayName("시도가 무효된다")
+ void it_makes_chance_canceled() {
+ assertThatThrownBy(() -> KeywordValidator.validate(글자수_제한에_맞지_않는_키워드, LENGTH_LIMIT)).isInstanceOf(
+ InvalidInputKeywordException.class);
+ }
+ }
+ }
+}
diff --git a/src/test/resources/words.txt b/src/test/resources/words.txt
new file mode 100644
index 00000000..e67708f4
--- /dev/null
+++ b/src/test/resources/words.txt
@@ -0,0 +1,7 @@
+alexz
+holde
+mutoz
+bunso
+jason
+johnz
+bobzz