언젠가 도전해봐야지 하며 한참을 미루다 어제부터 착수한 작업! 바로 우아한 테크코스 프론트엔드 과정 프리 코스
의 3주치 과제 따라만들기! 그 첫 번째 과제는 바로 숫자 야구 게임
이다. 본 과제에서 구현해야 하는 기능 요구사항
은 다음과 같다.
- 같은 수가 같은 자리에 있으면
스트라이크
, 다른 자리에 있으면볼
, 같은 수가 전혀 없으면낫싱
이란 힌트를 얻고, 그 힌트를 이용해서 먼저 상대방(컴퓨터)의 수를 맞추면 승리한다.
- 예) 상대방(컴퓨터)의 수가 425일 때
- 123을 제시한 경우 : 1스트라이크
- 456을 제시한 경우 : 1볼 1스트라이크
- 789를 제시한 경우 : 낫싱
- 위 숫자 야구게임에서 상대방의 역할을 컴퓨터가 한다. 컴퓨터는 1에서 9까지 서로 다른 임의의 수 3개를 선택한다. 게임 플레이어는 컴퓨터가 생각하고 있는 3개의 숫자를 입력하고, 컴퓨터는 입력한 숫자에 대한 결과를 출력한다.
- 이 같은 과정을 반복해 컴퓨터가 선택한 3개의 숫자를 모두 맞히면 게임이 종료되고, 재시작 버튼이 노출된다.
- 게임이 종료된 후 재시작 버튼을 클릭해 게임을 다시 시작할 수 있다.
- 사용자가 잘못된 값을 입력한 경우
alert
으로 에러 메시지를 보여주고, 다시 입력할 수 있게 한다.
사실 코드 구현 자체는 javascript
를 조금이라도 만져 본 사람이라면 크게 어렵지 않으리라 생각한다. 다만 코드 작성 시에 염두에 두어야 하는 몇 가지 요구사항들이 까다로웠다.
아래는 우아한 테크코스 과제 작업 시 지켜야 할 사항들이다.
play
(컴퓨터의 랜덤 값, 유저의 입력 값) 메서드를 만들어 게임을 진행한다.play
메서드는String
으로 결과값을 return 한다.
- 스트라이크와 볼이 같이 있는 경우 볼을 먼저쓰고, 스트라이크를 쓴다.
index.js
에서 아래의 function 또는 class 형태를 활용한다.
export default function BaseballGame() {
this.play = function (computerInputNumbers, userInputNumbers) {
return "결과 값 String";
};
}
export default class BaseballGame {
play(computerInputNumbers, userInputNumbers) {
return "결과 값 String";
}
}
// 예시
play(123, 456); // '낫싱'
play(123, 345); // '1볼'
play(123, 432); // '2볼'
play(123, 312); // '3볼'
play(123, 145); // '1스트라이크'
play(123, 134); // '1볼 1스트라이크'
play(123, 132); // '2볼 1스트라이크'
play(123, 124); // '2스트라이크'
- 외부 라이브러리(jQuery, Lodash 등)를 사용하지 않고, 순수 Vanilla JS로만 구현한다.
- 자바스크립트 코드 컨벤션을 지키면서 프로그래밍 한다.
- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
- 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
- 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다.
- 함수(또는 메소드)가 한 가지 일만 하도록 최대한 작게 만들어라.
- 변수 선언시
var
를 사용하지 않는다.const
와let
을 사용한다.import
문을 이용해 스크립트를 모듈화하고 불러올 수 있게 만든다.- 함수(또는 메소드)의 길이가 15라인을 넘어가지 않도록 구현한다.
- 함수(또는 메소드)가 한 가지 일만 잘 하도록 구현한다.
- 미션은 javascript-baseball-precourse 저장소를 Fork/Clone해 시작한다.
- 기능을 구현하기 전에 javascript-baseball-precourse/docs/README.md 파일에 구현할 기능 목록을 정리해 추가한다.
- Git의 커밋 단위는 앞 단계에서 README.md 파일에 정리한 기능 목록 단위로 추가한다.
- AngularJS Commit Message Conventions 참고해 commit log를 남긴다.
위의 요구사항들을 받아들고 난감했다. 여태껏 함수의 깊이라든지, Module화라든지, class니 function이니, commit message니 전혀 신경써본 적이 없었기에 이걸 다 지키면서 만들어낼 수 있을까 막막하기만 했다. 그렇게 각종 문서를 뒤지며 고민하기를 1시간째... 일단 기능부터 구현하고, 추후에 요구사항에 맞춰 수정하기로 가닥을 잡았다! 행동으로 옮기지 않으면 아무 일도 일어나지 않으니까!
아래는 내가 작성한 코드와 기능 구현 결과물이다.
# 📁 디렉토리 구조
├─ src
│ ├─ components
│ │ ├─ compareNumbers.js
│ │ ├─ createCPUNumbers.js
│ │ ├─ validateUserNumbers.js
│ ├─ index.js
# index.js
import { compareNumbers } from './components/compareNumbers.js';
const checkButton = document.querySelector('#submit');
export let restartButton = document.querySelector('#game-restart-button');
checkButton.addEventListener('click', handleCheckButtonFunction);
restartButton.style.display = 'none';
function handleCheckButtonFunction(e) {
e.preventDefault();
compareNumbers();
}
# compareNumbers.js
import { createCPUNumbers } from './createCPUNumbers.js';
import { validateUserNumbers } from './validateUserNumbers.js';
import { restartButton } from '../index.js';
const CPUNumbers = createCPUNumbers();
export function compareNumbers() {
let userNumbers = validateUserNumbers();
let strike = 0;
let ball = 0;
if (!userNumbers) {
return;
} else {
userNumbers.forEach((value, index) => {
if (value === CPUNumbers[index]) strike++;
else if (CPUNumbers.includes(value)) ball++;
});
}
const resultMessage = document.querySelector('#result');
if (strike === 0 && ball === 0) {
resultMessage.innerText = '낫싱';
} else if (strike > 0 && ball > 0) {
resultMessage.innerText = `${ball}볼 ${strike}스트라이크`;
} else if (ball > 0) {
resultMessage.innerText = `${ball}볼`;
} else if (strike > 0) {
resultMessage.innerText = `${strike}스트라이크`;
}
if (strike === 3) {
resultMessage.innerText = `🎉정답을 맞히셨습니다🎉
게임을 새로 시작하시겠습니까?
`;
restartButton.style.display = 'block';
}
restartButton.addEventListener('click', () => location.reload());
}
# createCPUNumbers.js
export function createCPUNumbers() {
let CPUNumbersArray = [];
while (CPUNumbersArray.length <= 2) {
let randomCPUNums = MissionUtils.Random.pickNumberInRange(1, 9);
if (CPUNumbersArray.indexOf(randomCPUNums) < 0) {
CPUNumbersArray.push(randomCPUNums);
}
}
const toStringCPUNumbers = CPUNumbersArray.map((val) => String(val));
return toStringCPUNumbers;
}
# validateUserNumbers.js
export function validateUserNumbers() {
const userInput = document.querySelector('#user-input');
const userNumbers = userInput.value;
const numberedUserNumbers = +userNumbers;
if (
userNumbers.length > 3 ||
userNumbers.length < 3 ||
userNumbers.includes(0) ||
userNumbers.includes(' ') ||
userNumbers === '' ||
isNaN(numberedUserNumbers) === true
) {
alert('숫자를 조건에 맞게 다시 입력해주세요!');
return;
}
const userNumberMap = {};
for (let number of userNumbers)
userNumberMap[number] = userNumberMap[number] + 1 || 1;
for (let count in userNumberMap) {
if (userNumberMap[count] > 1) {
alert('숫자를 조건에 맞게 다시 입력해주세요!');
return;
}
}
const splitedUserNumbers = userNumbers.split('');
return splitedUserNumbers;
}
일단 기능은 완벽하게 구현했다.(고 생각한다...?!)
하지만 앞으로 할 일이 많이 남았다.
향후 계획
1. 기능 단위로 module화하기
2. 요구사항 지키며 코드 수정
3. 다른 사람들이 작성한 코드 참고하며 왜 그런 식으로 작성했을지 한 줄 한 줄 들여다보기
4. class, function, OOP, MVC 등의 개념에 대해 공부하고 실제 코드에 녹여보기
우아한 테크코스 프리코스 1주차 과제_숫자 야구 게임
compy님 velog 포스팅_우아한테크코스 4기 프론트엔드 과정, 프리코스 1주차 후기