본문 바로가기
개발/Javascript

[js] 자바스크립트로 가위바위보 게임 구현하기

by 코딩하는 갓디노 2020. 12. 27.

가위 바위 보 게임

 

자바스크립트를 이용하여,
가위바위보 게임을 구현하는 예제입니다.



예제는 인프런의 제로초, "조현영"님의 강의를 들으면서 공부한 내용입니다.


html 코드

<div id="computer"></div>
<div>
  <button id="바위" class="btn">바위</button>
  <button id="가위" class="btn">가위</button>
  <button id="보" class="btn">보</button>
</div>
<div id="resultShow"></div>

 

css 코드

#computer {
  height: 250px;
  width: 140px;
}

 

순수 자바스크립트

1. background 이미지 설정

const computer = document.querySelector('#computer');
let left = 0; //컴퓨터 결과 = left

//0.15초마다 가위/바위/보 이미지 변경
setInterval(function() {
  if (left === '0') {
    left = '-100px';
  } else if (left === '-100px') {
    left = '-270px';
  } else {
    left = '0';
  }
  computer.style.background = 'url(imgs/rock.jpg)' + left + ' 0 no-repeat'; //0 앞에 한자리 띄우기
}, 150);

 

2. 버튼 클릭 이벤트 시, 나의 결과, 컴퓨터 결과 출력

document.querySelectorAll('.btn').forEach(function(el) {
  el.addEventListener('click', function() {
  let myChoice = this.textContent;
  	console.log(`나의 결과: ${myChoice} / 컴퓨터의 결과: ` ${left});   
  });
});

· 클릭 이벤트 설정을 위하여 버튼 3개에 className을 붙여줍니다. 

 

유사배열
· document.querySelectorAll('.btn')은 유사배열입니다.

document.querySelectorAll('.btn')
//NodeList(3) [button#바위.btn, button#가위.btn, button#보.btn]

유사배열 이란
[]로 감싸 져 있지만 배열이 아닌 것을 유사배열이라고 합니다.
document.querySelectorAll('.btn')은 프로토타입에 forEach()를 사용할 수 있습니다.

 

리팩토링 no1.

left를 가위바위보로 출력을 위해 객체화(dictionary 구조)

const computer = document.querySelector('#computer');
let left = 0;
let computerPosition = {
  바위: '0',
  가위: '-100px',
  보: '-270px',
}

let computerPosition2 = {
  '0': '주먹',
  '-100px': '가위',
  '-270px': '보',
}

setInterval(function() {
  if (left === computerPosition.바위) {
    left = computerPosition.가위;
  } else if (left === computerPosition.가위) {
    left = computerPosition.보;
  } else {
    left = computerPosition.바위;
  }
  computer.style.background = 'url(imgs/rock.jpg)' + left + ' 0 no-repeat';
}, 150);

document.querySelectorAll('.btn').forEach(function(el) {
  el.addEventListener('click', function() {
    let myChoice = this.textContent;
    console.log(`나의 결과: ${myChoice} / 컴퓨터의 결과: `${computerPosition2[left]});
  });
});

 

객체는 . 또는 [] 로 호출합니다.

 

리팩토링 no2

수치를 일일이 입력하는 하드코딩을 방지

const computer = document.querySelector('#computer');
let left = 0;
let computerPosition = {
  바위: '0',
  가위: '-100px',
  보: '-270px',
}

function computerChoice(value) {
  return Object.entries(computerPosition).find(function(el) {
    return el[1] === value; //value = left, value값이 계속 바뀌므로 매개변수로 넣어줌
  })[0]; //배열[0]
}

setInterval(function() {
  if (left === computerPosition.바위) {
    left = computerPosition.가위;
  } else if (left === computerPosition.가위) {
    left = computerPosition.보;
  } else {
    left = computerPosition.바위;
  }
  computer.style.background = 'url(imgs/rock.jpg)' + left + ' 0 no-repeat';
}, 150);

document.querySelectorAll('.btn').forEach(function(el) {
  el.addEventListener('click', function() {
    let myChoice = this.textContent;
    console.log(`나의 결과: ${myChoice} / 컴퓨터의 결과: ` + computerChoice(left));
  });
});

 

Object.entries() **

· Object.entries(객체): 객체를 배열로 변환합니다.
· 이차원 배열: 배열 안의 배열입니다. [[],[],[]]

console.log(Object.entries(computerPosition));
//결과: Array(3)
//0: (2) ["주먹", "0"]
//1: (2) ["가위", "-100px"]
//2: (2) ["보", "-270px"]
//length: 3
//__proto__: Array(0)

 

find() ***

Array.find(), 배열 안에 요소의 특정 값을 찾아주는(즉, return 이 true) 반복문입니다.

 

3. 승리 결과

