V tomto díle seriálu o programování her si napíšeme jednoduchou hru Bubble Breaker. Princip je jednoduchý - máte hrací pole 11 x 11 kuliček různých barev (celkem je jich 5 - červená, žlutá, zelená, modrá a fialová) a vaším úkolem je odstraňovat skupiny kuliček stejné barvy. Pokud tedy kliknete na kuličku, která sousedí s jednou nebo více kuličkami stejné barvy, celá tato oblast kuliček stejné barvy (sousedství v rozích se nepočítá) se označí a ukáže se počet bodů, kolik získáte, když oblast odbouchnete. Čím více je kuliček pohromadě, tím více je bodů, přičemž je to zařízeno tak, abyste za dvě trojice získali méně bodů než za jednu šestici. Pokud vám daný počet bodů stačí, kliknete do skupiny znovu a kuličky explodují. Vlivem gravitace se kuličky nad nově vzniklými volnými místy propadnou dolů a ihned po propadnutí se všechny kuličky natlačí na pravou stranu, jak to jen nejvíc jde. Pokud takto vznikne nalevo prázdný sloupec, vygeneruje se na do tohoto sloupce náhodné vysoký nový sloupec a opět se "natlačí" doprava. Hra končí, pokud již nejsou v hracím poli žádné dvě sousední kuličky stejné barvy. Vaším úkolem je samozřejmě udělat co nejvíce co největších skupin, abyste měli co nejvíc bodů. Pro představu jsou zde ilustrační obrázky:
Úvodní nastavení formuláře
Vytvořte si ve Visual Basic .NET nový projekt (Windows Application) a formuláři nastavte vlastnost DoubleBuffered na hodnotu True. Tím zamezíme problikávání při vykreslování. Dále nastavte barvu pozadí formuláře na černou nejčernější, takže Color.Black. Aby formulář nešel roztahovat, nastavte mu FormBorderStyle na hodnotu FixedSingle a maximalizaci zakažte nastavením MaximizeButton na hodnotu False. Nakonec na formulář poklepejte a otevře se nám procedura Form1_Load, která se spustí při vytváření formuláře. Ještě před jeho zobrazením musíme vygenerovat nové pole a nastavit velikost klientské oblasti formuláře (tam, kam budeme kreslit) na hodnotu 221x221 pixelů (velikost kuličky budeš 20x20 pixelů, máme pole 11x11 kuliček a 1 pixel je tam kvůli rámečku). Do této procedury tedy vložte tento kód:
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Me.SetClientSizeCore(221, 221)
Generate()
End Sub
Pole a jeho generování
Informace o hracím poli si uložíme do dvou oddělených polí. Jednak budeme mít pole p, které bude nést informaci o barvě kuličky, a pole s, které bude nést informaci o tom, jestli je kulička označená, nebo není. Obě pole budou mít velikost 11x11 buněk. Datový typ druhého pole s je jasný - Boolean. Co ale s prvním polem? Vytvoříme si pro něj tzv. Enum, což je seznam pojmenovaných hodnot. Někomu to možná přijde zbytečné, mohli bychom použít normální čísla (třeba 0 = žádná kulička, 1 = červená, 2 = žlutá atd.), ale za půl roku si už asi sotva budeme pamatovat, které číslo měla fialová. Pokud použijeme Enum, vnitřně to bude fungovat stejně, akorát v kódu místo jedničky napíšeme název barvy. Je to tedy hlavně pro nás přehlednější.
Enum Barva
None = 0
Red = 1
Green = 2
Yellow = 3
Blue = 4
Purple = 5
End Enum
Dim p(10, 10) As Barva
Dim s(10, 10) As Boolean
Dim r As New Random()
Dim selected As Integer = 0
Dim score As Integer = 0
Vidíme, že fyzicky je pole p opravdu pole čísel, ale místo p(1,2) = 1 napíšeme p(1,2) = Barva.Red. Je to delší, ale Visual Basic .NET nám zobrazí nabídku všech možností, takže stejně většinou stačí stisknout jednu klávesu a zbytek se doplní sám. Všimněte si také, že pole deklaruji (10,10), i když je jejich velikost 11x11. Buňky se v .NET vždy číslují od nuly.
Generování proběhne velmi jednoduše. Nadeklarovali jsme si proměnnou r jako nový objekt Random, což je generátor náhodných čísel. Má metodu Next, které předáme celé číslo N, a tato metoda vrací náhodné číslo od nuly včetně do N - 1 včetně. My musíme generovat čísla od 1 do 5, to znamená, že jako argument předáme 5 (funkce nám tedy vrátí hodnoty od 0 do 4) a přičteme k výsledku jedničku, čímž pádem se trefíme do potřebného intervalu. Takto přiřadíme nějakou barvukaždému políčku ve hře. Úplně nakonec ještě vynulujeme skóre a zobrazíme jej do titulkového pruhu okna.
Public Sub Generate()
For i As Integer = 0 To 10
For j As Integer = 0 To 10
p(i, j) = r.Next(5) + 1
Next
Next
score = 0
Me.Text = score & " bodů - Bubble Breaker"
End Sub
Vykreslování kuliček
Je na čase zobrazit vygenerovaný výsledek. Pozadí okna je černé, stačí na něj jen vykreslit kuličky. V okně kódu tedy nahoře do prvního rozbalovacího seznamu nastavte Form1 Events a do druhého Paint. Vytvoří se nám procedura události Paint na formuláři, která zaručí, že se v případě potřeby obsah vykreslí. Dovnitř dáme dvojitý cyklus, který projde všechny buňky v poli a vykreslí potřebné kuličky.
Pokud bychom ale kuličky vybarvili čistě danou barvou, nevypadalo by to moc hezky. Grafika .NETu nám ovšem umožňuje udělat přechod dvou barev a tím vybarvit nějaký útvar. Jak na to? Vytvoříme objekt Drawing2D.LinearGradientBrush a předáme mu jako argumenty dva body a dvě barvy. V prvním bodě bude první barva, v druhém druhá, a ostatní body se dopočítají podle poměru vzdáleností od obou bodů. Aby kulička dobře vypadala, provedeme to takto:
Čára spojuje dva krajní body přechodu. Vidíme, že první bod je trochu dál od středu než druhý. Přechod totiž nechceme rovnoměrně, kulička musí být více modrá než bílá. Na první bod přiřadíme barvu bílou (Color.Write) a na druhý právě modrou (Color.Blue). Kulička tedy bude vypadat zajímavěji, samotné vykreslení provedeme funkcí FillEllipse, které předáme objekt LinearGradientBrush, čili štětec kreslící přechod barev, a pak souřadnice horního levého rohu opsaného obdélníku a jeho výšku a šířku. Pokud na daném místě kulička není, vybarvíme toto místo černým čtvercem a hned pokračujeme dalším políčkem. Pokud přebarvujeme kuličku jinou barvou, bát se nemusíme, kuličky jsou vždy stejně velké.
První bod štětce budou souřadnice levého horního políčka, od kterých odečteme 5, abychom měli bod posunutý trochu nahoru a doleva. Druhý bod nastavíme na souřadnice pravého dolního rohu pole. První barva bude bílá, druhá bude v proměnné c, kterou jsme určili v rozhodovací struktuře Select Case podle hodnoty v poli p. Pokud je pole prázné, hned v rozhodovací struktuře se vybarví černě a Continue For přeskočí zbytek kódu uvnitř cyklu a pokračuje dalším průchodem.
Zde je tedy procedura vykreslení obsahu okna:
Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
With e.Graphics
For i As Integer = 0 To 10
For j As Integer = 0 To 10
Dim c As Color
Select Case p(i, j)
Case Barva.Red : c = Color.Red
Case Barva.Yellow : c = Color.Yellow
Case Barva.Green : c = Color.Green
Case Barva.Blue : c = Color.Blue
Case Barva.Purple : c = Color.Purple
Case Else : .FillRectangle(Brushes.Black, i * 20, j * 20, 20, 20) : Continue For
End Select
.FillEllipse(New Drawing2D.LinearGradientBrush(New Point(i * 20 - 5, j * 20 - 5), New Point(i * 20 + 20, j * 20 + 20), Color.White, c), i * 20 + 1, j * 20 + 1, 18, 18)
Next
Next
End With
End Sub
Označení oblasti stejnobarevných kuliček
Pokud chceme označit oblast kuliček stejné barvy, máme několik možností. My se naučíme používat tzv. rekurzi. Je to taková speciální programátorská metoda, která spočívá v zavolání procedury, ve které se aktuálně nacházíme, znovu. Celé to vypadá složitě, chce to ale jen zapnout mozek a trochu zapřemýšlet.
Představte si, že máme proceduru OznacSousedy, které předáme souřadnice políčka. Tato procedura se podívá na okolní políčka a pokud označená nejsou, označí je. Co když ale jedno ze sousedních políček má nějakého souseda, kterého je také třeba označit? A co když je jich více? Můžeme tedy proceduru OznacSousedy pustit i pro každého souseda, kterého najdeme. Protože tato procedura dělá přesně to, co chceme.
Jak na to? Stačí proceduru mírně upravit. Všude, kde se nastavuje, že je políčko vybrané, přidáme i volání procedury pro označení sousedů na toto nově nalezené políčko. Vůbec nevadí, že to je ta samá procedura, kde teď jsme. Stav programu a všechny proměnné se uloží a procedura se spustí znovu od začátku, provede se a v okamžiku, kdy skončí, se obnoví uložený stav a pokračuje se tam, kde se před tím přestalo.
Public Sub HighLightNeighbours(ByVal x As Integer, ByVal y As Integer)
s(x, y) = True
selected += 1
If x > 0 AndAlso p(x, y) = p(x - 1, y) AndAlso Not s(x - 1, y) Then HighLightNeighbours(x - 1, y)
If x < 10 AndAlso p(x, y) = p(x + 1, y) AndAlso Not s(x + 1, y) Then HighLightNeighbours(x + 1, y)
If y > 0 AndAlso p(x, y) = p(x, y - 1) AndAlso Not s(x, y - 1) Then HighLightNeighbours(x, y - 1)
If y < 10 AndAlso p(x, y) = p(x, y + 1) AndAlso Not s(x, y + 1) Then HighLightNeighbours(x, y + 1)
End Sub
Na následujícím obrázku je rekurze popsána. Všechny tři obdélníky znázorňují tu stejnou proceduru, v kódu je však zapsána pouze jednou. Kód ve všech obdélnících je úplně stejný. Program začíná vstupem do modré procedury. Podmínka se splní, protože vybrané políčko má nějakého souseda stejné barvy, narazíme tedy na rekurzívní volání. Spustíme tedy tu samou proceduru znovu, akorát pro jiné parametry, totiž pro souřadnice nově nalezeného políčka. Opět v ní platí podmínka, protože nové políčko má ještě jednoho neoznačeného souseda. Spustíme tedy žlutou proceduru, opět je to ta samá procedura, akorát má jiné parametry. Její políčko ovšem žádného neoznačeného souseda stejné barvy nemá, proto se podmínka nesplní. Procedura dojde až na konec, k řádku End Sub. Skončí a předá řízení zpět červené proceduře. Ta pokračuje tam, kde přestala, totiž provede další podmínky, popřípadě se opět zavolá, pokud najde jiného souseda, ale jednou zcela jistě skončí a pak předá řízení modré proceduře, která opět pokračuje dál. Je to vlastně stejné, jako když jedna procedura volá jinou, taky počká, než se ta nová provede a pak pokračuje dál. Tady akorát voláme tu samou proceduru.
Teď tedy k tomu, jak funguje kód: Proceduře HighLightNeighbours (vyznač sousedy) při zavolání předáme parametry x a y, což jsou souřadnice nového políčka. Procedura toto políčko označí, přidá jedničku do proměnné obsahující počet označených políček, kterou jsme nadeklarovali na začátku, a pak začne samotné hledání sousedů. Každá podmínka je složená ze tří částí - první část vždy kontroluje, jestli nebudeme sahat mimo pole. Pokud budeme v prvním sloupci, souseda zleva kontrolovat nemusíme (a vlastně ani nesmíme, protože bychom požadovali políčko s indexem -1, které v poli není, a nastala by chyba). To samé platí pro zbývající tři případy.
Další část podmínky kontroluje, jestli soused má stejnou barvu. Pokud nemá, není co řešit, pokračujeme až dalším řádkem. Pokud ano, pokračuje se kontrolou třetí části. Ta je velice důležitá - ověří, jestli políčko, které zkoušíme, již není označené. Pokud by bylo a my přesto rekurzivní proceduru volali, vraceli bychom se pořád na políčka, kde jsme již byli. To by byl pěkný průšvih, protože rekurze by nikdy neskončila a za chvíli by operačnímu systému došla trpělivost a vyhodil by chybu přetečení zásobníku (tam se totiž ukládají hodnoty proměnných a návratové adresy procedur, jenže zásobník není bezedný a za chvíli ho máme plný). Pokud ale políčko označené nebude, máme jistotu, že jsme na něm ještě nebyli. Můžeme jej tedy označit a případně označit i jeho sousedy, pokud je má, o to už se postará naše procedura.
Volání rekurzivní procedury musí být vždy v podmínce, která někdy zcela jistě přestane platit! V našem případě to tak je - určitě někdy natrefíme na políčko, které už nemá žádného neoznačeného souseda stejné barvy. Jinak nám rekurze nikdy neskončí a přeteče zásobník, nemůžeme ji volat donekonečna. Pokud jste rekurzi nepochopili, nevadí, zkuste nad tím trochu zapřemýšlet, pokud stále na nic nepřijdete, napište do fóra nebo to nechte plavat, určitě to časem pochopíte. Rekurze je již velmi pokročilá technika a je dosti ošemetná, snadno se v ní udělá chyba. A ještě k tomu se špatně vysvětluje.
Také jste si možná všimli, že jsem místo operátoru And použil AndAlso. To má také svůj význam. Pokud použijeme AndAlso a první podmínka se nesplní, druhá se už testovat nebude. To je v tomto případě nutné, protože pokud by se testovala, vyskočili bychom z pole a nastala by chyba. Pokud máme samotné And, vyhodnotí se obě části a výsledek se určí až na konec. Obdobně jsou na tom operátory Or a OrElse. Pokud použijeme OrElse a první podmínka platí, druhá se už také testovat nebude.
Znázornění vybrané oblasti
Když na kuličky klikneme, je třeba, aby se označená oblast obtáhla. To však v tomto případě není složité. Při procházení kuliček ve vykreslovací proceduře dáme podmínku a pokud je aktuálně vykreslovaná kulička označená, budeme vykreslovat ohraničení oblasti. Ovšem jen na těch stranách, na kterých sousední kulička nemá stejnou barvu. To je totiž zaručeně kraj této oblasti. Pokud je napravo od nás kulička stejné barvy, hranici na pravou stranu kreslit nemůžeme. Pokud je tam ale jiná barva, hranice tam zcela určitě bude. Opět nesmíme zapomenout nejdříve zkontrolovat případy, kdy jsme na kraji pole a sahali bychom na neexistující indexy. Pokud dané podmínky přidáme do procedury vykreslování, bude vypadat takto:
Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
With e.Graphics
For i As Integer = 0 To 10
For j As Integer = 0 To 10
Dim c As Color
Select Case p(i, j)
Case Barva.Red : c = Color.Red
Case Barva.Yellow : c = Color.Yellow
Case Barva.Green : c = Color.Green
Case Barva.Blue : c = Color.Blue
Case Barva.Purple : c = Color.Purple
Case Else : .FillRectangle(Brushes.Black, i * 20, j * 20, 20, 20) : Continue For
End Select
.FillEllipse(New Drawing2D.LinearGradientBrush(New Point(i * 20 - 5, j * 20 - 5), New Point(i * 20 + 20, j * 20 + 20), Color.White, c), i * 20 + 1, j * 20 + 1, 18, 18)
If s(i, j) Then
If i > 0 AndAlso p(i, j) <> p(i - 1, j) Then .DrawLine(Pens.White, i * 20, j * 20, i * 20, j * 20 + 20)
If i < 10 AndAlso p(i, j) <> p(i + 1, j) Then .DrawLine(Pens.White, i * 20 + 20, j * 20, i * 20 + 20, j * 20 + 20)
If j > 0 AndAlso p(i, j) <> p(i, j - 1) Then .DrawLine(Pens.White, i * 20, j * 20, i * 20 + 20, j * 20)
If j < 10 AndAlso p(i, j) <> p(i, j + 1) Then .DrawLine(Pens.White, i * 20, j * 20 + 20, i * 20 + 20, j * 20 + 20)
End If
Next
Next
End With
End Sub
Výbuch a posuny kuliček
Pokud na označenou skupinu klikneme ještě jednou, kuličky vybuchnou a místo nich se na chviličku zobrazí prázdné mezery. To je velmi jednoduché - zkrátka tam, kde je políčko označené, nastavíme jako barvu Barva.None a je to.
Public Sub Explode()
For x As Integer = 0 To 10
For y As Integer = 0 To 10
If s(x, y) Then p(x, y) = Barva.None
Next
Next
Me.Invalidate() : Application.DoEvents() : Threading.Thread.Sleep(50)
End Sub
Zajímavé je zde snad jen to, že po skončení cyklu zavoláme Me.Invalidate, což řekne systému, že je třeba co nejdříve znovu vykreslit celou plochu, že se na ní asi něco změnilo. Důležité je ale to, že se to neprovede hned, ale až když má systém čas. Proto mu jej přidělíme zavoláním Application.DoEvents, které pozastaví na chvíli naši aplikaci (v řádu zlomků sekundy, nic dlouhého) a dá systému prostor, aby si vyřešil to, co má. Provede se tedy mimo jiné překreslení formuláře. Poslední příkaz Threading.Thread.Sleep(50) počká ještě 50 milisekund, aby ve hře byl alespoň náznak nějaké jednoduché animace.
Po výbuchu nejprve kuličky vlivem gravitace spadnou dolů. To vyřešíme jednoduše. Použijeme opět dvojitý cyklus, akorát trochu složitější. Vnější cyklus projde sloupce a s každým sloupcem zvlášť provede gravitační spád. Jak tento spád provést? Jednoduše. Uděláme si tzv. kurzor - proměnnou, která si bude pamatovat pozici, kam se má posunout následující kulička. Nastavíme jej na 10, tedy na poslední řádek, nejspodnější místo. A spustíme cyklus, který projde sloupce zespoda nahoru (protože obracíme pořadí, musíme zapsat For i = 10 to 0 Step -1). Pokud narazíme v tomto sloupci na kuličku, přesuneme ji na místo kurzoru a tento kurzor posuneme o jednu pozici nahoru. Jakmile v cyklu dojdeme na konec řádku, vyčistíme všechny pozice od kurzoru nahoru, aby nám tam nezůstaly kuličky, zkrátka je nastavíme na Barva.None.
Pokud budete mít čáru a po ní rozházeno náhodně několik míčů, jeden člověk projde celou řadu a když narazí na míč, hodí jej druhému, který stojí na začátku. Ten míč položí na zem a udělá krok dopředu. A jak mu házíte další a další míče a on pořád postupuje dopředu, za chvíli máte míče v řadě hned vedle sebe. Když jsou míče seřazení, člověk, který chytal, doběhne na konec čáry za tím prvním. A přesně takhle funguje i naše procedura. Kontrolní otázka - který z nich je kurzor? První nebo druhý? Jistěže ten druhý.
Public Sub MoveDown()
For x As Integer = 0 To 10
Dim t As Integer = 10
For y As Integer = 10 To 0 Step -1
If p(x, y) > 0 Then p(x, t) = p(x, y) : t -= 1
Next
For y As Integer = t To 0 Step -1
p(x, y) = Barva.None
Next
Next
Me.Invalidate() : Application.DoEvents() : Threading.Thread.Sleep(50)
End Sub
Kuličky tedy po výbuchu spadnou. Nyní se musí ještě posunout doprava, co nejvíce to jde. To je naprosto identický případ, akorát prohodíme souřadnice - spád probíhá v řádcích a ne ve sloupcích. Pokud se tím uvolní celý poslední sloupec (stačí když je volné políčko v dolním levém rohu, nad ním nic být nemůže, gravitací to muselo spadnout), vygenerujeme náhodně vysoký sloupec hned na levý kraj a zavoláme posunutí doprava znovu, aby se kuličky zařadily. To je vlastně také rekurze, skončí ve chvíli, kdy poslední sloupec volný nebude, což se jednou určtiě stane.
Public Sub MoveRight()
For y As Integer = 0 To 10
Dim t As Integer = 10
For x As Integer = 10 To 0 Step -1
If p(x, y) > 0 Then p(t, y) = p(x, y) : t -= 1
Next
For x As Integer = t To 0 Step -1
p(x, y) = 0
Next
Next
Me.Invalidate() : Application.DoEvents() : Threading.Thread.Sleep(50)
If p(0, 10) = 0 Then
For y As Integer = 10 To r.Next(10) Step -1
p(0, y) = r.Next(5) + 1
Next
Me.Invalidate() : Application.DoEvents() : Threading.Thread.Sleep(50)
MoveRight()
End If
End Sub
Samozřejmě opět si necháváme pauzu, aby to vypadalo animovaně, výsledný efekt je však dostatečný. Intervaly nejsou příliš dlouhé, takže to hráče nezdržuje.
Ostatní obslužné funkce
Ještě než napíšeme samotné ovládání kliknutím, musíme vytvořit ještě několik funkcí, kterými si ulehčíme práci. Určitě se bude hodit funkce ClearSelection, která všem políčkům nastaví, že nejsou onačené. Adekvátně také nastaví hodnotu proměnné selected, která obsahuje počet označených kuliček.
Public Sub ClearSelection()
For i As Integer = 0 To 10
For j As Integer = 0 To 10
s(i, j) = False
Next
Next
selected = 0
HideLabel()
End Sub
Vidíme, že tato procedura volá ještě jakousi proceduru HideLabel. Hned ji napíšeme. Abychom totiž mohli snadno u vybrané oblasti zobrazit, kolik bodů za ni bude, přidáme na formulář komponentu Label. Barvu pozadí jí nastavíme na bílou a barvu textu na černou. Vymažeme z ní veškerý text a nastavíme jí Visible na False. Procedura HideLabel tuto komponentu schová a musíme napsat ještě proceduru SetLabel, které předáme souřadnice políčka a počet bodů, a tato procedura umístí Label někam k danému políčku, zobrazí ho a vypíše do něj počet bodů. Při nastavování pozice komponenty Label ještě kontrolujeme, jestli již není za okrajem okna. Pokud je políčko v pravé polovině okna, posuneme ji o dvě políčka směrem dolva. Podobně je to i svisle. Obě procedury jsou zde:
Public Sub SetLabel(ByVal text As String, ByVal x As Integer, ByVal y As Integer)
Label1.Text = text
Label1.Left = x * 20 + 20 : If Label1.Left > 110 Then Label1.Left -= 40
Label1.Top = y * 20 + 20 : If Label1.Top > 110 Then Label1.Top -= 40
Label1.Visible = True
End Sub
Public Sub HideLabel()
Label1.Visible = False
End Sub
Dále se nám bude hodit procedura GameOver, která Label zobrazí, roztáhne na celou plochu formuláře, napíše do něj text Konec hry, počká 2 sekundy a nakonec vrátí Label do původního stavu.
Public Sub GameOver()
Label1.Text = "KONEC HRY"
Label1.Visible = True
Label1.AutoSize = False
Label1.Location = New Point(0, 0)
Label1.Size = Me.ClientSize
Application.DoEvents()
Threading.Thread.Sleep(2000)
Label1.Visible = False
Label1.AutoSize = True
Generate()
End Sub
Komponentě Label, kterou jste vytvořili na formuláři, ještě nastavte hodnotu vlastnosti TextAlign na hodnotu MiddleCenter, tedy úplně doprostřed (svisle i vodorovně). To proto, aby se text Konec hry zobrazil na středu formuláře.
A poslední funkce, která nám bude zjišťovat, jestli hra už neskončila, bude funkce HasPossibilities. Projde celé pole a pokud najde dvě stejné barvy vedle sebe nebo nad sebou (opět s kontrolou, abychom nevyskočili z pole), vrátí True. Pokud nic takového nenajde, vrátí False. V takovém případě hra končí, hráč již nemá žádnou skupinu, kterou by mohl odstřelit.
Public Function HasPossibilities() As Boolean
For x As Integer = 0 To 10
For y As Integer = 0 To 10
If p(x, y) > 0 Then
If x < 10 AndAlso p(x, y) = p(x + 1, y) Then Return True
If y < 10 AndAlso p(x, y) = p(x, y + 1) Then Return True
End If
Next
Next
Return False
End Function
Ovládání
Pokud jste hru zkoušeli spustit, až dosud se nic moc nedělo. Touto poslední procedurou ale vše napravím a hra bude kompletní. Nalistujte proceduru Form1_MouseDown, která se spustí, pokud uživatel klikne myší (v okamžiku, kdy tlačítko jede dolů). A vložte do ní tento kód:
Private Sub Form1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseDown
Dim x As Integer = e.X \ 20
Dim y As Integer = e.Y \ 20
If s(x, y) And selected > 1 Then
score += CInt(Label1.Text)
Me.Text = score & " bodů - Bubble Breaker"
Explode()
ClearSelection()
MoveDown()
MoveRight()
If Not HasPossibilities() Then GameOver()
Else
ClearSelection()
If p(x, y) > 0 Then HighLightNeighbours(x, y)
Me.Invalidate()
If selected > 1 Then SetLabel(selected * (selected - 1), x, y)
End If
End Sub
Nejprve podle souřadnice myši zjistíme souřadnice políčka (vydělením jeho velikostí a zaokrouhlením dolů). Pokud jsme kliknuli na políčko, které je vybráno, a počet vybraných kuliček je větší než 1 (je vybrána skupina a ne samostatné políčko), přičteme spočítané skóre z komponenty Label k celkovému skóre a vypíšeme jej do titulku okna. Dále provedeme explozi, odznačíme všechny kuličky, provedeme gravitační spád a posun doprava s případným doplněním sloupců, to jsme již vše napsali a víme, jak to funguje. Pokud již nezbývají žádné možnosti, ukončíme hru. Pokud jsme kliknuli na neoznačené políčko, opět odznačíme všechny kuličky a zavoláme rekurzivní proceduru pro označení celé stejnobarevné oblasti. Pokud má oblast více než 1 kuličku, zobrazíme Label s počtem bodů za tuto oblast. Počet bodů spočítáme podle vzorce skóre = n . (n - 1), kde n je počet vybraných kuliček, tedy selected. Tento vzorec je navržen tak, aby bylo výhodnější udělat jednu větší oblast než dvě menší.
A to je celá hra. Pokud jste jakoukoliv pasáž nepochopili, ptejte se ve fóru, rád zodpovím případné dotazy.