Táto stránka pre svoju správnu funkčnosť vyžaduje súbory cookies. Slúžia na authentifikáciu návštevníka, analýzu návštevnosti a reklamnú personalizáciu.
logo
Prihlásenie / Registrácia
mobile

Zavrieť
 

Tetris v HTML5 – IV. Herná logika 3/4

V tomto diely seriálu si vytvoríme ďalšie časti hernej logiky, vrátane detekcie konca hry a počítadla skóre.
Na úvod začneme niečím jednoduchším. Pre zlepšenie hrateľnosti hry, pridáme možnosť zhodiť kocku nadol okamžite pomocou medzerníka.

Vytvoríme funkciu GetLowerPosition, ktorá pomocou funkcie CheckCollision (definovanej v predchádzajúcom diely) postupne vyhľadá najnižšiu možnú pozíciu pre padajúcu kocku. Pozíciu následne pomocou MovePiece aplikuje na kocku v event handleri KeyDown.

Zdrojový kód:
KeyDown: function (e)
    {
        switch (e.keyCode)
            {
            ...
            case 32: //SpaceBar
                {
                    var LowerTop = Game.GetLowerPosition(Game.ActivePiece);
                    if (LowerTop <= 0)
                        return;

                    Game.MovePiece(LowerTop - Game.ActivePiece.Top, 0, false);
                    e.preventDefault();
                } break;
            ...
            }
    },
...
GetLowerPosition: function (piece)
    {
        var Top = piece.Top;
        var MaxTop = this.GameArrayHeight - piece.Height;

        while (Top <= MaxTop)
        {
            if (this.CheckCollision(piece, Top - piece.Top, 0))
            { //Ak doslo ku kolizii, tak posledna platna pozicia je jednu pozadu            
                return Top - 1;
            }

            Top++;
        }

        return Top - 1;
    },

Nasleduje implementácia ďalšieho dôležitého prvku hernej logiky a to je koniec hry.

Podla pravidiel sa hra končí, keď už nie je možné pridať ďalšiu kocku do hry z dôvodu zaplnenia herného poľa. Detekcia konca hry bude vo funkcii PlacePiece, kde pomocou CheckCollision zistíme, či máme dostatok miesta na vloženie novej kocky. Ak nie, zavoláme funkciu GameOver ktorá zastaví hru, vyčistí hraciu plochu a zobrazí hlásenie „Game Over“. Samotné hlásenie bude obrázok, ktorý si môžete stiahnuť z nasledujúceho odkazu: > link < a uložiť ho priečinku imgs v našom projekte.

Do časti /* Obrazky */ v premenných objektu Game zadefinujeme novú premennú img_gameover, ktorú zároveň inicializujeme v LoadImages. Okrem nej si zadefinujeme GameOverLeft a GameOverTop, ktoré budú určovať pozíciu obrázka na ktorú sa má vykresliť.

Zdrojový kód:
Game =
{

    /* /Nastavenia */
...
    //Pozicia obrazku GameOver
    GameOverLeft: 1,
    GameOverTop : 157,
    /* /Nastavenia */
...
    /* Obrazky */
    img_empty: null, //Prazde policko
    img_colors: [], //Farebne kocky
    img_gameover: null, //Napis game over
    /* /Obrazky */

LoadImages: function ()
    {
    ...
    this.img_gameover = this.CreateImage("gameover");
    ...
    }
}

Nasleduje vytvorenie funkcie GameOver a úprava funkcie PlacePiece.

Zdrojový kód:
...
GameOver: function ()
    {
        this.Status = GameStates.gs_Stop;

        this.DrawGameField();

        this.Context.drawImage(this.img_gameover, this.GameOverLeft, this.GameOverTop);
    },
...
PlacePiece: function (Piece)
    {
        var Left = Math.floor(this.GameArrayWidth / 2 - Piece.Width / 2);
        var Top = 0;
        Piece.Left = Left;
        Piece.Top = Top;

        //Overime aktualne zamyslanu poziciu
        if (this.CheckCollision(Piece, 0, 0))
        {
            this.GameOver();

        } else
        {
            this.ActivePiece = Piece;
            this.DrawPiece(Piece);
        }
    },

