본문 바로가기

Ecmascript/Canvas

[테트리스 만들기] 점수와 난이도, 게임 종료/중지

하드드랍 안전장치

  • space를 눌러서 블록을 내릴때 가끔 발생하는 에러 해결
  • 현재 블록이 하드드롭인 상태일때는 x축 y축을 변경 못하는 코드를 추가하자
class Piece {
  hardDropped;

  spawn() {
    // ... 기존 코드

    this.hardDropped = false;
  }

  move(p) {
    if(!this.hardDropped){
      this.x = p.x;
      this.y = p.y;
    }
    this.shape = p.shape;
  }

  hardDrop(){
    this.hardDropped = true;
  }
}

//*** main.js
    // 스페이스 누를 경우 하드 드롭
    if (event.keyCode === KEY.SPACE) {
      while (board.valid(p)) {
        account.score += POINTS.HARD_DROP; // 하드 드롭시 점수 증가
        board.piece.move(p);   
        p = moves[KEY.DOWN](board.piece);
      }
      board.piece.hardDrop();
    } else if (board.valid(p)) {
      board.piece.move(p);
      if (event.keyCode === KEY.DOWN) {
        account.score += POINTS.SOFT_DROP; // 아래 방향키 눌러서 빨리 내리면 점수 증가
      }
    }

    // 아래 로직 삭제 -> animate()로 이전한 로직
    // if(board.valid(p)) {
    //   // 이동 가능한 조각을 이동
    //   board.piece.move(p);
    //   // 그리기 전에 이전 좌표를 삭제
    //   ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    //   // 테트로미노 그림
    //   board.piece.draw();
    // }

난이도

  • 게임 루프에서 인터벌 속도를 줄이면 난이도가 상승함
//*** constants.js
// 상단에 변수 선언
const LINES_PER_LEVEL = 10;

// ... 기존 코드

// 난이도
const LEVEL = {
  0: 800,
  1: 720,
  2: 630,
  3: 550,
  4: 470,
  5: 380,
  6: 300,
  7: 220,
  8: 130,
  9: 100,
  10: 80,
  11: 80,
  12: 80,
  13: 70,
  14: 70,
  15: 70,
  16: 50,
  17: 50,
  18: 50,
  19: 30,
  20: 30,
}
Object.freeze(LEVEL);
  • main에 level을 추가하고 모든 게임 요소(점수, 라인, 레벨, 보드판)를 초기화하는 리셋게임 함수 추가해서 호출
// 기존 time 삭제
// time = { start: 0, elapsed: 0, level: 1000 };

//*** main.js
function play() {
  resetGame();
  time.start = performance.now();
  if (requestId) {
    cancelAnimationFrame(requestId);
  }

  animate();
}

// ... 기존 코드

// 점수 계산
let accountValues = {
  score: 0,
  lines: 0,
  level: 0,
}

// ... 기존 코드

function resetGame() {
  account.score = 0;
  account.lines = 0;
  account.level = 0;
  board.reset(); // 보드판 초기화
  time = { start: 0, elapsed: 0, level: LEVEL[account.level] };
}
  • 점수가 레벨에 따라 다르게 적용되게 점수 로직 수정
//*** board.js
class Board {
  requestId;
  time;

  clearLines() {
    // ... 기존 코드

    if (lines > 0) {    
      // 지워진 라인들이 있다면 점수를 추가
      account.score += this.getLineClearPoints(lines);  
      account.lines += lines;

      // 다음 레벨을 위한 라인 수에 달성했다면
      if (account.lines >= LINES_PER_LEVEL) {
        account.level++;  

        // 다음 레벨을 작동하기 위해 라인을 지운다.
        account.lines -= LINES_PER_LEVEL;

        // 게임 속도를 높인다.
        time.level = LEVEL[account.level];
      }
    }
  }

  // 지운 라인 갯수당 점수 제공
  getLineClearPoints(lines) {  
    let score = 
      lines === 1 ? POINTS.SINGLE :
      lines === 2 ? POINTS.DOUBLE :  
      lines === 3 ? POINTS.TRIPLE :     
      lines === 4 ? POINTS.TETRIS : 
      0;
    // 레벨에 따라 점수 차등 적용
    return (account.level + 1) * score;
  }
}  

게임 종료

  • 더이상 내릴수 없으면 게임 루프 함수를 종료

//*** main.js
function animate(now=0){
  time.elapsed = now - time.start;

  // 1초마다 아래로 한칸씩 움직이는 drop()메서드를 호출
  if(time.elapsed > time.level) {
    time.start = now;
    if (!board.drop()) {
      gameOver();
      return;
    }
  }  

    // ... 기존 코드
}
function gameOver() {
  cancelAnimationFrame(requestId);
  ctx.fillStyle = 'black';
  ctx.fillRect(1, 3, 8, 1.2);
  ctx.font = '1px Arial';
  ctx.fillStyle = 'red';
  ctx.fillText('GAME OVER', 1.8, 4);
}

게임중지

  • p 키누르면 현재 움직임이 중지되고 다시 누르면 재개되도록 작업

//*** main.js
document.addEventListener('keydown', event => {
  if (event.keyCode === KEY.P) {
    pause();
  }
  // ... 기존 코드
})

  // ... 기존 코드

function pause() {
  if (!requestId) {
    animate();
    return;
  }
  cancelAnimationFrame(requestId);
  requestId = null;

  ctx.fillStyle = 'black';
  ctx.fillRect(1, 3, 8, 1.2);
  ctx.font = '1px Arial';
  ctx.fillStyle = 'yellow';
  ctx.fillText('PAUSED', 3, 4);
}