Boxfall

Článek popisuje mou první hru pod enginem Quinty. Boxfall je klonem legendárního tetrisu s několika málo odlišnostma (a s několika pěknýma myšlenkama, které možná někdy uskutečním :) ).

Úvod

Základní myšlenku tetrisu (wikipedia) zná bezpochyby každý. Myšlenka Alexeye Pajitnova dává dokonalou možnost na procvičení programátorského umu a v mém případě taky k vylepšení Quinty.

Programovací jazyk: C++
Nestandartní knihovny: SDL
Poznámka: hra je vybudována na enginu Quinty v1.0.0

Stavební prvky

Základní struktury použité ve hře jsou Tetroid (pro popsání "kostky") a World (třída, která mimo jiné zastřešuje herní oblast).
Struktura Tetroid:

struct Tetroid
{
    int    posX;
    int    posY;
    int    block[4][4][4];
    int    width;
    int    height;
    int    color;
    int    angle;
};

Význam jednotlivých proměných:
posX, posY: pozice kostky vzhledem k herní oblasti (použito u padající kostky)
block[4][4][4]: v poli uchovávám pozice jednotlivých částí kostky pro všechny 4 možné směry natočení (máme 4 směry; každá kostka je definovaná polem 4x4)
width, height: šířka výška kostky
color: index barvy
angle: uchovává úhel kostky (0, 1, 2, 3)

Index barvy (color) udává pozici z následujícího obrázku:
Squares

Číslo v proměné úhel (angle) udává násobky 45ti stupňů.

Následuje fragment třídy Word, který má na starosti základní práci s herní oblastí.

class World: public GameEngine
{
public:
    // Constructor(s)/Destructor
    World();
    virtual ~World();
 
    // General methods
    void    CreateArea(int width, int height, int square_dimensoin, int x, int y);
    void    SetAreaPics(char* border);
    void    BlitAreaBounds();
    void    BlitArea();
 
    ...
 
private:
    // Member variables
    int**            m_area;
    int              m_area_x;
    int              m_area_y;
    int              m_area_width;
    int              m_area_height;
    int              m_square_dimension;
    Image*           m_area_border;
 
    ...
};

Popis třídy (relevantních proměných a metod):
Jak je vidět třída World dědí od třídy GameEngine, která poskytuje základní funkcionalitu enginu.
Dynamické dvourozměrné pole (m_area) uchovává hrací oblast, definovanou šířkou (m_area_width) a výškou (m_area_height). Dále je uchovávána pozice (m_area_x; m_area_y), velikost čtverečku vyznačujícího hranici herní oblasti (m_square_dimension) a referenci na samotný obrázek čtverečku (m_area_border).
Metoda CreateArea slouží k vytvoření dynamické hrací plochy a SetAreaPics k nastavení obrázku okraje.

Další zajímavá metoda třídy World je SetTetroids().

void World::SetTetroids()
{
    Tetroid tetroids[m_number_tetroids] =
    {
        {
            0,
            0,
            {
                {
                    {1,0,0,0},
                    {1,1,1,0},
                    {0,0,0,0},
                    {0,0,0,0}
                },
                {
                    {0,1,0,0},
                    {0,1,0,0},
                    {1,1,0,0},
                    {0,0,0,0}
                },
                {
                    {1,1,1,0},
                    {0,0,1,0},
                    {0,0,0,0},
                    {0,0,0,0}
                },
                {
                    {1,1,0,0},
                    {1,0,0,0},
                    {1,0,0,0},
                    {0,0,0,0}
                }
            },
            3,
            2,
            0,
            0
        },
 
        ...
 
    }
}

Všechny dílčí proměnné jsou nastaveny na výchozí hodnotu. Trojrozměrné pole block je pro každý útvar a jeho úhel nastaveno jednoduchým způsobem - 1: plné pole, 0: prázdné pole. Tímto způsobem jsou nastaveny a uloženy všechny tvary.

Algoritmy

Základní funkčnost je v podstatě dost jednoduchá:

Pokud je pod aktivním (padajícím) tvarem místo, posuň ho o jednu kostičku dolů. Pokud se kostička dotkne vrchní hrany hrací plochy, hra končí.

V případě vstupu od uživatele je nutné provádět další kontroly:

Jestli tvar neopustí herní plochu (ať už posunem, nebo důsledkem rotace) a jestli rotací nedochází k dalším kolizím.

Jako první uvedu zdrojový kód obsluhy klávesnice.

