class DeleteAccountScene(Scene): header = None confirm_text = None yes_btn = None no_btn = None # Create the various gui elements def start(self, screen): width = screen.get_width() height = screen.get_height() if not self.already_loaded: self.header = Label(pygame.rect.Rect(width / 2 - 200, 10, 400, 30), "Delete an account", options={ Options.BACKGROUND: (20, 61, 89), Options.FOREGROUND: (244, 180, 26), Options.BORDER_WIDTH: 0, }) self.confirm_text = Label(pygame.rect.Rect(width / 2 - 200, height / 2 - 50, 400, 30), "", options={ Options.BACKGROUND: (82, 173, 200), Options.FOREGROUND: (20, 61, 89), Options.BORDER_WIDTH: 0 }) self.yes_btn = Button(pygame.rect.Rect(width / 2 - 200, height / 2 + 50, 50, 40), "Yes", options={ Options.BACKGROUND: (20, 61, 89), Options.FOREGROUND: (244, 180, 26), Options.HOVERED_BACKGROUND: (10, 30, 45), Options.BORDER_WIDTH: 0, }) self.no_btn = Button(pygame.rect.Rect(width / 2 + 150, height / 2 + 50, 50, 40), "No", options={ Options.BACKGROUND: (20, 61, 89), Options.FOREGROUND: (244, 180, 26), Options.HOVERED_BACKGROUND: (10, 30, 45), Options.BORDER_WIDTH: 0, }) self.already_loaded = True self.confirm_text.text = f"Are you sure you want to delete account {Account.account_to_delete.name}?" self.confirm_text.recreate() def update(self, event): for elt in (self.yes_btn, self.no_btn): elt.update(event) if self.yes_btn.clicked: Account.delete_account(Account.account_to_delete) Account.account_to_delete = None Scene.push_scene(1) if self.no_btn.clicked: Scene.push_scene(1) # Clear the screen and draw the gui def draw(self, screen): screen.fill((82, 173, 200)) for elt in (self.header, self.confirm_text, self.yes_btn, self.no_btn): elt.draw(screen)
class GameScene(Scene): GAME_OVER_EVENT = pygame.USEREVENT + 1 game_over = False ENEMY_MOVE_DELAY = 100 world = None table = None players = None current_player_index = -1 victory_state = -1 move_made = True turn_count = 0 ai_counter = 0 turn_text = None victory_text = None drag_start_pos = None mouse_dragging = False drag_end_pos = None player_sprites = [] width, height = 0, 0 def start(self, screen): if not self.already_loaded: self.width, self.height = screen.get_width(), screen.get_height() self.turn_text = Label( pygame.Rect(10, 10, 100, 30), "", { Options.BACKGROUND: (0, 51, 102), Options.FOREGROUND: (255, 255, 255), Options.BORDER_WIDTH: 0, }) self.victory_text = Label( pygame.Rect(self.width / 2 - 100, self.height / 2 - 30, 200, 60), "", { Options.FONT: pygame.font.SysFont("Comic Sans MS", 25), Options.BACKGROUND: (0, 51, 102), Options.FOREGROUND: (255, 255, 255), Options.BORDER_WIDTH: 0, }) self.reset_game_data() self.already_loaded = True if GameScene.game_over: self.reset_game_data() def reset_game_data(self): GameScene.game_over = False self.world = World((0, 0)) world_center = screen_to_world((self.width / 2, self.height / 2)) table_size = screen_to_world((1200, 600)) self.table = Table(world_center, table_size, self.world) self.players = [ Pen(PenData.current_pen, (world_center[0] - 35, world_center[1]), self.table.body, self.world), Pen(PenData.current_enemy_pen, (world_center[0] + 35, world_center[1]), self.table.body, self.world) ] self.ai_counter = GameScene.ENEMY_MOVE_DELAY self.turn_count = 0 self.move_made = True self.current_player_index = 1 self.victory_state = -1 self.player_sprites = { p: PenSprite(Resources.get(p.data.image_file)) for p in self.players } self.next_turn() def update(self, event): if event.type == pygame.KEYDOWN and event.key == pygame.K_p: Scene.push_scene(8) elif self.current_player_index == 0 and not self.move_made: if event.type == pygame.MOUSEBUTTONDOWN and not self.mouse_dragging: self.drag_start_pos = self.drag_end_pos = pygame.mouse.get_pos( ) self.mouse_dragging = True elif event.type == pygame.MOUSEMOTION and self.mouse_dragging: self.drag_end_pos = pygame.mouse.get_pos() elif event.type == pygame.MOUSEBUTTONUP and self.mouse_dragging: self.drag_end_pos = pygame.mouse.get_pos() drag_start = Vec2( screen_to_world((self.drag_start_pos[0], self.height - self.drag_start_pos[1]))) drag_end = Vec2( screen_to_world((self.drag_end_pos[0], self.height - self.drag_end_pos[1]))) hit_point = GameScene.cast_ray(self.players[0].fixture, drag_start, drag_end) if hit_point is not None: randomness = random.random() * 5 - 2.5 force = (hit_point - drag_start) * (20 + randomness) self.players[0].apply_force(hit_point, force) self.move_made = True self.mouse_dragging = False elif event.type == GameScene.GAME_OVER_EVENT: pygame.time.set_timer(GameScene.GAME_OVER_EVENT, 0) GameScene.game_over = True GameOverScene.result = GameResult(self.victory_state) Scene.push_scene(10) def draw(self, screen): screen.fill((0, 51, 102)) table_rect = pygame.Rect((0, 0), world_to_screen(self.table.dimensions)) table_rect.center = world_to_screen(self.table.center) pygame.draw.rect(screen, (204, 102, 0), table_rect) if self.victory_state == -1: self.turn_text.draw(screen) self.draw_player(screen, self.players[0]) self.draw_player(screen, self.players[1]) elif self.victory_state != GameResult.VictoryState.TIE: self.draw_player(screen, self.players[self.victory_state.value]) self.check_bounds() if self.mouse_dragging: drag_start = Vec2( screen_to_world((self.drag_start_pos[0], self.height - self.drag_start_pos[1]))) drag_end = Vec2( screen_to_world((self.drag_end_pos[0], self.height - self.drag_end_pos[1]))) hit_point = GameScene.cast_ray(self.players[0].fixture, drag_start, drag_end) if hit_point is not None: end = world_to_screen(hit_point) end = (end[0], self.height - end[1]) GameScene.draw_arrow(screen, (0, 255, 0), self.drag_start_pos, end) else: GameScene.draw_arrow(screen, (255, 0, 0), self.drag_start_pos, self.drag_end_pos) if self.victory_state != -1: self.victory_text.draw(screen) if self.victory_state == -1 and not self.move_made and self.current_player_index == 1: if self.ai_counter == 0: self.ai_counter = GameScene.ENEMY_MOVE_DELAY target, hit_point = PenData.current_enemy_strategy( self.players[1], self.players[0], self.table) randomness = random.random() * 10 - 5 force = (target - hit_point) * (20 + randomness) self.players[1].apply_force(hit_point, force) self.move_made = True else: self.ai_counter -= 1 self.world.Step(1.0 / 30.0, 20, 20) self.world.ClearForces() def next_turn(self): self.move_made = False self.current_player_index = 1 - self.current_player_index self.turn_text.text = f"Player's Turn" if self.current_player_index == 0 else f"Computer's Turn" self.turn_text.recreate(False) def check_bounds(self): if self.victory_state == -1: player_outside = not self.table.contains_point( self.players[0].body.position) comp_outside = not self.table.contains_point( self.players[1].body.position) if player_outside and comp_outside: self.set_winner(GameResult.VictoryState.TIE) elif (GameScene.velocity_near_zero(self.players[0]) or player_outside) and \ (GameScene.velocity_near_zero(self.players[1]) or comp_outside): if player_outside: self.set_winner(GameResult.VictoryState.LOSE) elif comp_outside: self.set_winner(GameResult.VictoryState.WIN) elif self.move_made: self.next_turn() def set_winner(self, result): self.victory_state = result if result == GameResult.VictoryState.TIE: self.victory_text.text = f"It's a tie!" elif result == GameResult.VictoryState.LOSE: self.victory_text.text = f"Computer wins!" elif result == GameResult.VictoryState.WIN: self.victory_text.text = f"Player wins!" self.victory_text.recreate() pygame.time.set_timer(GameScene.GAME_OVER_EVENT, 2000) def draw_player(self, screen, player): position = player.body.position position = world_to_screen(position) position = (position[0], self.height - position[1]) rotation = player.body.angle rotation = math.degrees(rotation) sprite = self.player_sprites[player] sprite.set_transform(position, rotation) sprite.draw(screen) @staticmethod def cast_ray(fixture, start, end): input = RayCastInput(p1=start, p2=end, maxFraction=1.1) output = RayCastOutput() if fixture.RayCast(output, input, 0): hit_point = start + (end - start) * output.fraction return hit_point return None @staticmethod def draw_arrow(surface, color, start, end, line_size=3, arrow_size=7): pygame.draw.line(surface, color, start, end, line_size) rotation = math.atan2(start[1] - end[1], end[0] - start[0]) + math.pi / 2 pygame.draw.polygon( surface, color, [(end[0] + arrow_size * math.sin(rotation), end[1] + arrow_size * math.cos(rotation)), (end[0] + arrow_size * math.sin(rotation - 2.0944), end[1] + arrow_size * math.cos(rotation - 2.0944)), (end[0] + arrow_size * math.sin(rotation + 2.0944), end[1] + arrow_size * math.cos(rotation + 2.0944))]) @staticmethod def velocity_near_zero(player, linear_threshold=0.2, angular_threshold=0.5): return player.body.linearVelocity.lengthSquared <= linear_threshold ** 2 \ and player.body.angularVelocity <= angular_threshold
class PenSelectScene(Scene): header = None back_btn = None select_btn = None purchase_btn = None name_text = None density_text = None restitution_text = None description_lines = [] coins_image = None coins_text = None pen_images = [] visible_pen_images = [] left_btn, right_btn = None, None center_pos = None pen_index = 0 def start(self, screen): if not self.already_loaded: PenData.load_all_pens(Resources.get("all_pens")) width = screen.get_width() height = screen.get_height() self.back_btn = Button( pygame.Rect(10, 10, 60, 40), "Back", { Options.BORDER_WIDTH: 0, Options.BACKGROUND: (20, 61, 89), Options.FOREGROUND: (244, 180, 26), Options.HOVERED_BACKGROUND: (10, 30, 45), Options.FONT: pygame.font.SysFont("Comic Sans MS", 15) }) label_options = { Options.BACKGROUND: (20, 61, 89), Options.FOREGROUND: (244, 180, 26), Options.BORDER_WIDTH: 0, } self.header = Label(pygame.Rect(width / 2 - 200, 10, 400, 30), "Select your weapon!", label_options) self.coins_text = Label( pygame.Rect(width - 110, height - 55, 100, 40), "0", label_options) label_options = { Options.BACKGROUND: (82, 173, 200), Options.FOREGROUND: (20, 61, 89), Options.BORDER_WIDTH: 0, Options.FONT: pygame.font.SysFont("Comic Sans MS", 18) } self.density_text = Label(pygame.Rect(width / 5, 110, 100, 20), "Density: ", label_options) self.restitution_text = Label(pygame.Rect(width / 5, 130, 100, 20), "Restitution: ", label_options) self.name_text = Label( pygame.Rect(width / 2 - 45, height - 125, 90, 50), "", label_options) self.description_lines = [ Label(pygame.Rect(width * 2 / 3, 100 + i * 25, 100, 20), "", label_options) for i in range(0, 3) ] self.coins_image = Image( pygame.Rect(width - 175, height - 60, 50, 50), Resources.get("coin"), {Options.BACKGROUND: (82, 173, 200)}) btn_options = { Options.BORDER_WIDTH: 0, Options.BACKGROUND: (20, 61, 89), Options.FOREGROUND: (244, 180, 26), Options.HOVERED_BACKGROUND: (10, 30, 45), Options.FONT: pygame.font.SysFont("Comic Sans MS", 25) } self.left_btn = Button(pygame.Rect(10, height / 2 - 20, 20, 30), "<", btn_options) self.right_btn = Button( pygame.Rect(width - 30, height / 2 - 20, 20, 30), ">", btn_options) self.select_btn = Button( pygame.Rect(width / 2 - 45, height - 75, 90, 50), "Select", btn_options) self.purchase_btn = Button( pygame.Rect(width / 2 - 125, height - 75, 250, 40), "", btn_options) self.center_pos = pygame.Rect(width / 2 - 50, height / 2 - 50, 100, 100) for pen in PenData.all_pens: self.pen_images.append( Image(self.center_pos, Resources.get(pen.image_file), {Options.BACKGROUND: (82, 173, 200)})) self.already_loaded = True self.reposition_images() self.update_shop_data() self.reset_coin_text() def update(self, event): for elt in (self.back_btn, self.left_btn, self.right_btn): elt.update(event) cur_pen = PenData.all_pens[self.pen_index] if cur_pen.name in Account.current_account.pens: self.select_btn.update(event) if self.select_btn.clicked: PenData.current_pen = cur_pen Scene.push_scene(6) else: self.purchase_btn.set_enabled( Account.current_account.money >= cur_pen.cost) self.purchase_btn.update(event) if self.purchase_btn.clicked: Account.current_account.purchase_pen(cur_pen) self.reset_coin_text() if self.back_btn.clicked: Scene.pop_scene() if self.left_btn.clicked or self.right_btn.clicked: if self.left_btn.clicked: self.pen_index -= 1 self.reposition_images() elif self.right_btn.clicked: self.pen_index += 1 self.reposition_images() self.update_shop_data() def reposition_images(self): self.visible_pen_images.clear() if self.pen_index > 0: img = self.pen_images[self.pen_index - 1] self.visible_pen_images.append(img) img.rect = self.center_pos.copy().move(-300, 40) self.visible_pen_images.append(self.pen_images[self.pen_index]) self.pen_images[self.pen_index].rect = self.center_pos if self.pen_index < len(self.pen_images) - 1: img = self.pen_images[self.pen_index + 1] self.visible_pen_images.append(img) img.rect = self.center_pos.copy().move(300, 40) self.left_btn.set_enabled(self.pen_index > 0) self.right_btn.set_enabled(self.pen_index < len(self.pen_images) - 1) def reset_coin_text(self): self.coins_text.text = str(Account.current_account.money) self.coins_text.recreate() def update_shop_data(self): cur_pen = PenData.all_pens[self.pen_index] self.name_text.text = cur_pen.name self.name_text.recreate() self.density_text.text = f"Density: {cur_pen.density}" self.density_text.recreate() self.restitution_text.text = f"Restitution: {cur_pen.restitution}" self.restitution_text.recreate() line_index = 0 for line in cur_pen.description: self.description_lines[line_index].text = line self.description_lines[line_index].recreate() line_index += 1 if cur_pen.name not in Account.current_account.pens: self.purchase_btn.text = f"Purchase for {cur_pen.cost}" self.purchase_btn.recreate() def draw(self, screen): screen.fill((82, 173, 200)) for elt in (self.header, self.back_btn, self.left_btn, self.right_btn, self.name_text, self.coins_image, self.coins_text, self.density_text, self.restitution_text): elt.draw(screen) for line in self.description_lines: line.draw(screen) if PenData.all_pens[ self.pen_index].name in Account.current_account.pens: self.select_btn.draw(screen) else: self.purchase_btn.draw(screen) for img in self.visible_pen_images: img.draw(screen)
class EnemySelectScene(Scene): header = None back_btn = None select_btn = None name_text = None random_diff_btn = None easy_btn = None normal_btn = None hard_btn = None difficulty = -1 random_pen_data = None pen_images = [] visible_pen_images = [] left_btn, right_btn = None, None center_pos = None pen_index = 0 def start(self, screen): if not self.already_loaded: PenData.load_all_pens() width = screen.get_width() height = screen.get_height() self.back_btn = Button( pygame.Rect(10, 10, 60, 40), "Back", { Options.BORDER_WIDTH: 0, Options.BACKGROUND: (20, 61, 89), Options.FOREGROUND: (244, 180, 26), Options.HOVERED_BACKGROUND: (10, 30, 45), Options.FONT: pygame.font.SysFont("Comic Sans MS", 15) }) self.header = Label( pygame.Rect(width / 2 - 200, 10, 400, 30), "Choose your opponent!", { Options.BACKGROUND: (20, 61, 89), Options.FOREGROUND: (244, 180, 26), Options.BORDER_WIDTH: 0, }) self.name_text = Label( pygame.Rect(width / 2 - 45, height - 125, 90, 50), "", { Options.BACKGROUND: (82, 173, 200), Options.FOREGROUND: (20, 61, 89), Options.BORDER_WIDTH: 0, Options.FONT: pygame.font.SysFont("Comic Sans MS", 18) }) btn_options = { Options.BORDER_WIDTH: 0, Options.BACKGROUND: (20, 61, 89), Options.FOREGROUND: (244, 180, 26), Options.HOVERED_BACKGROUND: (10, 30, 45), Options.FONT: pygame.font.SysFont("Comic Sans MS", 25) } self.left_btn = Button(pygame.Rect(10, height / 2 - 20, 20, 30), "<", btn_options) self.right_btn = Button( pygame.Rect(width - 30, height / 2 - 20, 20, 30), ">", btn_options) self.select_btn = Button( pygame.Rect(width / 2 - 45, height - 75, 90, 50), "Select", btn_options) btn_options[Options.TOGGLED_BACKGROUND] = (5, 20, 30) self.random_diff_btn = ToggleButton( pygame.Rect(width * 1 / 5 - 50, 100, 100, 30), "Random", btn_options) self.easy_btn = ToggleButton( pygame.Rect(width * 2 / 5 - 50, 100, 100, 30), "Easy", btn_options) self.normal_btn = ToggleButton( pygame.Rect(width * 3 / 5 - 50, 100, 100, 30), "Normal", btn_options) self.hard_btn = ToggleButton( pygame.Rect(width * 4 / 5 - 50, 100, 100, 30), "Hard", btn_options) toggle_group = [ self.random_diff_btn, self.easy_btn, self.normal_btn, self.hard_btn ] for elt in toggle_group: elt.set_group(toggle_group) self.easy_btn.toggle() self.center_pos = pygame.Rect(width / 2 - 50, height / 2 - 50, 100, 100) self.random_pen_data = PenData.dict_to_pen({ "name": "Random", "image_file": "random_pen" }) for pen in [self.random_pen_data] + PenData.all_pens: self.pen_images.append( Image(self.center_pos, Resources.get(pen.image_file), {Options.BACKGROUND: (82, 173, 200)})) self.already_loaded = True self.reposition_images() self.update_enemy_data() def update(self, event): for elt in (self.back_btn, self.left_btn, self.right_btn, self.select_btn, self.random_diff_btn, self.easy_btn, self.normal_btn, self.hard_btn): elt.update(event) if self.select_btn.clicked: PenData.current_enemy_pen = \ random.choice(PenData.all_pens) if self.pen_index == 0 else \ PenData.all_pens[self.pen_index - 1] PenData.current_enemy_difficulty = \ AIDifficulty.EASY if self.easy_btn.toggled else \ AIDifficulty.NORMAL if self.normal_btn.toggled else \ AIDifficulty.HARD if self.hard_btn.toggled else \ random.choice(list(AIDifficulty)) Scene.push_scene(7) if self.back_btn.clicked: Scene.pop_scene() if self.left_btn.clicked or self.right_btn.clicked: if self.left_btn.clicked: self.pen_index -= 1 self.reposition_images() elif self.right_btn.clicked: self.pen_index += 1 self.reposition_images() self.update_enemy_data() def reposition_images(self): self.visible_pen_images.clear() if self.pen_index > 0: img = self.pen_images[self.pen_index - 1] self.visible_pen_images.append(img) img.rect = self.center_pos.copy().move(-300, 40) self.visible_pen_images.append(self.pen_images[self.pen_index]) self.pen_images[self.pen_index].rect = self.center_pos if self.pen_index < len(self.pen_images) - 1: img = self.pen_images[self.pen_index + 1] self.visible_pen_images.append(img) img.rect = self.center_pos.copy().move(300, 40) self.left_btn.set_enabled(self.pen_index > 0) self.right_btn.set_enabled(self.pen_index < len(self.pen_images) - 1) def update_enemy_data(self): self.name_text.text = self.random_pen_data.name if self.pen_index == 0 else \ PenData.all_pens[self.pen_index - 1].name self.name_text.recreate() def draw(self, screen): screen.fill((82, 173, 200)) for elt in (self.header, self.back_btn, self.left_btn, self.right_btn, self.name_text, self.select_btn, self.easy_btn, self.normal_btn, self.hard_btn, self.random_diff_btn): elt.draw(screen) for img in self.visible_pen_images: img.draw(screen)