Aby bolo možné hru zvonu začať, pridáme volanie NewGame do KeyDown na miesto reagujúce na Enter (kód 13).

Zdrojový kód:
KeyDown: function (e)
    {
        switch (e.keyCode)
        {
            case 13: //Enter - Restart game
                {
                    if (Game.Status === GameStates.gs_Stop)
                        {
                            Game.NewGame();
                        }

                    e.preventDefault();
                }break;
            …
        }
    }

Ďalším podstatným prvkom hernej logiky je miznutie zaplnených riadkov a zvyšovanie dosiahnutého skóre. Samotná hodnota konkrétneho zaplneného riadka sa bude riadiť podľa počtu zaplnených riadkov a úrovne (levelu) hry v ktorej sme. Do časti /* Nastavenia */ pridáme nové hodnoty, ktoré budú obsahovať základnú hodnotu vymazaného riadka. Zároveň do časti /* Herne premenne */ budú pridané premenné Score, Level a LinesCounter.

Zdrojový kód:
    /* Nastavenia */

    //Zakladne pocty bodov pre zaplnenie 1-4 riadku
    Line1Score: 40,
    Line2Score: 100,
    Line3Score: 300,
    Line4Score: 1200,
    ...
    /* Nastavenia */

    /* Herne premenne */
    ...
    Score: 0, //Score
    Level: 1, //Level            
    LinesCounter: 0, //Pocet riadkov plnych riadkov
    ...
    /* /Herne premenne */

Herné premenné je potrebné resetovať vždy, keď sa začína nová hra, preto úvod funkcie NewGame trochu upravíme.

Zdrojový kód:
NewGame: function ()
    {
        this.LastUpdate = new Date();

        //Reset hernych premennych
        this.Level = 1;
        this.Score = 0;
        this.LinesCounter = 0;

        //Vycistit hracie pole
        this.CreateGameArray();
    ...
}

Hodnota premennej Level má vplyv aj na rýchlosť hry. Čím je jej hodnota vyššia, tým je rýchlejšia aj hra. Rýchlosť hry je definovaná v premennej LevelInterval funkcie GameUpdateRequest a momentálne ma natvrdo určenú hodnotu 500 (ms). Túto hodnotu nahradíme nasledujúcim výpočtom: [LevelInterval] = Math.round(1000 / ([Level] +1)). To nám zabezpečí, aby hra každým ďalším levelom zrýchľovala.

Zdrojový kód:
GameUpdateRequest: function ()
    {
        var LevelInterval = Math.round(1000 / (this.Level + 1));

        if (LevelInterval <= 0)
            LevelInterval = 50;
...
    }

Na detekciu zaplneného riadka vytvoríme funkciu CheckForFullLine a funkciu ClearLine, ktorá bude zabezpečovať vyčistenie riadku z herného poľa vizuálne aj dátovo.

Zdrojový kód:
CheckForFullLine: function (Top)
    {
        var IsFull = true;
        for (var j = 0; j < this.GameArrayWidth; j++)
        {
            if (this.GameArray[Top][j] !== FieldStates.fs_Block)
            {
                IsFull = false;
                break;
            }
        }

        return IsFull;
    },
ClearLine: function (Top)
    {
        var RealTop = this.GetRealPosition(Top);
        var imgData = this.Context.getImageData(0, 1, this.GameAreaWidth, RealTop - this.BorderWidth);
        this.Context.putImageData(imgData, 0, 0 + this.GameFieldWidth + this.BorderWidth);

        for (var i = Top; i > 0; i--)
        {
            this.GameArray[i] = this.GameArray[i - 1];
        }

        //Novy prazdny riadok na zaciatok
        this.GameArray[0] = this.CreateEmptyLine();

        for (var j = 0; j < this.GameArrayWidth; j++)
        {
            this.GameArray[0][j] = FieldStates.fs_Empty;
            this.DrawBlock(0, j, this.img_empty);
        }
    },

