-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontroller.py
227 lines (174 loc) · 7.23 KB
/
controller.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
import pygame
import sys
from pygame.locals import DOUBLEBUF # flag to enable double buffering
from View import View
from models.Bird import Bird
from models.PipeGenerator import PipeGenerator
from models.Score import Score
class Controller:
def __init__(self):
# config
self.WIDTH = 1280 / 1.3
self.HEIGHT = 720 / 1.3
self.FPS_LIMIT = 144
# gravity
self.GRAVITY = 1
self.X_GRAVITY = 1
self.CAPTION = "Bird Jumper"
self.GAME_ICON = pygame.image.load('images/duck-ga9276d9c3_640.png')
# noinspection SpellCheckingInspection
self.BG_IMG = pygame.image.load('images/Mountains_Loopable_56x31.png')
self.background = None
self.BG_COLOR = 'lightblue'
self.PLAYER_CHARACTER = None
self.PIPES_ARRAY = None
self.last_pipe = None
self.PIPE_GAP = 250
self.PIPE_SPACING = 300
self.pipe_total_width = None
self.SCORE = None
self.SCORE_FONT_SIZE = 30
self.SCORE_MARGIN = 15
self.counter = 0
self.running = False
self.is_paused = True
self.game_over = False
def play(self):
pygame.init()
# initialize view
flags = DOUBLEBUF
view = View(self.WIDTH, self.HEIGHT, flags, self.CAPTION, self.GAME_ICON, None)
window = view.get_window() # get window
self.BG_IMG = self.BG_IMG.convert_alpha() # convert bg
# game clock
clock = pygame.time.Clock()
# noinspection PyUnusedLocal
# dt = 0 # delta time
# initialize player_character, pipes and background
self.initialize_assets()
self.SCORE = Score(self.SCORE_FONT_SIZE, self.SCORE_MARGIN)
self.running = True
while self.running:
# limit FPS
# dt is delta time in seconds since last frame, used for frame-rate-independent physics.
dt = clock.tick(self.FPS_LIMIT) / 1000 # divide by 1000 to convert to seconds
# clock.tick(self.FPS_LIMIT) # set tick
# 60 / fps -> adjusting physics to behave the same as on 60fps
dt += 60 / (clock.get_fps() or 60) # or 60 -> div 0 fail safe
# events (keyboard / mouse inputs)
self.handle_events()
# if game over -> continue
if self.game_over:
continue
# background
window.fill(self.BG_COLOR)
window.blit(self.background, (0, 0))
# continue if the game is paused
if self.is_paused:
self.PLAYER_CHARACTER.draw_static(window)
pygame.display.update()
continue
# increment counter on every tick, adjusted with dt
self.increment_counter(dt)
# bird animation
self.PLAYER_CHARACTER.animate_wings(int(self.counter)) # rounding timer because it will be list's index
# render bird
self.PLAYER_CHARACTER.draw(window, dt)
# render Pipes
for pipe in self.PIPES_ARRAY:
if pipe.is_visible():
pipe.draw(window, dt)
else:
pipe.recycle(self.last_pipe.get_x() + self.pipe_total_width)
self.last_pipe = pipe
# increment score
self.SCORE.increment()
# noinspection PyStatementEffect
# self.SCORE + 1
# checking for collisions with player_character
if self.PLAYER_CHARACTER.check_for_collision(pipe):
# print warning to the console
print("\033[91m {}\033[00m".format(f'collision {pipe.get_positions()}'))
print("\033[94m {}\033[00m".format(f'your score: {self.SCORE.get_score()}'))
print()
self.restart()
# restart if the bird is out of screen
if self.PLAYER_CHARACTER.is_out_of_bounds(self.WIDTH):
self.restart()
# render score
self.SCORE.draw(window)
# print fps and dt
# print(f'fps: {clock.get_fps()}')
# print(f'delta time: {dt}')
# update the display
pygame.display.update()
pygame.quit()
def restart(self):
# self.is_paused = True
self.game_over = True
self.counter = 0
self.initialize_assets()
@staticmethod
def quit():
if pygame.get_init(): # True if pygame is currently initialized
pygame.quit()
sys.exit()
def initialize_assets(self):
# background
self.background = pygame.transform.scale(self.BG_IMG, (self.WIDTH, self.HEIGHT))
# player character
self.PLAYER_CHARACTER = Bird(self.WIDTH / 4, self.HEIGHT / 4, self.GRAVITY)
# pipes setup
self.PIPES_ARRAY = self.generate_pipes()
self.last_pipe = self.PIPES_ARRAY[-1]
# score
# self.SCORE = Score(self.SCORE_FONT_SIZE, self.SCORE_MARGIN)
def handle_events(self):
# pygame.QUIT event => the user clicked X
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
self.quit()
return
if event.type == pygame.KEYDOWN:
# on [space, w, arrow_up] press -> jump
if event.key in [pygame.K_SPACE, pygame.K_UP, pygame.K_w]:
self.PLAYER_CHARACTER.jump()
if self.game_over:
self.SCORE.reset()
if self.is_paused or self.game_over:
self.is_paused = False
self.game_over = False
return
# quit the game on escape key press
if event.key == pygame.K_ESCAPE:
self.running = False
self.quit()
return
if event.type == pygame.MOUSEBUTTONDOWN:
# on mouse left click -> jump
if event.button == pygame.BUTTON_LEFT:
self.PLAYER_CHARACTER.jump()
if self.game_over:
self.SCORE.reset()
if self.is_paused or self.game_over:
self.is_paused = False
self.game_over = False
return
def increment_counter(self, dt):
self.counter += dt # incrementing with dt helps with bird's wing animation (normalizing their speed)
# reset counter if it exceeds FPS_LIMIT
if self.counter > self.FPS_LIMIT:
self.counter = 0
def generate_pipes(self):
pipe_width = PipeGenerator(0, 1000, 0, 0).get_width()
pipe_total_width = pipe_width + self.PIPE_SPACING
self.pipe_total_width = pipe_total_width
pipes_needed = int(self.WIDTH // pipe_total_width) + 1 # + 1 for extra pipe
pipes = []
new_pipe_position = self.WIDTH
for i in range(pipes_needed):
new_pipe = PipeGenerator(new_pipe_position, self.HEIGHT, self.PIPE_GAP, self.X_GRAVITY)
new_pipe_position += pipe_total_width
pipes.append(new_pipe)
return pipes