[Javascript] 쿵쿵따 게임

hyejinJo·2023년 1월 19일
0
post-thumbnail

처음 제시된 단어의 맨 뒷글자를 앞글자로 사용하여 단어를 나열하는 게임으로, 한시간은 30초, 단어는 세 글자여야만 한다

웹게임 링크

html, css 코드

```jsx
<body>
  <div class="word-relay">
    <img class="character" src="img/character-fred-01.png" alt=""><em></em>
    <div class="participant">차례 : <span id="order">1</span> 번째 참가자 </div>
    <div class="suggestion"><em class="hidden"></em>제시어 <span id="word">Start! </span></div>
    <div class="timer"><span id="timer"></span></div>
    <form action="" id="word-relay__form">
      <input type="text" maxlength="5" />
      <input type="submit" value=" 쿵쿵따 ! " />
    </form>
  </div>
</body>
```

```scss

@font-face {
  font-family: 'SBAggroM';
  src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2108@1.1/SBAggroM.woff') format('woff');
  font-weight: normal;
  font-style: normal;
}

@font-face {
  font-family: 'HangeulNuri-Bold';
  src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_three@1.0/HangeulNuri-Bold.woff') format('woff');
  font-weight: normal;
  font-style: normal;
}

body {
  background-color: rgba(238, 238, 238, 0.589);
  font-family: 'SBAggroM';
  background: url(../img/word-relay-bg.jpeg) no-repeat;
  background-size: cover;
}

@mixin x-center {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  @content;
}

@mixin center {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.word-relay {
  @include x-center {
    transform: translate(-50%, -45%);
  }

  top: 50%;
  text-align: center;
  padding: 50px;
  padding-top: 100px;
  border-radius: 20px;
  background-color: #fff;
  box-shadow: 0 0 8px #ddd;

  .character {
    @include x-center;
    bottom: 98%;
    width: 250px;
    z-index: 10;

    &+em {
      @include x-center;
      top: -80px;
      width: 150px;
      height: 90px;
      border-radius: 50%;
      z-index: 9;
      box-shadow: 0px 15px 10px rgba(0, 0, 0, 0.336);
    }
  }

  .participant {
    border-radius: 20px 20px 0 0;
    position: absolute;
    padding: 40px 0 20px 0;
    text-align: center;
    top: 0;
    left: 0;
    width: 100%;
    background-color: rgb(235, 106, 59);
    color: #fff;
    font-size: 2rem;
    font-weight: 600;
    font-family: 'HangeulNuri-Bold';
  }

  #order {
    display: inline-block;
    width: 35px;
    height: 35px;
    border-radius: 50%;
    color: #fff;
    background-color: #fff;
    color: chocolate;
    font-weight: 700;
    font-size: 2rem;
  }

  .suggestion {
    @include center;
    margin: 20px;
    font-size: 2.5rem;
    margin: 30px;
    padding: 10px;
    color: #888;
    width: 500px;
    position: relative;

    .hidden {
      display: none;
    }

    em {
      position: absolute;
      top: 60%;
      right: 27%;
      transform: translateY(-50%);
      width: 16%;
      height: 34%;
      background-color: rgb(253, 253, 114);
      opacity: 0;
      animation: blink .5s infinite alternate;
    }

    &::after {
      @include x-center;
      content: '';
      height: 1px;
      width: 95%;
      top: 30%;
      background-color: #ccc;
    }

    #word {
      display: inline-block;
      height: 150px;
      margin-top: 15px;
      font-size: 5rem;
      line-height: 150px;
      color: #ddd;
      position: relative;
      letter-spacing: 3px;
    }
  }

  .timer {
    font-size: 25px;
    padding: 13px;
  }
}

@keyframes blink {
  0% {
    opacity: 0;
  }

  100% {
    opacity: 1;
  }
}

#word-relay__form {
  @include center;

  input[type=text] {
    display: block;
    margin-bottom: 20px;
    text-align: center;
    width: 350px;
    outline: none;
    border-radius: 50px;
    font-size: 2.5rem;
    padding: 10px;

    &:focus {
      border: 5px solid coral;
    }

    &::placeholder {
      font-size: 2rem;
      opacity: .5;
    }
  }

  input[type=submit] {
    width: 30%;
    padding: 10px;
    font-size: 1.8rem;
    margin-top: 10px;
    background-color: rgb(242, 115, 69);
    color: white;
    outline: none;
    border: none;
    border-radius: 50px;
    box-shadow: -5px -7px 10px rgb(194, 98, 29) inset;
    cursor: pointer;
    font-family: 'SBAggroM';

    &:hover {
      box-shadow: none;
    }
  }
}
```

끝말잇기

const numOfUser = Number(prompt('몇 명이 참가하나요?'))
const $input = document.querySelector('input')
const $button = document.querySelector('button')
const $word = document.querySelector('#word')
const $order = document.querySelector('#order')

let word
let newWord

