아시다시피 독창적인 구현을 선호하는 코세입니다.


프갤 필수요소 테트리스에 도전하는 친구들이 많아서 좀 독특한 방법을 소개합니다.

도전하기 전인 친구들은 최대한 안보고 해보는게 좋습니다만,

어느정도 좌절했거나, 스스로 구현해본적 있다면 다른 사람의 방법론이 도움이 됩니다.


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

    //  젤 아래 nibble4비트 ) 부터 총 네 개의 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;

}


실행하면,


아래는 잘랐지만 회전하는것 보이죠?


오늘은 블럭 클래스를 만들고 회전해서 출력하는것 까지 해 보았습니다.