컴퓨터와 가위바위보를 해서 몇 번 이겼는지 점수를 기록한다.
객체의 사용법을 익히고 타이머를 멈췄다가 재개하는 방법을 배운다.
- 0.05초마다 가위바위보 값이 바뀌도록 구현한다.
- 가위 바위 보 버튼을 누른다.
- 돌아가는 그림을 멈춘다.
- 결과에 대한 점수를 계산한다.
- 점수를 표시한다.
- 1초 후에 다시 그림을 돌린다.
- 반복.
<html>
<head>
<meta charset="utf-8" />
<title>가위바위보</title>
<style>
#computer {
width: 142px;
height: 200px;
}
</style>
</head>
<body>
<div id="computer"></div>
<div>
<button id="scissors" class="btn">가위</button>
<button id="rock" class="btn">바위</button>
<button id="paper" class="btn">보</button>
</div>
<div id="score">0</div>
<script>
const $computer = document.querySelector('#computer');
const $score = document.querySelector('#score');
const $rock = document.querySelector('#rock');
const $scissors = document.querySelector('#scissors');
const $paper = document.querySelector('#paper');
const IMG_URL = './rsp.png';
$computer.style.background = `url(${IMG_URL}) 0 0`;
$computer.style.backgroundSize = 'auto 200px';
</script>
</body>
</html>
위 코드에서 IMG_URL
변수에 주어진 이미지는 다음과 같다.
이미지 스프라이트?
이미지가 가위, 바위, 보로 각각 분리된 게 아니라 하나의 이미지로 합쳐져 있다.
- 서버에 이미지를 요청하는 횟수를 줄이기 위한 기법으로, 이렇게 이미지가 합쳐져 있는 것을 이미지 스프라이트(image sprite)라고 한다.
- 하나의 이미지로 합쳐져 있기 때문에 CSS와 javascript를 통해 적절히 잘라서 화면에 표시해야 한다.
background
속성은url(주소) x좌표 y좌표
로 구성되어있다.const IMG_URL = './rsp.png'; // url의 주소가 될 변수 $computer.style.background = `url(${IMG_URL}) 0 0`; // 가위 $computer.style.background = `url(${IMG_URL}) -220px 0`; // 바위 $computer.style.background = `url(${IMG_URL}) -440px 0`; // 보
위 이미지들의 y좌표는 모두 0이라는 공통점이 있다. 따라서 x좌표를 변수에 저장하면 된다.
그렇다면 이 변수들은 모두 x좌표라는 공통점이 있기 때문에 객체로 묶어서 표현하는 것이 좋다.
const scissorsX = '0'; // 가위
const rockX = '-220px'; // 바위
const paperX = '-440px'; // 보
// 위 변수들을 객체로 묶어서 표현
const rspX = {
scissors: '0', // 가위
rock: '-220px', // 바위
paper: '-440px', // 보
}
backgroundSize
속성을 활용한다. 가로는 auto
, 세로는 200px
로 설정하려면 다음과 같다.$computer.style.backgroundSize = 'auto 200px';
0.05초(50밀리초)마다 가위바위보 그림을 바꾼다.
let computerChoice = 'scissors';
const changeComputerHand = () => {
if (computerChoice === 'rock') {
computerChoice = 'scissors';
} else if (computerChoice === 'scissors') {
computerChoice = 'paper';
} else if (computerChoice === 'paper') {
computerChoice = 'rock';
}
$computer.style.background = `url(${IMG_URL}) ${rspX[computerChoice]} 0`;
$computer.style.backgroundSize = 'auto 200px';
setTimeout(changeComputerHand, 50);
}
setTimeout(changeComputerHand, 50);
위와 같이 setTimeout(changeComputerHand, 50);
을 사용하여 기능을 구현할 수 있지만, 같은 효과를 내는 setInterval
이라는 함수가 존재한다.
setInterval(() => {
// 내용
}, 밀리초);
setTimeout
을 setInterval
로 대체하면 아래와 같다.const changeComputerHand = () => {
...
$computer.style.backgroundSize = 'auto 200px';
}
setInterval(changeComputerHand, 50);
⭐️
setTimeout
과setInterval
의 차이점 및 특징 (참고 블로글)
- 두 메소드 모두 일정 간격을 두고 실행하도록 만들어주는 스케줄링 메소드이다.
- 두 메소드 모두 반환값으로
Timer Identifier
를 반환해서clearTimeout(timerId)
,clearInterval(timerId)
의 매개변수로 걸어놓았던 스케줄링을 취소할 수 있다.- 기본적으로
setTimeout
은 한번만 실행하고,setInterval
은 무한번 반복한다.setTimeout
으로setInterval
과 같은 기능을 수행하게 하려면중첩 setTimeout
방식을 사용하면 된다.중첩 setTimeout
은 시간 지연 간격을 보장하지만setInterval
은 시간 지연을 보장하지 않는다.
setInterval
은 지연 시간 속에 함수를 실행하는 데 소모되는 시간도 포함시킨다.- 🗑️
setTimeout
과setInterval
의 콜백함수로 넘기면 함수에 대한내부 참조
가 새롭게 만들어지고, 이 참조 정보는스케줄러
에 저장된다. 따라서 해당 함수를 참조하는 것이 없어도 가비지 컬렉션의 대상이 되지 않으며, 스케줄러가 함수를 호출할 때까지 함수는메모리에 유지
된다. 실제 함수가 차지해야 하는 공간보다 더 많은 메모리 공간이 사용될 수 있으므로 스케줄링할 필요가 없어진 함수는 아무리 작더라도 취소하는 게 좋다.
버튼을 클릭하는 순간 승부를 확인할 수 있게 잠깐 멈추는 기능을 추가한다.
setInterval
함수를 취ㅗ할 수 있는 clearInterval
함수가 있다.
let 아이디 = setInterval(함수, 밀리초);
clearInterval(아이디);
setInterval
이 멈췄다가 1초 뒤에 다시 실행하도록 추가한다.const changeComputerHand = () => {...}
let intervalId = setInerval(changeComputerHand, 50);
const clickButton = () => {
clearInterval(intervalId);
// 점수 계산 및 화면 표시
setTimeout(() => {
intervalId = setInterval(changeComputerHand, 50);
}, 1000);
};
$rock.addEventListener('click', clickButton);
$scissors.addEventListener('click', clickButton);
$paper.addEventListener('click', clickButton);
버튼을 클릭하면 그림이 멈췄다가 1초 뒤에 다시 돌아온다.
하지만 여기에 한 가지 버그가 있다.
그림이 멈춘 동안 버튼을 여러 번 클릭하면, 1초 뒤에 그림이 평소보다 훨씬 더 빠르게 돌아간다.
- 그 이유는 버튼을 클릭할 때 마다 각각
setTimeout
타이머가 실행되기 때문이다.- 버튼을 클릭할 때
clearInterval
으로 실행 중인setInterval
을 멈추지만,setTimeout
을 멈추고 있지 않다.- 따라서 버튼을 누른 횟수만큼
setTimeout
타이머가 실행되고 각각 1초 뒤에setInterval
을 하게 되어 그림이 매우 빠른 속도로 돌아간다.- 이 현상을 막기 위해 그림이 멈춰 있는 동안 버튼을 클릭해도
clickButton
함수가 호출되지 않게 하거나clickButton
함수를 호출해도 아무 일도 일어나지 않게 만들어야 한다.
removeEventListener
메서드를 사용하면 된다.function 함수() {}
태그.addEventListener('이벤트', 함수);
태그.removeEventListener('이벤트', 함수);
let clickable = true;
const clickButton = () => {
if (clickable) {
clearInterval(intervalId);
clickable = false;
// 점수 계산 및 화면 표시
setTimeout(() => {
clickable = true;
intervalId = setInterval(changeComputerHand, 50);
}, 1000);
}
};
$rock.addEventListener('click', clickButton);
$scissors.addEventListener('click', clickButton);
$paper.addEventListener('click', clickButton);
버튼을 클릭하면 점수를 계산해서 화면에 점수를 표시하는 부분을 구현한다.
버튼을 클릭할 때 어떤 선택지를 클릭했는지 알아야 한다.
event.target.textContent
를 사용하면 글자를 알아낼 수 있다.
const clickButton = () => {
if (clickable) {
clearInterval(intervalId);
clickable = false;
const myChoice = event.target.textContent === '바위'
? 'rock'
: event.target.textContent === '가위'
? 'scissors'
: 'paper';
setTimeout(() => {
clickable = true;
intervalId = setInterval(changeComputerHand, 50);
}, 1000);
}
};
간단한 코드 구현을 위해 가위를 낸 경우 1, 바위를 0, 보를 -1이라고 가정하고 두 값의 차이를 구해 다음과 같이 표를 만들어본다.
나\컴퓨터 | 가위 | 바위 | 보 |
---|---|---|---|
가위 | 0 | 1 | 2 |
바위 | -1 | 0 | 1 |
보 | -2 | -1 | 0 |
0
, 이기면 2 또는 -1
, 지면 1 또는 -2
가 나온다.const scoreTable = {
rock: 0,
scissors: 1,
paper: -1,
};
let clickable = true;
const clickButton = () => {
if (clickable) {
clearInterval(intervalId);
clickable = false;
const myChoice = event.target.textContent === '바위'
? 'rock'
: event.target.textContent === '가위'
? 'scissors'
: 'paper';
const myScore = scoreTable[myChoice];
const computerScore = scoreTable[computerChoice];
const diff = myScore - computerScore;
if ([2, -1].includes(diff)) { // diff === 2 || diff === -1와 같다.
console.log('승리');
} else if ([-2, 1].includes(diff)) {
console.log('패배');
} else {
console.log('무승부');
}
setTimeout(() => {
clickable = true;
intervalId = setInterval(changeComputerHand, 50);
}, 1000);
}
};
#score
태그에 메시지까지 표시하는 절차를 완성한다.const scoreTable = {
rock: 0,
scissors: 1,
paper: -1,
};
let clickable = true;
let score = 0;
const clickButton = () => {
if (clickable) {
clearInterval(intervalId);
clickable = false;
const myChoice = event.target.textContent === '바위'
? 'rock'
: event.target.textContent === '가위'
? 'scissors'
: 'paper';
const myScore = scoreTable[myChoice];
const computerScore = scoreTable[computerChoice];
const diff = myScore - computerScore;
let message;
if ([2, -1].includes(diff)) {
score += 1;
message = '승리';
} else if ([-2, 1].includes(diff)) {
score -= 1;
message = '패배';
} else {
message = '무승부';
}
$score.textContent = `${message} 총: ${score}점`;
setTimeout(() => {
clickable = true;
intervalId = setInterval(changeComputerHand, 50);
}, 1000);
}
};