Na vizuálny výmaz riadka presunieme obsah hracej plochy nad naším riadkom nadol tak, aby ho prekryl. Prvý riadok na hernom poli sa nám týmto uvoľní, čiže ho vyplníme prázdnymi bunkami. Dátový výmaz dosiahneme reorganizáciou herného poľa GameArray.

Pri výmaze riadka by malo dôjsť aj k navýšeniu skóre a pre tento účel vytvoríme funkciu WriteLinesToScore

Zdrojový kód:
WriteLinesToScore: function (Lines)
    {
        if (Lines <= 0)
            return;

            //Aktualizacia score
            var LinesScore = 0;
            switch (Lines)
            {
                case 1:
                    LinesScore = this.Line1Score;
                    break;
                case 2:
                    LinesScore = this.Line2Score;
                    break;
                case 3:
                    LinesScore = this.Line3Score;
                    break;
                case 4:
                    LinesScore = this.Line4Score;
                    break;
                default:
                    {
                        //Neplatny pocet
                        return;
                    }
            }

        this.Score += LinesScore * (this.Level - 1) + LinesScore;
        this.LinesCounter += Lines;
        this.Level = Math.ceil(this.LinesCounter / 10);
    },

Funkcia zabezpečuje navýšenie skóre na základe počtu vymazaných riadkov a aktuálneho levelu. Zároveň navýši premennú LinesCounter, ktorá nám slúži na výpočet nového levelu po zapísaní aktuálne vymazaných riadkov.

Samotná detekcia plného riadka bude prebiehať vo funkcii DockPiece, ktorú upravíme nasledovne:

Zdrojový kód:
//Ukotvit kocku na hraciu plochu
    DockPiece: function (piece)
    {
        var Top = 0;
        var Left = 0;

        //Kolko riadkov bolo vyplnenych
        var Lines = 0;

        for (var i = 0; i < piece.Height; i++)
        {
            Top = piece.Top + i;

            for (var j = 0; j < piece.Width; j++)
            {
                Left = piece.Left + j;
                if (piece.Blocks[i][j] === FieldStates.fs_Block)
                {
                    this.GameArray[Top][Left] = piece.Blocks[i][j];
                }
            }

            if (this.CheckForFullLine(Top))
            {
                Lines++;
                this.ClearLine(Top);
            }
        }

        if (Lines > 0)
        {
            //Zapisat riadky do score
             this.WriteLinesToScore(Lines);
        }

        this.NewPiece();
    },

V tomto momente môžeme opäť uložiť a vyskúšať, či funguje výmaz riadkov korektne. Ak je všetko OK, môžeme pokračovať ďalej.

Systém rátania skóre už máme definovaný, no hráč ho zatiaľ nikde nevidí. Na tento účel si vytvoríme skóre panel, ktorý bude obsahovať údaj o aktuálnom skóre a leveli v ktorom sa nachádzame. Okrem toho, budeme v ňom zobrazovať aj kocku, ktorá sa pridá do hry po položení aktuálnej.

Na tento účel budeme potrebovať obrázok s popisnými textami a samotné ukazovatele hodnôt, ktoré budú zložené z obrázkových číslic. Všetky potrebné obrázky stiahneme z nasledujúceho odkazu > link < a rozbalíme do priečinka imgs.

Obrázkové číslice sú pomenované ako d0.pngd9.png a panel s popiskami sa volá scorepanel.png.

Číslice si zadefinujeme do poľa obrázkov img_digits a skóre panel do premennej img_scorepanel a všetky ich inicializujeme v LoadImages.

Zdrojový kód:
    /* Obrazky */
    ...
    img_scorepanel: null, //Score panel
    img_digits: [], //Digitalne cislice
    /* /Obrazky */
    …
LoadImages: function ()
    {
     ...
        this.img_scorepanel = this.CreateImage("scorepanel");
        //Nacitanie obrazkov cisel
        for (var i = 0; i <= 9; i++)
            this.img_digits[i] = this.CreateImage("d" + i.toString());
    }

Pokračujeme doplnením potrebných nastavení do objektu Game.

