js를 공부하며✍ 스스로 얼마나 잘 이해하고 있는지 궁금하던 시점에
바닐라로 간단한 로직을 한번 구현해보자라는 생각이 들었습니다.
다양한 아이디어가 있지만 그 중 가위바위보는 대중성있는 게임이고
요구사항이 명확하기 때문에 첫 토이 프로젝트로 채택 하였습니다.
* Html -> 요소들의 부모, 자식간의 관계에 대해 이해하고자 id와 class는 부여하지 않고 구성하기
* JS -> 최대한 직관적이고, 간결하게 코드 구성하기

사용자(Me) 패 선택 -> 상대방(Com)도 랜덤하게 패 선택 -> 승패 확인
-> 승리한 쪽에 점수 부여 -> 5번 이상 이기면 완전한 승리 -> 리셋버튼을 클릭 시 게임 초기화
사용자(Me)가 패를 선택 시, 상대방(Com)도 패 선택을 일시적으로 멈춥니다.
승패가 확인 된 후 상대방(Com)은 다시 랜덤하게 패를 선택 합니다.
사용자가(Me) 버튼을 연속적으로 클릭해도 승패는 1번으로 판단됩니다.
완전한 승리가 되면 사용자는 버튼을 클릭해도 이벤트가 발생하지 않습니다.
리셋 버튼을 클릭하면 게임이 초기화 됩니다.
<section>
<!-- 상단 -->
<div>
<span>가위 바위 보! Com을 이기세요!</span>
<input type="reset" value="reset">
</div>
<!-- main -->
<div>
<div>
<span>Me</span>
<img src="./images/rock.png">
</div>
<div>
<span>Com</span>
<img src="./images/rock.png">
</div>
</div>
<!-- score -->
<div>
<span>0</span>
<span>:</span>
<span>0</span>
</div>
<!-- btnArea -->
<div>
<input type="button" name="가위">
<input type="button" name="바위">
<input type="button" name="보">
</div>
</section>
목표하는 바와 같이 id와 class는 적용하지 않고 만들었습니다.
const title = document.querySelector('div > span') // 상단 제목
const [meImg, comImg] = document.querySelectorAll('img'); // 화면 이미지
const bottons= document.querySelectorAll('input[type=button]'); // 하단 버튼들
// 화면 점수
const meScoreSpan= document.querySelector('section > div:nth-child(3) > span');
const comScoreSpan= document.querySelector('section > div:nth-child(3) > span:last-child');
/* 이미지 */
const rpsGameList = {
0 : "./images/scissors.png",
1 : "./images/rock.png",
2 : "./images/paper.png"
};
/* score */
const scoreList = {
'가위' : 0,
'바위' : 1,
'보' : -1,
};
let meScore; // 사용자(me) 점수
let comScore; // com 점수
Com이 사용할 이미지와 가위바위보의 점수는 객체에 담았습니다.
/* 컴퓨터 선택 */
function comPick(){
// 0 ~ 2 랜덤 값 생성
let key = Math.floor(Math.random() * 3);
// 주소에 넣어 준다
comImg.src = rpsGameList[key];
// 컴퓨터 점수에 넣어 준다
// key = 0 = 가위 / key = 1 = 바위 / key = 2 = 보
switch (key) {
case 0 :
comScore = scoreList['가위'];
break;
case 1 :
comScore = scoreList['바위'];
break;
case 2 :
comScore = scoreList['보'];
break;
}
}
랜덤으로 key 값을 정해준 뒤, rpsGameList 객체를 이용 해 주소를 불러오는 형식으로
Img의 src를 변경해 주었습니다. 또한 이미지에 맞는 점수가 들어가도록 case문을 작성했습니다.
setInterval(comPick, 110);
랜덤으로 Com의 이미지를 변경하는 메소드입니다.
/* 내 선택 */
function mePick(){
// 점수 검사
// 가위 바위 보 버튼들 순회
[...bottons].forEach( btn => {
// 클릭한 버튼에 따라 이미지, 점수 부여
btn.onclick = throttle(e => {
if (setting === -1) return;
switch (e.target.name) {
case '가위' :
meImg.src = rpsGameList[0]
meScore = scoreList['가위']
break;
case '바위' :
meImg.src = rpsGameList[1]
meScore = scoreList['바위']
break;
case '보' :
meImg.src = rpsGameList[2]
meScore = scoreList['보']
break;
}
win_check();
start();
game_over_check();
}, 200)
});
}
fotEach문을 통해 버튼을 순회하며 각 버튼을 클릭했을 때 이벤트가 발생하도록 하였습니다.
클릭한 버튼에 맞는 이미지를 화면에 보여주고, 점수를 meScore 변수에 담았습니다.
function win_check(){
let setMeScore = (+meScoreSpan.textContent) + 1;
let setComScore = (+comScoreSpan.textContent) + 1;
// 컴퓨터 픽 멈춤
stop_comPick();
// 가위 0 / 보 -1
if(meScore > comScore && meScore === 0){
title.textContent = '야호 이겼어요~!'
meScoreSpan.textContent = setMeScore;
}
// 가위 0 / 바위 1
else if(meScore < comScore && meScore === 0){
title.textContent = '앗 졌어요ㅜㅜ'
comScoreSpan.textContent = setComScore;
}
... 생략
사용자가 버튼을 클릭하고 난 뒤, Com도 패 정하는 걸 멈춥니다.
승패를 확인하고 이겼다면? 점수를 1점 올리고 화면 상단에 이겼다는 text로 변경되도록 하였습니다.
function start(){
setTimeout(() => {
if( setting > 0){
return;
}
//음수 값이 이면? 실행
setting = setInterval(comPick, 110);
}, 800);
}
win_check() 함수에서 Com이 패를 선택하는 것을 멈추었기 때문에
지속적인 게임 진행을 위해 다시 랜덤으로 Com이 이미지를 변경하고 패를 결졍하도록 하였습니다.
setting = setInterval(comPick, 110);
/// setting 라는 변수 정의
function stop_comPick(){
clearInterval(setting);
setting = -1;
}
stop_comPick 함수에서 clearInterval을 통해 setting를 종료하고
변수 setting 에 다시 음수값(-1)을 넣습니다.
function start(){
setTimeout(() => {
if( setting > 0){
return;
}
//음수 값이 이면? 실행
setting = setInterval(comPick, 110);
}, 800);
}
차후 버튼을 클릭하며 게임을 진행 시, setting가 음수 값이면
setInterval을 실행하도록 조건을 걸었습니다.
function throttle(callback, delay){
let timerID; //타이머 ID
return (...args) => {
if(timerID) return;
timerID = setTimeout(() => {
callback(...args);
timerID = null;
}, delay);
};
};
콜백되는 함수 가 일정시간(delay) 뒤 실행되도록 하는 throttle 함수를 만들었습니다.
// 가위 바위 보 버튼들 순회
[...bottons].forEach( btn => {
// 클릭한 버튼에 따라 이미지, 점수 부여
btn.onclick = throttle(e => {
if (setting === -1) return;
switch (e.target.name) {
case '가위' :
meImg.src = rpsGameList[0]
meScore = scoreList['가위']
break;
case '바위' :
meImg.src = rpsGameList[1]
meScore = scoreList['바위']
break;
case '보' :
meImg.src = rpsGameList[2]
meScore = scoreList['보']
break;
}
win_check();
start();
game_over_check();
}, 200)
});
버튼들을 클릭할 때마다 throttle 함수가 실행되어 여러번 버튼을 클릭하더라도
0.2초 뒤에 클릭 이벤트가 1번 실행되도록 했습니다.
이론공부를 할 때만해도 setInterval, clearTimeout, setTimeout 메소드에 대한 개념 정리가
되었다고 생각했는데 막상 사용을 하여 기능 구현을 하려하니 쉽지 않았습니다.
해당 프로젝트를 하며 이 메소드들에 대한 용도와 사용법을 확실히 알게 되었습니다.
중복 버튼 클릭의 문제점을 throttle 함수를 통해 일부분 해결하긴 하였으나,
Com이 랜덤값을 돌 때 하나의 패가 2회이상 나오지 않게 한다면 더 확실히 문제점을
막을 수 있지 않을까하는 아쉬움이 듭니다.

아직 배워야할 것이 많다고 느꼈고, 스스로 '무언가'를 구현했다는 점이 보람 차고 즐거웠습니다.
자신감도 붙고 동기부여도 되고 해당 프로젝트를 기점으로 지속적으로 프로젝트를 기획하고 제작해야겠습니다! 👀