Dzisiaj wejdziemy w buty Developera gier komputerowych który dołączył do projektu gdzie zostają tworzone stare gry retro. Na pierwszy ogień trafiła nam się gra z lat 80, Snake.

Gra ta polega na tym, że gracz steruje wężem, który porusza się po planszy i zbierając jedzenie, próbuje nie uderzyć własną głową o ściany otaczające plansze jak również o część swojego ciała. Kiedy wąż zje jedzenie, wydłuża się co przyczynia się do utrudnienia gry. Gracz kontroluje kierunek ruchu węża za pomocą klawiszy strzałek, wąż porusz się bez przerwy. Zatrzymuje go jedynie zakończenie gry.

Spis treści

Analiza przed pracą

Zanim zaczniemy pisać program przeanalizujmy co wiemy o tej grze i co z tego wynika

  • Wiemy, że gracz steruje postacią którą jest tytułowy snake, któremu po zjedzeniu posiłku wydłuża się ogon
  • Będziemy potrzebowali również obiektu jakim jest jabłko na które będzie polować nasza postać
  • Potrzebujemy również stworzenie planszy po której snake będzie się poruszał

Musimy więc stworzyć klasę która umożliwi nam stworzenie naszego węża i metody w niej zawartej muszą rozwiązać problem poruszania się, wydłużania ogona naszego węża i w wypadku uderzenia głową w siane zresetowania go

Potrzebujemy również klasę jabłka której metody będzie umożliwiać nam wyrysowania go w różnych położeniach na planszy

Będziemy musieli również przygotować funkcję do stworzenia naszej planszy

Jak również na końcu musimy zająć się zarządzeniem wszystkich naszych elementów w pętli głównej

Tworzenie klas i metod

Pierwsze zajmiemy się stworzeniem klasy naszej postaci grywalnej czyli Snaka

class Snake():
    def __init__(self):
        pass

    def pozycja_Glowy(self):
        pass

    def kierunek(self):
        pass

    def ruch(self):
        pass

    def reset(self):
        pass

    def rysowanie(self):
        pass

Klasa ta będzie posiadać metodę pozycja _Glowy() która będzie nam zwracać pozycje naszego węża, kierunek() będzie służył do określenia kierunku poruszania się węża. Metoda ruch() będzie odpowiedzialna za przemieszczanie się węza a rysowanie() za wyświetlenie go na ekranie. Ostatnia metoda reset() będzie odpowiadała, za zresetowanie węża kiedy uderzy głową o ścianę, albo o swoje ciało

Klasa Jabłko

class Jablko():
    def __init__(self):
        pass

    def pozycja_Losowa(self):
        pass

    def rysuj(self,):
        pass

Klasa ta jest dość prosta, posiada ona metodę rusuj() która będzie odpowiadać za wyświetlenie naszego celu, a metoda pozycja_Losowa() ma odpowiadać za to by jabłko pokazywało się w losowych miejscach na planszy

I na koniec zostały nam dwie funkcje

 

def rysowanieSiatki():
    pass

def main():
     pass

rysowanieSiatki() będzie wyrysowywała plansze po której będziemy się poruszać, a w main() znajdzie się definicja okna gry, pętla główna gry, sterowanie itp.

Wypełnianie klas

Klasy Snake

Pierwsze zdefiniujemy zmiennej naszej klasy

    def __init__(self):
        self.__dlugosc = 1
        self.__pozycja = [((szerokosc_Okna / 2), (wysokosc_Okna / 2))]
        self.__kierunek_ruchu = gora
        self.punkty = 0
        self.kolor = (0, 255, 0)

Nie chcemy by nasze zmienne były dostępne poza klasą ( z wyjątkiem koloru i punktów) dlatego zastosujemy tutaj hermetyzacje (enkapsulacje).

  • self.__dlugosc – definiuje ilość fragmentów ogona, jago długość
  • self.__pozycja – lista w której będą zapisywane pozycja
  • self.__kierunek_ruchu – kierunek poruszania się
  • self.punkty – ilość zdobytych punktów
  • self.kolor – kolor naszej postaci

Następnie wypełniamy trzy metody które będą wykorzystane w metodzie ruch()

    def pozycja_Glowy(self):
        return self.__pozycja[-1]

    def kierunek(self, kierunek_ruchu):
        # Jeśli wąż ma ogon nie może się cofnąć
        if self.__dlugosc > 1 and (kierunek_ruchu[0] * -1, kierunek_ruchu[1] * -1) == self.__kierunek_ruchu:
            return
        else:
            self.__kierunek_ruchu = kierunek_ruchu
     def reset(self):
            self.__dlugosc = 1
            self.__pozycja = [((szerokosc_Okna / 2), (wysokosc_Okna / 2))]
            self.__kierunek_ruchu = random.choice([gora, dol, lewo, prawo])
            self.__punkty = 0


