우찬쓰 개발블로그

자바스크립트로 웹 테트리스 만들기(3) 본문

테트리스 개발기

자바스크립트로 웹 테트리스 만들기(3)

이우찬 2021. 3. 27. 22:20
반응형

이제 블럭을 좌우로 움직일 때 입니다!

 

화살표에 따라 블럭이 움직여 줘야 게임이겠죠 ㅎㅎ

 

그리서 key 이벤트를 받아 블럭이 움직이도록 해봅시다.

 

window.onkeydown = (e) => {
  handleKeyboardEvent(e);
};

function handleKeyboardEvent(e) {
  switch (e.code) {
    case "ArrowLeft":
      onClickLeftArrow(controlBlocks, stackedBlocks);
      break;
    case "ArrowRight":
      onClickRightArrow(controlBlocks, stackedBlocks);
      break;
    case "ArrowDown":
      onClickDownArrow(controlBlocks, stackedBlocks);
      break;
  }
  reDraw();
}

 

왼쪽으로 오른쪽으로 아래로 블럭이 움직일 수 있도록 키 이벤트를 처리하고,

 

이제 블럭을 움직이는 로직을 넣어봅니다.

 

function moveToBottomOneLine(blockArray) {
  for (let x = 0; x < widthBlockCount; x++) {
    for (let y = heightBlockCount - 1; y != 0; y--) {
      blockArray[x][y] = blockArray[x][y - 1];
    }
    blockArray[x][0] = false;
  }
}

function moveToLeftOneLine(blockArray) {
  for (let y = 0; y < heightBlockCount; y++) {
    for (let x = 0; x < widthBlockCount - 1; x++) {
      blockArray[x][y] = blockArray[x + 1][y];
    }
    blockArray[widthBlockCount - 1][y] = false;
  }
}

function moveToRightOneLine(blockArray) {
  for (let y = 0; y < heightBlockCount; y++) {
    for (let x = widthBlockCount - 1; x != 0; x--) {
      blockArray[x][y] = blockArray[x - 1][y];
    }
    blockArray[0][y] = false;
  }
}

function onClickLeftArrow(controlBlocks, stackedBlocks) {
  if (couldBlockMoveToLeft(controlBlocks, stackedBlocks)) {
    moveToLeftOneLine(controlBlocks);
  }
}

function onClickRightArrow(controlBlocks, stackedBlocks) {
  if (couldBlockMoveToRight(controlBlocks, stackedBlocks)) {
    moveToRightOneLine(controlBlocks);
  }
}

function onClickDownArrow(controlBlocks, stackedBlocks) {
  if (couldBlockMoveToBottom(controlBlocks, stackedBlocks)) {
    moveToBottomOneLine(controlBlocks);
  }
}

function couldBlockMoveToBottom(controlBlocks, stackedBlocks) {
  let collisionCheckTmpArray = copyBlockArray(controlBlocks);

  let isBlockReachToBottomSide = isBlockReachToBottomBorder(controlBlocks);
  let isBottomCollided = isBottomSideCollided(
    collisionCheckTmpArray,
    stackedBlocks
  );

  if (isBlockReachToBottomSide || isBottomCollided) {
    return false;
  }

  return true;
}

function couldBlockMoveToLeft(controlBlocks, stackedBlocks) {
  let collisionCheckTmpArray = copyBlockArray(controlBlocks);

  let isBlockReachToLeftSide = isBlockReachToLeftBorder(controlBlocks);
  let isLeftCollided = isLeftSideCollided(
    collisionCheckTmpArray,
    stackedBlocks
  );

  if (isBlockReachToLeftSide || isLeftCollided) {
    return false;
  }

  return true;
}

function couldBlockMoveToRight(controlBlocks, stackedBlocks) {
  let collisionCheckTmpArray = copyBlockArray(controlBlocks);

  let isBlockReachToRightSide = isBlockReachToRightBorder(controlBlocks);
  let isRightCollided = isRightSideCollided(
    collisionCheckTmpArray,
    stackedBlocks
  );

  if (isBlockReachToRightSide || isRightCollided) {
    return false;
  }

  return true;
}

