본문 바로가기
Project/테트리스 (javascript)

[JavaScript] 테트리스 웹 게임 만들기 (5) 블록 체크

by _temp 2022. 2. 18.

테트리스 웹 게임 만들기 (5) 블록 체크

 

 

1. 기존의 호출했던 renderBlock을 checkNextBlock으로 바꿈

기존의 바로 renderBlock을 해주던 것을 checkNextBlock을 거쳐서 renderBlock을 해주기 위해

전부 checkNextBlock으로 바꿔준다.

전체 코드는 아래와 같이 바뀌게 된다.

// js/tetris.js
'use strict'
import Blocks from './blocks.js'

export default class Tetris {
  constructor() {
    //setting
    this.N = 20
    this.M = 10
    this.downInterval = undefined

    //block
    this.blockInfo = undefined

    //dom
    this.stage = document.querySelector('.stage')

    //events
    document.addEventListener('keydown', (e) => {
      switch (e.keyCode) {
        case 39:
          this.moveBlock('m', 1)
          break
        case 37:
          this.moveBlock('m', -1)
          break
        case 40:
          this.moveBlock('n', 1)
          break
        case 38:
          this.changeDirection()
          break
        case 32:
          this.dropBlock()
          break
        default:
          break
      }
    })
  }

  init() {
    this.makeGround()
    this.nextBlocks = []
    for (let i = 0; i < 4; i++) {
      this.makeNextBlock()
    }
    this.renderNextBlock()
    this.makeNewBlock()
  }

  makeGround() {
    this.ground = []
    for (let i = 0; i < this.N; i++) {
      this.ground.push('<tr>')
      for (let j = 0; j < this.M; j++) {
        this.ground.push('<td></td>')
      }
      this.ground.push('</tr>')
    }
    this.stage.innerHTML = this.ground.join('')
  }

  makeNextBlock() {
    const blockArray = Object.entries(Blocks)
    const randomIndex = Math.floor(Math.random() * blockArray.length)
    this.nextBlocks.push(blockArray[randomIndex][0])
  }

  renderNextBlock() {
    const next = document.querySelector('.next')
    let temp = []
    for (let i = 0; i < 4; i++) {
      temp.push(
        `<img class='tetris' src="./img/${this.nextBlocks[i]}.png" alt=${this.nextBlocks[i]}"/>`
      )
    }
    next.innerHTML = temp.join('')
  }

  makeNewBlock() {
    const next = this.nextBlocks.shift()
    this.blockInfo = {
      type: next,
      direction: 0,
      n: 0,
      m: 3,
    }
    this.movingBlock = { ...this.blockInfo }
    this.makeNextBlock()
    this.renderNextBlock()
    this.renderBlock()
    this.checkNextBlock('start')
  }

  checkNextBlock(where = ''){

  }

  renderBlock() {
    const { type, direction, n, m } = this.movingBlock
    const temp = document.querySelectorAll('.moving')
    temp.forEach((x) => {
      x.classList.remove(type, 'moving')
    })
    Blocks[type][direction].forEach((block) => {
      const x = block[0] + n
      const y = block[1] + m
      const target = this.stage.childNodes[x].childNodes[y]
      target.classList.add(type, 'moving')
    })
    this.blockInfo.n = n
    this.blockInfo.m = m
    this.blockInfo.direction = direction
  }

  moveBlock(where, amount) {
    this.movingBlock[where] += amount
    this.checkNextBlock(where)
  }

  changeDirection() {
    const direction = this.movingBlock.direction
    direction === 3
      ? (this.movingBlock.direction = 0)
      : (this.movingBlock.direction += 1)
    this.checkNextBlock(direction)
  }

  dropBlock() {
    clearInterval(this.downInterval)
    this.downInterval = setInterval(() => {
      this.moveBlock('n', 1)
    }, 8)
  }
}

 

2. 블록체크

테트리스 게임을 만들면서 가장 많이 수정했던 부분이다 그래서인지 코드가 여기저기 꼬였다.

 

1. 방향 전환으로 블록체크를 할 경우 moveAndTurn()을 실행해줄 예정

    - 블록이 바닥에 도착하지 않았으나 방향 전환했을 시, 옆에 블록이 이미 있다면 그 자리에서 freeze 되는 것을 방지