void World::GE_HandleKeys()
{
    m_keys = GE_GetKeyState();
 
    if (m_keys[GEK_ESCAPE] == GE_PRESSED) GE_Exit();
 
    if (m_keys[GEK_RIGHT] == GE_PRESSED)
        if (GetFallTetroidCollision(1,0) == false) MoveFallTetroid(1,0);
 
    if (m_keys[GEK_LEFT] == GE_PRESSED)
        if (GetFallTetroidCollision(-1,0) == false) MoveFallTetroid(-1,0);
 
    if ((m_up_key++ > 1) && (m_keys[GEK_UP] == GE_PRESSED))
    {
        if (GetFallTetroidCollision(0,0,true) == false) RotateFallTetroid();
        m_up_key = 0;
    }
 
    if (m_keys[GEK_DOWN] == GE_PRESSED) m_count = 3;
}

Názvy metod určují jejich použití. Poněkud nejasné mohou být proměnné m_up_key a m_count. Obě slouží k nastavení určité prodlevy a to tak, že uměle protahují (popř. zkracují) pauzu mězi dvěma akcema. První proměnná m_up_key hlídá, aby nebyla akce otočení kostky tak citlivá a m_count hlídá určitou prodlevu pro padání aktivní kostky (po stisku šipky dolů se nastaví na hodnotu, která způsobí ignorování prodlevy). Jinak se proměnná m_count iteruje každým průchodem hlavního cyklu hry.

Přesouváme se ke klíčové metodě celé hry.

void World::GE_MainFunction()
{
    if (m_count++ == 3)
    {
        if (GetFallTetroidCollision(0,1) == false)
            MoveFallTetroid(0,1);
        else
        {
            if (GetUpCollision()) GE_Exit();
            GenerateFallTetroid();
            GenerateNextTetroid();
        }
 
        m_count = 0;
    }
}

Jako první zkoumáme, zda se kostka může posunout dolů. Pokud ano, posuneme ji.

Následuje metoda na určování kolizí.

bool World::GetFallTetroidCollision(int x_move, int y_move, bool rotate)
{
    // Bounds
    if (rotate) IncFallTetroidAngle();
 
    if ((y_move>0) || rotate)
        if ( (m_fall_tetroid.posY + m_fall_tetroid.height > m_area_height) && (rotate)
                || (m_fall_tetroid.posY + m_fall_tetroid.height == m_area_height) && (rotate == false) )
        {
            if (rotate) DecFallTetroidAngle();
            SeatFallTetroid();
            return true;
        }
 
    if ((x_move>0) || rotate)
        if ( (m_fall_tetroid.posX + m_fall_tetroid.width > m_area_width) && (rotate)
                || (m_fall_tetroid.posX + m_fall_tetroid.width == m_area_width) && (rotate == false) )
        {
            if (rotate) DecFallTetroidAngle();
            return true;
        }
 
    if (x_move<0)
        if (m_fall_tetroid.posX == 0)
        {
            if (rotate) DecFallTetroidAngle();
            return true;
        }
 
    //**************************************************************
 
    if (rotate) DecFallTetroidAngle();
 
    EraseFallTetroid();
 
    if (rotate) IncFallTetroidAngle();
 
    int index;
 
    for (int i = 0; i < 4; i++)
        for (int o = 0; o < 4; o++)
        {
            index = GetAddress(i+y_move,o+x_move);
            if ((index < 0) || (m_area[index] < 1)) continue;
 
            if (m_fall_tetroid.block[m_fall_tetroid.angle][i][o] == 1)
            {
                if ((x_move == 0) && (rotate == false))
                {
                    if (rotate) DecFallTetroidAngle();
                    SeatFallTetroid();
                    return true;
                }
 
                if (rotate) DecFallTetroidAngle();
                MoveFallTetroid(0,0);
                return true;
            }
        }
 
    if (rotate) DecFallTetroidAngle();
 
    return false;
}

I když je metoda relativně dlouhá, její pochopení by neměl být problém. V první části se řeší kolize s okrajem (ať už vlivem rotace či posunu) a ve druhé pak kolize s už spadanýma kostkama.

Tetris 1.0

Závěrečná poznámka: text berte pouze jako inspiraci a nastínění možného řešení. Celá hra vznikla pouze jako testovací projekt mého enginu. Určitě se dá spousta věcí vylepšit a spousta udělat jinak, ale v této chvíli už pracuji na nečem jiném a s Boxfall tím pádem končím. Zdrojové kódy nedám k dispozici, veškeré podstané části jsou v článku.

Tisknout tento příspěvek

Štítky: ,

Tento příspěvek byl přidán 24. 6. 2009 v 14.00 a je přiřazen do kategorie Programování. Můžete sledovat všechny odezvy na tento příspěvek prostřednictvím RSS 2.0. Můžete napsat komentář, nebo trackback z vašich stránek.

Napsat komentář