function isBottomSideCollided(collisionCheckTmpArray, stackedBlocks) {
  moveToBottomOneLine(collisionCheckTmpArray);

  for (let x = 0; x < widthBlockCount; x++) {
    for (let y = 0; y < heightBlockCount; y++) {
      if (collisionCheckTmpArray[x][y] && stackedBlocks[x][y]) {
        return true;
      }
    }
  }

  return false;
}

function isLeftSideCollided(collisionCheckTmpArray, stackedBlocks) {
  moveToLeftOneLine(collisionCheckTmpArray);

  for (let x = 0; x < widthBlockCount; x++) {
    for (let y = 0; y < heightBlockCount; y++) {
      if (collisionCheckTmpArray[x][y] && stackedBlocks[x][y]) {
        return true;
      }
    }
  }

  return false;
}

function isRightSideCollided(collisionCheckTmpArray, stackedBlocks) {
  moveToRightOneLine(collisionCheckTmpArray);

  for (let x = 0; x < widthBlockCount; x++) {
    for (let y = 0; y < heightBlockCount; y++) {
      if (collisionCheckTmpArray[x][y] && stackedBlocks[x][y]) {
        return true;
      }
    }
  }

  return false;
}

function isBlockReachToBottomBorder(blockArray) {
  for (let x = 0; x < widthBlockCount; x++) {
    if (blockArray[x][heightBlockCount - 1]) {
      return true;
    }
  }
  return false;
}

function isBlockReachToLeftBorder(controlBlocks) {
  for (let y = 0; y < heightBlockCount; y++) {
    if (controlBlocks[0][y]) {
      return true;
    }
  }

  return false;
}

function isBlockReachToRightBorder(controlBlocks) {
  for (let y = 0; y < heightBlockCount; y++) {
    if (controlBlocks[widthBlockCount - 1][y]) {
      return true;
    }
  }
  return false;
}

 

코드가 복잡해보이지만.. 왼쪽 오른쪽 아래쪽의 충돌 소스가 분류되었을 뿐, 같은 로직입니다. (아래부터는 비슷한 중복 소스는 줄여서 표시하겠습니다.)

 

자 이제 확인해 볼까요?

 

와 벌써 여기까지 오니 블럭을 회전시키고, 스페이스바를 눌러 내리꽂고 싶은 욕구가 솟아납니다!

 

하지만 회전시키는 과정에 필요한 로직을 생각해보니 고민할 것이 많군요.

 

회전을 하려는 블럭의 설계도, 회전을 하려다 벽에 부딪힐 경우, 회전을 하려다 쌓여있는 블럭에 부딪힐 경우..

 

벌써부터 머리가 아프지만 한번 해봅시다!

 

스페이스바 이벤트를 추가하고, 바닥까지 한번에 내려오는 로직을 넣어줍니다.

 

onEventSpace() {
  this.isSpaceDownRunning = true;
  while (this.isSpaceDownRunning) {
    this.flowGravity();
  }
}

 

스페이스바를 누를 경우엔 바닥에 닿을때까지 flowGravity()를 해주고, 닿고나서는 isSpaceDownRunning 변수를 false로 바꿔줍니다.

 

flowGravity() {
  let collisionCheckTmpArray = copyBlockArray(this.controlBlock.blockArray);
  if (
    couldBlockMoveToBottom(
      collisionCheckTmpArray,
      this.stackedBlock.blockArray
    )
  ) {
    moveToBottomOneLine(this.controlBlock.blockArray);
  } else {
    this.isSpaceDownRunning = false;
    this.addBlocksToStackedArray(
      this.controlBlock.blockArray,
      this.stackedBlock.blockArray
    );
    this.controlBlock.makeRandomType();
    this.controlBlock.addNewControlBlock();
  }

  this.reDraw();
}

 