2. 그 외 이동이 범위 밖이라면 이전의 블록을 현재 블록에 업데이트

    - 세로의 값이 범위 밖이라면 가장 아래라는 뜻이므로 finishBlock()으로 블록을 프리징 해줄 예정

3. 그 외 이동이 범위 안이라면 아래로 이동인지 좌우로 이동인지 두 가지를 체크

    - 아래로 이동일 경우 : 다음 이동위치에 이미 블록이 있다면 이전의 블록을 현재 블록에 업데이트

    - 그 외 이동일 경우 : 다음 이동위치에 이미 블록이 있다면 이전의 블록을 현재 블록에 업데이트

       만약 where가 'start'일 경우 : 블록이 생성될 위치에 블록이 있으니 게임 종료

4. 그 외 :나머지 블록의 구성요소를 모두 체크

5. 4가지의 블록 요소들을 체크해서 모두 유효하다면 renderBlock()

checkNextBlock(where = ''){
    const { type, direction, n, m } = this.movingBlock
    let isFinished = false
    Blocks[type][direction].some((block) => {
      const x = block[0] + n
      const y = block[1] + m
      
      // changeDirection일 경우
      if (where === 0 || where === 1 || where === 2 || where === 3) {
        // moveAndTurn()
        // change Direction의 유효성 체크
      }
      // 그외 이동일 경우 : 좌우 밖이라면 기존의 bolockInfo으로 render
      else if (y < 0 || y >= this.M) {
        this.movingBlock = { ...this.blockInfo }
        this.renderBlock()
        return true
      }
      // 그외 이동일 경우 : 아래 범위 밖이라면 blockInfo상태를 finishBlock(블록 얼리기)
      else if (x >= this.N) {
        this.movingBlock = { ...this.blockInfo }
        isFinished = true
        this.finishBlock()
        return true
      }
      // 그외 이동일 경우 : 범위 안이라면
      else {
        const target = this.stage.childNodes[x]
          ? this.stage.childNodes[x].childNodes[y]
          : null
        // 아래로 이동일 경우
        if (where === 'm') {
          // 다음 블록이 가야할 자리가 finish클래스(먼저 정착한 블록이 있다면)을 가지고 있다면
          // 기존의 blockInfo로 movingBlock 업데이트
          if (target && target.classList.contains('finish')) {
            this.movingBlock = { ...this.blockInfo }
          }
        }
        // 그외 : 좌,우 이동 & 처음 시작했을 경우
        else {
          // 범위 내이지만 해당 위치에 블록이 이미 있을 경우
          if (target && target.classList.contains('finish')) {
            isFinished = true
            this.movingBlock = { ...this.blockInfo }
            // 만약 처음 렌더링 된 블록이라면 게임 종료
            if (where === 'start') {
              setTimeout(() => {
                //finishGame()
                console.log('게임종료')
              }, 0)
              return true
            }
            // 그외 : 블록 얼리기 (finishBlock)
            else {
              this.finishBlock()
              return true
            }
          }
        }
      }
    })
    // 블록을 구성하는 4가지 요소가 모두 유효하다면
    if ((where === 'n' || where === 'm') && !isFinished) {
      this.renderBlock()
    }
  }

 


 

3. 블록 얼리기

이제 블록이 바닥에 도달하거나 다른 블록 위에 올려졌을 때 블록을 얼려보자

경우의 수는 위 checkNextBlock에서 체크를 해줬으니 finishBlock을 정의해주면 된다.

downInterval를 clear 해주고 moving이라는 클래스를 없애주고 전부 finish라는 클래스를 넣어준다.

이후 makeNewBlock으로 새로운 블록 생성

finishBlock() {
    clearInterval(this.downInterval)
    const temp = document.querySelectorAll('.moving')
    temp.forEach((block) => {
      block.classList.remove('moving')
      block.classList.add('finish')
    })
    // 이후 부실수 있는 블록 체크후 부시기
    this.makeNewBlock()
  }

 

 

 

다음에는 방향 전환을 완성하고 자동으로 블록 이동과 속도 및 lv설정 블록 부수기를 구현해보자