document.querySelectorAll('.btn').forEach(function(el) {
  el.addEventListener('click', function() {
    let myChoice = this.textContent;
    console.log(`나의 결과: ${myChoice} / 컴퓨터의 결과: ` + computerChoice(left));

    //최종 결과
    if (myChoice === '바위') {
      if (computerChoice(left) === '가위') {
        console.log('이겼습니다.');
      } else if (computerChoice(left) === '바위') {
        console.log('비겼습니다.');
      } else {
        console.log('졌습니다.');
      }
    } else if (myChoice === '가위') {
      if (computerChoice(left) === '보') {
        console.log('이겼습니다.');
      } else if (computerChoice(left) === '가위') {
        console.log('비겼습니다.');
      } else {
        console.log('졌습니다.');
      }
    } else if (myChoice === '보') {
      if (computerChoice(left) === '바위') {
        console.log('이겼습니다.');
      } else if (computerChoice(left) === '보') {
        console.log('비겼습니다.');
      } else {
        console.log('졌습니다.');
      }
    }
  });
});

 

4. Timer 작동

· 버튼 클릭시 이미지 변경 stop
· 1초 후 이미지 변경 재작동

//clearInterval을 위해 변수명 지정
let timeControl = setInterval(function() {
  if (left === computerPosition.바위) {
    left = computerPosition.가위;
  } else if (left === computerPosition.가위) {
    left = computerPosition.보;
  } else {
    left = computerPosition.바위;
  }
  computer.style.background = 'url(imgs/rock.jpg)' + left + ' 0 no-repeat'; 
}, 150);

document.querySelectorAll('.btn').forEach(function(el) {
  el.addEventListener('click', function() {
  	//이미지 움직임 stop
    clearInterval(timeControl);
    //1초 후 timeControl 재실행
    setTimeout(function() {
      timeControl = setInterval(function() { //timeControl 지정해야 clearInterval 계속 작동
        if (left === computerPosition.바위) {
          left = computerPosition.가위;
        } else if (left === computerPosition.가위) {
          left = computerPosition.보;
        } else {
          left = computerPosition.바위;
        }
        computer.style.background = 'url(imgs/rock.jpg)' + left + ' 0 no-repeat'; 
      }, 150);
    }, 1000)
    let myChoice = this.textContent;
    console.log(`나의 결과: ${myChoice} / 컴퓨터의 결과: ` + computerChoice(left));

    if (myChoice === '바위') {
      if (computerChoice(left) === '가위') {
        console.log('이겼습니다.');
      } else if (computerChoice(left) === '바위') {
        console.log('비겼습니다.');
      } else {
        console.log('졌습니다.');
      }
    } else if (myChoice === '가위') {
      if (computerChoice(left) === '보') {
        console.log('이겼습니다.');
      } else if (computerChoice(left) === '가위') {
        console.log('비겼습니다.');
      } else {
        console.log('졌습니다.');
      }
    } else if (myChoice === '보') {
      if (computerChoice(left) === '바위') {
        console.log('이겼습니다.');
      } else if (computerChoice(left) === '보') {
        console.log('비겼습니다.');
      } else {
        console.log('졌습니다.');
      }
    }
  });
});

 

리팩토링 no3

중복되는 setInterval 함수화

let timeControl; //전역 변수 선언
function timeControlFn() {
  timeControl = setInterval(function() {
    if (left === computerPosition.바위) {
      left = computerPosition.가위;
    } else if (left === computerPosition.가위) {
      left = computerPosition.보;
    } else {
      left = computerPosition.바위;
    }
    computer.style.background = 'url(imgs/rock.jpg)' + left + ' 0 no-repeat'; //0 앞에 한자리 띄우기
  }, 150);
}
//함수 실행
timeControlFn();

