리팩토링을 하기에 앞서, 코드를 봤을 때 굉장히 가독성이 떨어졌다.
따라서 코드의 문제점이 무엇인지 곰곰이 생봤다.
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.forEach
와 block.shape.forEach
의 차이일 뿐 로직은 동일하다!
하지만 문제는 for문 안에 걸려있는 조건문이었는데, 이것 역시 이번 코드의 문제점 중 하나였다.
사실 아직도 이걸 어떤 용어로 불러야할지 잘 모르겠지만, 함수의 흐름에 의해 결정되는 당연한 결과에 대해서 생각하지 않았다.
예를 들으면 쉽게 알 수 있다. 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문으로 확인할 필요가 없다.