class Stage: def __init__(self, window_size: Size, window_title: str, stage_size: Size, stage_colors: Tuple[Color, Color], clock_font: Font, clock_font_color: Color, text_box_font: Font): Rectangle.set_window(window_size, window_title) self.__width = stage_size.width self.__height = stage_size.height self.__stage_color = stage_colors[0] self.__walls_color = stage_colors[1] self.__window_width = Constants.WINDOW_WIDTH self.__window_height = Constants.WINDOW_HEIGHT self.__wall_width = (self.__window_width - self.__width) / 2 self.__wall_height = (self.__window_height - self.__height) / 2 self.__walls, self.__stage = self.__initialize_stage() self.__text_boxes = self.__initialize_text_boxes(text_box_font) clock_pos = Point(self.__width + self.__wall_width + 1, self.__height + self.__wall_height) self.__clock = Clock(clock_pos, self.__stage_color, self.__walls_color, clock_font, clock_font_color) # Initializes the stage and returns its walls and stage. def __initialize_stage(self) -> Tuple[List[Rectangle], Rectangle]: wall_rects = [ PointSize(0, 0, self.__wall_width, self.__window_height), PointSize(0, 0, self.__window_width, self.__wall_height), PointSize(self.__wall_width + self.__width, 0, self.__wall_width, self.__window_height), PointSize(0, self.__wall_height + self.__height, self.__window_width, self.__wall_height) ] stage_rect = PointSize(self.__wall_width, self.__wall_height, self.__width, self.__height) walls = list() for wall in wall_rects: walls.append( Rectangle(wall, self.__walls_color, self.__stage_color)) stage = Rectangle(stage_rect, self.__stage_color, self.__walls_color) return walls, stage # Initializes the text boxes. This part is partly hard_coded. def __initialize_text_boxes(self, font: Font) -> List[TextBox.TextBox]: text_boxes = list() colors = (self.__stage_color, self.__walls_color) position = Point(self.__width + (self.__wall_width) + 10, self.__wall_height) separations = (5, 10) per_row = 2 is_input = Constants.TEXTBOX_MATRIX_IS_INPUT data = Constants.TEXTBOX_MATRIX text_boxes = TextBox.create_matrix(position, colors, separations, per_row, is_input, data, font) position = Point(5, self.__wall_height) is_input = Constants.INSTRUCTIONS_INPUT data = Constants.INSTRUCTIONS_TEXTBOXES text_boxes += TextBox.create_matrix(position, colors, separations, per_row, is_input, data, font) return text_boxes # Returns the index of the box with the desired name. def __box_index(self, name: str) -> int: for i in range(len(self.__text_boxes)): if self.__text_boxes[i].get_name() == name: return i # Should never reach this case. return -1 # Returns the walls. def get_walls(self) -> List[Rectangle]: return self.__walls # Returns the stage. def get_stage(self) -> Rectangle: return self.__stage # Returns the stage color. def get_stage_color(self) -> Color: return self.__stage_color # Returns the wall color. def get_walls_color(self) -> Color: return self.__walls_color # Returns the Stage limits as in: x_min, y_min, x_max, y_max def get_stage_limits(self) -> Limits: return self.__stage.get_limits() # Gets the TTL in seconds. def get_ttl_seconds(self) -> int: return self.__clock.get_ttl() / 1000 def get_fps(self) -> int: return self.__clock.get_fps() # Draws all the text boxes. def draw_input_boxes(self): for box in self.__text_boxes: if box.is_input(): box.draw() # Returns True if it's under its Time To Live, otherwise False. def update_clock(self): self.__clock.update_clock() self.__clock.draw() return self.__clock.still_valid() # Return the value of each text_box on a list. def get_text_values(self) -> Dict[str, int]: return_values = dict() for text_box in self.__text_boxes: if text_box.is_input(): key_value = text_box.get_name_value() return_values[key_value[0]] = key_value[1] return return_values # Returns the closest wall to the object, its direction towards it, # and the distance to travel. def closest_wall_to(self, a: Rectangle) -> Tuple[Rectangle, Direction, int]: selected_wall, distance = Distances.closest_of_all_Linf( a, self.__walls) direction = Distances.cardinal_system_direction(a, selected_wall) return selected_wall, direction, distance # Resets the clock back to 0. def reset_clock(self): self.__clock.reset() # Sets the new TTL. def set_ttl_seconds(self, ttl: int): self.__clock.set_ttl(ttl * 1000) # Sets the new FPS. def set_fps(self, fps: int): self.__clock.set_fps(fps) # Handle the events for each text box. def handle_event(self, event: pygame.event): for text_box in self.__text_boxes: text_box.handle_event(event) # Handles the in-game updates. def handle_in_game(self, key_value: Dict[str, int]) -> bool: for key, value in key_value.items(): self.__text_boxes[self.__box_index(key)].write(str(value)) self.draw_input_boxes() return self.update_clock() # Handles the updates necessary for the new round. def new_round_stage(self, key_value: Dict[str, int]): self.reset_clock() self.handle_in_game(key_value) # Changes the boxes that won't be updated anymore to output only, and those # that will be to input. The naming is weird, but input are the only ones # that get updated. def initialize_game(self): for box in self.__text_boxes: if box.has_name(): if box.get_name() != Constants.INITIAL_CHARACTERS \ and box.get_name() != Constants.INITIAL_FOODS: box.change_type() # Changes the boxes that won't be updated anymore to output only, and those # that will be to input. The naming is weird, but input are the only ones # that get updated. def continue_game(self): for box in self.__text_boxes: if box.has_name(): if box.get_name() != Constants.TTL \ and box.get_name() != Constants.FPS: box.change_type() self.__clock.reset()
class Game: WINDOW_WIDTH = 1500 WINDOW_HEIGHT = 750 GROUND = WINDOW_HEIGHT DT = 0.01 def __init__(self, c, is_last_level=False, time_this=False, start_time=time.time()): self.time_this = time_this self.start_time = start_time self.jump_sound = None self.is_last_level = is_last_level self.SPEED_OF_LIGHT = c # 80 -> 8 -> 5.5 pygame.init() pygame.font.init() pygame.display.set_caption("Relativity Man") self.screen = pygame.display.set_mode( (self.WINDOW_WIDTH, self.WINDOW_HEIGHT), pygame.FULLSCREEN) self.player = Player(150, Game.GROUND - 300) thickness = 25 self.platforms = ( Platform(self.GROUND - 100, 100, 1600, 2 * thickness), Platform(self.GROUND - 200, 200, 200, thickness), Platform(self.GROUND - 500, 500, 200, thickness), Platform(self.GROUND - 400, 300, 200, thickness), Platform(self.GROUND - 300, 700, 200, thickness), ) self.clock_rel = Clock(50, 100, 200, 100, self.SPEED_OF_LIGHT) self.clock = Clock(160, 100, 200, 100, self.SPEED_OF_LIGHT) self.keys = {} # Automatically initialized in handle user input. self.ball = Ball(50, 50, 0) def main_loop(self): clock = pygame.time.Clock() prize = Nobel_Prize(550, 100) myfont = pygame.font.SysFont('Comic Sans MS', 30) if not self.jump_sound: self.jump_sound = pygame.mixer.Sound('assets/jump.wav') while True: try: self.clear_scene() prize.draw(self.screen) if not prize.hidden: if prize.in_prize(self.player.x, self.player.y): prize.hidden = True prize_sound = pygame.mixer.Sound('assets/powerup.wav') prize_sound.play() else: textsurface = myfont.render( "Congratulations! You got Einstein a Nobel Prize!", False, (100, 100, 100)) self.screen.blit(textsurface, (self.WINDOW_WIDTH / 2.0 - 200, 50)) prize.time_held_for += 1 if prize.time_held_for > 200: if not self.is_last_level: return 'Next Level' else: prize.hidden = False prize.time_held_for = -1 textsurface = myfont.render("Einstein", False, (100, 100, 100)) self.screen.blit(textsurface, (0, 200)) textsurface = myfont.render("Lab", False, (100, 100, 100)) self.screen.blit(textsurface, (130, 200)) if self.time_this: textsurface = myfont.render( "Time: {0:.2f}".format( (time.time() - self.start_time)), False, (100, 100, 100)) self.screen.blit( textsurface, (Game.WINDOW_WIDTH * 0.9, Game.WINDOW_HEIGHT * 0.8)) self.player.draw(self.screen) for platform in self.platforms: platform.draw(self.screen) self.clock.draw(self.screen) self.clock_rel.draw(self.screen) clock_rel_copy = copy.deepcopy(self.clock_rel) clock_rel_copy.x = self.player.x clock_rel_copy.y = self.player.y - self.player.height - 50 clock_rel_copy.draw(self.screen) vx2_c2 = self.player.vx**2 / self.SPEED_OF_LIGHT**2 vy2_c2 = self.player.vy**2 / self.SPEED_OF_LIGHT**2 v2_c2 = vx2_c2 + vy2_c2 theta = atan2(self.player.vy, self.player.vx) gamma = 1.0 / sqrt(1 - v2_c2) lorentz_y = gamma**1 * cos(theta)**2 + gamma**3 * sin(theta)**2 lorentz_x = gamma**3 * cos(theta)**2 + gamma**1 * sin(theta)**2 speed = (self.player.vx**2 + self.player.vy**2)**0.5 / self.SPEED_OF_LIGHT * 100 textsurface = myfont.render('v = {:.2f}% c'.format(speed), False, (100, 100, 100)) self.screen.blit(textsurface, (self.WINDOW_WIDTH - 200, 50)) textsurface = myfont.render('m_x = {:.2f}'.format(lorentz_x), False, (100, 100, 100)) self.screen.blit(textsurface, (self.WINDOW_WIDTH - 200, 100)) textsurface = myfont.render('m_y = {:.2f}'.format(lorentz_y), False, (100, 100, 100)) self.screen.blit(textsurface, (self.WINDOW_WIDTH - 200, 150)) textsurface = myfont.render( 'Walking Speed = {:.1f}%c'.format( self.player.walking_speed / self.SPEED_OF_LIGHT), False, (100, 100, 100)) self.screen.blit(textsurface, (300, 0)) self.show_scene() clock.tick(60) for frames in range(300): try: if self.keys['space'].state: if self.player.onGround: # self.jump_sound.play() pass except: pass player_wants_to_return = self.update_key_states() if player_wants_to_return: return 'Menu' self.handle_user_input() self.player.update(self.DT, self.SPEED_OF_LIGHT) self.player.handle_collisions(self.platforms) self.clock.update(self.DT, 0, 0, self.SPEED_OF_LIGHT) self.clock_rel.update(self.DT, self.player.vx, self.player.vy, self.SPEED_OF_LIGHT) if self.player.is_dead(self.GROUND): self.player = Player(150, Game.GROUND - 300) except ValueError: self.player.x = 150 self.player.y = Game.GROUND - 300 self.player.vx = 0 self.player.vy = 0 print( "Crashed: [Slightly] Exceeded the speed of light (its a bug)." ) def update_key_states(self): if using_joy_stick() and pygame.joystick.get_count(): joystick = pygame.joystick.Joystick(0) joystick.init() jump_button = joystick.get_button(0) d_pressed = joystick.get_hat(0)[0] > 0 a_pressed = joystick.get_hat(0)[0] < 0 for char, key_object in self.keys.items(): if char == 'space': self.keys[char] = Key(key_object.py_key, jump_button) if char == 'a': self.keys[char] = Key(key_object.py_key, a_pressed) if char == 'd': self.keys[char] = Key(key_object.py_key, d_pressed) for e in pygame.event.get(): if e.type == pygame.QUIT: pygame.quit() exit(0) if e.type == pygame.KEYDOWN: for char, key_object in self.keys.items(): if e.key == 8: return True if e.key == key_object.py_key: self.keys[char] = Key(key_object.py_key, True) if e.type == pygame.KEYUP: for char, key_object in self.keys.items(): if e.key == key_object.py_key: self.keys[char] = Key(key_object.py_key, False) def handle_user_input(self): """ To add a new key function, just add it in this if else block. It will be automatically added in the exception. Keys should be named according to: https://www.pygame.org/docs/ref/key.html under the 'Common Name' column. """ try: if self.keys['a'].state: self.player.fx = -(self.player.walking_speed + self.player.vx / self.DT) * self.player.mass if self.keys['d'].state: self.player.fx = -(-self.player.walking_speed + self.player.vx / self.DT) * self.player.mass if self.keys['space'].state: if self.player.onGround: self.jump_sound.play() # self.player.vy = -self.player.jumping_speed self.player.fy = -self.player.jumping_speed / 0.01 self.keys['space'].state = False if self.keys['escape'].state: pygame.quit() exit(0) except KeyError as e: ''' If it can't access a key (exception thrown), it will add the key automatically. ''' invalid_key = str(e).strip("'") num_keys_possible = 133 for i in range(num_keys_possible): if pygame.key.name(i) == invalid_key: self.keys[invalid_key] = Key(i, False) break @staticmethod def show_scene(): pygame.display.flip() def clear_scene(self): self.screen.fill(0)