document.querySelectorAll('.btn').forEach(function(el) {
      el.addEventListener('click', function() {
        clearInterval(timeControl);
        setTimeout(function() {
          timeControlFn();
        }, 1000);
        .............
      });

 

리팩토링 no4

최종 결과 객체화

· 객체로 바위, 가위, 보를 1, 0, -1로 각각 지정합니다.

나의 결과: 컴퓨터의 결과    
이길때 비길때 질때
1 : 0 1 : -1 1 : -1
0 : -1 0 : 0 0 : 1
-1 : 1 -1 : -1 -1 : 0

· 규칙성:
이길 때- 나의 결과 - 컴퓨터의 결과 = 1 or -2
비길 때- 나의 결과 - 컴퓨터의 결과 = 0

· 객체의 property를 [myChoice], [computerChoice(left)]로 value값을 찾습니다.

const score = {
  바위: 1,
  가위: 0,
  보: -1,
}

if (score[myChoice] - score[computerChoice(left)] === 0) {
  console.log('비겼습니다.');
} else if (score[myChoice] - score[computerChoice(left)] === 1 || 
  score[myChoice] - score[computerChoice(left)] === -2) {
  console.log('이겼습니다.');
} else {
  console.log('졌습니다.ㅠ');
}

 

리팩토링 no5

긴 코드 간결화

· 또는 || 관계일 경우, 
[Array].include() () 안의 값이 배열 안의 요소를 포함하면 true

if (score[myChoice] - score[computerChoice(left)] === 0) {
  console.log('비겼습니다.');
} else if ([1, -2].includes(score[myChoice] - score[computerChoice(left)])) {
  console.log('이겼습니다.');
} else {
  console.log('졌습니다.ㅠ');
}

 

리팩토링 no6

중복 부분 변수화

let myScore = score[myChoice];
let computerScore = score[computerChoice(left)];
let scoreDifference = myScore - computerScore;
//결과 화면 출력
resultShow = document.querySelector('#resultShow');

if (scoreDifference === 0) {
  console.log('비겼습니다.');
  resultShow.textContent = '비겼습니다.';
} else if ([1, -2].includes(scoreDifference)) {
  console.log('이겼습니다.');
  resultShow.textContent = '이겼습니다.';
} else {
  console.log('졌습니다.ㅠ');
  resultShow.textContent = '졌습니다.ㅠ';
}

 

리팩토링 no7.

버그 제거

중복 클릭할 때 clearInterval 작동 안 하고, 
timeControlFn 함수가 연속적으로 작동하면서 급격히 빨라지는 현상

해결: 버튼 비활성화, 활성화 처리

let btnActive = false;

document.querySelectorAll('.btn').forEach(function(el) {
  el.addEventListener('click', function() {

    if (btnActive) {
      return false;
    }
    btnActive = true;
    //setInterval 멈추기
    clearInterval(timeControl);

    //1초 후 다시 timeControl    
    setTimeout(function() {
      timeControlFn();
      btnActive = false;
    }, 1000);

    let myChoice = this.textContent;
    console.log(`나의 결과: ${myChoice} / 컴퓨터의 결과: ` + computerChoice(left));

    let myScore = score[myChoice];
    let computerScore = score[computerChoice(left)];
    let scoreDifference = myScore - computerScore;
    resultShow = document.querySelector('#resultShow');

    if (scoreDifference === 0) {
      resultShow.textContent = '비겼습니다.';
    } else if ([1, -2].includes(scoreDifference)) {
      resultShow.textContent = '이겼습니다.';
    } else {
      resultShow.textContent = '졌습니다.ㅠ';
    }
  });
});

 

스크립트 전체 코드(리팩토링 후)

<!DOCTYPE html>
<html lang="ko">

  <head>
    <meta charset="UTF-8">
    <title>가위 바위 보 게임</title>
  </head>
  <style>
    #computer {
      height: 250px;
      width: 140px;
    }
  </style>

  <body>
    <div id="computer"></div>
    <div>
      <button id="바위" class="btn">바위</button>
      <button id="가위" class="btn">가위</button>
      <button id="보" class="btn">보</button>
    </div>
    <div id="resultShow"></div>
    <script>
      const computer = document.querySelector('#computer');
      let left = 0;
      let computerPosition = {
        바위: '0',
        가위: '-100px',
        보: '-270px',
      }

      function computerChoice(value) {
        return Object.entries(computerPosition).find(function(el) {
          return el[1] === value;
          console.log(el[0])
        })[0];
      }
      
      let timeControl;

      function timeControlFn() {
        timeControl = setInterval(function() {
          if (left === computerPosition.바위) {
            left = computerPosition.가위;
          } else if (left === computerPosition.가위) {
            left = computerPosition.보;
          } else {
            left = computerPosition.바위;
          }
          computer.style.background = 'url(imgs/rock.jpg)' + left + ' 0 no-repeat'; //0 앞에 한자리 띄우기
        }, 150);
      }

      timeControlFn();

      const score = {
        바위: 1,
        가위: 0,
        보: -1,
      }

      let btnActive = false;

      document.querySelectorAll('.btn').forEach(function(el) {
        el.addEventListener('click', function() {

          if (btnActive) {
            return false;
          }
          btnActive = true;
          clearInterval(timeControl); 
          setTimeout(function() {
            timeControlFn();
            btnActive = false;
          }, 1000);

          let myChoice = this.textContent;
          console.log(`나의 결과: ${myChoice} / 컴퓨터의 결과: ` + computerChoice(left));

          let myScore = score[myChoice];
          let computerScore = score[computerChoice(left)];
          let scoreDifference = myScore - computerScore;
          resultShow = document.querySelector('#resultShow');

          if (scoreDifference === 0) {
            resultShow.textContent = '비겼습니다.';
          } else if ([1, -2].includes(scoreDifference)) {
            resultShow.textContent = '이겼습니다.';
          } else {
            resultShow.textContent = '졌습니다.ㅠ';
          }
        });
      });
    </script>
  </body>
</html>

 

화면 결과(Result 클릭)

반응형

댓글