Vanilla JS로 숫자 야구 게임 만들기! - 1

solmii·2020년 5월 4일
0

Project

목록 보기
2/14
post-thumbnail

⚠️ 글이 좀 길어질 것 같으니 결과는 여기서 확인
(아직 반도 완성 못함 주의😂)


wecode 사전스터디 3주차 (나 혼자 정한) 과제는 숫자 야구 게임 만들기!
우선, 구현하고 싶은 기능등을 README.md로 정리했다.

음.... 근데 이렇게 적는거 아닌것 같아...
README 처음 작성하다보니 주절주절 글이 엄청 길어졌다😂
thinking 부분은 삭제하는게 낫겠당.. 일단 이건 다음주에!!

아무튼 3주차에 구현한 기능은

  1. 1~9 사이의 중복되지 않는 무작위 정수 출력
  2. 이를 토대로 정답 체크 => 정답이면 Homerun!!
  3. 오답이면 스트라이크, 볼 체크해서 출력
  4. 기회 10번 이상 소모하면 Game Over
    까지이당!!

(와 되게 별거없어 보이네 짱 힘들었는데😩)
아무튼 여기까지 구현하면서 힘들었던 점과 새로 배운 내용 정리!!


정답 숫자 생성(할때 막힘😚)

pop 과 push

numberList.pop();
numberList.shift();

.pop() = 배열의 맨 끝에 요소를 제거한다.
.shift() = 배열의 맨 앞에 요소를 제거한다.

numberList.push();
numberList.unshift();

.push() = 배열의 맨 끝에 요소를 추가한다.
.unshift() = 배열의 맨 앞에 요소를 추가한다.

⇒ 하지만 pop과 shift는 차례대로 뽑기 때문에 랜덤이 아니다. 기각!!


splice

const numberList = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const correctNumber = [];

for (let i = 0; i < 4; i += 1) {
  const randomNumber = numberList.splice(
     Math.floor(Math.random() * numberList.length), 1)[0];
  correctNumber.push(randomNumber);
}

.splice() = 배열의 특정 위치의 요소를 제거하거나 추가한다.

우와 정말이지 어렵고 낯설고 긴 코드다.....
이럴땐 무식한 방법이 짱이지, 하나하나 뜯어보자

☝️numberList.splice(Math.floor(Math.random() * numberList.length), 1)[0];

splice 로 numberList 배열 안의 값을 제거한다.

.splice (수정할 배열 요소의 시작 위치, 삭제할 요소 갯수(0 = 삭제 안함), 추가할 요소) 의 형식으로 작성할 수 있다.

var arr = [1, 2, 3, 4, 5];
arr.splice(3, 2); // arr = [1, 2, 3];
arr.splice(2, 0, 6, 7, 8); // arr = [1, 2, 3, 6, 7, 8];

위의 코드를 다시 보면, 뭔가 복잡해 보이지만 결국 numberList.splice(시작 위치(Math.random() 함수로 랜덤 위치 지정), 1(숫자를 1개만 제거)) 이다!

✌️numberList.splice (Math.floor(Math.random() * numberList.length), 1)[0];

⇒ Math.random 으로 뽑은 소수값에서 소수점 이하는 버림한다.

💁🏻‍♀️ 소수점을 없애는 메서드

Math.floor() : 소수점 이하를 버림 한다.
Math.ceil() : 소수점 이하를 올림 한다.
Math.round() : 소수점 이하를 반올림 한다.

여러 메서드 중에서 Math.floor()를 사용하는 이유는?

우리가 얻고자 하는 값은 1~9 사이의 중복되지 않는 랜덤한 정수! 즉, 다시 말해 numberList 배열 안에 있는 숫자들이다.
컴퓨터는 위치를 셀때 0부터 카운트하기 때문에 0~8까지의 랜덤 정수를 splice의 시작 위치에 넣어줘야 한다.

그런데, Math.ceil() 을 사용하면 0.xx가 올림 되어 1~8 사이의 정수가 되어버리고
Math.round() 를 사용하면, 0~8까지의 정수가 나오지만, 0과 8이 나올 확률이 낮아진다.

0이 나오는 값: 0 ~ 0.4999
1이 나오는 값: 0.5 ~ 1.4999
...
7이 나오는 값: 6.5 ~ 7.4999
8이 나오는 값: 7.5 ~ 7.999

🤟numberList.splice(Math.floor(Math.random() * numberList.length), 1)[0];

Math.random()0 이상 1 미만의 랜덤한 소수값을 반환하는 함수이다.
여기에 * numberList.length 를 곱해주어, 0 이상 numberList 배열의 갯수 (처음은 9) 미만의 랜덤 소숫값을 얻을 수 있다.
앞 과정에서 소수점 이하는 버려지기 때문에 (처음은 0~8, 그 다음은 0~7) 사이의 랜덤한 정수를 얻을 수 있다.

