diff --git a/README.md b/README.md
index 8b2833c..9349030 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,47 @@
-# javascript-baseball-precourse
\ No newline at end of file
+
+
+
+
+
+# 1회차 미니 과제 - ⚾️ 숫자 야구 게임
+
+> 카카오 테크 캠퍼스 2기 FE 1회차 미니 과제, 숫자 야구 게임 레포입니다.
+
+
+
+
+## 기능 목록
+
+### UI
+* 게임 타이틀과 규칙 및 예시를 표시한다.
+* 입력을 받을 텍스트 필드를 배치한다.
+* 답 제출 버튼을 배치한다.
+* 결과 섹션을 배치한다.
+
+
+
+### 문제 출제
+* 1부터 9까지 서로 다른 임의의 수로 이루어진 3자리의 수가 생성된다.
+
+
+
+### 문제 풀기
+* 사용자로부터 서로 다른 3자리의 수를 입력 받는다.
+* 정상 입력된 경우 입력에 따른 결과 메시지를 표시한다.
+ * 같은 수가 같은 자리에 있으면 스트라이크
+ * 다른 자리에 있으면 볼
+ * 같은 수가 전혀 없으면 낫싱
+* 게임을 종료한 후 게임을 다시 시작하거나 완전히 종료할 수 있다.
+* 잘못 입력된 경우 alert()을 이용한 에러 메시지 출력 후 그 부분부터 다시 입력받는다.
+
+
+
+### 로그
+* 사용자가 입력한 값과 그 결과값을 표시한다.
+
+
+
+### 정답 이후 처리
+* 3개의 숫자를 모두 맞히면 게임이 종료되고 '🎉정답을 맞추셨습니다🎉' 메시지 출력과 함께 재시작 문구 및 버튼이 표시된다.
+
+
diff --git a/images/favicon.png b/images/favicon.png
new file mode 100644
index 0000000..a74328e
Binary files /dev/null and b/images/favicon.png differ
diff --git a/index.html b/index.html
index b021b5c..dcaf87e 100644
--- a/index.html
+++ b/index.html
@@ -1,12 +1,56 @@
-
-
+
+
-
+ 숫자 야구
+
+
+
+
-
-
+
+
+
+
+
+
⚾️
+
Bulls and Cows
+
Start
+
+
+
+
⚾️ 숫자 야구 게임
+
+
+ 1~9까지의 수 를 중복없이 3개 입력해주세요.
+
+ 올바른 예) 139
+ 틀린 예) 122
+
+
+
+
+
+
+
+
+
📋 기록
+
+
+
+
diff --git a/src/components/domManipulation.js b/src/components/domManipulation.js
new file mode 100644
index 0000000..58a03fd
--- /dev/null
+++ b/src/components/domManipulation.js
@@ -0,0 +1,48 @@
+import {
+ inputTextFieldEl,
+ inputBtnEl,
+ resultTxtEl,
+ restartMsgEl,
+ logsEl,
+ restartBtnEl,
+ finishBtnEl,
+} from "./elements.js";
+
+export function showElement(element) {
+ element.classList.remove("hidden");
+}
+
+export function hideElement(element) {
+ element.classList.add("hidden");
+}
+
+export function clearElements() {
+ resultTxtEl.innerText = "";
+ restartMsgEl.innerText = "";
+ logsEl.innerHTML = "";
+ restartBtnEl.type = "hidden";
+ finishBtnEl.type = "hidden";
+ enableBtn(inputBtnEl);
+}
+
+export function enableInputTextField() {
+ inputTextFieldEl.disabled = false;
+}
+
+export function disableInputTextField() {
+ inputTextFieldEl.disabled = true;
+}
+
+export function enableBtn(btnEl) {
+ btnEl.disabled = false;
+}
+
+export function disableBtn(btnEl) {
+ btnEl.disabled = true;
+}
+
+export function appendLog(log) {
+ const li = document.createElement("li");
+ li.textContent = log;
+ logsEl.appendChild(li);
+}
diff --git a/src/components/elements.js b/src/components/elements.js
new file mode 100644
index 0000000..506cebc
--- /dev/null
+++ b/src/components/elements.js
@@ -0,0 +1,10 @@
+export const inputTextFieldEl = document.querySelector(".input #input__textfield");
+export const inputBtnEl = document.querySelector(".input #input__btn");
+export const resultTxtEl = document.querySelector(".result .result__text");
+export const restartBtnEl = document.querySelector(".result .result__btn--restart");
+export const finishBtnEl = document.querySelector(".result .result__btn--finish");
+export const restartMsgEl = document.querySelector(".result .result__restartMessage");
+export const titleStartBtnEl = document.querySelector(".titleScreen .titleScreen__startBtn");
+export const titleScreenEl = document.querySelector(".titleScreen");
+export const titleContainersEl = document.querySelector(".titleScreen .container");
+export const logsEl = document.querySelector(".loglist");
diff --git a/src/games/eventHandlers.js b/src/games/eventHandlers.js
new file mode 100644
index 0000000..dea81d4
--- /dev/null
+++ b/src/games/eventHandlers.js
@@ -0,0 +1,79 @@
+import {
+ inputTextFieldEl,
+ inputBtnEl,
+ restartBtnEl,
+ resultTxtEl,
+ restartMsgEl,
+ finishBtnEl,
+ titleScreenEl,
+ titleContainersEl,
+} from "../components/elements.js";
+
+import {
+ showElement,
+ hideElement,
+ clearElements,
+ enableInputTextField,
+ disableInputTextField,
+ enableBtn,
+ disableBtn,
+ appendLog,
+} from "../components/domManipulation.js";
+
+import { RESTART_MESSAGE } from "../utils/constants.js";
+
+let game;
+
+export function initGameInstance(gameInstance) {
+ game = gameInstance;
+}
+
+export function handleInputTextFieldKeyPress(event) {
+ if (event.key === "Enter") {
+ inputBtnEl.click();
+ }
+}
+
+export function handleInputBtnClick() {
+ if (game.compareResult(inputTextFieldEl)) {
+ resultTxtEl.innerText = game.getResult();
+ inputTextFieldEl.value = "";
+ inputTextFieldEl.focus();
+
+ if (game.isGameSet) {
+ disableInputTextField();
+ disableBtn(inputBtnEl);
+ restartMsgEl.innerText = RESTART_MESSAGE;
+ restartBtnEl.type = "button";
+ finishBtnEl.type = "button";
+ }
+
+ appendLog(game.getLastLog());
+ }
+}
+
+export function handleRestartBtnClick() {
+ game.restart(() => {
+ enableInputTextField();
+ enableBtn(inputBtnEl);
+ clearElements();
+ inputTextFieldEl.focus();
+ });
+}
+
+export function handleTitleStartBtnClick() {
+ enableInputTextField();
+ hideElement(titleScreenEl);
+ inputTextFieldEl.focus();
+ game.generateRandomNumber();
+}
+
+export function handleDOMContentLoaded() {
+ showElement(titleContainersEl);
+}
+
+export function handleFinishBtnClick() {
+ showElement(titleScreenEl);
+ clearElements();
+ game.isGameSet = false;
+}
diff --git a/src/games/game.js b/src/games/game.js
new file mode 100644
index 0000000..c6f586c
--- /dev/null
+++ b/src/games/game.js
@@ -0,0 +1,126 @@
+import "../utils/isNotIncludes.js";
+import { ERROR_MESSAGE, RESULT_NOTHING, RESULT_CORRECT } from "../utils/constants.js";
+
+export class Game {
+ #answer;
+
+ constructor() {
+ this.#answer = undefined;
+ this.result = "";
+ this.isGameSet = false;
+ this.logs = [];
+ }
+
+ generateRandomNumber() {
+ let number = "";
+
+ while (number.length < 3) {
+ const randomNumber = Math.floor(Math.random() * 9) + 1;
+ if (number.isNotIncludes(randomNumber)) {
+ number += randomNumber;
+ }
+ }
+ console.log(number);
+ this.#answer = number;
+ }
+
+ compareResult(inputEl) {
+ let inputValue = inputEl.value;
+ if (!this.#isValid(inputValue)) {
+ this.#alertHandler(inputEl);
+ return false;
+ }
+ const { strikeCount, ballCount, nothingCount } = this.#countHits(inputValue);
+ this.#setResult(strikeCount, ballCount, nothingCount, () => {
+ this.logs.push(`${inputValue}: ${this.result}`);
+ });
+
+ return true;
+ }
+
+ #countHits(inputValue) {
+ let nothingCount = 0;
+ let strikeCount = 0;
+ let ballCount = 0;
+ for (let i = 0; i < this.#answer.length; i++) {
+ if (inputValue[i] === this.#answer[i]) {
+ strikeCount += 1;
+ continue;
+ }
+
+ if (inputValue.isNotIncludes(this.#answer[i])) {
+ nothingCount += 1;
+ } else {
+ ballCount += 1;
+ }
+ }
+
+ return { strikeCount, ballCount, nothingCount };
+ }
+
+ #alertHandler(inputEl) {
+ alert(ERROR_MESSAGE);
+ this.#resetInputEl(inputEl);
+ }
+
+ #resetInputEl(inputEl) {
+ inputEl.value = "";
+ inputEl.focus();
+ }
+
+ #setResult(strikeCount, ballCount, nothingCount, callback) {
+ if (nothingCount === 3) {
+ this.result = RESULT_NOTHING;
+ } else if (strikeCount === 3) {
+ this.result = RESULT_CORRECT;
+ this.isGameSet = true;
+ } else {
+ if (strikeCount === 0) {
+ this.result = `${ballCount}볼`;
+ } else if (ballCount === 0) {
+ this.result = `${strikeCount}스트라이크`;
+ } else {
+ this.result = `${ballCount}볼 ${strikeCount}스트라이크`;
+ }
+ }
+
+ callback();
+ }
+
+ getResult() {
+ return this.result;
+ }
+
+ getLastLog() {
+ return this.logs[this.logs.length - 1];
+ }
+
+ addLog(logsEl) {
+ const li = document.createElement("li");
+ li.textContent = game.getLastLog();
+ logsEl.appendChild(li);
+ }
+
+ #isValid(input) {
+ let numPattern = /^[1-9]+$/;
+
+ if (!numPattern.test(input)) {
+ return false;
+ }
+ if (input.length !== 3) {
+ return false;
+ }
+ if (new Set(input).size !== input.length) {
+ return false;
+ }
+
+ return true;
+ }
+
+ restart(complete) {
+ this.generateRandomNumber();
+ this.result = "";
+ this.isGameSet = false;
+ complete();
+ }
+}
diff --git a/src/main.css b/src/main.css
new file mode 100644
index 0000000..e24cc95
--- /dev/null
+++ b/src/main.css
@@ -0,0 +1,114 @@
+body {
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+}
+
+#app {
+ margin: 50px;
+}
+
+.titleScreen {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ background-color: #edeae1;
+ transition: 0.4s;
+ visibility: visible;
+ opacity: 1;
+ user-select: none;
+ z-index: 5;
+}
+
+.titleScreen.hidden {
+ visibility: hidden;
+ opacity: 0;
+}
+
+.titleScreen .triangle-red {
+ top: 0;
+ left: 0;
+ border-bottom: 40vh solid transparent;
+ border-left: 60vw solid #f1535a;
+ border-right: 360px solid transparent;
+}
+
+.titleScreen .triangle-offblack {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ border-top: 30vh solid transparent;
+ border-left: 50vw solid transparent;
+ border-right: 70vw solid #2f2f2f;
+}
+
+.titleScreen .container {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ text-align: center;
+ transform: translate(-50%, -50%);
+ opacity: 1;
+ transition: 2s;
+}
+
+.titleScreen .container.hidden {
+ opacity: 0;
+}
+
+.titleScreen .titleScreen__image {
+ font-size: 300px;
+}
+
+.titleScreen .titleScreen__title {
+ color: #2f2f2f;
+ font-weight: 700;
+ font-size: 120px;
+ margin-bottom: 100px;
+}
+
+.titleScreen .titleScreen__startBtn {
+ font-size: 40px;
+ margin-bottom: 120px;
+ color: #2f2f2f;
+ cursor: pointer;
+ transition: 0.2s;
+ font-weight: 700;
+ display: inline-block;
+}
+
+.titleScreen .titleScreen__startBtn:hover {
+ color: #f1535a;
+}
+
+.result {
+ min-height: 150px;
+}
+
+.result .result__text {
+ font-weight: 700;
+ margin-bottom: 24px;
+}
+
+.result .result__restartMessage {
+ margin-bottom: 24px;
+}
+
+.log {
+ width: 500px;
+ height: 500px;
+ position: absolute;
+ overflow: auto;
+ margin: 20px;
+ background-color: rgba(95, 95, 95, 0.15);
+ border-radius: 20px;
+}
+
+.log .loglist {
+}
+
+.log .loglist li {
+ margin: 30px;
+}
diff --git a/src/main.js b/src/main.js
index e69de29..50bc186 100644
--- a/src/main.js
+++ b/src/main.js
@@ -0,0 +1,21 @@
+import { Game } from "./games/game.js";
+import { inputTextFieldEl, inputBtnEl, restartBtnEl, finishBtnEl, titleStartBtnEl } from "./components/elements.js";
+import {
+ initGameInstance,
+ handleInputTextFieldKeyPress,
+ handleInputBtnClick,
+ handleRestartBtnClick,
+ handleTitleStartBtnClick,
+ handleDOMContentLoaded,
+ handleFinishBtnClick,
+} from "./games/eventHandlers.js";
+
+const game = new Game();
+
+initGameInstance(game);
+inputTextFieldEl.addEventListener("keypress", handleInputTextFieldKeyPress);
+inputBtnEl.addEventListener("click", handleInputBtnClick);
+restartBtnEl.addEventListener("click", handleRestartBtnClick);
+titleStartBtnEl.addEventListener("click", handleTitleStartBtnClick);
+document.addEventListener("DOMContentLoaded", handleDOMContentLoaded);
+finishBtnEl.addEventListener("click", handleFinishBtnClick);
diff --git a/src/utils/constants.js b/src/utils/constants.js
new file mode 100644
index 0000000..2eec6b2
--- /dev/null
+++ b/src/utils/constants.js
@@ -0,0 +1,4 @@
+export const RESTART_MESSAGE = "게임을 새로 시작하시겠습니까?";
+export const ERROR_MESSAGE = "1~9까지의 수를 중복없이 3개 입력해주세요.";
+export const RESULT_NOTHING = "낫싱";
+export const RESULT_CORRECT = "🎉정답을 맞추셨습니다🎉";
diff --git a/src/utils/isNotIncludes.js b/src/utils/isNotIncludes.js
new file mode 100644
index 0000000..7f42372
--- /dev/null
+++ b/src/utils/isNotIncludes.js
@@ -0,0 +1,3 @@
+String.prototype.isNotIncludes = function (char) {
+ return !this.includes(char);
+};