-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFlappy Bird Python Edition V1.10.py
378 lines (318 loc) · 15.6 KB
/
Flappy Bird Python Edition V1.10.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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
# THIS PROJECT IS ABANDONED! FOR A BETTER VERSION, SEE FLAPPY BIRD V2!
# The following script is licensed under the MIT license.
# Flappy Bird Python © 2024 by Earth1283 is licensed under CC BY-NC-SA 4.0.
# This game has been known to be highly addictive, so please play this under your own discreetion
# Game recommended for 0+
# Requirements for playing:
# Hands. Reason: Spacebar
# Eyes. Reasons: Observation
# +===========================+
# +PUBLIC DISTRIBUTION EDITION+
# +===========================+
# PDE Ver 1.11
# Release 1.11 - Enhanced the hitbox warning system
# Release 1.10 - The update which I finally fixed crashes
# Controls:
# [Space] Jump or restart the game
# [G] Show guidance (you still have to mannually play it)
# [H] Show hitboxes, the hitbox of the player gets thicker when it is closer to the pipes
# Info:
# Avoid the green pipes, and jump to avoid them
# The blue dot makes you invincible for 5 seconds (there will be a countdown)
# If you are inside a pipe when the invincibility ends, you will die (lol)
# Code :)
import tkinter as tk
import random
class FlappyBird:
def __init__(self, root):
self.root = root
self.root.title("Flappy Bird Python Edition - V1.10")
# Canvas
self.canvas = tk.Canvas(root, width=400, height=600, bg='sky blue')
self.canvas.pack()
# Bird
self.bird = self.canvas.create_rectangle(50, 250, 70, 270, fill='yellow')
# Variables
self.gravity = 0.75
self.bird_velocity = 0
self.game_active = True
self.score = 0
self.high_score = 0
self.hitbox_visible = False # Hitbox indicator flag
self.optimal_path_visible = False # Optimal path indicator flag
# Invincibility buff
self.invincible = False
self.invincibility_duration = 5000 # 5 seconds in milliseconds
self.invincibility_end_time = 0
self.invincibility_countdown = None
self.invincibility_glow = None
# Invincibility item
self.invincibility_item = None
self.invincibility_item_exists = False
self.pipes_passed_since_last_item = 0
# Difficulty settings
self.pipe_gap = 200
self.min_pipe_gap = 120
self.pipe_gap_decrement = 2
# Score display
self.score_text = self.canvas.create_text(200, 50, text=f'Score: {self.score}', font=('Arial', 24), fill='white')
self.high_score_text = self.canvas.create_text(200, 100, text=f'High Score: {self.high_score}', font=('Arial', 24), fill='white')
# Pipes
self.pipes = []
self.pipe_speed = 5
self.create_pipes()
self.pipe_passed = False
# Key Bindings
self.root.bind('<space>', self.flap)
self.root.bind('h', self.toggle_hitbox) # Toggle hitbox visibility
self.root.bind('g', self.toggle_optimal_path) # Toggle optimal path visibility
# Game Loop
self.update_game()
def flap(self, event):
if self.game_active:
self.bird_velocity = -10
else:
self.restart_game()
def toggle_hitbox(self, event):
self.hitbox_visible = not self.hitbox_visible
if not self.hitbox_visible:
self.canvas.delete("hitbox")
def toggle_optimal_path(self, event):
self.optimal_path_visible = not self.optimal_path_visible
if not self.optimal_path_visible:
self.canvas.delete("optimal_path")
self.canvas.delete("predicted_path")
def activate_invincibility(self):
if self.invincibility_end_time:
self.canvas.after_cancel(self.invincibility_end_time)
self.invincible = True
self.invincibility_end_time = self.canvas.after(self.invincibility_duration, self.deactivate_invincibility)
if not self.invincibility_countdown:
self.invincibility_countdown = self.canvas.create_text(200, 300, text=f'Invincibility: 5', font=('Arial', 24), fill='blue')
self.update_invincibility_countdown(5)
if not self.invincibility_glow:
self.invincibility_glow = self.canvas.create_oval(self.canvas.coords(self.bird)[0] - 5,
self.canvas.coords(self.bird)[1] - 5,
self.canvas.coords(self.bird)[2] + 5,
self.canvas.coords(self.bird)[3] + 5,
outline='blue', width=3)
def update_invincibility_countdown(self, time_left):
if time_left > 0 and self.invincible:
if self.canvas.winfo_exists():
self.canvas.itemconfig(self.invincibility_countdown, text=f'Invincibility: {time_left}')
self.canvas.after(1000, self.update_invincibility_countdown, time_left - 1)
else:
if self.canvas.winfo_exists():
self.canvas.delete(self.invincibility_countdown)
self.invincibility_countdown = None
def deactivate_invincibility(self):
self.invincible = False
if self.canvas.winfo_exists():
self.canvas.delete(self.invincibility_countdown)
self.canvas.delete(self.invincibility_glow)
self.invincibility_countdown = None
self.invincibility_glow = None
self.invincibility_end_time = None
def create_pipes(self):
for i in range(2):
pipe_x = 400 + i * 200
self.pipes.append(self.create_pipe(pipe_x))
def create_pipe(self, pipe_x):
pipe_height = random.randint(150, 450)
top_pipe = self.canvas.create_rectangle(pipe_x, 0, pipe_x + 50, pipe_height, fill='green')
bottom_pipe = self.canvas.create_rectangle(pipe_x, pipe_height + self.pipe_gap, pipe_x + 50, 600, fill='green')
return top_pipe, bottom_pipe
def move_pipes(self):
for pipe_pair in self.pipes:
for pipe in pipe_pair:
self.canvas.move(pipe, -self.pipe_speed, 0)
if self.canvas.coords(self.pipes[0][0])[2] < 0:
for pipe in self.pipes.pop(0):
self.canvas.delete(pipe)
self.pipes.append(self.create_pipe(400))
self.pipes_passed_since_last_item += 1
def move_invincibility_item(self):
if self.invincibility_item_exists:
self.canvas.move(self.invincibility_item, -self.pipe_speed, 0)
if self.canvas.coords(self.invincibility_item)[2] < 0:
self.canvas.delete(self.invincibility_item)
self.invincibility_item_exists = False
def check_collision(self):
bird_coords = self.canvas.coords(self.bird)
if bird_coords[1] < 0 or bird_coords[3] > 600:
self.game_active = False
return
if not self.invincible:
for pipe_pair in self.pipes:
for pipe in pipe_pair:
if self.canvas.bbox(pipe) and self.canvas.bbox(self.bird):
if self.check_overlap(self.canvas.bbox(pipe), self.canvas.bbox(self.bird)):
self.game_active = False
return
# Check collision with invincibility item
if self.invincibility_item_exists and self.canvas.bbox(self.invincibility_item) and self.canvas.bbox(self.bird):
if self.check_overlap(self.canvas.bbox(self.invincibility_item), self.canvas.bbox(self.bird)):
self.activate_invincibility()
self.canvas.delete(self.invincibility_item)
self.invincibility_item_exists = False
def check_overlap(self, box1, box2):
if (box1 is None) or (box2 is None):
return False
return (box1[0] < box2[2] and box1[2] > box2[0] and box1[1] < box2[3] and box1[3] > box2[1])
def update_game(self):
if self.game_active:
self.bird_velocity += self.gravity
self.canvas.move(self.bird, 0, self.bird_velocity)
self.move_pipes()
self.move_invincibility_item()
self.check_collision()
self.update_score()
# Update invincibility glow position
if self.invincible and self.invincibility_glow:
bird_coords = self.canvas.coords(self.bird)
self.canvas.coords(self.invincibility_glow, bird_coords[0] - 5, bird_coords[1] - 5, bird_coords[2] + 5, bird_coords[3] + 5)
# Draw hitboxes if enabled
if self.hitbox_visible:
self.draw_hitboxes()
# Draw optimal path and predicted path if enabled
if self.optimal_path_visible:
self.draw_optimal_path()
self.draw_predicted_path()
# Possibly spawn an invincibility item
self.spawn_invincibility_item()
self.root.after(30, self.update_game)
def update_score(self):
bird_coords = self.canvas.coords(self.bird)
for pipe_pair in self.pipes:
pipe_coords = self.canvas.coords(pipe_pair[0])
if not self.pipe_passed and pipe_coords[0] < bird_coords[0] and pipe_coords[2] < bird_coords[2]:
self.score += 1
self.pipe_passed = True
self.canvas.itemconfig(self.score_text, text=f'Score: {self.score}')
self.increase_difficulty()
if pipe_coords[2] < bird_coords[0]:
self.pipe_passed = False
def increase_difficulty(self):
if self.pipe_gap > self.min_pipe_gap:
self.pipe_gap -= self.pipe_gap_decrement
def draw_hitboxes(self):
# Remove existing hitboxes
self.canvas.delete("hitbox")
# Draw bird hitbox
bird_coords = self.canvas.coords(self.bird)
thick = self.is_near_pipe(bird_coords)
self.canvas.create_rectangle(*bird_coords, outline='red', width=thick, tag="hitbox")
# Draw pipes hitboxes
for pipe_pair in self.pipes:
for pipe in pipe_pair:
pipe_coords = self.canvas.coords(pipe)
self.canvas.create_rectangle(*pipe_coords, outline='red', tag="hitbox")
def draw_optimal_path(self):
# Remove existing optimal path
self.canvas.delete("optimal_path")
# Draw new optimal path
bird_coords = self.canvas.coords(self.bird)
bird_center_x = (bird_coords[0] + bird_coords[2]) / 2
bird_center_y = (bird_coords[1] + bird_coords[3]) / 2
path_points = [(bird_center_x, bird_center_y)]
for pipe_pair in self.pipes:
pipe_coords = self.canvas.coords(pipe_pair[0])
pipe_center_x = (pipe_coords[0] + pipe_coords[2]) / 2
pipe_top = pipe_coords[3]
pipe_bottom = self.canvas.coords(pipe_pair[1])[1]
optimal_y = (pipe_top + pipe_bottom) / 2
path_points.append((pipe_center_x, optimal_y))
for i in range(len(path_points) - 1):
self.draw_curve(path_points[i], path_points[i + 1])
def draw_curve(self, point1, point2):
steps = 20
x1, y1 = point1
x2, y2 = point2
for i in range(steps):
t = i / steps
xt = (1 - t) * x1 + t * x2
yt = (1 - t) * y1 + t * y2
self.canvas.create_oval(xt, yt, xt + 1, yt + 1, fill='orange', outline='orange', tag="optimal_path")
def draw_predicted_path(self):
# Remove existing predicted path
self.canvas.delete("predicted_path")
# Draw predicted path
bird_coords = self.canvas.coords(self.bird)
bird_center_x = (bird_coords[0] + bird_coords[2]) / 2
bird_center_y = (bird_coords[1] + bird_coords[3]) / 2
predicted_y = bird_center_y
velocity = self.bird_velocity
path_points = [(bird_center_x, predicted_y)]
for _ in range(30):
predicted_y += velocity
velocity += self.gravity
path_points.append((bird_center_x, predicted_y))
for i in range(len(path_points) - 1):
self.canvas.create_line(path_points[i], path_points[i + 1], fill='lime', width=3, tag="predicted_path")
def is_near_pipe(self, bird_coords):
for pipe_pair in self.pipes:
for pipe in pipe_pair:
pipe_coords = self.canvas.coords(pipe)
if abs(bird_coords[0] - pipe_coords[2]) < 20 or abs(bird_coords[2] - pipe_coords[0]) < 20:
return 3 # Thicker hitbox
return 1 # Normal hitbox
def spawn_invincibility_item(self):
if not self.invincibility_item_exists and self.pipes_passed_since_last_item >= random.randint(7, 10):
if random.random() < 0.5: # 50% chance to spawn the item
item_x = 400
item_y = self.get_valid_item_y_position()
self.invincibility_item = self.canvas.create_oval(item_x, item_y, item_x + 20, item_y + 20, fill='blue', outline='blue')
self.invincibility_item_exists = True
self.pipes_passed_since_last_item = 0
def get_valid_item_y_position(self):
attempts = 0
max_attempts = 50000 # Prevent infinite loop, however, if you put this number too high it will crash the game
# For modders: The sweet spot is around 50000 attempts for balance.
# If the program cannot find a good place to put it, it will stuff it at Y250.
# Below 10K attempts is for smooth playing, and above 10K is basically for performance
# If it goes above 100K it might lag the game.
while attempts < max_attempts:
item_y = random.randint(100, 500)
valid_position = True
for pipe_pair in self.pipes:
top_pipe_coords = self.canvas.coords(pipe_pair[0])
bottom_pipe_coords = self.canvas.coords(pipe_pair[1])
if (top_pipe_coords[1] < item_y < top_pipe_coords[3] or
bottom_pipe_coords[1] < item_y < bottom_pipe_coords[3]):
valid_position = False
break
if valid_position:
return item_y
attempts += 1
# If no valid position found after max_attempts, return a default position
return 250
# This part is critical to prevent crashes
# Typically, Python spends a long time trying to figure out a valid Y coord.
# Now, after an x amount of tries, it simply "gives up" and outputs 250 for Y.
# Note to future Earth1283: Opimize this crap by making Python try differient numbers every time
# Reset the game to its origional configuration after death
def restart_game(self):
if self.score > self.high_score:
self.high_score = self.score
self.canvas.itemconfig(self.high_score_text, text=f'High Score: {self.high_score}')
self.canvas.delete("all")
self.bird = self.canvas.create_rectangle(50, 250, 70, 270, fill='yellow')
self.score_text = self.canvas.create_text(200, 50, text=f'Score: {self.score}', font=('Arial', 24), fill='white')
self.high_score_text = self.canvas.create_text(200, 100, text=f'High Score: {self.high_score}', font=('Arial', 24), fill='white')
self.pipes.clear()
self.pipe_gap = 215 # Reset the pipe gap
self.create_pipes()
self.bird_velocity = 0
self.score = 0
self.pipe_passed = False
self.game_active = True
self.invincible = False
self.invincibility_item_exists = False
self.pipes_passed_since_last_item = 0
self.invincibility_countdown = None
self.invincibility_glow = None
if __name__ == "__main__":
root = tk.Tk()
game = FlappyBird(root)
root.mainloop()
# Copyright Earth1283 2024