👉 혹은 * 9를 해주어 0이상 9미만의 랜덤 소수값을 얻어도 된다. 단, 이 경우에는 splice를 반복할때마다 numberList 배열에 남아있는 숫자는 하나씩 줄어드는데, splice의 시작 위치는 계속 0~8까지 이기 때문에 undefined 값이 뽑힐 수 있다.
그래서 이렇게 * 9 를 해줬을 경우에는 이렇게 코드를 작성해주면 undefined 값이 사라진다.

(Math.random() * (9 - i)), 1)

numberList.splice(Math.floor(Math.random() * numberList.length), 1)[0];

splice는 기존 배열에서 분리한 값을 배열로 다시 반환하는 메서드이다.

즉, 실컷 splice를 돌려도, 결과값은 [2, 5, 6, 8] 이 아닌, [2], [5], [6], [8]이 되는것.
이 때, 마지막에 [0]; 을 붙여주면 해당 배열에서 0번째 값만 반환할 수 있다.
([2]의 0번째 값이니까 그냥 숫자 2!!)


정답 체크(할때 막힘😋)

addEventListener

특정 이벤트가 발생했을 때, 특정 함수를 실행하게 한다!

gameForm.addEventListener("submit", function callBack(event) {
  event.preventDefault();
  const correct = gameInput.value;
});

이벤트를 실행할 요소.addEventListener("이벤트 종류", function 함수명(생략 가능) (event) { }); 의 형식으로 작성하며, { } 안에는 이벤트가 발생했을 때 실행할 내용을 적는다.

예제에서 "submit" 칸에 들어갈 수 있는 다른 event 종류

event내용
blur포커스를 다른곳으로 옮길 경우
click링크나 폼의 구성원을 클릭할 때
change선택값을 바꿀 경우
dbclick더블 클릭할 때
dragdrop마우스를 누른채 움직일 경우
focus포커스가 위치할 경우
mouseover마우스가 올라올 경우
mouseout마우스가 떠날 경우
mousedown마우스를 누를 경우
mousemove마우스를 움직일 경우
mouseup마우스를 눌렀다 놓을 경우
keydown키보드를 눌렀을 때
keyup키보드를 눌렀다 떼는 순간
keypress키보드를 눌러 어떤 텍스트가 작성되는 순간
select입력 양식의 하나가 선택될 때
submit폼을 전송하는 경우
load페이지, 윈도우가 브라우저로 읽혀질 때
unload현재의 브라우저, 윈도우를 떠날 때
error문서나 이미지에서 에러를 만났을 때
reset리셋 버튼이 눌렸을 때
move윈도우나 프레임을 움직일 경우
resize윈도우나 프레임 사이즈를 움직일 경우

event.preventDefault(); ⇒ 이벤트의 전파를 막지 않고 이벤트를 취소할 수 있다.

"submit" 이벤트는 폼을 전송할때, 즉 매번 정답을 입력할 때 마다 새로고침 되는 기본 동작을 갖고 있기 때문에 이벤트를 취소하는 event.preventDefault();를 입력한다.

addEventListener는 사실 아직도 이해를 못했당... 다음에 다시 정리하기로!!


join 과 Number, split

join

if (guess === answer.join("")) {
	gameHint.textContent = "Homerun!!";
	} else { 어쩌구 저쩌구
}

guess ⇒ 정답자가 입력한 정답 ⇒ input으로 정보를 받아옴 ⇒ 문자열로 저장

answer ⇒ 1~9까지의 numberList 배열에서 랜덤으로 뽑은 숫자 4개 ⇒ splice 메서드로 뽑았기 때문에, 배열로 저장

guessanswer 를 비교해야 하는데, 둘의 type이 다르기 때문에 guess에 어떤 값이 와도 무조건 불일치!
비교하기 전에 먼저 둘을 같은 type으로 바꿔줘야 한다!

이럴때 사용할 수 있는 Array.join() : 배열을 문자열로 변환

test = ["A", "B", "C"];

test.join(); // "A,B,C"
test.join(''); // "ABC"
test.join('-'); // "A-B-C"
test.join('/'); // "A/B/C"

join( ) 을 비우면 콤마 , 로 구분하고 다른 문자를 넣으면 입력한 문자로 구분된다.

answer 은 배열, guess 는 문자열이다.
배열끼리의 비교는 복잡하고 어렵다는 느낌이어서, join 메서드를 이용해 answer 을 문자열로 변경후 비교해주었다.
(시간나면 배열끼리의 비교도 정리하는걸로!)


Number

