테트리스 코드 리팩토링

리팩토링을 하기에 앞서, 코드를 봤을 때 굉장히 가독성이 떨어졌다.

따라서 코드의 문제점이 무엇인지 곰곰이 생봤다.

  1. 중복 코드가 굉장히 많음.
const drawBlock = (
  board: number[][],
  block: blockInterface,
  ghost: blockInterface,
  ctx: CanvasRenderingContext2D,
  img: HTMLImageElement
) => {
  block.shape.forEach((row: Array<number>, y: number) => {
    ctx.globalAlpha = 1;
    row.forEach((value: number, x: number) => {
      const nX: number = block.posX + x;
      const nY: number = block.posY + y;

      if (TETRIS.withInRange(nX, nY) && board[nY][nX] === 0) {
        ctx.drawImage(
          img,
          TETRIS.BLOCK_ONE_SIZE * (value - 1),
          0,
          TETRIS.BLOCK_ONE_SIZE,
          TETRIS.BLOCK_ONE_SIZE,
          (block.posX + x) * TETRIS.BOARD_ONE_SIZE,
          (block.posY + y - TETRIS.START_Y) * TETRIS.BOARD_ONE_SIZE,
          TETRIS.BLOCK_ONE_SIZE,
          TETRIS.BLOCK_ONE_SIZE
        );
      }
    });
  });

  ctx.globalAlpha = 0.7;
  ghost.shape.forEach((row: Array<number>, y: number) => {
    row.forEach((value: number, x: number) => {
      const nX: number = ghost.posX + x;
      const nY: number = ghost.posY + y;

      if (TETRIS.withInRange(nX, nY) && board[nY][nX] === 0) {
        ctx.drawImage(
          img,
          TETRIS.BLOCK_ONE_SIZE * (value - 1),
          0,
          TETRIS.BLOCK_ONE_SIZE,
          TETRIS.BLOCK_ONE_SIZE,
          (ghost.posX + x) * TETRIS.BOARD_ONE_SIZE,
          (ghost.posY + y - TETRIS.START_Y) * TETRIS.BOARD_ONE_SIZE,
          TETRIS.BLOCK_ONE_SIZE,
          TETRIS.BLOCK_ONE_SIZE
        );
      }
    });
  });
};

심지어, board를 그려줄 때도, 단지 board.forEachblock.shape.forEach의 차이일 뿐 로직은 동일하다!

하지만 문제는 for문 안에 걸려있는 조건문이었는데, 이것 역시 이번 코드의 문제점 중 하나였다.

  1. 작동 시나리오를 생각하지 않음

사실 아직도 이걸 어떤 용어로 불러야할지 잘 모르겠지만, 함수의 흐름에 의해 결정되는 당연한 결과에 대해서 생각하지 않았다.

예를 들으면 쉽게 알 수 있다. draw() 함수를 부른 부분들을 살펴보자.

if (isNotConflict({ ...nextBlock, posY: nextBlock.posY + 1 }, board)) {
        nextBlock.posY += 1;
        draw(board, nextBlock, ghostBlock, ctx, img);
        nowBlock = nextBlock;
        ghostBlock = hardDropBlock(board, nowBlock);
      }

뭐가 문제인가 싶겠지만, isNotConflict() 와 draw의 for문에서 설정해준 if문이 겹치게 된다.

isNotConflict() 에서 board[y][x] ≠ 0 인 경우와 outOfRange인 경우를 걸러주기 때문에 굳이 draw에서 if문으로 확인할 필요가 없다.