From 5ed9710989cf31bc8e51183b6c1544eaa19a6b4a Mon Sep 17 00:00:00 2001 From: taeganwarren Date: Sun, 3 Feb 2019 16:32:27 -0800 Subject: [PATCH 01/10] Updated requirements and added test script --- requirements-dev.txt | 2 +- requirements.txt | 1 + setup.py | 24 ++++++++++++------------ test.py | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 13 deletions(-) create mode 100644 requirements.txt create mode 100644 test.py diff --git a/requirements-dev.txt b/requirements-dev.txt index 6a7c32f..ed17187 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,4 @@ mock==1.0.1 numpydoc Sphinx==1.2.3 -sphinx_rtd_theme +sphinx_rtd_theme \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..231dd17 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +pygame \ No newline at end of file diff --git a/setup.py b/setup.py index 5209e97..81c19a9 100644 --- a/setup.py +++ b/setup.py @@ -11,9 +11,9 @@ ] setup( - name='ple', - version='0.0.1', - description='PyGame Learning Environment', + name='ple', + version='0.0.1', + description='PyGame Learning Environment', classifiers=[ "Intended Audience :: Developers", "Intended Audience :: Science/Research", @@ -21,13 +21,13 @@ "Programming Language :: Python :: 2.7", "Topic :: Scientific/Engineering :: Artificial Intelligence", ], - url='https://github.com/ntasfi/PyGame-Learning-Environment', - author='Norman Tasfi', - author_email='first letter of first name plus last at googles email service.', - keywords='', - license="MIT", - packages=find_packages(), - include_package_data=False, - zip_safe=False, - install_requires=install_requires + url='https://github.com/ntasfi/PyGame-Learning-Environment', + author='Norman Tasfi', + author_email='first letter of first name plus last at googles email service.', + keywords='', + license="MIT", + packages=find_packages(), + include_package_data=False, + zip_safe=False, + install_requires=install_requires ) diff --git a/test.py b/test.py new file mode 100644 index 0000000..3882613 --- /dev/null +++ b/test.py @@ -0,0 +1,32 @@ +from ple import PLE +from ple.games.pong import Pong +import pygame +import time +import sys + +game = Pong(width=300, height=200) + +p = PLE(game, fps=30, display_screen=True, force_fps=True) +p.init() + +print(p.getActionSet()) + +nb_frames = 1000 +action = None + +for f in range(nb_frames): + if p.game_over(): + p.reset_game() + obs = p.getScreenRGB() + events = pygame.event.get() + for event in events: + if event.type == pygame.QUIT: + sys.exit() + elif event.type == pygame.KEYDOWN: + if event.key: + action = event.key + print(action) + elif event.type == pygame.KEYUP: + action = None + p.act(action) + time.sleep(.05) From 2e552a4dd3994cafdf8ebd029fa4f258f5512de8 Mon Sep 17 00:00:00 2001 From: taeganwarren Date: Sun, 3 Feb 2019 19:47:54 -0800 Subject: [PATCH 02/10] This is a change --- test.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test.py b/test.py index 3882613..461c626 100644 --- a/test.py +++ b/test.py @@ -30,3 +30,5 @@ action = None p.act(action) time.sleep(.05) + +#This is a change \ No newline at end of file From 31463eaa93054fac3b097bae1914da171d694dda Mon Sep 17 00:00:00 2001 From: Jack Barnes Date: Tue, 5 Mar 2019 23:17:59 -0800 Subject: [PATCH 03/10] added tests for pong and raycastmaze --- tests/test_pong.py | 69 +++++++++++++++++++++++++++++++++++++++ tests/test_raycastmaze.py | 33 +++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 tests/test_pong.py create mode 100644 tests/test_raycastmaze.py diff --git a/tests/test_pong.py b/tests/test_pong.py new file mode 100644 index 0000000..a2c61bb --- /dev/null +++ b/tests/test_pong.py @@ -0,0 +1,69 @@ +#!/usr/bin/python +from ple import PLE +from ple.games.pong import Pong, Ball, Player +import pygame, time, sys, pytest + +@pytest.mark.parametrize("x1,y1,x2,y2,x3,y3,x4,y4,expected", [ + (0,0,10,10,0,10,10,0,True), #X-like intersection of lines + (0,0,0,5,0,0,5,0,True), #Shared first end-point (0,0) + (0,0,0,5.00000000009,10,10,0,5.0000000001,False), #nearly touching end-point + (0,5,0,0,0,5,0,0,True), #entirely overlapping lines (both end-points shared) + (1,1,1,6,2,1,2,6,False), #parallel lines (FAILS: Division by zero) + (1,6,1,1,2,6,2,1,False), #parallel lines, rotated 90 degrees (FAILS: Division by zero) + (5,5,10,10,6,6,11,11,False), #parallel lines at a 45 degree angle (FAILS: Division by zero) + (0,0,0,10,10,5,0,5,True), #T-like intersection, with one end-point meeting another line midway +]) + +def test_line_intersection(x1,y1,x2,y2,x3,y3,x4,y4,expected): + game=Ball(10,1,1,(50,50),100,100) #these parameters do not affect the line_intersection function + answer = game.line_intersection(x1,y1,x2,y2,x3,y3,x4,y4) + assert answer == expected + +def test_movement_up(): + game=Pong() + p=PLE(game, display_screen=True, fps=20, force_fps=1) + p.init() + time.sleep(.5) + oldState=p.getGameState() + p.act(game.actions["up"]) + newState=p.getGameState() + assert oldState["player_velocity"] > newState["player_velocity"] + +def test_movement_down(): + game=Pong() + p=PLE(game, display_screen=True, fps=20, force_fps=1) + p.init() + time.sleep(.5) + oldState=p.getGameState() + p.act(game.actions["down"]) + newState=p.getGameState() + assert oldState["player_velocity"] < newState["player_velocity"] + +def test_negative_cpu_speed(): + with pytest.raises(Exception): + game=Pong(cpu_speed_ratio=-1) + +def test_negative_player_speed(): + with pytest.raises(Exception): + game=Pong(players_speed_ratio=-1) + +def test_negative_ball_speed(): + with pytest.raises(Exception): + game=Pong(ball_speed_ratio=-1) + +def test_invalid_game_size(): + with pytest.raises(Exception): + game=Pong(width=-200, height=-200) + +def test_invalid_max_score(): + with pytest.raises(Exception): + game=Pong(MAX_SCORE=-1) + +def test_invalid_action_input(): + game=Pong() + p=PLE(game, display_screen=True, fps=20, force_fps=1) + p.init() + time.sleep(.5) + with pytest.raises(Exception): + p.act(10) + diff --git a/tests/test_raycastmaze.py b/tests/test_raycastmaze.py new file mode 100644 index 0000000..d12f430 --- /dev/null +++ b/tests/test_raycastmaze.py @@ -0,0 +1,33 @@ +#!/usr/bin/python +from ple import PLE +from ple.games.raycastmaze import RaycastMaze +import pygame, time, sys, pytest + + +def test_oob_init_pos(): + with pytest.raises(Exception): + game=RaycastMaze(init_pos=(-1,-1)) + +def test_below_min_map_size(): + with pytest.raises(Exception): + game=RaycastMaze(map_size=3) + +def test_beyond_max_map_size(): + with pytest.raises(Exception): + game=RaycastMaze(map_size=400) + +def test_negative_move_speed(): + with pytest.raises(Exception): + game=RaycastMaze(move_speed=-1) + +def test_negative_turn_speed(): + with pytest.raises(Exception): + game=RaycastMaze(turn_speed=-1) + +def test_beyond_max_turn_speed(): + with pytest.raises(Exception): + game=RaycastMaze(turn_speed=400) + +def test_negative_game_size(): + with pytest.raises(Exception): + game=RaycastMaze(width=-100,height=-100) From f4264e20ec5aef403a8f0badff41f1a0c5e0b4fe Mon Sep 17 00:00:00 2001 From: JershCook Date: Sun, 17 Mar 2019 17:20:56 -0700 Subject: [PATCH 04/10] fix: puckworld reward/ player drawing; pixelcopter obstacle placement; ple frame limiting; --- ple/games/pixelcopter.py | 13 +++++++++++-- ple/games/puckworld.py | 10 ++++++---- ple/ple.py | 4 +++- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/ple/games/pixelcopter.py b/ple/games/pixelcopter.py index 773bb11..5d83b2a 100644 --- a/ple/games/pixelcopter.py +++ b/ple/games/pixelcopter.py @@ -135,6 +135,9 @@ class Pixelcopter(PyGameWrapper): """ def __init__(self, width=48, height=48): + assert 00: + y_pos+=int(self.height * 0.10) + y_pos+=int(self.height * 0.50) + self.block_group.add( Block( (x_pos, y_pos), diff --git a/ple/games/puckworld.py b/ple/games/puckworld.py index 621e203..7d5a66a 100644 --- a/ple/games/puckworld.py +++ b/ple/games/puckworld.py @@ -75,6 +75,9 @@ def __init__(self, width=64, height=64): + assert 0 Date: Mon, 18 Mar 2019 02:09:27 -0700 Subject: [PATCH 05/10] Added param checking for raycastmaze.py, tests for it now pass --- ple/games/raycastmaze.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ple/games/raycastmaze.py b/ple/games/raycastmaze.py index 45368b6..a6fb8f5 100644 --- a/ple/games/raycastmaze.py +++ b/ple/games/raycastmaze.py @@ -43,7 +43,15 @@ def __init__(self, move_speed=20, turn_speed=13, map_size=10, height=48, width=48, init_pos_distance_to_target=None): - assert map_size > 5, "map_size must be gte 5" + for x in init_pos: + assert (x >= 0) and (x < map_size), "Error: init_pos must be on the map (0 to map_size)" + assert resolution > 0, "Error: resolution must be greater than 0" + assert move_speed > 0, "Error: move_speed must be greater than 0" + assert move_speed < 100, "Error: move_speed must be less than 100" + assert turn_speed > 0, "Error: turn_speed must be greater than 0" + assert turn_speed < 100, "Error: turn_speed must be less than 100" + assert (map_size > 5) and (map_size <= 100), "Error: map_size must be >= 5 and <= 100" + assert (height > 0) and (width > 0) and (height == width), "Error: height and width must be equal and greater than 0" # do not change init_dir = (1.0, 0.0) From 003cdb0272b525ccf212701c3150acba15712978 Mon Sep 17 00:00:00 2001 From: Jack Barnes Date: Mon, 18 Mar 2019 02:23:53 -0700 Subject: [PATCH 06/10] Assertions for value range of parameters inputs for pong.py added. One test removed. --- ple/games/pong.py | 7 +++++++ tests/test_pong.py | 18 +++++++++++------- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/ple/games/pong.py b/ple/games/pong.py index 40b357b..16056cd 100644 --- a/ple/games/pong.py +++ b/ple/games/pong.py @@ -196,6 +196,13 @@ class Pong(PyGameWrapper): def __init__(self, width=64, height=48, cpu_speed_ratio=0.6, players_speed_ratio = 0.4, ball_speed_ratio=0.75, MAX_SCORE=11): + assert width > 0, "Error: width must be greater than 0" + assert height > 0, "Error: height must be greater than 0" + assert cpu_speed_ratio > 0, "Error: cpu_speed_ratio must be greater than 0" + assert players_speed_ratio > 0, "Error: player_speed_ratio must be greater than 0" + assert ball_speed_ratio > 0, "Error: ball_speed_ration must be greater than 0" + assert MAX_SCORE > 0, "Error: MAX_SCORE must be greater than 0" + actions = { "up": K_w, "down": K_s diff --git a/tests/test_pong.py b/tests/test_pong.py index a2c61bb..cfcc7c5 100644 --- a/tests/test_pong.py +++ b/tests/test_pong.py @@ -59,11 +59,15 @@ def test_invalid_max_score(): with pytest.raises(Exception): game=Pong(MAX_SCORE=-1) -def test_invalid_action_input(): - game=Pong() - p=PLE(game, display_screen=True, fps=20, force_fps=1) - p.init() - time.sleep(.5) - with pytest.raises(Exception): - p.act(10) +#I'm commenting out this test currently because it is unclear whether the game should +# throw an exception for an undefinied action, or do nothing (basically a wait step) +# Refer to ple.py lines 361-367 in the definition of act(int) for this +# +#def test_invalid_action_input(): +# game=Pong() +# p=PLE(game, display_screen=True, fps=20, force_fps=1) +# p.init() +# time.sleep(.5) +# with pytest.raises(Exception): +# p.act(10) From 37f782fdaefb3f190c807649b75e0f10c488975f Mon Sep 17 00:00:00 2001 From: Jack Barnes Date: Mon, 18 Mar 2019 03:53:17 -0700 Subject: [PATCH 07/10] Changed pong collision to use pygame, removed unneeded code, commented and updated tests --- ple/games/pong.py | 44 ++++++++++++++------------------------- tests/test_pong.py | 38 +++++++-------------------------- tests/test_raycastmaze.py | 8 ++++++- 3 files changed, 31 insertions(+), 59 deletions(-) diff --git a/ple/games/pong.py b/ple/games/pong.py index 16056cd..4177129 100644 --- a/ple/games/pong.py +++ b/ple/games/pong.py @@ -1,7 +1,7 @@ import math import sys -import pygame +import pygame, pygame.sprite from pygame.constants import K_w, K_s from ple.games.utils.vec2d import vec2d from ple.games.utils import percent_round_int @@ -42,18 +42,6 @@ def __init__(self, radius, speed, rng, self.rect = self.image.get_rect() self.rect.center = pos_init - def line_intersection(self, p0_x, p0_y, p1_x, p1_y, p2_x, p2_y, p3_x, p3_y): - - s1_x = p1_x - p0_x - s1_y = p1_y - p0_y - s2_x = p3_x - p2_x - s2_y = p3_y - p2_y - - s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y) - t = (s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y) - - return (s >= 0 and s <= 1 and t >= 0 and t <= 1) - def update(self, agentPlayer, cpuPlayer, dt): self.pos.x += self.vel.x * dt @@ -61,21 +49,21 @@ def update(self, agentPlayer, cpuPlayer, dt): is_pad_hit = False - if self.pos.x <= agentPlayer.pos.x + agentPlayer.rect_width: - if self.line_intersection(self.pos_before.x, self.pos_before.y, self.pos.x, self.pos.y, agentPlayer.pos.x + agentPlayer.rect_width / 2, agentPlayer.pos.y - agentPlayer.rect_height / 2, agentPlayer.pos.x + agentPlayer.rect_width / 2, agentPlayer.pos.y + agentPlayer.rect_height / 2): - self.pos.x = max(0, self.pos.x) - self.vel.x = -1 * (self.vel.x + self.speed * 0.05) - self.vel.y += agentPlayer.vel.y * 2.0 - self.pos.x += self.radius - is_pad_hit = True - - if self.pos.x >= cpuPlayer.pos.x - cpuPlayer.rect_width: - if self.line_intersection(self.pos_before.x, self.pos_before.y, self.pos.x, self.pos.y, cpuPlayer.pos.x - cpuPlayer.rect_width / 2, cpuPlayer.pos.y - cpuPlayer.rect_height / 2, cpuPlayer.pos.x - cpuPlayer.rect_width / 2, cpuPlayer.pos.y + cpuPlayer.rect_height / 2): - self.pos.x = min(self.SCREEN_WIDTH, self.pos.x) - self.vel.x = -1 * (self.vel.x + self.speed * 0.05) - self.vel.y += cpuPlayer.vel.y * 0.006 - self.pos.x -= self.radius - is_pad_hit = True + #Using pygame collision detection, we can pass in the objects directly, first the player + if pygame.sprite.collide_rect(self, agentPlayer): + self.pos.x = max(0, self.pos.x) + self.vel.x = -1 * (self.vel.x + self.speed * 0.05) + self.vel.y += agentPlayer.vel.y * 2.0 + self.pos.x += self.radius + is_pad_hit = True + + #then we test the cpu, much cleaner! + if pygame.sprite.collide_rect(self, cpuPlayer): + self.pos.x = min(self.SCREEN_WIDTH, self.pos.x) + self.vel.x = -1 * (self.vel.x + self.speed * 0.05) + self.vel.y += cpuPlayer.vel.y * 0.006 + self.pos.x -= self.radius + is_pad_hit = True # Little randomness in order not to stuck in a static loop if is_pad_hit: diff --git a/tests/test_pong.py b/tests/test_pong.py index cfcc7c5..3e095c1 100644 --- a/tests/test_pong.py +++ b/tests/test_pong.py @@ -3,22 +3,7 @@ from ple.games.pong import Pong, Ball, Player import pygame, time, sys, pytest -@pytest.mark.parametrize("x1,y1,x2,y2,x3,y3,x4,y4,expected", [ - (0,0,10,10,0,10,10,0,True), #X-like intersection of lines - (0,0,0,5,0,0,5,0,True), #Shared first end-point (0,0) - (0,0,0,5.00000000009,10,10,0,5.0000000001,False), #nearly touching end-point - (0,5,0,0,0,5,0,0,True), #entirely overlapping lines (both end-points shared) - (1,1,1,6,2,1,2,6,False), #parallel lines (FAILS: Division by zero) - (1,6,1,1,2,6,2,1,False), #parallel lines, rotated 90 degrees (FAILS: Division by zero) - (5,5,10,10,6,6,11,11,False), #parallel lines at a 45 degree angle (FAILS: Division by zero) - (0,0,0,10,10,5,0,5,True), #T-like intersection, with one end-point meeting another line midway -]) - -def test_line_intersection(x1,y1,x2,y2,x3,y3,x4,y4,expected): - game=Ball(10,1,1,(50,50),100,100) #these parameters do not affect the line_intersection function - answer = game.line_intersection(x1,y1,x2,y2,x3,y3,x4,y4) - assert answer == expected - +#This test passes if a positive difference in movement speed is detected def test_movement_up(): game=Pong() p=PLE(game, display_screen=True, fps=20, force_fps=1) @@ -29,6 +14,7 @@ def test_movement_up(): newState=p.getGameState() assert oldState["player_velocity"] > newState["player_velocity"] +#This tests passes is a negative difference in movement speed is detected def test_movement_down(): game=Pong() p=PLE(game, display_screen=True, fps=20, force_fps=1) @@ -39,35 +25,27 @@ def test_movement_down(): newState=p.getGameState() assert oldState["player_velocity"] < newState["player_velocity"] +#This test passes if an exception is thrown when a negative cpu speed is specified def test_negative_cpu_speed(): with pytest.raises(Exception): game=Pong(cpu_speed_ratio=-1) +#This test passes if an exception is thrown when a negative player speed is specified def test_negative_player_speed(): with pytest.raises(Exception): game=Pong(players_speed_ratio=-1) +#This test passes if an exception is thrown when a negative ball speed is specified def test_negative_ball_speed(): with pytest.raises(Exception): game=Pong(ball_speed_ratio=-1) +#This test passes if an exception is thrown when a negative game size is specified def test_invalid_game_size(): with pytest.raises(Exception): game=Pong(width=-200, height=-200) +#This test passes if an exception is thrown when a negative max score is specified def test_invalid_max_score(): with pytest.raises(Exception): - game=Pong(MAX_SCORE=-1) - -#I'm commenting out this test currently because it is unclear whether the game should -# throw an exception for an undefinied action, or do nothing (basically a wait step) -# Refer to ple.py lines 361-367 in the definition of act(int) for this -# -#def test_invalid_action_input(): -# game=Pong() -# p=PLE(game, display_screen=True, fps=20, force_fps=1) -# p.init() -# time.sleep(.5) -# with pytest.raises(Exception): -# p.act(10) - + game=Pong(MAX_SCORE=-1) \ No newline at end of file diff --git a/tests/test_raycastmaze.py b/tests/test_raycastmaze.py index d12f430..1dc2f79 100644 --- a/tests/test_raycastmaze.py +++ b/tests/test_raycastmaze.py @@ -3,31 +3,37 @@ from ple.games.raycastmaze import RaycastMaze import pygame, time, sys, pytest - +#This test passes if an exception is thrown when a negative initial position is specified def test_oob_init_pos(): with pytest.raises(Exception): game=RaycastMaze(init_pos=(-1,-1)) +#This test passes if an exception is thrown when the map size is below the minimum for the algorithm (5) def test_below_min_map_size(): with pytest.raises(Exception): game=RaycastMaze(map_size=3) +#This test passes if an exception is thrown when a large map size is specified def test_beyond_max_map_size(): with pytest.raises(Exception): game=RaycastMaze(map_size=400) +#This test passes if an exception is thrown when a negative move speed is specified def test_negative_move_speed(): with pytest.raises(Exception): game=RaycastMaze(move_speed=-1) +#This test passes if an exception is thrown when a negative turn speed is specified def test_negative_turn_speed(): with pytest.raises(Exception): game=RaycastMaze(turn_speed=-1) +#This test passes if an exception is thrown when a non-usefully large turn speed is specified def test_beyond_max_turn_speed(): with pytest.raises(Exception): game=RaycastMaze(turn_speed=400) +#This test passes if an exception is thrown when a negative window size is specified def test_negative_game_size(): with pytest.raises(Exception): game=RaycastMaze(width=-100,height=-100) From c216f0b87ce8fa9a369dfca7464c65b5e3c154f1 Mon Sep 17 00:00:00 2001 From: CiscoBoschetti <46571728+CiscoBoschetti@users.noreply.github.com> Date: Mon, 18 Mar 2019 11:13:36 -0700 Subject: [PATCH 08/10] Update __init__.py --- ple/games/flappybird/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ple/games/flappybird/__init__.py b/ple/games/flappybird/__init__.py index e24cfbd..8efbc6e 100644 --- a/ple/games/flappybird/__init__.py +++ b/ple/games/flappybird/__init__.py @@ -6,6 +6,7 @@ from pygame.constants import K_w from .. import base +#Testing class BirdPlayer(pygame.sprite.Sprite): From c5c904e4159eaad84047cc4594a53dcac4637185 Mon Sep 17 00:00:00 2001 From: CiscoBoschetti <46571728+CiscoBoschetti@users.noreply.github.com> Date: Mon, 18 Mar 2019 11:42:35 -0700 Subject: [PATCH 09/10] Update __init__.py --- ple/games/flappybird/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ple/games/flappybird/__init__.py b/ple/games/flappybird/__init__.py index 8efbc6e..5fd6e51 100644 --- a/ple/games/flappybird/__init__.py +++ b/ple/games/flappybird/__init__.py @@ -6,7 +6,6 @@ from pygame.constants import K_w from .. import base -#Testing class BirdPlayer(pygame.sprite.Sprite): @@ -114,6 +113,7 @@ def __init__(self, self.image.set_colorkey((0, 0, 0)) self.init(gap_start, gap_size, offset, color) + assert gap_size >=30, "Gap size not big enough!" #check if the bird can't fly through the pipes. Update to adjust after testing if needed def init(self, gap_start, gap_size, offset, color): self.image.fill((0, 0, 0)) From 05fa154f6bf9863ca9484f2c6b2ac0f9c8bf3f73 Mon Sep 17 00:00:00 2001 From: CiscoBoschetti <46571728+CiscoBoschetti@users.noreply.github.com> Date: Mon, 18 Mar 2019 11:46:15 -0700 Subject: [PATCH 10/10] Update __init__.py --- ple/games/flappybird/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ple/games/flappybird/__init__.py b/ple/games/flappybird/__init__.py index 5fd6e51..b2b5f50 100644 --- a/ple/games/flappybird/__init__.py +++ b/ple/games/flappybird/__init__.py @@ -113,7 +113,7 @@ def __init__(self, self.image.set_colorkey((0, 0, 0)) self.init(gap_start, gap_size, offset, color) - assert gap_size >=30, "Gap size not big enough!" #check if the bird can't fly through the pipes. Update to adjust after testing if needed + assert gap_size >=30, "Gap size not big enough!" #check if the bird can't fly through the pipes. Update to adjust after testing if needed def init(self, gap_start, gap_size, offset, color): self.image.fill((0, 0, 0))