테트리스 게임을 두 가지 방법으로 개발하면서

하나의 제품이나 기능을 다양하게 개발하는 방법을

다음과 같이 단계별로 정리하였다.

1. 개요

2. 배열 방식의 도형 회전

3. Bitmask 방식의 도형 회전

4. 배열기반 회전 방식의 테트리스

5. Bitmask기반 회전 방식의 테트리스

6. requestAnimationFrame


이번에는 Bitmask를 이용한 테트리스 도형 회전에 대하여 정리한다.

Bitmask를 이용한 테트리스 도형 회전은

7개의 테트리스 도형에 대한 모든 회전(4가지) 정보를 가지고 있는 것으로

데이터 양을 줄이고 관리를 편리하게 하기 위해

도형 정보를 bit로 변환해서 저장한다.


그림과 같이 하나의 행이 하나의 워드(word - 4bit)가 되고,

색이 있는 부분을 1 로 표기하고

색이 없는 부분은 0 으로 지정하면,

2 진수로 처리되어 오른쪽 표와 같이 된다.

즉, L 자 도형은

2 진수로 0100 0100 0110 0000 이 되고

16 진수로 4460 이 된다 (진수 변환은 본문 마지막의 해설 참조).


이렇게 모든 도형과 회전후 도형 모습을

다음과 같이 2 진수로 처리하여 16진수 값으로 저장한다.

출처: Code in Complete

데이터를 저장하는 방법을 이해하면

다음 코드와 같이 간단하게 구현할 수 있다.

위 그림의 계산 값과 다음 코드의 계산값(shapes)에 차이가 있다.

둘다 방식은 같은데 도형의 배열내 위치가 조금씩 다르기 때문이다.

이미지와 기타 코드는 Code in Complete에서 가져왔고

다음 샘플은 stackoverflow에서 가져와서 수정했기 때문에 차이가 있다.


이상의 코드에서 7 개의 도형(행)과 4 개의 각도(열)를

2 차원 배열로 구성해서 shapes 변수에 저장한다.

배열법보다 상당히 줄어든 코드를 볼 수 있다 [라인 13].

배열법에서는 현재 도형의 모양을 별도의 변수에서 저장했지만

Bitmask에서는 도형의 모양 (curShapeType – 행 위치)과

도형의 현재 각도(curRotation)에 대한 정보 [라인 23]만 있으면 된다.

curRotation 변수에 현재 각도에 대한

배열의 위치 (열 위치)를 다음과 같이 저장한다.

0 이면 기본 각도 (0 or 360)

1 이면 90 도

2 이면 180 도

3 이면 270 도를 의미하는 숫자가 된다.

즉 왼쪽으로 회전하는 것은

앞으로 가야 하기 때문에 curRotation 값에 -1을 계산하고 [라인 46]

오른쪽으로 회전하는 것은

뒤로 가야 하기 때문에 curRotation 값에 +1 계산한다 [라인 52].

다만, 왼쪽으로 회전해서 0 보다 작으면

다시 3 부터 시작하게 되고 [라인 47],

오른쪽으로 회전해서 3 보다 크면 0 부터 시작하도록 처리한다 [라인 52].


마지막으로 가장 중요한 도형을 그리는 방법을 정리한다 [라인 33].

이 부분이 가장 어렵고 중요한데,

데이터 저장을 2 진수로 저장하는 일종의 암호화를 하고,

이것을 다시 쉽게 풀어 쓰는 복호화를 하기 때문이다.

비트 연산을 잘하는 사람이 아닌 이상

33 라인을 이해하기 어렵다.

어렵다기 보다는 개발 중에 비트 연산을 보는 경우가 드물어

익숙하지 않기 때문에 어렵게 느껴질 수 있다.

기본 개념은 앞서 2 진수로 계산한 값을

기본 값과 이진 연산을 통해서 값이 있으면 (1 이면)

테트리스 도형을 그리기 위한 사각형을 그린다 [라인 34].

예로, L 자 도형은 16 진수 0x4460 으로 저장되어 있다.

이 값을 다시 2 진수 0100 0100 0110 0000 로 변환해서

하나의 문자(bit)가 1 이면 사각형을 출력하도록 하면 된다.

