ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 지뢰찾기를 만들어보자 #3 - 게임 로직 구성
    기술 관련/etc 2022. 9. 24. 10:32

    지난번 어느정도 필요한 부분을 확인해 보았다. 이제는 이를 이용해서 좀 더 상세 구성을 해 보기로 하자.
    https://mc500.tistory.com/669

    우선은 랜덤으로 지뢰를 생성하고 어떻게 나오는지 확인해 보자. 먼저 각 상자에 대한 정보를 초기화 한다.

    // Init box
    let boxes = []
    let n = this.mines
    for (let y=0;y<this.height;y++) {
      for (let x=0;x<this.width;x++) {
        let box = {
          row: x,
          column: y,
          value: 0,
          flagged: false,
          revealed: false,
        }
    
        if (n != 0 && Math.floor(Math.random() * this.mines) == 0) {
          box.value = 'M' // Mine
          n--
        }
        boxes.push(box)
      }
    }


    그리고, 앞서 만들었던 drawBox 함수를 변경하여 box의 정보를 표기하도록 하면 다음과 같이 출력되는 것을 볼 수 있다.

    이제 상자 정보를 초기화 할 때 각 위치에서 주변에 Mine이 몇 개나 있는지를 생성해 주면 된다.  현재 상자를 기준으로 8곳(상, 하, 좌, 우, 좌상, 우상, 좌하, 우하)을 확인하며 주변에 지뢰가 있는 경우를 판단하여 자신이 가진 값을 증가시킨다.

    evaluateMineHint(x, y) {
      let value = Number(this.boxes[x+y*this.width].value)
      if (!Number.isNaN(value)) {
        this.boxes[x+y*this.width].value = value + 1
      }
    },

    각 위치별 Mine의 갯수가 몇 개 있는지 확인해 볼 수 있다


    힌트 숫자가 완성되었다면 이제 M 표시 대신 폭탄 그림을 나오도록 코드를 변경해 보자

    그 다음으로는 마우스로 상자를 클릭했을 때 해당 영역의 정보가 어떤 것인지를 확인하는 부분이다. 마우스 우 클릭을 한 경우 해당 박스의 정보에 따라 깃발, 힌트 또는 폭탄 정보를 확인한다.

    this.canvas.addEventListener("mouseup", function(evt) {
      let x = Number.parseInt(evt.offsetX/self.box_width)
      let y = Number.parseInt(evt.offsetY/self.box_width)
      evt.preventDefault()
      evt.stopPropagation()
    
      console.log('mouse up: '+evt.button+': '+x+','+y)
      let box = self.boxes[x+y*self.width]
      if (!box) {
        console.error('box is undefined: '+x+','+y)
        return false
      }
      if (evt.button == 0) {
        if (box.flagged) {
          alert('Flagged')
        } else if (box.value == CONST_MINE) {
          alert('Bomb!')
        } else {
          alert(box.value)
        }
      } else if (evt.button == 2) {
        if (box.flagged) {
          box.flagged = false
          alert('Unflagged')
        } else {
          box.flagged = true
          alert('Flagged')
        }
      }
      return false
    }, false)

    마우스 좌/우 버튼을 동시에 클릭을 인식해서 처리할 수도 있지만 나중에 기능 개선 항목으로 남겨 둔다.

    마우스 동작 인식이 되었다면 이제 우클릭을 하여 정보를 확인한 경우와 좌클릭일 때 깃발 정보를 표기하는 부분을 구현한다.

    if (box.flagged) {
      box.flagged = false
      self.drawBox(box)
    } else {
      box.flagged = true
      self.drawBox(box)
    }

    여기에 현재 깃발이 몇 개 생성되었는지 정보를 보여주는 정보를 등록하면 다음과 같이된다.

    if (box.flagged) {
      box.flagged = false
      self.flags--
      if (self.flags < 0) {
        self.flags = 0
      }
      self.drawBox(box)
    } else {
      box.flagged = true
      self.flags++
      self.drawBox(box)
    }

    이제 0인 경우 자동으로 확장해주는 부분을 구현하면 기본적인 게임으로서의 모양은 완성된다고 본다.  앞서 지뢰 주변의 숫자 힌트를 구성할 때도 8곳을 확인 했던것 처럼 이 경우도 특정 박스의 주변에 대한 해당 정보를 확인하고 값이 0인 경우 이를 열어야 한다. 이를 위해 다음과 같은 공통 함수를 만들어 사용해 볼 수 있다.

    traverseBoxes(x, y, callback) {
      let limitW = this.width - 1
      let limitH = this.height - 1
    
      if (x > 0) {
        // Left 
        callback(x-1, y)
    
        // Left-Top
        if (y > 0) {
          callback(x-1, y-1)
        }
    
        // Left-Bottom
        if (y < limitH) {
          callback(x-1, y+1)
        }
      }
    
      if (x < limitW) {
        // Right
        callback(x+1, y)
    
        // Right-Top
        if (y > 0) {
          callback(x+1, y-1)
        }
    
        // Right-Bottom
        if (y < limitH) {
          callback(x+1, y+1)
        }
      }
    
      // Top 
      if (y > 0) {
        callback(x, y-1)
      }
    
      // Bottom
      if (y < limitH) {
        callback(x, y+1)
      }
    }

    자동으로 열어주는 것은 사용자가 클릭을 했을 때 0인 경우 부터 시작하며 각각의 박스를 탐색하여 0인 경우 이를 다시 호출하는 재귀 구조를 가지게 된다.

    expandSafeArea(x, y) {
      let box = this.boxes[x+y*this.width]
      if (box.value == 0 && !box.revealed) {
        box.revealed = true
        this.drawBox(box)
        this.traverseBoxes(x, y, this.expandSafeArea)
      }
    },

    근데 보통은 0 주변까지는 확인해주므로 이를 구현하면 다음과 같다.

    expandSafeArea(x, y) {
      let box = this.boxes[x+y*this.width]
      if (!box.revealed) {
        box.revealed = true
        this.drawBox(box)
        if (box.value == 0) {
          this.traverseBoxes(x, y, this.expandSafeArea)
        }
      }
    },

    이제 0인 경우는 굳이 표현할 필요가 없으므로 그리지 않도록 코드를 수정하면 다음과 같이 나타난다.

     

    어느정도 완성이 된 상태지만 아직 게임이 완성되려면 몇 가지 손봐야 할 부분이 남았다. 현재 구현은 깃발을 아무곳에나 둘 수 있는데, 이미 박스가 열려서 확인이 된 곳도 둘 수 있고, 심지어 숫자 위에도 둘 수 있다.

     

    따라서, 깃발을 놓거나 빼기 전에 이미 박스가 열려있는지 여부를 확인하도록 해야 한다.

    } else if (evt.button == 2) {
            if (box.revealed) {
              return false
            }
            if (box.flagged) {

    그리고 깃발 관련하여 또 하나 수정해야 하는 부분이 있는데, 상자가 아무 것도 열리지 않은 상태에서 깃발을 두고 자동으로 확장되어 열렸을 때 해당 깃발이 놓여져 있는 부분이 빈곳이라면 같이 열리도록 되어 있다.

     

    따라서, 만약 상자에 깃발이 있다면 더 이상 확장되지 않도록 조치가 되어야 한다. 

        expandSafeArea(x, y) {
          let box = this.boxes[x+y*this.width]
          if (!box.revealed && !box.flagged) {
            box.revealed = true

    깃발이 정상적으로 동작하면 다음과 같이 깃발이 있는 곳은 자동으로 열지 않는다.

     

    지뢰찾기에 대한 어느정도 기능이 완성되었다. 남은건 게임의 승리 조건을 구현하는 것인데 이것은 다음 글에서 다루도록 하겠다.

     

Designed by Tistory.