for (let i = 0; i < 3; i += 1) {
  if (Number(guessList[i]) === answer[i]) { //스트라이크 체크
    strike += 1;
  } else if (answer.indexOf(Number(guessList[i])) > -1) { //볼 체크
    ball += 1;
  }

if (Number(guessList[i]) === answer[i])

else if (answer.indexOf(Number(guessList[i])) > -1)

guessList = 문자열을 조각내서 만든, 문자가 모여있는 배열 (ex : ["1", "3", "5", "8"])

answer = 1~9까지의 숫자 배열에서 무작위로 뽑아낸, 숫자 배열 (ex : [1, 3, 5, 8])

그래서 guessList[i]) === answer[i] 만 비교하면 type이 다르기 때문에 무조건 불일치!
비교하기 전에 먼저 둘을 같은 type으로 바꿔줘야 한다!

이럴때 사용할 수 있는 Number() : 문자열을 숫자로 변환
(숫자로 변환할 수 없는 값인 경우 NaN 를 반환한다.)

처음엔 이렇게 작성했지만, Number()가 계속 반복되서 나중에 map 메서드를 이용해 guessList (문자열) 자체를 숫자 배열로 바꿔주었다.
이 내용은 2편에 정리했음!


String

Number 배우면서 뽀너스로~!!!

String은 말 그대로 숫자를 문자열로 변환해준다.

if (string[i] === String(number[i]))

split

let guessList = guess.split("");

split 은 반대로, 문자열을 배열로 바꿔준다.

정확히 말하자면 바꿔주는게 아니라, 문자열을 특정 기준으로 자르고 그 값을 배열로 반환한다.


guess 에 정답자가 4자리의 숫자를 입력한다.

예를들어 1234 를 입력하고 submit 하면.... 이 데이터는 우리에게 어떻게 전송될까?
정답은 "1234"!! 그냥 문자열 통으로 도착해버린다.

정답자가 입력한 정보를 guessList 라는 배열로 저장하고 싶다. (정답 배열과 비교해야 하니까!)

그럼 우선 "1234"를 "1", "2", "3", "4" 총 4개의 문자로 잘라줘야 한다.

test = "A는 B가 C"; //가운데 스페이스가 있음

test.split(""); // "A", "는", " ", "B", "가", " ", "C" (스페이스까지 출력)
test.split(" "); // "A는", "B가", "C"
test.split("",2); // "A", "는"

split( ) 에 ("")를 입력하면 모든 단어, 공백을 분리해서 배열로 만든다.

(" ")는 띄워쓰기(스페이스) 한 부분을 기준으로 자른다.

("",2) 두번째 인자에 숫자를 넣으면, 그 수만큼만 문자를 분리한다. (나머지 남은 문자는 반환되지 않는다.)


indexOf

for (let i = 0; i < 3; i += 1) {
	if (Number(guessList[i]) === answer[i]) { //스트라이크 체크
		strike += 1;
	} else if (answer.**indexOf**(Number(guessList[i])) > -1) { //볼 체크
		ball += 1;
	}

스트라이크, 볼 체크할때 사용한 함수이다.

우선 스트라이크 체크 부분은 각 배열끼리 === 로 비교하는 거니 넘어가고, 볼 체크가 상당히 까다로웠다ㅠ

else if (answer.indexOf(Number(guessList[i])) > -1)

.indexOf 함수는, 앞에 지정된 문자열에서 특정 문자의 위치를 알려주는 함수이다.

쉽게 말하면 문자열 왼쪽부터 읽어 나가다가, 특정 문자를 만나면 해당 위치를 반환하는데 첫번째는 당연히 0으로 반환하고, 두번째는 2, 그다음은 3 이런식으로 반환한다.

만약, 특정 문자를 만나지 못하면 그때는 -1 을 반환한다.

이를 이용해서 함수 뒤에 > -1 을 붙여주면, 특정 문자를 만나지 못하는 경우는 제외할 수 있다.

배열의 위치와 값이 모두 일치하는 경우는 앞의 if에서 스트라이크 체크로 걸려서 따로 빠지니까, .indexOf > -1 로 나오는 값은 자연스럽게 볼이 된다!!


우선 3주차에 구현한 부분은 이정도...!
이제 제일 큰 숙제가 남았당....

매 시도마다 도전한 정답과, 힌트를 input 창 밑에 log로 쭈르륵 나오게 하고 싶은데 이건 정말 내 능력 밖이고😂 구글링과의 사투인것.....

이거 개발 공부 이전에 영어 공부가 더 시급한거 아닐까....? 엉엉😭

혹시 잘못되었거나 더 좋은 방향으로 작성 가능한 코드는 댓글 남겨주시면 정말 감사하겠습니다!!

profile
하루는 치열하게 인생은 여유롭게

0개의 댓글