ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 지뢰찾기를 만들어보자 #5 - 사용성 개선
    기술 관련/etc 2022. 9. 28. 21:47

    지난 번에 글까지 어느정도 지뢰 찾기 게임으로서 동작 확인할 수 있었다. https://mc500.tistory.com/671 이번 글에서는 게임으로서의 사용성에 개선에 대한 부분을 다루고자 한다.

    1. 느린 클릭 반응 속도
    지뢰 찾기 게임에 익숙한 사람들은 이 게임을 했을 때 약간 불편하기도 하고 좀 어색한 부분이 있었을 것이다. 뭔가 반응이 느린듯한 느낌을 받기 때문인데, 이는 마우스의 클릭에 대한 인식을 mouse up event에서 하기 때문이다. 특히 터치패드와 같은 경우는 지연 처리 시간이 포함되어 있기에 더욱 그렇게 느껴진다. 그래서 mouse up event 대신 mouse down event에서 처리되도록 코드를 수정하면 사용자 경험이 개선되는 효과를 느낄 수 있다.

    2. Auto dig
    Windows에서 제공하던 지뢰 찾기 기능 중에 이미 사용자가 열어 놓은 곳에서 마우스 왼쪽, 오른쪽을 동시에 클릭하면 해당 상자를 중심으로한 8개 부분의 상자를 자동으로 확인해 주는 기능이 있다. 단, 다음과 같은 조건이 붙는다.

    - 현재 위치에 숫자가 있어야 한다
    - 깃발이 있는 곳은 확인하지 않는다
    - 현재 위치의 숫자를 기반으로 모든 열리지 않는 상자에 지뢰가 없는 경우만 자동으로 확인한다
    - 명시적으로 지뢰 없는 것이 파악되지 않으면 모든 상자를 열지 않는다

    예를 들어, 빨간색 동그라미 위치에서는 주변에 지뢰 1개가 있는 것을 알고 있고, 주변 열리지 않은 공간이 2개이나 깃발로 1개를 지뢰가 있는 곳으로 판단했으므로 나머지 공간은 안전하다고 판단하여 확인을 진행한다.


    이 경우에는 주변에 지뢰 2개가 있는 것을 알고 있고, 주변 열리지 않은 공간은 2개로서 깃발 1개로 지뢰 하나가 있는 곳을 파악했지만 나머지 한 개는 파악하지 못했으므로 열리지 않은 공간에 지뢰가 있다고 판단하여 상자를 열지 않는다.

    이 기능을 구현하기 전에 먼저 마우스 왼쪽 오른쪽을 동시에 누르는 경우를 어떻게 먼저 알아보자.

    현재 mouse down 이벤트를 통해 오른쪽 클릭과 왼쪽 클릭은 구분해서 인식하고 있다. 그러나, 동시에 눌렀을 때에 대한 이벤트는 따로 없기 때문에 mouse down 이벤트에서 두 개의 버튼 중이 인식되고 난 후 나머지 버튼이 인식되기까지 일정 시간을 기다렸다가 timeout이 되기 까지 해당 이벤트가 없다면 단일 버튼으로 인식되도록 하는 코드가 필요하다.

    다음과 같이 JavaScript의 setTimeout()과 clearTimeout()을 이용하여 대략 150ms 동안 양쪽 버튼의 입력 상태를 확인 해 볼 수 있다.

      let dblclick_wait = 150 // msec
      if (evt.button == 0) { // left
        if (self.rclick) {
          console.log('both from rclick')
          clearTimeout(self.rclick)
          self.rclick = undefined
          return false
        }
        self.lclick = setTimeout(() => {
          console.log('left click')
          self.lclick = undefined
        }, dblclick_wait)
      }
      else if (evt.button == 2) { // left
        if (self.lclick) {
          console.log('both from lclick'+self.lclick)
          clearTimeout(self.lclick)
          self.lclick = undefined
          return false
        }
        self.rclick = setTimeout(() => {
          console.log('right click')
          self.rclick = undefined
        }, dblclick_wait)
      }


    이렇게 버튼 인식 방법이 확보되었다면, 각 경우에 대해 기존 mousedown 이벤트에 구현되어 있던 로직을 dig()와 flagup() method로 구분해 볼 수 있다.

    dig(x, y) {
      let box = this.boxes[x+y*this.width]
      if (!box) {
        console.error('box is undefined: '+x+','+y)
        return
      }
    
      if (box.flagged) {
        this.alert('Flagged')
      } else if (box.value == CONST_MINE) {
        this.alert('Bomb!')
        this.gameover = true
        this.revealBoxes()
      } else if (!box.revealed) {
        this.cleared++
        box.revealed = true
        this.drawBox(box)
        if (box.value == 0) {
          this.traverseBoxes(x, y, this.expandSafeArea)
        }
        console.log(this.target, this.cleared)
        if (this.cleared == this.target) {
          this.alert('You Win')
          this.gameover = true
        }
      }
    },
    flagup(x, y) {
      let box = this.boxes[x+y*this.width]
      if (!box) {
        console.error('box is undefined: '+x+','+y)
        return
      }
    
      if (box.revealed) {
        return
      }
      if (box.flagged) {
        box.flagged = false
        this.flags--
        if (this.flags < 0) {
          this.flags = 0
        }
      } else {
        box.flagged = true
        this.flags++
      }
      this.drawBox(box)
    },


    그리고, 앞서 만든 mouseup event 처리 함수도 다음과 같이 변경 해 준다.

      if (self.gameover) {
        return false
      }
    
      let dblclick_wait = 150 // msec
      if (evt.button == 0) { // left
        if (self.rightTimerId) {
          clearTimeout(self.rightTimerId)
          self.rightTimerId = undefined
          self.examine(x, y)
          return false
        }
        self.leftTimerId = setTimeout(() => {
          self.leftTimerId = undefined
          self.dig(x, y)
        }, dblclick_wait)
      }
      else if (evt.button == 2) { // left
        if (self.leftTimerId) {
          clearTimeout(self.leftTimerId)
          self.leftTimerId = undefined
          self.examine(x, y)
          return false
        }
        self.rightTimerId = setTimeout(() => {
          self.rightTimerId = undefined
          self.flagup(x, y)
        }, dblclick_wait)
      }


    이제 실제 주변을 확인 하는 코드를 추가해 보자

    examine(x, y) {
      let box = this.boxes[x+y*this.width]
      if (!box) {
        console.error('box is undefined: '+x+','+y)
        return
      }
      // Get number of unrevealed and flagged
      let self = this
      let n = 0, f = 0
      this.traverseBoxes(x, y, function(x, y) {
        let b = self.boxes[x+y*self.width]
        if (!b.revealed) {
          n = n + 1
        }
        if (b.flagged) {
          f = f + 1
        }
      })
    
      // It's not safe
      if (box.value != f) {
        return
      }
    
      // fully flagged 
      if (n == f) {
        return
      }
    
      this.traverseBoxes(x, y, function(x, y) {
        let b = self.boxes[x+y*self.width]
        if (!b.flagged && !b.revealed) {
          self.dig(x, y)
        }
      })
    },


    지금까지 사용자 사용성 개선에 대해서 구현해 보았다. 다음 글에서는 새로운 게임을 생성하고 게임 판 넓이나 지뢰 갯수 등을 조절하여 새로운 게임을 만드는 것에 대한 내용을 다루도록 하겠다.

Designed by Tistory.