[JavaScript Toy Project] Mini Game | 가위바위보 게임 만들기 토이프로젝트

이은진·2020년 11월 22일
25

바닐라 자바스크립트로 간단한 가위바위보 게임을 만들어보았습니다. 사용자가 가위, 바위, 보 중에 한 개를 선택하면 컴퓨터 측에서 랜덤으로 선택해, 그 결과를 위에 출력해주는 방식입니다. 애초에 기획했던 비주얼만으로는 난이도 최하일 줄 알았는데 생각보다 시간이 더 소요된 프로젝트였습니다.

contents

  1. Mini Game UI/UX
  2. Main Features
    2-1. How the game works
    2-2. Update status
    2-3. Save data into the local storage
    2-4. Reset button
  3. Details
    3-1. Hover effects
    3-2. Total score stays when refreshed

1. Mini Game UI/UX

가위바위보 게임은 크게 스코어를 알려주는 상단 바, 사용자와 컴퓨터가 선택한 것을 보여주는 박스, 사용자가 선택할 항목이 있는 박스로 나뉘어 있습니다.

2. Main Features

2-1. How the game works

사용자의 측에서 선택한 옵션과 컴퓨터 측에서 랜덤으로 출력한 옵션을 비교해서 일반적인 가위바위보 규칙에 따라 점수가 업데이트되는 방식으로 작동합니다.

//selecting buttons function
const selectImage = () => {
  if (rockBtn.classList.contains('click') && 
  !scissorsBtn.classList.contains('click') &&
  !paperBtn.classList.contains('click')) {
    playerSelectedImg.style.background = bgImage[0]
    playerSelectedText.innerText = selectedText[0]
  } else if (!rockBtn.classList.contains('click') && 
  scissorsBtn.classList.contains('click') &&
  !paperBtn.classList.contains('click')) {
    playerSelectedImg.style.background = bgImage[1]
    playerSelectedText.innerText = selectedText[1]
  } else if (!rockBtn.classList.contains('click') && 
  !scissorsBtn.classList.contains('click') &&
  paperBtn.classList.contains('click')){
    playerSelectedImg.style.background = bgImage[2]
    playerSelectedText.innerText = selectedText[2]
  } else {
    return
  }
}

컴퓨터가 출력할 랜덤 이미지는 Math.floor() 메서드와 Math.random() 메서드로 가져왔습니다.

const bgImage = [
  "url(image/rock.png) no-repeat center",
  "url(image/scissors.png) no-repeat center",
  "url(image/paper.png) no-repeat center"
]
const selectedText = ["Rock", "Scissors", "Paper"]

//get random computer image
const getRandomComputerImage = () => {
  const randomNum = Math.floor(Math.random() * 3)
  computerSelectedImg.style.background = bgImage[randomNum]
  computerSelectedText.innerText = selectedText[randomNum]
}

사용자가 클릭한 엘리먼트에 'click'이라는 클래스가 생성되면 그 결과에 따라 중간 영역의 왼쪽에 이미지가 출력됩니다. 그리고 나서 0.3초 후 컴퓨터가 랜덤한 이미지를 오른쪽에 출력하면 최종 결과가 나오게 됩니다.

//select button event listener
const selectBox = document.getElementById('select-box')
const rockBtn = document.getElementById('rock')
const scissorsBtn = document.getElementById('scissors')
const paperBtn = document.getElementById('paper')

//select button event listener
selectBox.addEventListener('click', (e) => {
  computerSelectedImg.style.background = ''
  computerSelectedText.innerText = ''
  if (e.target === rockBtn) {
    rockBtn.classList.add('click')
    scissorsBtn.classList.remove('click')
    paperBtn.classList.remove('click')
  } else if (e.target === scissorsBtn) {
    rockBtn.classList.remove('click')
    scissorsBtn.classList.add('click')
    paperBtn.classList.remove('click')
  } else if (e.target === paperBtn) {
    rockBtn.classList.remove('click')
    scissorsBtn.classList.remove('click')
    paperBtn.classList.add('click')
  } else {
    return false
  }
  selectImage()
  setTimeout(() => {
    getRandomComputerImage()
    updateStatus()
    updateLocalstorage()
  }, 300);

})

