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.
Ostatné texty v seriály
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ť.{
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;
},
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.{ /* /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");
...
}
}
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).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);
}
},
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.{
switch (e.keyCode)
{
case 13: //Enter - Restart game
{
if (Game.Status === GameStates.gs_Stop)
{
Game.NewGame();
} e.preventDefault();
}break;
…
}
}
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.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 */
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.{
this.LastUpdate = new Date(); //Reset hernych premennych
this.Level = 1;
this.Score = 0;
this.LinesCounter = 0; //Vycistit hracie pole
this.CreateGameArray();
...
}
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.{
var LevelInterval = Math.round(1000 / (this.Level + 1)); if (LevelInterval <= 0)
LevelInterval = 50;
...
}
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{
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);
}
},
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:{
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);
},
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.png až d9.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.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();
},
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....
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());
}
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....
//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 */
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.{
...
//Nakreslenie score panelu
this.Context.drawImage(this.img_scorepanel, this.ScorePanelTop, this.SocrePanelLeft);
},
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.{
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;
}
},
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.
{
...
//Aktualizacia score hodnoty
this.UpdateScore();
},
WriteLinesToScore: function (Lines)
{
...
this.UpdateScore();
},
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 |
Žiadne príspevky v diskusii.
Na prispievanie do diskusie musíte byť prihlásený.