
Chcesz nauczyć się programowania gier w C#?
Zbuduj od podstaw kultowego Tetrisa i poznaj kluczowe koncepcje programowania! W tym kursie przeprowadzimy Cię przez cały proces – od minimalnej wersji gry (MVP) po bardziej zaawansowane mechaniki i optymalizację.
Czego się nauczysz?
Rozpoczniemy od implementacji z podziałem na warstwy i utrzymaniem najlepszych praktyk programowania.
Zaczniemy od utworzenie projektu w Visual Studio z odpowiednimi warstwami:
Tetris.Domain: Logika gry i modele.
Tetris.Application: Zasady działania gry, logika aplikacji.
Tetris.Infrastructure: Integracja z zewnętrznymi komponentami (jeśli będzie potrzebne).
Tetris.Presentation: WPF – interfejs użytkownika.
Krok 1. MVP naszej gry Tetris, skupiamy się na najbardziej podstawowych elementach gry, takich jak:
Reprezentacja planszy jako siatki.
Jeden prosty klocek.
Mechanika opadania bloku w dół.
Podstawowy interfejs (np. wyświetlanie planszy w WPF).
Nie dodajemy od razu zaawansowanych funkcji, takich jak różne kształty klocków, poziomy trudności czy interakcje gracza, ponieważ najważniejsze jest przetestowanie podstawowej mechaniki. Gdy MVP działa poprawnie i spełnia oczekiwania,
możemy zacząć rozwijać bardziej zaawansowane funkcjonalności.
Następnie przeanalizujemy kod pod kątem zgodności z najlepszymi praktykami i zasadami:
1. SOLID
· Single Responsibility Principle (SRP):
· Open/Closed Principle (OCP):
· Liskov Substitution Principle (LSP):
· Interface Segregation Principle (ISP):
· Dependency Inversion Principle (DIP):
2. Separation of Concerns (SoC)
3. KISS (Keep It Simple, Stupid)
4. DRY (Don’t Repeat Yourself)
5. YAGNI (You Aren’t Gonna Need It)
6. Cohesion i Coupling
Krok 2. Dodamy testy jednostkowe zgodnie z podejściem TDD dla naszego kodu gry Tetris.
Stworzymy testy dla głównych komponentów, zaczynając od podstawowych funkcjonalności.
Testy naszej planszy do gry - klasy GameBoard:
Sprawdzenie poprawnego tworzenia planszy czyli - Testy konstruktora
Weryfikacja, czy komórki są początkowo puste - Testy metody IsCellEmpty
Testowanie umieszczania bloków na planszy - Testy metody PlaceBlock
Testy warunków brzegowych
Testy naszego serwisu - klasy GameService:
Sprawdzenie poprawnego tworzenia nowych bloków - SpawnBlock
Testowanie ruchu bloków w dół - MoveBlockDown
Weryfikacja zachowania przy osiągnięciu dna
Sprawdzenie poprawnego zbierania wszystkich bloków na planszy - GetBlocks
Testy będą wykorzystywać:
xUnit jako framework testowy
Moq do mockowania zależności
FluentAssertions do czytelniejszych asercji
Krok 3 Dodanie różnych typów bloków
Dodanie różnych typów bloków w projekcie wymaga wprowadzenia odpowiednich modyfikacji w kodzie, które będą zgodne z zasadami projektowymi. W szczególności zastosujemy polimorfizm, co pozwoli na efektywne zarządzanie różnymi kształtami bloków. Dzięki temu nasz kod stanie się bardziej czytelny, elastyczny i zgodny z najlepszymi praktykami programowania.
Krok 4 Zastosowanie wzorca Factory Method.
Cel wzorca :
Oddzielenie logiki tworzenia klocków od reszty kodu,
co ułatwi zarządzanie i rozszerzanie aplikacji.
Zamiast tworzyć obiekty GameBlock bezpośrednio w różnych miejscach kodu,
będziemy korzystać z fabryki (GameBlockFactory),
co poprawia czytelność i elastyczność kodu.
Plan działań:
Przeniesiemy IGameBoard do folderu Interfaces, aby lepiej uporządkować kod nasz kod.
Dodamy GameBlockType (enum) – reprezentujący różne typy klocków (I, O, T, S, Z, J, L).
Stworzymy interfejs IGameBlockFactory, który określi, jak powinna działać fabryka klocków.
Zaimplementujemy GameBlockFactory, która będzie:
Tworzyć klocek na podstawie zadanego typu (CreateGameBlock).
Tworzyć losowy klocek (CreateRandomGameBlock).
Dzięki temu jeśli w przyszłości dodamy nowe typy klocków, wystarczy zmodyfikować fabrykę, zamiast zmieniać kod w wielu miejscach!
Krok 5 Dodanie obsługi obracania klocków za pomocą klawiatury.
Aby poprawnie zaimplementować obrót klocków w grze, należy uwzględnić kolizje zarówno ze ścianami planszy, jak i innymi blokami. Jeśli obrót nie jest możliwy, kształt klocka powinien pozostać bez zmian.
Plan Implementacji:
1. Modyfikacja klasy GameBlock
- Istniejąca metoda Rotate zostanie dostosowana tak, aby zwracała obrócony kształt klocka.
2. Aktualizacja klasy GameService
- Dodanie metody CanRotate, która sprawdzi, czy obrót jest możliwy.
- Implementacja RotateCurrentBlock do obsługi rotacji klocka.
- Dodanie metod MoveBlockLeft i MoveBlockRight w kontekście obrotów.
3. Dodanie Strategi
- Dodamy interfejs IRotationStrategy.
- Dodamy implementację strategii standardowej rotacji klocka o 90 stopni w prawo.
- Dodamy implementacja braku rotacji dla klocka.
- Dodamy Rotacja o 180 stopni
- Dodamy Obracanie klocek przeciwnie do ruchu wskazówek zegara
- Dodamy Rotację Losową, wybiera jedną z dostępnych strategii rotacji
Krok 6 Dodanie logiki detekcji i usuwania pełnych linii i dodanie punktacji
Dodamy logike detekcji i usuwania pełnych linii oraz punktację do naszej gry. Zacznijmy od stworzenia odpowiednich elementów w domenie.
Implementacja zapewnia:
- Prawidłowe wykrywanie pełnych linii – system skutecznie identyfikuje wiersze wypełnione klockami.
- Usuwanie linii i przesuwanie klocków w dół – po usunięciu pełnych linii pozostałe klocki są automatycznie przesuwane w dół.
- Naliczanie punktów według zadanej tabeli – wynik jest aktualizowany zgodnie z liczbą usuniętych linii i przyjętymi zasadami punktacji.
- Wyświetlanie aktualnego wyniku – UI natychmiast odświeża wynik oraz licznik usuniętych linii po każdej zmianie.
Zastosuj wzorzec Observer Pattern
Observer Pattern idealnie pasuje do powiadamiania UI o zmianach w grze. Observer Pattern pozwoli na oddzielenie logiki gry od interfejsu użytkownika. Dzięki temu UI będzie reagować na zmiany, takie jak:
- Aktualizacja punktacji
- Licznik usuniętych linii
- Czy Możliwość dodania innych zmian (np. Informowanie o końcu gry co teraz też dodamy)
Krok 7 Dodanie Zapisywania wyników i wyswietlania Top 5
Dodamy funkcjonalność zapisywania wyników oraz ich wyświetlania
w formie listy Top 5 najlepszych rezultatów.
Dzięki temu gracze będą mogli śledzić swoje osiągnięcia
oraz rywalizować o najwyższe miejsca w rankingu.
Następnie Dodamy teraz wzorzec Memento,
który pozwala na zapamiętanie stanu wyników graczy w danym momencie - HighScoreMemento
Dzięki temu można cofnąć się do wcześniejszego stanu listy wyników
bez ryzyka ich przypadkowej zmiany.