C# – Zbuduj Własnego Tetrisa! Kompletny Przewodnik
- Opis
- Program
- Notice
- Recenzje
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)
-
1Rozpoczecie Projektu - Podział Na WarstwyPodgląd 09:39
-
2Rozpoczynamy Kodowaniw - warstwa Domain - klasa BlockPodgląd 02:39
-
3Warstwa Domain - klasa GameBoardPodgląd 11:56
-
4Warstwa Application - klasa GameService02:49
-
5GameService dodajemy metodę Utworzenie Nowego Klocka02:41
-
6GameService dodajemy metodę do Przesuwania Klocka w dół06:40
-
7GameService dodajemy metodę do Pobierania Bloków Na Planszy07:13
-
8Warstwa Presentation - klasa MainWindow11:19
-
9MainWindow dodajemy metodę do Aktualizowania Stanu Gry01:42
-
10MainWindow dodajemy metodę do Rysowania Elementów Gry07:05
-
11Zagadka02:47
-
12MainWindow dodajemy metodę do Pobierania Koloru02:41
-
13Analiza kodu pod kątem Najlepszych Praktyk i Zasad SOLID08:24
-
14Rozwiązanie Zagadki01:07
-
15Dwa Podejścia do kolejności wymiarów w tablicy 2D05:47
-
16Optymalizacja Renderowania02:18
-
17Najważniejsze Aspekty Testów02:06
-
18Dodanie Testów do klasy GameBoard02:15
-
19GameBoard dodanie testów dla Konstruktora08:24
-
20GameBoard jeszcze więcej testów Konstruktora12:53
-
21GameBoard dodanie testów sprawdzających czy komórka jest pusta05:15
-
22GameBoard dodanie testów dla nieprawidłowych współrzędnych08:45
-
23GameBoard dodanie testów dla metodu umieść blok który powinien rzucić wyjątek06:11
-
24GameBoard dodanie testów dla metodu umieść blok gdy pozycja jest prawidłowa04:31
-
25GameBoard dodanie testów dla metodu umieść blok gdy pozycja jest zajęta04:12
-
26GameBoard dodanie testów dla metodu umieść blok gdy pozycja jest poza zakresem04:29
-
27Dodanie Testów do klasy GameService11:03
-
28GameService dodanie testów dla metody wygeneruj blok05:08
-
29GameService dodanie testów dla metody przesuń blok w dół gdy jest przestrzeń07:18
-
30GameService dodanie testów dla metody przesuń blok gdy osiągnięto dno07:39
-
31GameService dodanie testów dla metody przesuń blok gdy gdy nie powinien przesuwa08:03
-
32GameService dodanie testów dla metody pobierz bloki09:46
-
33Podsumowanie Rozdziału I Co Dalej...09:25
-
34Wprowadzenie01:59
-
35Utworzenie Klasy Bazowej GameBlock04:14
-
36GameBlock - Zwracanie Wspołrzędnych Zajmowanych Przez Klocek09:57
-
37GameBlock - Obracanie Klocka w Prawo08:09
-
38Dziedziczenie - Dodanie Różnych Typów Klocków09:36
-
39Modyfikacja GameService Aby Działał z GameBlock11:50
-
40GameService - Sprawdzenie Czy Klocek Można Przesunąć05:41
-
41GameService - Losowe Generowanie Klocków03:58
-
42GameService - Umieszczanie Klocka Na Planszy Gry05:02
-
43Podsumowanie Rozdziału I Co Dalej...10:15
-
44Wprowadzenie02:42
-
45Dodanie GameBlockType01:53
-
46Dodanie Interfejsu IGameBlockFactory02:52
-
47Dodanie GameBlockFactory10:00
-
48Modyfikacja GameService01:44
-
49Modyfikacja MainWindow01:40
-
50Zalety Implementacj Factory Method01:00
-
51Dostosowanie Testów Klasy GameService11:17
-
52Dostosowanie Testów Klasy GameBlockFactory01:41
-
53CreateGameBlock Powinien Utworzyć Poprawny Typ klocka07:03
-
54CreateGameBlock Powinien Rzucić Argument Exception Dla Niepoprawne03:09
-
55CreateRandomGameBlock Powinien Zwrócić Poprawny Blok Gry (losowy)02:23
-
56CreateRandomGameBlock Powinien Tworzyć Różne Typy05:21
-
57CreateGameBlock Powinien Mieć Poprawne Wymiary07:01
-
58CreateGameBlock Wszystkie Typy Powinny Mieć Cztery Bloki04:39
-
59Podsumowanie Rozdziału I Co Dalej...04:44
-
60Wprowadzenie01:55
-
61Modyfikacja klasy GameBlock02:56
-
62Dodanie metody CanRotate w GameService14:24
-
63Dodanie RotateCurrentBlock w GameService03:45
-
64Dodanie MoveBlockLeft i MoveBlockRight w GameService02:35
-
65Aktualizacja MainWindow06:25
-
66Dodamy interfejsu IRotationStrategy02:37
-
67Dodanie StandardRotationStrategy02:19
-
68Dodanie NoRotationStrategy02:03
-
69Modyfikacja GameBlock aby korzystał z wybranej strategii rotacji03:58
-
70Strategie i co dalej02:14
-
71Dodanie Rotation180Strategy04:56
-
72Dodanie CounterClockwiseRotationStrategy03:24
-
73Dodanie MirrorRotationStrategy03:46
-
74Podsumowanie Rozdziału I Co Dalej...06:33
-
75Wprowadzenie00:31
-
76Dodanie Klasa Score07:19
-
77GameBord Dodanie Usuwania Lini03:06
-
78Znajdujemy wszystkie pełne linie03:11
-
79Sprawdza czy dana linia jest pełna02:29
-
80Znajdujemy wszystkie pełne linie cz.201:28
-
81Usuwamy wskazane linie02:34
-
82Przesuwa klocki w dół po usunięciu linii06:17
-
83Znajdujemy wszystkie pełne linie cz.302:41
-
84Modyfikacja GameService08:39
-
85Modyfikacja MainWindow Formularz07:15
-
86Modyfikacja MainWindow Kod06:20
-
87Implementujemy Observer Patern03:38
-
88Dodajemy listę obserwatorów - GameSubject06:51
-
89Modyfikacja GameService02:03
-
90Modyfikacja MainWindow05:20
-
91Podsumowanie Rozdziału I Co Dalej...Video lesson
-
92Wprowadzenie00:42
-
93Dodanie klasy HighScore02:24
-
94Dodanie interfejsu IHighScoreRepository02:53
-
95Dodanie JsonHighScoreRepository10:09
-
96Dodanie HighScoreService12:08
-
97Dodanie InputDialog07:44
-
98Dodanie InputDialog cz.210:16
-
99Modyfikacja MainWindow Forms02:16
-
100Modyfikacja MainWindow Code07:08
-
101Dodanie UpdateHighScoresList05:30
-
102Modyfikacja OnGameOver08:26
-
103InpuDialog poprawki12:43
-
104Podsumowani rozdziału I Co Dalej05:51
Kod źródłowy dostępny na githubie
