[Let's get it 자바스크립트 프로그래밍] - 7.1~7.5 객체 다루기_가위바위보 게임

신혜린·2025년 1월 28일
0
post-thumbnail

7.1 순서도 및 UI 그리기

컴퓨터와 가위바위보를 해서 몇 번 이겼는지 점수를 기록한다.
객체의 사용법을 익히고 타이머를 멈췄다가 재개하는 방법을 배운다.

  • 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>

실행 결과



7.2 객체로 변수 묶기

위 코드에서 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';


7.3 일정 시간마다 반복하기

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

setInterval(() => {
  // 내용
}, 밀리초);
  • setTimeoutsetInterval로 대체하면 아래와 같다.
const changeComputerHand = () => {
  ...
  $computer.style.backgroundSize = 'auto 200px';
}
setInterval(changeComputerHand, 50);

⭐️ setTimeoutsetInterval 의 차이점 및 특징 (참고 블로글)

  • 두 메소드 모두 일정 간격을 두고 실행하도록 만들어주는 스케줄링 메소드이다.
  • 두 메소드 모두 반환값으로 Timer Identifier를 반환해서 clearTimeout(timerId), clearInterval(timerId)의 매개변수로 걸어놓았던 스케줄링을 취소할 수 있다.
  • 기본적으로 setTimeout은 한번만 실행하고, setInterval은 무한번 반복한다.
  • setTimeout으로 setInterval과 같은 기능을 수행하게 하려면 중첩 setTimeout 방식을 사용하면 된다.
  • 중첩 setTimeout은 시간 지연 간격을 보장하지만 setInterval은 시간 지연을 보장하지 않는다.
  • setInterval은 지연 시간 속에 함수를 실행하는 데 소모되는 시간도 포함시킨다.
  • 🗑️ setTimeoutsetInterval의 콜백함수로 넘기면 함수에 대한 내부 참조가 새롭게 만들어지고, 이 참조 정보는 스케줄러에 저장된다. 따라서 해당 함수를 참조하는 것이 없어도 가비지 컬렉션의 대상이 되지 않으며, 스케줄러가 함수를 호출할 때까지 함수는 메모리에 유지된다. 실제 함수가 차지해야 하는 공간보다 더 많은 메모리 공간이 사용될 수 있으므로 스케줄링할 필요가 없어진 함수는 아무리 작더라도 취소하는 게 좋다.


7.4 타이머 멈췄다 다시 실행하기

버튼을 클릭하는 순간 승부를 확인할 수 있게 잠깐 멈추는 기능을 추가한다.
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 함수를 호출해도 아무 일도 일어나지 않게 만들어야 한다.

버그 방지 방법 1

  • 첫 번째 방법은 removeEventListener 메서드를 사용하면 된다.
function 함수() {}
태그.addEventListener('이벤트', 함수);
태그.removeEventListener('이벤트', 함수);

버그 방지 방법 1

  • 이벤트를 제거하는 대신 함수가 아무 일도 하지 않게 만든다.
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);


7.5 가위바위보 규칙 찾기

버튼을 클릭하면 점수를 계산해서 화면에 점수를 표시하는 부분을 구현한다.
버튼을 클릭할 때 어떤 선택지를 클릭했는지 알아야 한다.
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이라고 가정하고 두 값의 차이를 구해 다음과 같이 표를 만들어본다.

나\컴퓨터가위바위
가위012
바위-101
-2-10
  • 무승부면 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);
  }
};
  • 마지막으로 승리 시 1점, 패배 시 -1점을 주고 #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);
  }
};

결과

profile
개 발자국 🐾

0개의 댓글