다만, 16 진수 값을 2 진수의 문자열로 바꾸는 것이 까다롭고 복잡해서

비트 연산(bitmask)을 통해서 간단하게 구현한다.


먼저 두 개의 for문을 사용하면

0 부터 15 (4 * 4 = 16)까지의 값을 구할 수 있다 [라인 31, 32].

즉, 16 번의 비트 이동(>>)을 하게 된다.


16 진수 0x8000은

2 진수로 1000 0000 0000 0000 이다.

이것을 16 번 비트 이동(>>)하면

0x8000 >> 0  : 1000 0000 0000 0000
0x8000 >> 1  : 0100 0000 0000 0000
0x8000 >> 2  : 0010 0000 0000 0000
0x8000 >> 3  : 0001 0000 0000 0000
….
0x8000 >> 15 : 0000 0000 0000 0001 이 된다.

이 각각의 값을

L 자 도형의 2진수인

0100 0100 0110 0000 과 & (and) 비트 연산을 하면

두 개의 값이 1일때만 1 이 반환되기 때문에

이때만 사각형을 그리도록 한다 [라인 33].


예로 처음에는 다음과 같이 0 이 반환된다.

0x8000 >> 0  : 1000 0000 0000 0000
0x4460       : 0100 0100 0110 0000
-------------------------------
& 결과         0000 0000 0000 0000  => 0x0000

두 번째에는 1 이 반환된다.

0x8000 >> 1  : 0100 0000 0000 0000
0x4460       : 0100 0100 0110 0000
-------------------------------
& 결과         0100 0000 0000 0000  => 0x4000

이렇게 16번을 비교하면

테트리스 도형을 그리기 위한 4개의 사각형을 얻을 수 있다.


16 진수로 비트 연산(>>, &)을 하면

자바 스크립트 내부에서

위와 같은 이진 연산을 알아서 처리해준다.


지금까지 테트리스의 도형을

회전시키는 2가지 방법에 대해서 정리하였다.

배열법은 코드 양이 많은 것 같지만 단순한 원리가 적용되었고,

Bitmask 방법은 코드 양이 적지만

익숙하지 않은 bit에 대한 개념과 사용법을 알아야 한다.

둘 다 장단점이 있고,

인터넷으로 검색해 보면 Bitmask로 개발한 예제가 더 많고

더 나은 방법이라는 글을 많이 볼 수 있다.


개인적으로도 Bitmask가 더 좋은 방식이라고 생각하지만

개발을 시작하는 사람들에게는

배열법도 게임에 대한 이해를 돕기에 좋은 방식이라고 생각한다.

더우기 배열법 예제에서 나타난 문제를 해결하면서

실력향상을 도모할 수 있다.

그리고, 실제 게임을 제작하면

Bitmask는 복잡한 방식으로 구현되기 때문에

초보자가 이해하기 어렵다 (Code in Complete참조).

배열법이 코드양이 많아 보여서 복잡하게 느껴질 수 있지만

단순한 구조라 실제 계발에서도 직관적으로 구조를 파악할 수 있다.

(여기서 참고한 예제는 DIV를 사용해서 조금 더 어려울 수 있다.)


이렇게 테트리스의 도형을 회전시키는 방법을 통해

다양한 방식으로 개발할 수 있다는 것을 맛보고,

이것을 좀더 심화해서 간단하지만 즐길 수 있는

실제 게임을 제작하면서 어떤 차이가 있는지 정리한다.


2진수를 16진수로 변환하는 방법

출처: Code in Complete

이 그림에서 보듯이 가장 오른쪽 부터

2 ^ 0 으로 1

2 ^ 1 으로 2

2 ^ 2 으로 4

2 ^ 3 으로 8 의 값이 된다.

해당 자리에 값이 지정되면 (2진수 이므로 1 이면)

1, 2, 4, 8의 숫자가 해당 위치의 값이 되어 사용된다.


예로,

2진수 1000 은 오른쪽에서 4번째에 값이 있기 때문에 2 ^ 3 으로 16 진수 8 이 된다.

2진수 1100 은 2 ^ 3 과 2 ^ 2 에 값이 있으니 8 + 4 로 16진수 C (십진수 12) 가 된다.



+ Recent posts