class Game: def __init__(self, canvas, starting_level): self.canvas = canvas self.clock = pygame.time.Clock() self.images = self.load_images() self.help_image = pygame.image.load(os.path.join('images', 'help.png')) self.event_handler = EventHandler(self) self.boids_need_food = False self.state = enums.GameState.READ_INTRO self.font = pygame.font.Font(os.path.join('fonts', 'Acme-Regular.ttf'), Settings.font_size) lines = """Your butterfly will be affected by the others around it and vice versa But you do have some control over it. And, as the leader, you have the most influence Left and right arrow keys to steer your butterfly Up and down arrows keys to go faster and slower Space bar to reset speed to normal You can move to the next level once you have fullfilled your mission You, and only you, can also go back to the previous level Don't worry if you fly off the screen - wait and you'll get back """ self.help_lines = [line.strip() for line in lines.split('\n')] self.time_keeper = None self.recent_buttons = {} self.food_sources = [] self.active_forces = { 'Separation', 'Alignment', 'Cohesion', 'Boundaries', 'Attractor', } self.levels = [ levels.Level01(self), levels.Level02(self), levels.Level03(self), levels.Level04(self), levels.Level05(self), ] # Start at level starting_level - 1 # Zero index the level first starting_level -= 1 self.level = self.levels[starting_level] if starting_level > 0: # Move the flock to the starting level for boid in self.levels[0].flock.boids: self.level.flock.boids.append(boid) boid.flock = self.level.flock self.levels[0].flock.boids = [] leader = self.levels[0].flock.leader self.levels[0].flock.leader = None self.level.flock.leader = leader for level in self.levels[:starting_level + 1]: level.leader_enters() self.buttons = { 'ok': Button(self, 'ok', pygame.Vector2(1723, 915)), 'help': Button(self, 'help', pygame.Vector2(1723, 915)), 'pause': Button(self, 'pause', pygame.Vector2(1655, 915)), 'play': Button(self, 'play', pygame.Vector2(1655, 915)), } for name in 'help', 'pause', 'play': self.buttons[name].visible = False def next_level(self, level): current_level_index = self.levels.index(level) if current_level_index > len(self.levels): return None return self.levels[current_level_index + 1] def previous_level(self, level): current_level_index = self.levels.index(level) if current_level_index == 0: return None return self.levels[current_level_index - 1] @property def leader(self): return self.level.flock.leader @property def is_mating_season(self): return self.time_keeper.is_mating_season def handle_event(self, event, boid=None): self.event_handler.handle_event(event, boid) def load_images(self): result = {} for size in 'small', 'large': for sex in 'male', 'female': source_image = pygame.image.load( os.path.join('images', 'butterflies', f'{size}_{sex}.png')) for angle in range(0, 360, 15): result[(size, sex, angle)] = utilities.rotate_in_place( source_image, angle + 90) # for butterfly_type in os.listdir(source_folder): # for butterfly_type in ['large_red']: # if not os.path.isdir(source_folder + butterfly_type): # continue # # for angle in range(0, 360, 15): # for step in range(16): # result[(butterfly_type, angle, step)] = pygame.image.load( # f'{source_folder}{butterfly_type}/butterfly_{angle:03}_step_{step:02}.png' # ) return result def write_text(self, lines, location): for i, line in enumerate(lines): y = location.y + i * Settings.line_spacing self.canvas.blit( self.font.render(line, True, pygame.Color('white')), (location.x, y)) def show_intro(self): lines = [ line.strip() for line in self.level.introduction_text.split('\n') if line.strip() ] self.write_text(lines, Settings.console_text_location) def show_help(self): self.canvas.blit(self.help_image, (0, 0)) self.write_text(self.help_lines, Settings.help_text_location) def show_play_console(self): lines = [self.level.aim_text] boid_count = [(i, len(level.flock.boids)) for (i, level) in enumerate(self.levels, 1)] status = ', '.join(f'{i}: {count}' for (i, count) in boid_count) lines.append(f'Butterflies by level: {status}') self.write_text(lines, Settings.console_text_location) def show_console(self): for button in self.buttons.values(): button.visible = False if self.state == enums.GameState.READ_INTRO: self.buttons['ok'].visible = True self.show_intro() elif self.state == enums.GameState.PLAY: self.buttons['pause'].visible = True self.buttons['help'].visible = True self.show_play_console() elif self.state == enums.GameState.PAUSE: self.buttons['play'].visible = True elif self.state == enums.GameState.HELP: self.buttons['ok'].visible = True self.show_help() [button.draw() for button in self.buttons.values()] def draw(self): # self.canvas.blit(self.background, (-self.background_offset, 0)) # [flower.draw() for flower in self.flowers] # [flock.draw() for flock in self.flocks] # [food_source.draw() for food_source in self.food_sources] # if self.is_mating_season: # pygame.draw.rect(self.canvas, pygame.Color('orange'), (20, 20, 50, 50), 5) self.level.draw() if self.time_keeper: self.time_keeper.draw() self.show_console() pygame.display.flip() def unbounce_button(self, button): if button in [ pygame.K_UP, pygame.K_LEFT, pygame.K_RIGHT, pygame.K_DOWN ]: return True last_time = self.recent_buttons.get(button) now = datetime.datetime.now() self.recent_buttons[button] = now return last_time is None or (now - last_time) > datetime.timedelta( milliseconds=200) def plant_seed(self, location): self.flowers.append(Flower(self, location)) def set_current_level(self): for level in self.levels: if level.contains_leader: self.level = level return # TODO: Tidy this up, in wrong place def mouse_pressed(self, name): if name in ['ok', 'play']: self.state = enums.GameState.PLAY elif name == 'pause': # self.state = enums.GameState.PAUSE self.state = enums.GameState.READ_INTRO elif name == 'help': self.state = enums.GameState.HELP def update(self, duration): # [flower.update(duration) for flower in self.flowers] # [flock.update(duration) for flock in self.flocks] # [food_source.update(duration) for food_source in self.food_sources] # leader_boids = [ # boid # for flock in self.flocks # for boid in flock.boids # if boid.is_leader # ] done = False for event in pygame.event.get(): if event.type == pygame.KEYDOWN: if self.unbounce_button(event.key): self.event_handler.handle_key(event.key) elif event.type == pygame.MOUSEBUTTONDOWN: mouse_position = pygame.mouse.get_pos() [ button.handle_mouse_click(mouse_position) for button in self.buttons.values() ] elif event.type == pygame.QUIT: done = True if self.state == enums.GameState.PLAY: # [level.update(duration) for level in self.levels] self.level.update(duration) self.set_current_level() return done def run(self): self.clock.tick(30) done = False while not done: duration = self.clock.tick(30) / 1000 done = self.update(duration) self.draw()