Metoda pozycja_Głowy() będzie zwracała nam pozycje głowy naszego węża, a kierunek(kierunek_ruchu) zapewnia, że jeśli wąż będzie miał ogon nie będzie mógł zawrócić (warunek gry )

a gdzie reset() jest to metoda odpowiadająca za ustawienie węża w pozycji domyślnej

Teraz wypełnimy metodę odpowiedzialną za poruszanie się naszego węża, będzie ona korzystać z powyższych metod.

    
        def ruch(self):
        glowa = self.pozycja_Glowy()
        x,y = self.__kierunek_ruchu
        # Nowa pozycja Snake na bazie pozycji jego głowy
        nowa_Pozycja = (((glowa[0] + (x * rozmiar_Kratki)) % szerokosc_Okna), (glowa[1] + (y * rozmiar_Kratki)) % wysokosc_Okna)

        # Jeśli nowa pozycja jest w miejscu ogona, Snake jest restowany
        if len(self.__pozycja) > 2 and nowa_Pozycja in self.__pozycja[2:]:
            self.reset()
        # Jeśli nowa pozycja będzie się równać krawędzi scian wąż zostanie zresetowany
        elif  ((nowa_Pozycja[1]) == 0 and y == 1) or ((nowa_Pozycja[1]) == 460 and y == -1):
            self.reset()
        elif ((nowa_Pozycja[0]) == 0 and x == 1) or ((nowa_Pozycja[0]) == 460 and x == -1):
            self.reset()
        else:
            # Przypisanie nowej pozycji do listy __pozycja
            self.__pozycja.append(nowa_Pozycja)
            # Usuwanie starej pozycji lub ominięcie kroku w wyniku snek się wydłuża 
            if len(self.__pozycja) > self.__dlugosc:  del self.__pozycja[0]

Pierwszym zadaniem tej metody jest ustalenie nowego położenia naszego węża, które zostanie zapisane w zmiennej nowa_Pozycja

Następnie sprawdza czy wąż nie uderzył głową w którąś ze ścian lub w samego siebie

Jeśli nie to pozycja jest dodana do listy self.__pozycja

Ostatni warunek sprawdza czy długość pozycji jest większa od długości węża

Jeśli tak, oznacza to, że mamy za dużo danych w naszej tablicy i musimy usunąć pierwszy element, ponieważ ostatni to nowa pozycja głowy węza

Jeśli nie, oznacza to, że posiadamy współrzędne dla głowy naszego węża i członów jego ogona

Ostatnie metody w klasie Snake wyglądają następująco

  
    def rysowanie(self, okno_Gry):
        # Odczytywanie listy od końca ponieważ nowa pozycja głowy jest na końcu listy
        # Dalsze współżędne na liście wskazują położenie części ogona węża
        for p in self.__pozycja[::-1]:
            r = pygame.Rect((p[0], p[1]), (rozmiar_Kratki, rozmiar_Kratki))
            pygame.draw.rect(okno_Gry, self.kolor, r)
            pygame.draw.rect(okno_Gry, (128, 128, 128), r, 1)

    def zjedzenie(self):
        self.__dlugosc += 1
        self.__punkty += 1

   

zjedzenie() odpowiada za dodanie długości ogona i punktów kiedy wąż zje jabłko

rysowanie() odpowiadająca za przedstawienie graficzne naszej postaci

Klasa Jablko

Ta klasa w porównaniu do Snake jest prostsza, ale równie ważna dla gry

class Jablko():
    def __init__(self):
        self.pozycja = (0, 0)
        self.kolor = (255, 0, 0)
        self.pozycja_Losowa()

    def pozycja_Losowa(self):
        self.pozycja = (random.randint(0, szerokosc_Kratki - 1) * rozmiar_Kratki, random.randint(0, wysokosc_Kratki - 1) * rozmiar_Kratki)

    def rysuj(self, okno_Gry):
        r = pygame.Rect((self.pozycja[0], self.pozycja[1]), (rozmiar_Kratki, rozmiar_Kratki))
        pygame.draw.rect(okno_Gry, self.kolor, r)

Mamy tutaj tylko dwie metody pozycja_Losowa() oraz rysuj(okno_Gry)

pozycja_Losowa() odpowiada za przypisanie do zmiennej self.pozycja losową wartość w wyniku czego otrzymujemy jabłko w różnych miejscach na planszy

Metoda rysuj(okno_Gry) jest już nam znana i odpowiada za wyrysowanie obiektu klasy na planszy

Metody gry

Pierwsze zajmiemy się metodą która będzie rysować nam siatkę na naszej planszy

def rysowanieSiatki(surface):
    # Linia pozioma
    surface.fill((0, 0, 0))
    for i in range(0, int(szerokosc_Kratki)):
        pygame.draw.line(surface, (128, 128, 128), (0 , i * rozmiar_Kratki), (szerokosc_Okna, i * rozmiar_Kratki))
    # vertical lines
    for j in range(0, int(wysokosc_Kratki)):
        pygame.draw.line(surface, (128, 128, 128), (j * rozmiar_Kratki, 0), (j * rozmiar_Kratki, wysokosc_Okna))

