Gra w węża :P

Dzisiaj zrobimy sobie bardzo dobrze znaną grę „Snake” znaną głównie ze starych telefonów. Grę taką można udoskonalić o elementy wizualne, jak i elementy rozgrywki, możemy konkurować o tokeny, grać na czas, kreować poziomy z przeszkodami, tylko od nas zależy, co w takiej grze umieścimy. To, co ja zrobiłem, prezentuje w grze do pobrania poniżej, można kreować poziomy, konkurować ze sobą na jednym komputerze z kolegą lub przy użyciu sieci komputerowej:

 

 

Snake Neon 1.0

 

 

 

Zaczniemy od stworzenia formy. Nie będziemy dodawać żadnych obiektów, ponieważ wszystko, co będzie potrzebne, będziemy generować na bieżąco.

 

 

 

Rodzaj elementu Nazwa elementu Ustawienia
Form Form1 Name: Form1
Text: Wąż VisualMonsters.cba.pl
Size: 416; 439
BackgroundImageLayout: Center
DubleBuffered: True
 Timer1  Timer1  Interval: 110

 

 

 

 

Opcja BackgroundImageLayout jest opcjonalna, ponieważ forma wizualna gry, będzie wyświetlana jako obrazek tła formy, sposób jej ułożenia jest dosyć istotny. Jeśli wybierzemy opcje, Tile (sąsiadujący) nasza forma wizualna będzie powielona i gracz może nie wiedzieć, gdzie kończy się obszar gry:

No to zaczynamy:

Public Class Form1

    'Realizowe kierunki podczas działania Timera
    Private Enum Kierunek
        wPrawo
        wDol
        wLewo
        wGore
        Pauza
    End Enum

    'Struktura węża
    Private Structure StrukturaWeza
        Dim rect As Rectangle
        Dim x As Integer
        Dim y As Integer
        Dim KolorWeza As Color
    End Structure

    'Określa wielkość gry, przyjmujemy kwadrat
    Dim wielkoscGry As Integer = 20
    'Tablica przechowująca pola gry
    Private pola(,) As Rectangle
    'Tablica przechowująca zajęte pola, gra skończy się jeśli wąż ugryzie samego siebie
    'ta tablica czuwa nad tym
    Private zajetepola(,) As Boolean
    'zmienna przechowuje aktualny kierunek
    Private aktKierunek As Kierunek
    Dim punkty As Integer = 0

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        'inicjuje elementy gry, dzięki temu będzie można zagrać ponownie gdy gra się skończy
        initialize()
    End Sub

    Private Sub initialize()
        'kierunek startowy
        aktKierunek = Kierunek.Pauza
        punkty = 0 'startowe punkty
        'metoda określa 
        'GenerujPlanszeGry()
        'GenerujWeza()
        'GenerujToken()
        'Timer1.Start()
    End Sub
End Class

Na tym etapie, za dużo wyjaśniać nie trzeba. Będziemy przerabiać sobie poszczególne etapy w sekcji Initialize(). Najpierw wygenerujemy plansze, odblokujmy element:

    Private Sub initialize()
        'kierunek startowy
        aktKierunke = Kierunek.Pauza
        punkty = 0 'startowe punkty
        'metoda określa 
        GenerujPlanszeGry()
        'GenerujWeza()
        'GenerujToken()
        'Timer1.Start()
    End Sub

Generowanie planszy jest bardzo zbliżone do tego, które robiliśmy podczas tworzenia gry w statki.

    'główna plansza gry na której będzie wyświetlana grafika
    Private PlanszaGlowna As New Bitmap(400, 400)
    Dim kolorPola As New SolidBrush(Color.FromArgb(50, 255, 0, 0))

    Private Sub GenerujPlanszeGry()

        Dim g As Graphics = Graphics.FromImage(PlanszaGlowna)
        'określamy wysokość i szerokość planszy
        Dim szerokosc As Integer = (400 / wielkoscGry) - 1
        Dim wysokosc As Integer = (400 / wielkoscGry) - 1
        ''powtarzamy inicjacje tablic aby zmienić ich wielkość
        ReDim pola(wielkoscGry, wielkoscGry)
        ReDim zajetepola(wielkoscGry, wielkoscGry)
        'pętla ustawia wstępne wartości obiektów
        For j As Integer = 0 To wielkoscGry - 1
            For i As Integer = 0 To wielkoscGry - 1
                pola(i, j) = New Rectangle((szerokosc + 1) * j, (wysokosc + 1) * i, szerokosc, wysokosc)
                zajetepola(i, j) = False
                g.FillRectangle(kolorPola, pola(i, j)) 'dodaje grafikę do bitmapy
            Next
        Next
        'zmienia tło formy
        Me.BackgroundImage = PlanszaGlowna
        g.Dispose()
        Me.Refresh()
    End Sub