이제 회전로직을 추가하기 위해, 블럭 회전에 대한 설계도를 추가해줍니다.

 

저는 상대좌표를 찍기위해 현재 블록의 가장왼쪽위를 기준으로 잡아서 다음 회전할 상대좌표를 표시해줬습니다. 

 

/**
 ****□□□□****
 **/
class BlockTypeOne {
  constructor() {
    this.shape = [
      [3, 1],
      [4, 1],
      [5, 1],
      [6, 1],
    ];
    this.rotationBlueprint = [
      [
        [2, -1],
        [2, 0],
        [2, 1],
        [2, 2],
      ],
      [
        [-2, 1],
        [-1, 1],
        [0, 1],
        [1, 1],
      ],
    ];
  }
}

/**
 ****□□□*****
  *****□******
  **/
class BlockTypeTwo {
  constructor() {
    this.shape = [
      [4, 1],
      [5, 1],
      [5, 2],
      [6, 1],
    ];
    this.rotationBlueprint = [
      [
        [0, 0],
        [1, 1],
        [1, 0],
        [1, -1],
      ],
      [
        [0, 0],
        [1, -1],
        [1, 0],
        [2, 0],
      ],
      [
        [1, -1],
        [1, 0],
        [1, 1],
        [2, 0],
      ],
      [
        [-1, 1],
        [0, 1],
        [0, 2],
        [1, 1],
      ],
    ];
  }
}

/**
 *****□□*****
  *****□□*****
  **/
...
...

 

이제 회전을 시켜줘야겠죠?

 

일단 벽에 붙을경우, 회전한 블록이 경계밖을 벗어나면 회전하지 못하도록 로직을 넣었습니다.

실제 테트리스는 벽에서 회전을 할 경우 밀어주는 로직이 있지만 처음부터 한번에 다 구현하긴 힘드니 백로그에 넣어둡니다.

 

function getLotatedBlock(
  rotationBlueprint,
  currentRotateDirection,
  controlBlockArray
) {
  let tmpArray = copyBlockArray(controlBlockArray);
  let refPoint = findBlockRefPoint(tmpArray);
  clearBlockArray(tmpArray);

  if (rotationBlueprint.length == 0) {
    return null;
  }

  let r = rotationBlueprint[currentRotateDirection];
  for (let i = 0; i < r.length; i++) {
    let rotatedX = refPoint.x + r[i][0];
    let rotatedY = refPoint.y + r[i][1];

    if (
      rotatedX >= 0 &&
      rotatedX < widthBlockCount &&
      rotatedY >= 0 &&
      rotatedY < heightBlockCount
    ) {
      tmpArray[rotatedX][rotatedY] = true;
    } else {
      return null;
    }
  }

  return tmpArray;
}

 

그리고 회전한 블럭이 이미 쌓여있는 블럭을 덮어써도 안되겠죠?

 

그 부분에 대한 로직도 추가해봅시다.

 

rotateBlock(controlBlock, stackedBlockArray) {
  let controlBlockArray = controlBlock.blockArray;
  let rotatedBlockArray = getLotatedBlock(
    this.blockType.rotationBlueprint,
    this.currentRotateDirection,
    controlBlockArray
  );

  if (rotatedBlockArray == null) {
    return;
  }

  if (!isOverlaped(rotatedBlockArray, stackedBlockArray)) {
    this.currentRotateDirection = getNextRotateDirection(
      this.currentRotateDirection,
      this.blockType.rotationBlueprint.length
    );
    controlBlock.blockArray = rotatedBlockArray;
  }
}

 

자 이제 플레이 해볼까요?

 

와 이제 진짜 테트리스 같아졌네요!

 

자유자재로 블럭을 끼워넣는게 가능해졌습니다.

 

아니 그런데 한줄을 완성해도 블럭이 사라지지 않네요..

 

네 그렇습니다.. 이제 완성된 줄을 지워주는 로직을 넣어야합니다. 

 

4부에 계속..

반응형
Comments