아시다시피 독창적인 구현을 선호하는 코세입니다.
프갤 필수요소 테트리스에 도전하는 친구들이 많아서 좀 독특한 방법을 소개합니다.
도전하기 전인 친구들은 최대한 안보고 해보는게 좋습니다만,
어느정도 좌절했거나, 스스로 구현해본적 있다면 다른 사람의 방법론이 도움이 됩니다.
class TBLOCK
{
public:
using TYPE = unsigned short;
// 16비트 자료형이 필요합니다.
// 테트리스의 4*4 블럭 공간에서 타일이 있냐 없냐를 1비트씩 표현한다면 말이죠.
private:
const TYPE bits_set[ 8 ]
{
0xFFFF, 0xA808, 0x840A, 0x8888, 0xC808, 0xA088, 0x8848, 0xA880,
};
// 테트리스엔 총 7가지 블럭이 있습니다.
// 지우는 블럭도 필요하기 땜에 ( 다음 블럭 미리보기 청소용 ) 8개로 갑니다.
// 위 값은 아래와 같은 순서로 타일이 있냐 없냐를 표현한 것입니다.
// 저는 아래를 먼저 만들고 아래 지도에 따라 위 비트들을 조합했죠.
const TYPE spin_geometry[ 16 ]
{
0x0001, 0x0002, 0x0004, 0x0010,
0x4000, 0x0008, 0x0080, 0x0020,
0x2000, 0x8000, 0x0800, 0x0040,
0x1000, 0x0400, 0x0200, 0x0100,
};
// 일반적인 가로 우선 순서가 아니라 조금 독특한 구성을 갖고 있습니다.
// 0001
// 3011
// 3321
// 3222
// 젤 아래 nibble( 4비트 ) 부터 총 네 개의 nibble을 위의 순서로 한비트씩 자리잡게 합니다.
// 그렇게 순서를 조절한 이유는 4비트 쉬프트 연산만으로 전체 회전이 가능하게 하기 위해서죠.
TYPE bits = bits_set[ 0 ]; // 모양입니다. ( 회전하면 변합니다 )
int index = 0; // 종류입니다. ( 나중에 타일 색상 같은걸로 표현할겁니다 )
public:
// 연산 편의를 위해 다른 블럭을 대입할 수 있게 했습니다. 전 필요없을 것 같지만.
auto& operator=( const TBLOCK& other )
{
bits = other.bits;
index = other.index;
return *this;
}
// random 값을 타일 instance에 바로 대입해도 블럭이 만들어지게 합니다.
auto& operator=( const int index )
{
bits = bits_set[ this->index = index ];
return *this;
}
TBLOCK() = default;
TBLOCK( const int index ) : index( index )
{
*this = index;
}
// 위의 spin_geometry 를 이용해 4비트 쉬프트 시킵니다. 좌측 쉬프트가 시계방향이죠
// 최상단 nibble 은 최하단으로 옮겨줘야 rotate 가 됩니다.
auto rotate_cw()
{
bits = ( bits << 4 ) & 0xFFF0 | ( bits >> 12 ) & 0x000F;
}
// 시계 반대방향 입니다.
auto rotate_ccw()
{
bits = ( bits >> 4 ) & 0x0FFF | ( bits << 12 ) & 0xF000;
}
// 타입에 상관없이 임의의 2차원 배열의 특정 좌표에 자신의 모양을 index 값으로 그려넣어줍니다.
// for 문은 2차원 배열의 가로 - 세로 순서를 따르고 있기 때문에 spin_geometry를 이용해
// 4 * 4 해당 영역안에 타일을 찍을지 말지 정해줘야 합니다.
// 나중에 충돌 판정도 이런식으로 할겁니다.
template< class T, int H, int W >
auto draw_to( T (&dst)[ H ][ W ], const int x, const int y )
{
T* d = (T*)dst + y * W + x;
for( int v = 0; v < 4; ++v, d += W )
for( int u = 0; u < 4; ++u )
if( bits & spin_geometry[ v * 4 + u ] )
d[ u ] = index;
}
};
int board[ 24 ][ 12 ];
// 보통 테트리스엔 이정도 크기가 필요하죠 10 * 20 정도면 됩니다만
// 전 왼쪽 오른쪽 벽으로 이동할수 없는 처리를 위해 좌표 범위를 비교하는 조건문이 싫어서
// 좌우 벽엔 미리 타일을 넣어둘겁니다. ( 타일충돌만으로 모든 이동 여부 판정이 되게 )
#include <iostream>
using namespace std;
// 임의의 2차원 배열의 내용을 콘솔에 찍어 줍니다.
template< class T, int H, int W >
auto draw_array( T (&dst)[ H ][ W ] )
{
for( int v = 0; v < H; ++v, puts( "" ) )
for( int u = 0; u < W; ++u )
putchar( dst[ v ][ u ] ? '#' : ' ' );
}
#include <cstring> // for std::memset
int main()
{
TBLOCK blocks[]{ 0, 1, 2, 3, 4, 5, 6, 7 };
// 빈 블럭 포함 8 가지 블럭을 모두 생성
for( int direction = 0; direction < 4; ++direction ) // 4번 90씩도 돌려봅시다.
{
std::memset( board, 0, sizeof board );
// 바닥을 안지워두면 겹쳐져서 이상하게 보임
int x = 0;
for( auto& block : blocks ) // ranged for 개꿀~
block.draw_to( ( block.rotate_cw(), x++, board ),
// 하지만 ranged for 에서 증감 처리 추가는 귀찮음. 나름 콤마연산자와 괄호를 이용한 꼼수
( x & 1 ) * 5 + 1, ( x >> 1 ) * 5 + 1 );
// 가로 2개씩 그릴겁니다. ( 5칸 단위로 그려야 가장자리가 띄어져서 보기 좋겠죠 )
draw_array( board );
}
return 0;
}
실행하면,
아래는 잘랐지만 회전하는것 보이죠?
오늘은 블럭 클래스를 만들고 회전해서 출력하는것 까지 해 보았습니다.
일어나면 읽어보갯슴니다 -피에로
블록이 벽에 붙었을때 회전을 막느냐, 블록을 밀어서 회전하느냐.
난 회전을 막음. 그게 두 줄이라도 덜 짜니깐 ㅋㄷ
개발자입장에서 막는ㄱ 맞는데, 사용자 입장에서 엄청 답답하다.
응 글치 근데 신의 손들은 다 하잖아 ㅋㅋ
제목에 오타 죄송함다~