Dyspozytor zdarzeń - gra Mario¶
Piszesz grę platformową wzorowaną na świecie Mario. Postać, którą kieruje gracz, może zderzyć się z różnymi ruchomymi obiektami na planszy, taki jak moneta, żółw (Mario ginie), czy wisienka (supermoc). Aktualnie Twój kod prezentuje się następująco:
1game_actors = [(20, 5, "Turtle"), (24, 8, "Coin"),
2 (26, 8, "Coin"), (30, 2, "Cherry")]
3
4
5def process_collisions(mario, other_actors):
6 for a in other_actors:
7 # check if X and Y match; continue if it's not the case
8 if a[0] != mario[0] or a[1] != mario[1]: continue
9 # yes, we have a collision!
10 if a[3] == "Coin":
11 pass # update the number of coins collected by a player
12 if a[3] == "Cherry":
13 pass # superpower Mario!
14 if a[3] == "Turtle":
15 pass # no_lives -= 1
16
17if __name__ == "__main__":
18 # in the main loop of the game you update Mario's position
Powyższy kod jest nieoptymalny z wielu powodów:
Reprezentowanie aktorów gry (moneta, żółw) jako tuple trójelementowe: (x, y, nazwa) jest proszeniem się o kłopoty: kod jest nieczytelny i łatwo pomylić indeks
[0]z[1]. Dodatkowo rozbudowywanie aktorów o dodatkowe zmienne (np liczba monet zebranych przez Mario) stwarza dodatkowe problemy z indeksowaniem.instrukcja wielokrotnego wyboru, która podejmuje decyzję o reakcji na zdarzenie jest trudna w utrzymaniu / rozbudowie
Przepisz powyższy kod tak, aby:
każdy z aktorów dziedziczył po abstrakcyjnej klasie
Actor, zawierającej jego położenie na planszyklasa
Mariopowinna zawierać też aktualną liczbę żyć gracza, liczbę zebranych monet oraz flagę logiczną włączającą supermoce (po zderzeniu z wisienką)dla każdej z tych klas napisz też klasę-akcję, która modyfikuje obiekt
Marioodpowiednio do zdarzenia; klasy te powinny dziedziczyć po abstrakcyjnej klasie bazowejAbstractActionzaimplementu dyspozytor, który każdej z możliwych klas
Actorprzypisze odpowiedni obiektAbstractAction
Pierwsze dwie kropki z powyżej listy rozwiązują problem (1) - obiektową reprezentację aktorów. Dwie kolejne kropki
powinny wyeliminować instrukcję wielokrotnego wyboru (punkt (2)). Poprawnie napisany
kod powinien działać z poniższą sekcją __main__:
1game_actors = [Turtle(20, 5), Coin(24, 8),
2 Coin(26, 8), Cherry(30, 2)]
3
4if __name__ == "__main__":
5
6 # in the main loop of the game you update Mario's position
7 mario = Mario(0, 0)
8 # register collision action in a dispatch:
9 collisions = CollisionDispatch()
10 collisions.register(Coin.class, MarioHitsCoin)
11
12 # check for collisions:
13 for actor in game_actors:
14 # dispatch actions accordingly to the actor's type
15 if mario.if_crashes_into(actor):
16 collisions.dispatch(actor)
Rozwiązanie zadania znajduje się poniżej:
(rozwiń kod rozwiązania)
1from abc import ABC, abstractmethod
2from math import fabs
3
4class Actor:
5 def __init__(self, x, y, width, height):
6 self.x = x
7 self.y = y
8 self.width = width
9 self.height = height
10
11class Mario(Actor):
12 def __init__(self, x, y):
13 super().__init__(x, y, 20, 20)
14 self.coins = 0
15 self.lifes = 3
16 self.speed = 1.0
17
18 def if_crashes_into(self, actor: Actor):
19 dx = fabs(actor.x - self.x)
20 dy = fabs(actor.y - self.y)
21 return dx < (self.width+actor.width)/2.0 and dy < (self.height+actor.height)/2.0
22
23class Coin(Actor):
24 def __init__(self, x, y):
25 super(Coin, self).__init__(x, y, 10, 10)
26
27class Turtle(Actor):
28 def __init__(self, x, y):
29 super().__init__(x, y, 30, 10)
30
31class Cherry(Actor):
32 def __init__(self, x, y):
33 super().__init__(x, y, 10, 10)
34
35game_actors = [Turtle(20, 5), Coin(24, 8),
36 Coin(26, 8), Cherry(30, 2)]
37
38class CollisionAction(ABC):
39 @abstractmethod
40 def dispatch(self, a_mario: Mario): pass
41
42class MarioHitsTurtle(CollisionAction):
43 def dispatch(self, a_mario: Mario): a_mario.lifes -= 1
44
45
46class MarioHitsCoin(CollisionAction):
47 def dispatch(self, a_mario: Mario): a_mario.coins += 1
48
49class MarioHitsCherry(CollisionAction):
50 def dispatch(self, a_mario: Mario): a_mario.speed *= 1.5
51
52class CollisionDispatch:
53 def __init__(self):
54 self.__dict = {}
55
56 def register(self, cls_name: str, action: CollisionAction):
57 self.__dict[cls_name] = action
58
59if __name__ == "__main__":
60
61 # in the main loop of the game you update Mario's position
62 mario = Mario(0, 0)
63 # register collision action in a dispatch:
64 collisions = CollisionDispatch()
65 collisions.register(Coin.__class__.__name__, MarioHitsCoin())
66 collisions.register(Turtle.__class__.__name__, MarioHitsTurtle())
67 collisions.register(Cherry.__class__.__name__, MarioHitsCherry())
68
69 # check for collisions:
70 for actor in game_actors:
71 # dispatch actions accordingly to the actor's type
72 if mario.if_crashes_into(actor):
73 collisions.dispatch(actor)
74