2-2. Update status

updateStatus() 함수는 게임의 결과를 크게 세 가지 경우로 나누어, 결과를 status라는 객체에 push() 메서드로 추가해 줍니다. 사용자가 선택한 옵션, 컴퓨터가 선택한 옵션, 어느 쪽이 이겼는지에 대한 내용이 배열에 포함돼 있습니다. 사용자가 옵션을 클릭했을 때 이 함수가 실행됩니다.

//updating status
const updateStatus = () => {
  if ( ( (playerSelectedText.innerText === selectedText[0]) && 
  (computerSelectedText.innerText === selectedText[1]) ) ||
  ( (playerSelectedText.innerText === selectedText[1]) && 
  (computerSelectedText.innerText === selectedText[2]) ) || 
  ( (playerSelectedText.innerText === selectedText[2]) && 
  (computerSelectedText.innerText === selectedText[0]) ) ) {
    const result = {
      playerSelect: playerSelectedText.innerText,
      player: 'win',
      computerSelect: computerSelectedText.innerText,
      computer: 'lose',
    }
    results.push(result)
    playerStatusEl.innerText++

  } else if ( ( (playerSelectedText.innerText === selectedText[0]) && 
  (computerSelectedText.innerText === selectedText[2]) ) ||
  ( (playerSelectedText.innerText === selectedText[1]) && 
  (computerSelectedText.innerText === selectedText[0]) ) || 
  ( (playerSelectedText.innerText === selectedText[2]) && 
  (computerSelectedText.innerText === selectedText[1]) )) {

    const result = {
      playerSelect: playerSelectedText.innerText,
      player: 'lose',
      computerSelect: computerSelectedText.innerText,
      computer: 'win',
    }
    results.push(result)
    computerStatusEl.innerText++

  } else {
    results.push({
      playerSelect: playerSelectedText.innerText,
      player: 'draw',
      computerSelect: computerSelectedText.innerText,
      computer: 'draw',
    })
  }
}

나중에 게임 결과를 저장하고 출력할 때 이 객체의 키와 값에 접근하여 데이터를 활용합니다.

const playerStatusEl = document.getElementById('status-result__player')
const computerStatusEl = document.getElementById('status-result__computer')
//rendering status by localstorage data
const renderStatus = () => {
  const results = getLocalStorageData
  playerStatusEl.innerText = results
            .map((result) => result["player"])
            .filter((word) => word === "win").length
  computerStatusEl.innerText = results
            .map((result) => result["computer"])
            .filter((word) => word === "win").length
}

2-3. Save data into the local storage

로컬스토리지에 데이터가 저장됩니다. localStorage.setItem(), localStorage.getItem() 메서드로 데이터를 넣고 가져옵니다. 원래 굳이 로컬스토리지에 정보를 저장하지 않고 결과에 따라 스코어를 +1 하는 걸로 만족하려고 했는데, 나중에 기능을 또 추가하게 되면 이전 정보를 활용할 수 있어야 하기 때문에 로컬스토리지를 활용하기로 했습니다.

let results = []
//update data in local storage
const updateLocalstorage = () => {
  localStorage.setItem('results', JSON.stringify(results))
}

//get data from local storage
const getLocalStorageData = JSON.parse(localStorage.getItem('results'))
console.log(getLocalStorageData)

results = getLocalStorageData // 새로고침 시 로컬스토리지 데이터를 사용하겠다는 의미

2-4. Reset button

스코어를 리셋해주는 버튼입니다. resetStatus() 함수가 실행되면 상단의 스코어와 게임 창이 리셋될 뿐만 아니라 로컬스토리지에서도 모든 게임 정보가 지워집니다.

