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

[테트리스] 21. 블록 이동, 바닥에 도달했을 때 로직 개선

by 햄과함께 2022. 2. 28.
320x100

https://github.com/fpdjsns/Tetris/issues/18

오늘의 이슈.

아주 묵혀있던 이슈를 처리했다.

바닥 도달 시 블럭을 회전했을 때 정상작동하지 않음.


 

기존에는 (x, y)로 이동할 때, 이동가능한지 체크(1), 가능하다면 해당 좌표로 블럭 좌표 갱신(2), 화면에 출력(3). 을 따로 하고 있었다. 블럭이 바닥에 완전히 도달했을 때 호출되는 함수는 timer에서 콜백함수로 따로 주입받았는데 로직은 아래와 같다.

// gamescreen 배열에 nowBlock의 좌표를 세팅
setBlockInGameScreen(nowBlock);
// 삭제가능한 행이 있다면 삭제 후 gamescreen 갱신 & 캔버스 그리기
nowBlock.checkRowsAndErase();
// 새로운 블럭 생성
drawNewBlock();

 

정리해보면 바닥에 완전히 도달했을 때 하는 일은 gameScreen에 값을 세팅 & 삭제 가능한 행 삭제 후 행이 삭제된 적이 있다면 해당 블럭을 새로 그리고 다음 블럭을 생성해준다.

즉, 만약 삭제 가능한 행이 없다면 캔버스를 갱신하는 일이 없다는 뜻이다.

여기서 block의 좌표와  캔버스를 그려주는 로직 사이에 동기화가 제대로 안되고 있는게 아닐까란 의심이 들었다.

 

게임에서 호출하는 setInterval은 총 2개이다. speed 마다 블럭이 떨어지게 하는 것 하나와, 블럭이 지면에 닿았을 때 최대 특정시간만큼만 조작가능하게 하는 것 하나이다.

setInverval이 비동기로 작동하기 때문에 어디가 문제인지 추적이 더 어려웠다.

그래서 분산되어 있던 블럭 이동시 블럭의 x, y좌표를 세팅하는 로직과 블럭을 캔버스에 그리는 로직을 하나로 모으는 작업을 먼저했다.

 

// /js/block.js

function Point(x, y) {
    this.x = x;
    this.y = y;
}

class Block {

    constructor(blockTypeIndex, x, y) {
	    // ...
        this.position = new Point(x, y)
        this.bottomPosition = new Point(x, y)
    
        this.setPreviewCoordinate()
    }
    
    setPreviewCoordinate() {
        for (let k = 0; ; k++) {
            if (this.isBottom(this.position.x, this.position.y + k + 1)) {
                this.bottomPosition.x = this.position.x
                this.bottomPosition.y = this.position.y + k
                return;
            }
        }
    }

    drawPreview() {
        this.justDrawBlock(this.bottomPosition.x, this.bottomPosition.y, 'gray');
    }

    erasePreview() {
        this.eraseBlock(this.bottomPosition.x, this.bottomPosition.y);
    }
}

Block 객체에 현 좌표에서 바닥에 도달했을 때의 좌표를 bottomPosition 변수에 저장하고 preview를 캔버스에 그릴 때 해당 좌표를 사용하게 수정하였다.

 

// /js/block.js

class Block {
    // ...

    // 블럭을 nx, ny로 움직일 때 실행
    // 블럭이 성공적으로 (nx, ny)로 움직였는지 boolean 값 반환
    move(nx, ny, shape = this.shape) {
        let moved = false

        // 움직일 수 있는 곳인지 체크 가능하지 않다면
        if (this.isDuplicatedBlockOrOutOfGameScreen(nx, ny) != NONE_DUPLICATED) {
            console.log("duplicated!");
        } else {
            // bottom 좌표 갱신
            this.eraseBeforeBlock();

            // bottom 좌표와 동일한지 체크( = 바닥인지 체크)
            if (this.bottomPosition.isLower(nx, ny)) {
                ny = this.bottomPosition.y
            } else {
                // move
                this.shape = shape;
                moved = true
            }

            this.drawBlock(nx, ny)
        }

        return moved;
    }