Jest to dość prosta metoda której raczej nie trzeba tłumaczyć

W końcu się zajmiemy mechaniką działania naszej gry i zobaczymy co nam wyjdzie

Zdefiniujemy metode main() w której będzie znajdywać się metoda rysowania okna, pętla główna gry i sterowanie wężem

def main():
    pygame.init()
    # Inicjalizacja okna gry
    okno_Gry= pygame.display.set_mode((szerokosc_Okna, wysokosc_Okna), 0, 32)
    pygame.display.set_caption("SNAKE")
    # Wyrasowanie siatki
    rysowanieSiatki(okno_Gry)
    # Inicjalizacja obiektów klas
    snake = Snake()
    jablko = Jablko()
    czcionka = pygame.font.SysFont('comicsans', 30)

W pierwszej kolejności zainicjujemy wszystko co jest nam potrzebne, aby nasza gra mogła działać

teraz zabierzemy się za pętle główną

    run = True
    while (run):
        pygame.time.delay(100)
        # Wyłączenie gry
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False
            # Sterowanie do Snake'a
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_UP:
                    snake.kierunek(gora)
                elif event.key == pygame.K_DOWN:
                    snake.kierunek(dol)
                elif event.key == pygame.K_LEFT:
                    snake.kierunek(lewo)
                elif event.key == pygame.K_RIGHT:
                    snake.kierunek(prawo)

Pierwsze wprowadziliśmy warunki które mają sprawdzać w którym kierunku ma się poruszać wąż, jak również możliwość wyłączenia okna gry

Następnie poniżej dodamy warunek sprawdzający czy wąż zjadł jabłko

 snake.ruch()
        if snake.pozycja_Glowy() == jablko.pozycja:
            snake.zjedzenie()
            jablko.pozycja_Losowa()

uruchamiamy metodę snake.ruch() i sprawdzamy czy jego pozycja równa się pozycji jabłka. Po spełnieniu warunku w if’e aktywujemy metodę snake.zjedzenie() co powoduje wydłużenie węża i dodanie pkt graczowi

Jak również używamy jablko.pozycja_Losowa(), aby wyrysować jabłko w nowej pozycji

I na koniec meina wyrysowujemy wszystkie elementy gry

rysowanieSiatki(okno_Gry)
        snake.rysowanie(okno_Gry)
        jablko.rysuj(okno_Gry)
        okno_Gry.blit(okno_Gry, (0, 0))
        wynik = czcionka.render("Punkty {0}".format(snake.punkty), 1, (255, 255, 0))
        okno_Gry.blit(wynik, (5, 10))
        pygame.display.update()

W efekcie cała metoda main() wygląda następująco

def main():
    pygame.init()
    # Inicjalizacja okna gry
    okno_Gry= pygame.display.set_mode((szerokosc_Okna, wysokosc_Okna), 0, 32)
    pygame.display.set_caption("SNAKE")
    # Wyrasowanie siatki
    rysowanieSiatki(okno_Gry)
    # Inicjalizacja obiektów klas
    snake = Snake()
    jablko = Jablko()
    czcionka = pygame.font.SysFont('comicsans', 30)
    run = True
    while (run):
        pygame.time.delay(100)
        # Wyłączenie gry
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False
            # Sterowanie do Snake'a
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_UP:
                    snake.kierunek(gora)
                elif event.key == pygame.K_DOWN:
                    snake.kierunek(dol)
                elif event.key == pygame.K_LEFT:
                    snake.kierunek(lewo)
                elif event.key == pygame.K_RIGHT:
                    snake.kierunek(prawo)
        snake.ruch()
        if snake.pozycja_Glowy() == jablko.pozycja:
            snake.zjedzenie()
            jablko.pozycja_Losowa()
        rysowanieSiatki(okno_Gry)
        snake.rysowanie(okno_Gry)
        jablko.rysuj(okno_Gry)
        okno_Gry.blit(okno_Gry, (0, 0))
        wynik = czcionka.render("Punkty {0}".format(snake.punkty), 1, (255, 255, 0))
        okno_Gry.blit(wynik, (5, 10))
        pygame.display.update()

Kiedy uruchomimy nasz program naszym oczom ukaże się nasze dzieło, czyli działająca gra. Możemy być z siebie bardzo dumni !

Cały kod gry możesz znaleźć z repozytorium GitHuba

Podsumowanie

Dzisiaj zobaczyliśmy jak za pomocą biblioteki pygame można zrobić naprawdę fajną grę która z 25 lat temu była szczytem marzeń dzieciaków. Zobaczyliśmy, że jesteśmy wstanie tworzyć już naprawdę dość ciekawe projekty i w dalszych częściach artykułów będziemy tworzyć coraz bardziej zaawansowane, ale również ciekawsze gry.