const onClickBtn = () => {
  // 제시어가 비어있는가?
  if (!word) { // 비어있음
    word = newWord // 입력한 단어가 제시어가 됨
    $word.textContent = word // 제시어 화면에 표시
    const order = Number($order.textContent)
    if (order + 1 > numOfUser) { // order === number 도 가능
      $order.textContent = 1 // 다음 순서를 1로 
    } else {
      $order.textContent = order + 1 // 현재 순서 +1
    }
    $input.value = '' // 제시어 화면에 표시 후 텍스트창 비우기
    $input.focus()
  } else { // 비어있지 않음
    if (word[word.length - 1] === newWord[0]) { // length-1 : 문자열 제일 끝자리수
      word = newWord
      $word.textContent = newWord
      const order = Number($order.textContent)
      if (order + 1 > numOfUser) { // order === number 도 가능
        $order.textContent = 1 // 다음 순서를 1로 
      } else {
        $order.textContent = order + 1 // 현재 순서 +1
      }
      $input.value = ''
      $input.focus()
    } else {
      alert('올바르지 않은 단어입니다')
      $input.value =''
      $input.focus()
    }
  }
}

const onInput = (event) => {
  newWord = event.target.value
}

$button.addEventListener('click', onClickBtn)
$input.addEventListener('input', onInput)
  • 리팩토링 (or , and)
const numOfUser = Number(prompt('몇 명이 참가하나요?'))
const $input = document.querySelector('input')
const $button = document.querySelector('button')
const $word = document.querySelector('#word')
const $order = document.querySelector('#order')

let word
let newWord

const onClickBtn = () => {
  // 제시어가 비어있는가?
  if (!word || word[word.length - 1] === newWord[0]) { // 비어있음
    word = newWord // 입력한 단어가 제시어가 됨
    $word.textContent = word // 제시어 화면에 표시
    const order = Number($order.textContent)
    if (order + 1 > numOfUser) { // order === number 도 가능
      $order.textContent = 1 // 다음 순서를 1로 
    } else {
      $order.textContent = order + 1 // 현재 순서 +1
    }
  } else {
    alert('올바르지 않은 단어입니다')
  }
  $input.value = '' // 제시어 화면에 표시 후 텍스트창 비우기
  $input.focus()
}

const onInput = (event) => {
  newWord = event.target.value
}

$button.addEventListener('click', onClickBtn)
$input.addEventListener('input', onInput)
// prompt 에서 취소를 누르면 코드가 실행되지 않게 하는 법은 간단한데, 취소란 prompt 에서 값을 안받는
// 경우다. 즉 prompt 값을 받았을 때에만 코드가 실행되게 하면 된다

리뉴얼 작업

  • 끝자리에 깜박거리는 애니메이션 효과를 넣어 첫글자로 입력해야할 글자를 시각적으로 명시
  • 제한시간(타이머) 생성
  • 타임오버 됐을 시 누가 패배했는지에 대한 알림과 다시 하기 선택지 생성

문제 해결 (1)

끝말잇기를 할 때, 우리는 시간에 제한을 두고 한다. 그래서 타이머를 설정하고자 setInterval 을 사용하였지만, 중간에 다음 순서로 넘어가면서 새로고침 이후에 타이머가 점점 빠르게 작동하는 것이었다. 여러 구글링을 통해 원인을 알아냈는데, 바로 setInterval 은 한 번 작동후 완전히 사라지는 것이 아닌, 점점 쌓이면서 동작들이 중복되어 점점 빨라지는 버그가 있다는 것이었다. clearInterval 을 중간 여러 군데에 배치해보았지만 실패하였고, 재귀적 동작을 통한 setTimeout 으로 다시 시도를 해보았지만 결국 모두 실패하였다.

그러나 clearInterval 을 어디에 활용할 지 마침내 찾아냈다. 게임에서 글자를 [타임오버가 아닌 제한 시간내에 입력한 경우] input 을 누르는 그 타이밍에 clearInterval 을 넣어주었더니 타이머 중복 현상이 생기지 않았다. 무작정 넣는 것이 아닌, 타이머가 사라질 때의 조건을 제대로 인지한 후 그 부분에 clearInterval 을 배치했어야 했다.

// 제한 시간 내에 글자를 넣어 클릭했을 시 타이머를 삭제 (타이머 중복 방지)
 if (second <= 30 || second > 0) {
  clearInterval(timerId);
}

해당 코드는 두 개의 조건을 만족해야 실행되도록 하였다.

조건(1) -글자를 제대로 입력함

조건(2) -제한 시간 내에 입력함

⇒ 조건(1) 코드 내부에 해당 내용인 조건(2)의 내용을 넣었다.

또한, 조건을 갖춘 후 타이머가 리셋하면서 그전의 턴에서 깎인 second 도 같이 리셋해주어야 하므로 $timer.textContent 값을 10으로 리셋해주는 코드 역시 위에서 말한 조건(1) 부분에 삽입해주었다.

해당 버그에 시간을 너무 할애하게 되어 나는 시간이 남는 날이면 중간마다 틈틈히 고치는 노력을 하였고, 결국 해결했다. 이러한 과정은 내가 setInterval 에 대한 버그에 대해 제대로 이해하고 평상시의 문제 해결 능력에서 한 단계 더 발전됐다는 점에서 의미가 크다고 생각한다.

profile
FE Developer 💡

0개의 댓글