    // position의 갱신은 해당 함수에서만 이루어진다.
    drawBlock(x, y) {
        this.position.x = x
        this.position.y = y

        this.setPreviewCoordinate()
        this.drawPreview();
        this.justDrawBlock();
    }
}

또한 블럭의 현재위치 좌표의 갱신을 drawBlock 함수에서만 하게 수정하였고 좌표를 갱신하는 부분들을 찾아서 drawBlock 함수를 호출하게 수정하였다. drawBlock 내부에서는 좌표 갱신과 preview, 블럭 캔버스에 출력을 같이하여 좌표가 갱신될 때 항상 캔버스에 반영되게 수정하였다.

 

move 함수는 현블럭을 (nx, ny) 좌표에 shape 모양으로 세팅하고 싶을 때 가능한지 체크와 가능한경우 drawBlock을 호출하여 좌표세팅 & 캔버스 출력을 같이 하게 해주었다. 블럭의 이동을 하는 함수들은 내부적으로 모두 해당 함수를 호출하게 수정하였다.

// /js/gameAlgorithm.js

function moveBottomAndSetting(block = nowBlock) {
    console.log("below");
    block.moveBottom()
    setBlockInGameScreen(block);
    block.checkRowsAndErase();

    timer.stopBottom();
    timer.stopBottomTemp();
    drawNewBlock();
};

이렇게 코드 정리를 후 바닥에 완전히 도달했을 때 호출되는 함수를 스페이스바를 눌렀을 때 호출하던 함수(현블럭을 최하단으로 세팅 후 새로운 블럭 생성)를 호출하게 수정하였다.

해당함수 내에서는 block.moveBottom 함수를 호출하여 block의 좌표의 갱신과 캔버스 출력을 한 번 더 해주어 좌표의 동기화도 정상작동하였다.

 

위와 같이 왼쪽과 같은 상황에서 회전을 한 번 하면 오른쪽과 같은 모양이 된다.

회전시 회전한 모양이 좌우벽과 중복되는지, 다른 블럭들과 중복되는지는 체크하였으나 아래 스크린에 중복되는지는 체크를 안하고 있어서 발생하던 문제였다.

// /js/block.js

    isDuplicatedBlockOrOutOfGameScreen(x, y, shape = this.shape) {
        let checkShape = shape;

        for (let i = 0; i < this.blockNum; i++) {
            for (let j = 0; j < this.blockNum; j++) {
                // ...
                if (GAME_SCREEN_HEIGHT_NUM <= ny) {
                    return BOTTOM_DUPLICATED;
                }
            }
        }
        return NONE_DUPLICATED;
    }

그래서 중복체크 하는 함수에서 y 좌표가 게임스크린에 벗어나는지 체크하는 로직도 추가해주니 정상작동하였다.


결과 확인 타임.

 

개발 결과를 보면 왼쪽과 같은 상황에서 왼쪽과 같이 S 블럭이 떨어지고 있다면 오른쪽과 같은 상황에서 더이상 회전을 할 수 없다.

게임이 정상적으로 동작하는거 같으나 이는.. 게임의 재미를 반감시킨다. 플레이해봤는데 재미가 없다.

위와 같이 핑크색 부분으로 블럭이 변경될 수 있어야 하지 않나..? 라는 생각이 든다. 다른 테트리스 게임 해보면서 어떻게 처리하고 있나 시장분석해봐야겠다. 말로만 듣던 T스핀 이런거 하려면 개선이 필요하다.


- 접근자제어를 하고 싶은데 private, public을 자바스크립트가 지원하고 있지는 않은거같다. 타입스크립트로 가야하나..

- 근 1년반만에 다시 작업하다보니 이전에 어떻게 개발하였는지 확인하는 작업을 먼저 해야했다. 그래서 코드 수정 전에 로직 등을 위키에 정리하는 작업을 먼저 하였다.

 

깃허브 : 블록 이동, 바닥에 도달했을 때 로직 개선

320x100

댓글