Efektem będzie wyświetlenie siatki kwadratów:

Mamy już scenę, na której będzie poruszał się nasz wąż. Odblokujemy teraz element generujący naszego węża, nie będzie on chwilowo widoczny. Ponieważ ożywi go nam dopiero Timer:

    Private Sub initialize()
        'kierunek startowy
        aktKierunke = Kierunek.Pauza
        punkty = 0 'startowe punkty
        'metoda określa 
        GenerujPlanszeGry()
        GenerujWeza()
        'GenerujToken()
        'Timer1.Start()
    End Sub

Po odblokowaniu dodajemy metodę generującą węża:

    Private snake As Collection 'Kolekcja przechowujaca elementy naszego węża
    Private DlugoscStartowa As Integer = 3 'Startowa dlugość węża
    Private BiezacaDlugoscWeza As Integer 'Aktualna dlugość węża

    Private Sub GenerujWeza()
        'generujemy strukturę i tworzymy nową kolekcję
        Dim sWaz As StrukturaWeza
        snake = New Collection
        'określamy punkt startowy w którym pojawi się nasz wąż
        Dim x As Integer = 5
        Dim y As Integer = 5
        'kolekcje liczone są nie od 0 a od jeden
        For i As Integer = 0 To DlugoscStartowa - 1
            'okreslamy parametry startowe dla kazdego elementu struktury
            sWaz.rect = pola(x, y)
            sWaz.x = x
            sWaz.y = y
            sWaz.KolorWeza = Color.Red
            'dodajemy strukturę do kolekcji
            snake.Add(sWaz)
        Next
        'zajmujemy pole startowe na którym znajduje sie waz
        zajetepola(x, y) = True
        BiezacaDlugoscWeza = DlugoscStartowa
    End Sub

Powyższa metoda wygeneruje trzy struktury, o tej samej lokalizacji i kolorze. Czas ożywić naszego węża, odblokowujemy timer:

    Private Sub initialize()
        'kierunek startowy
        aktKierunke = Kierunek.Pauza
        punkty = 0 'startowe punkty
        'metoda określa 
        GenerujPlanszeGry()
        GenerujWeza()
        'GenerujToken()
        Timer1.Start()
    End Sub

Teraz przechodzimy to zdarzenia TimerTick:

Mamy zdarzenie i możemy dodać ruch węża. Będzie to metoda PoruszWeza(), która będzie poruszała naszymi obiektami, zgodnie z aktualnie wybranym kierunkiem (zmienna aktKierunek):

    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        PoruszWeza()
        Application.DoEvents()
    End Sub

    Private poswiata As Brush = New SolidBrush(Color.FromArgb(255, 255, 255)) 'KOLOR ZNIKAJĄCEGO KWADRACIKA POZOSTAWIAJACY OGON
    Dim jestemWruchu As Boolean = False
    Dim token As New Rectangle

    Private Sub PoruszWeza()
        'tworzymy czystą planszę o równych wymiarach jak plansza gry
        Dim podgladgry As New Bitmap(400, 400)
        Dim g As Graphics = Graphics.FromImage(podgladgry)
        'ZATRZYMUJE TIMER
        Timer1.Enabled = False
        'deklarujemy strukturę 
        Dim sWaz As StrukturaWeza

        'usówamy ostatnią strukturę z kolekcji, 
        sWaz = snake(snake.Count) 'wybieramy ostatni element
        g.FillRectangle(poswiata, sWaz.rect) 'element opcjonalny zostawia poświatę ogona
        snake.Remove(snake.Count) 'usówamy go
        zajetepola(sWaz.x, sWaz.y) = False 'zwalniamy zajęte pole

        'teraz wybieramy pierszy element węża
        sWaz = snake.Item(1)
        'deklarujemy zmienne w których umieścimy lokalizację pierwszego elementu węża
        Dim x As Integer = sWaz.x
        Dim y As Integer = sWaz.y

        'sprawdzamy czy po dodaniu 1 zgodnie z ustalonym kierunkiem nasz wąż nie uderzył w krawędź planszy
        Select Case aktKierunek
            Case Kierunek.wPrawo
                x = x + 1
                If x > wielkoscGry - 1 Then
                    'gracz uderzył w ścianę po prawej stronie
                    Timer1.Enabled = False 'zatrzymuje timer
                    If MessageBox.Show("Uderzyłeś w krawędź planszy, czy chcesz zacząć grę od nowa?",
                                       "Gra skończona!", MessageBoxButtons.YesNo, MessageBoxIcon.Question,
                                       MessageBoxDefaultButton.Button1) = Windows.Forms.DialogResult.Yes Then
                        'Jeśli gracz przycisną "Tak" gra zacznie się od nowa
                        initialize()
                        Exit Sub
                    Else
                        Exit Sub
                    End If
                End If
            Case Kierunek.wGore
                y = y - 1
                If y < 0 Then
                    Timer1.Enabled = False
                    If MessageBox.Show("Uderzyłeś w krawędź planszy, czy chcesz zacząć grę od nowa?",
                                       "Gra skończona!", MessageBoxButtons.YesNo, MessageBoxIcon.Question,
                                       MessageBoxDefaultButton.Button1) = Windows.Forms.DialogResult.Yes Then
                        initialize()
                        Exit Sub
                    Else
                        Exit Sub
                    End If
                End If
            Case Kierunek.wDol
                y = y + 1
                If y > wielkoscGry - 1 Then
                    Timer1.Enabled = False
                    If MessageBox.Show("Uderzyłeś w krawędź planszy, czy chcesz zacząć grę od nowa?",
                                       "Gra skończona!", MessageBoxButtons.YesNo, MessageBoxIcon.Question,
                                       MessageBoxDefaultButton.Button1) = Windows.Forms.DialogResult.Yes Then
                        initialize()
                        Exit Sub
                    Else
                        Exit Sub
                    End If
                End If
            Case Kierunek.wLewo
                x = x - 1
                If x < 0 Then
                    Timer1.Enabled = False
                    If MessageBox.Show("Uderzyłeś w krawędź planszy, czy chcesz zacząć grę od nowa?",
                                       "Gra skończona!", MessageBoxButtons.YesNo, MessageBoxIcon.Question,
                                       MessageBoxDefaultButton.Button1) = Windows.Forms.DialogResult.Yes Then
                        initialize()
                        Exit Sub
                    Else
                        Exit Sub
                    End If
                End If
        End Select

        'ten element odpowiedziany jest za przesunięcie pierwszego elementu węża
        'jeśli wciśnięta jest Pauza(Spacja) wąż nie zostanie przesunięty
        If Not aktKierunek = Kierunek.Pauza Then
            If zajetepola(x, y) = True Then
                'wąż zderzył się z samym sobą
                Timer1.Enabled = False
                If MessageBox.Show("Zjadłeś własny ogon, czy chcesz zacząć grę od nowa?",
                                   "Gra skończona!", MessageBoxButtons.YesNo, MessageBoxIcon.Question,
                                    MessageBoxDefaultButton.Button1) = Windows.Forms.DialogResult.Yes Then
                    initialize()
                    Exit Sub
                Else
                    Exit Sub
                End If
            End If
            'na podstawie kierunku, zmienna x lub y została zmieniona 
            'ta struktura (sWaz) nie jest jeszcze dodana do kolekcji, dlatego jej zmiennymi
            'możemy jeszcze manipulować
            sWaz.x = x
            sWaz.y = y
            sWaz.rect = pola(x, y)
            zajetepola(x, y) = True 'zajmuje nowe pole
        End If

        'nowa struktura koloru wężą, nie dodana jeszcze głowa
        'można ten element pominąć wstawiając w tym miejscu snake.Add(sWaz, , 1)
        'lub można zrobić inny kolor dla głowy węża
        g.FillRectangle(New SolidBrush(sWaz.KolorWeza), sWaz.rect)
        'koloruje leszcze elementów kolekcji
        For t As Integer = 1 To snake.Count
            g.FillRectangle(New SolidBrush(snake(t).KolorWeza), snake(t).rect)
        Next
        'dodaje strukture do kolekcji na 1-wszej pozycji
        snake.Add(sWaz, , 1)

        'jeśli wąż łyknie token
        If pola(x, y).Location = token.Location Then
            punkty += 1 'uaktualnia punktację
            snake.Add(sWaz, , , snake.Count) ' dodaje kawałek ogona na koniec
            BiezacaDlugoscWeza = snake.Count
            Timer1.Interval -= 2 'zwiększa szybkość gry
            If Timer1.Interval < 0 Then Timer1.Interval = 1
            GenerujToken() 'generuje nowy token
        End If

        'dodaje niezajęte elementy i token
        For j As Integer = 0 To wielkoscGry - 1
            For i As Integer = 0 To wielkoscGry - 1
                If zajetepola(i, j) = False Then
                    g.FillRectangle(kolorPola, pola(i, j))
                End If
                If token = pola(i, j) Then
                    g.FillRectangle(New SolidBrush(Color.Yellow), pola(i, j))
                End If

            Next
        Next
        ' ustawia nowy obrazek tła
        Me.BackgroundImage = podgladgry
        'informuje użytkownika o aktualnym poziomie trudności (szybkości) gry i punktacji
        Me.Text = "Zebrane punkty: " + punkty.ToString + " ,szybkość gry: " + (100 + (((100 - Timer1.Interval) / 100) * 100)).ToString
        g.Dispose()
        Refresh()
        'wznawia działanie timera
        Timer1.Enabled = True
        'odblokowuje możliwość zmiany kierunku
        jestemWruchu = False
    End Sub