const playerStatusEl = document.getElementById('status-result__player')
const computerStatusEl = document.getElementById('status-result__computer')
//reset status
const resetBtn = document.getElementById('reset')
const resetStatus = () => {
  playerStatusEl.innerText = 0
  computerStatusEl.innerText = 0
  playerSelectedImg.style.background = ''
  playerSelectedText.innerText = ''
  computerSelectedImg.style.background = ''
  computerSelectedText.innerText = ''
  results = []
  updateLocalstorage()
}

//reset button
resetBtn.addEventListener('click', resetStatus)

3. Details

3-1. Hover effects

약간의 마우스 오버 효과를 주어 사용자가 선택하려 하는 타겟을 잘 알아볼 수 있도록 했습니다.

.reset {
  all: unset;

  width: 100px;
  height: 36px;

  background: #fdfdfd;
  border: 1px solid #ddd;
  border-radius: 18px;

  font-size: 16px;
  font-family: Verdana, Geneva, Tahoma, sans-serif;

  display: flex;
  justify-content: space-evenly;
  align-items: center;

  cursor: pointer;
  padding: 0 6px;
  margin: 0 10px;

  transition: all .2s ease;
}

.reset:hover {
  background: #dec6eb;
  border: 1px solid #777;
}
.rock, .scissors, .paper {
  width: 80px;
  height: 80px;
  background: #aaa;

  border: 1px solid #444;
  border-radius: 40px;

  margin-right: 10px;
  cursor: pointer;

  transition: all .1s ease;
}

.rock:hover, .scissors:hover, .paper:hover{
  background-size: 56px 56px;
}

.rock {
  background: url("image/rock.png") center no-repeat;
  background-size: 50px 50px;
}

.scissors {
  background: url("image/scissors.png") center no-repeat;
  background-size: 50px 50px;
}

.paper {
  background: url("image/paper.png") center no-repeat;
  background-size: 50px 50px;
}

3-2. Total score stays when refreshed

로컬스토리지에 저장된 데이터를 새로고침했을 때 불러오도록 했습니다. 새로고침했을 때는 로컬스토리지에 데이터가 남아있지만 게임을 다시 이어나가면 데이터가 초기화되는 문제가 생겨서 꽤 오랜 시간 동안 고민을 했습니다. 고민끝에 마지막에 results = getLocalStorageData 한 줄을 추가해 주니 새로고침 후 다시 게임을 시작했을 때 로컬스토리지에서 가져온 results에 추가로 배열을 넣어줄 수 있게 됐습니다.

let results = []

//get data from local storage
const getLocalStorageData = JSON.parse(localStorage.getItem('results'))
console.log(getLocalStorageData)

results = getLocalStorageData
profile
빵굽는 프론트엔드 개발자

8개의 댓글

comment-user-thumbnail
2020년 11월 26일

안녕하세요 쓰신 글 많이 봤는데, 하시는 토이 프로젝트마다 UI가 너무 예쁘네요
인터랙티브 웹 개발자가 되고 싶은 사람으로써 많이 배우고 갑니다!

1개의 답글
comment-user-thumbnail
2020년 11월 27일

토이프로젝트로 연습한번 해봐야겠어요 동기부여 감사합니다!

1개의 답글
comment-user-thumbnail
2020년 12월 10일

안녕하세요~ 지나가다가 본 썸네일이 너무 아기자기하고 도전하고 싶은 맘을 불러일으켜서 댓글 남깁니다!
아무래도 게임이 제일 재밌는 토이 프로젝트 같아요 >_<

1개의 답글
comment-user-thumbnail
2020년 12월 22일

혹시 다음에 토이프로젝트 글 올리시는거면 깃허브에서 gitpage로 배포링크 남겨주실 수 있나요...? 너무 잘만들어서 직접 써보고 싶어요! :)

답글 달기