Zdrojový kód:
    /* Nastavenia */
    ...
    //Pozicia score panelu
    ScorePanelTop: 280,
    SocrePanelLeft: 5,

    DigitWidth: 12, //Sirka cislice        
    DigitHeight: 22, //Dlzka cislice
    DigitPadding: 2, //Padding medzi cislicami

    //Pozicia na umiestnenie ukazovatela score
    ScoreTop: 5,
    ScoreLeft: 375,

    //Pozicia na umiestnenie ukazovatela levelu
    LevelTop: 43,
    LevelLeft: 375,

    //Pozicia na umiestnenie ukazovatela nasledujucej kocky
    PreviewTop: 80,
    PreviewLeft: 375,
    ...
    /* /Nastavenia */

Na koniec funkcie DrawGameField pridáme kód nakreslenie obrázku img_scorepanel.

Zdrojový kód:
DrawGameField: function ()
    {
        ...
        //Nakreslenie score panelu
        this.Context.drawImage(this.img_scorepanel, this.ScorePanelTop, this.SocrePanelLeft);
    },

Opäť nastal čas na otestovanie funkčnosti doterajšieho kódu. Ak po uložení zobrazení v prehliadači je zobrazený panel z nápismi SCORE, LEVEL a NEXT (zatiaľ bez údajov) a hra beží v poriadku, môžeme pokračovať.

Na vykreslenie konkrétneho číselného údaju vytvoríme funkciu DrawDigits, ktorá rozoberie konkrétnu hodnotu na jednotlivé číslice a postupne ich pomocou obrázkov vykreslí na určenú pozíciu. Túto funkciu je potrebné pre každý údaj volať osobitne, preto jej volania vložíme do funkcie UpdateScore, ktorú budeme volať vždy, keď je potrebné aktualizovať tieto údaje.

Zdrojový kód:
UpdateScore: function ()
    {
        this.DrawDigits(this.ScoreLeft, this.ScoreTop, this.Score);
        this.DrawDigits(this.LevelLeft, this.LevelTop, this.Level);
    },
DrawDigits: function (Left, Top, Value)
    {
        //Textove vyjadrenie cisla
        var sValue = Value.toString();

        //Najskor vymaz stareho obsahu
        this.Context.fillStyle = this.BackColor;
        this.Context.fillRect(Left, Top, this.ContextWidth - Left, this.DigitHeight);

        //Aktualna pozicia na kreslenie
        var LeftPos = Left;

        //Pocet znakov v retazci
        var NumCount = sValue.length;

        var Img = null;
        for (var i = 0; i < NumCount; i++)
            {
                var char = sValue.charAt(i);
                if (!isNaN(char))
                    {
                        var ichar = parseInt(char);
                        if (ichar >= 0 && ichar <= 9)
                        {
                            Img = this.img_digits[ichar];
                            this.Context.drawImage(Img, LeftPos, Top);
                        }
                    }

                LeftPos += this.DigitWidth + this.DigitPadding;
            }
    },

Volanie UpdateScore pridáme na koniec funkcií DrawGameField a WriteLinesToScore.

Zdrojový kód:
DrawGameField: function ()
    {
     ...
        //Aktualizacia score hodnoty
        this.UpdateScore();
    },
WriteLinesToScore: function (Lines)
    {
        ...
        this.UpdateScore();
    },

Týmto sme ukončili implementáciu zobrazovania skóre a levelu, ostáva nám ešte ukazovateľ nasledujúcej kocky. To bude súčasťou posledného dielu tohto seriálu, v ktorom si hru skompletizujeme a pridáme aj zvukový efekt pri výmaze vyplneného riadka.

Codeblog
Ostatné texty v seriály

Tetris v HTML5 – I. Popis riešenia a základná konštrukcia 0
...
Tetris v HTML5 – III. Herná logika 2/4 0
Tetris v HTML5 – IV. Herná logika 3/4 0
Tetris v HTML5 – V. Herná logika 4/4 2
Diskusia

Žiadne príspevky v diskusii.

Nový príspevok

Na prispievanie do diskusie musíte byť prihlásený.