Bez metody GenerujToken() nasza plansza będzie czysta tylko z jednym kwadratem czerwonym symbolizującym naszego węża:

Zostało nam dodanie aktualizacji kierunków i generowanie tokena. Oba te elementy są w miarę proste:

    Private Sub Form1_KeyDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles MyBase.KeyDown
        'Zapobiega auto zderzeniu, gdyby nie było tego ograniczenia moglibyśmy zderzyć się wewnątrz węża 
        'zanim zmieni się tło formy
        If jestemWruchu = False Then
            Select Case e.KeyCode
                Case Keys.Down
                    'nie wykona operacji w przeciwnym kierunku która spowoduje wewnętrzne zderzenie elementów
                    If Not (aktKierunek = Kierunek.wDol Or aktKierunek = Kierunek.wGore) Then
                        aktKierunek = Kierunek.wDol
                        jestemWruchu = True
                    End If
                Case Keys.Left
                    If Not (aktKierunek = Kierunek.wLewo Or aktKierunek = Kierunek.wPrawo) Then
                        aktKierunek = Kierunek.wLewo
                        jestemWruchu = True
                    End If
                Case Keys.Right
                    If Not (aktKierunek = Kierunek.wPrawo Or aktKierunek = Kierunek.wLewo) Then
                        aktKierunek = Kierunek.wPrawo
                        jestemWruchu = True
                    End If
                Case Keys.Up
                    If Not (aktKierunek = Kierunek.wGore Or aktKierunek = Kierunek.wDol) Then
                        aktKierunek = Kierunek.wGore
                        jestemWruchu = True
                    End If
                Case Keys.Space
                    aktKierunek = Kierunek.Pauza
            End Select
        End If
        'Można usunąć "zajetepola(x, y) = True 'zajmuje nowe pole" w metodzie PoruszWeza() (linia 212) 
        'po usunięcia ograniczenia da to możliwość zawracania wężowi
    End Sub

    Private Sub GenerujToken()
        Dim rand As New Random
        Dim x As Integer
        Dim y As Integer
        Do
            x = rand.Next(0, wielkoscGry)
            y = rand.Next(0, wielkoscGry)
            If zajetepola(x, y) = False Then
                Exit Do
            End If
        Loop
        token = pola(x, y)
    End Sub

Gotowe, teraz gra działa, a my możemy rozkoszować się możliwością manipulacji. Można zwiększać i zmniejszać szybkość gry manipulując przy Intervale Timera1. Sprawdźcie sobie grę Snake Neon z początku tego posta. Takie rzeczy można zrobić, modyfikując i dodając elementy do tego algorytmu.

Do pobrania:

kod źródłowy: Waz_kodZrodlowy_Visualmonsters.cba.pl

pełen projekt: Snake_VisualMonsters.cba.pl

 

Permalink do tego artykułu: https://visualmonsters.cba.pl/gra-w-weza-p/

Dodaj komentarz

Twój adres email nie będzie publikowany.