def get_line_segments(self): top_left = make_vector(self.rect.left, self.rect.top) top_right = make_vector(self.rect.right, self.rect.top) bottom_right = make_vector(self.rect.right, self.rect.bottom) bottom_left = make_vector(self.rect.left, self.rect.bottom) return [(top_left, top_right), (top_right, bottom_right), (bottom_right, bottom_left), (bottom_left, top_left)]
def __init__(self, input_state, state, size, left_player_generator, right_player_generator): assert size.width > 0 and size.height > 0 self.bounds = pygame.Rect(0, 0, size.width, size.height) self._background = config.BACKGROUND_COLOR net = Board._create_net(self.bounds) self._ball = Board._create_ball( self.bounds, initial_velocity=Board._create_initial_velocity()) left_center = self._create_paddle(paddle_type=PaddleType.VERTICAL) left_top = self._create_paddle(paddle_type=PaddleType.TOP) left_bottom = self._create_paddle(paddle_type=PaddleType.BOTTOM) right_center = self._create_paddle(player=Board.RIGHT_PLAYER) right_top = self._create_paddle(player=Board.RIGHT_PLAYER, paddle_type=PaddleType.TOP) right_bottom = self._create_paddle(player=Board.RIGHT_PLAYER, paddle_type=PaddleType.BOTTOM) self._paddles = pygame.sprite.Group(left_center, left_top, left_bottom, right_center, right_top, right_bottom) self._passives = pygame.sprite.Group(net) self._status = Board.IN_PROGRESS self._left_player = left_player_generator(input_state, left_center, left_top, left_bottom) self._right_player = right_player_generator(input_state, right_center, right_top, right_bottom) left_score_pos = make_vector(left_top.rect.centerx, left_top.rect.bottom) right_score_pos = make_vector(right_top.rect.centerx, right_top.rect.bottom) self._left_score = self._create_text( left_score_pos, self._left_player.name + ": " + str(state.points[0])) self._right_score = self._create_text( right_score_pos, self._right_player.name + ": " + str(state.points[1])) # text is centered at given pos, but this means it will overlap the top paddles somewhat delta_pos = make_vector( 0, max(0.5 * self._left_score.rect.height, 0.5 * self._right_score.rect.height)) self._left_score.set_position(self._left_score.get_position() + delta_pos) self._right_score.set_position(self._right_score.get_position() + delta_pos) self._passives.add(self._left_score, self._right_score)
def _create_initial_velocity(cls): # select a random angle to move at angle = random.uniform(0, 3.14159 * 2.0) # select a random speed speed = random.uniform(0, config.BALL_MAX_SPEED - config.BALL_MIN_SPEED) + config.BALL_MIN_SPEED # calculate velocity return make_vector(math.cos(angle), math.sin(angle)) * speed
def __init__(self, bounds, radius, velocity_vector): super().__init__() self.velocity = velocity_vector self.bounds = bounds self.position = make_vector(self.bounds.centerx, self.bounds.centery) self.image = config.BALL_SURFACE self.rect = pygame.Rect(0, 0, radius * 2, radius * 2) self.rect.center = self.position
def __init__(self, input_state, previous_state=None): super().__init__(input_state, previous_state) # rather than clone this time, take a snapshot of the board's current status # this can be used to smoothly move board off-screen and results on-screen self._snapshot = pygame.Surface(config.WINDOW_SIZE) self._snapshot_rect = pygame.Rect(0, 0, self._snapshot.get_width(), self._snapshot.get_height()) self._snapshot_y_position = 0.0 previous_state.board.draw(self._snapshot, draw_ball=False) # create "X won game" message winner, score = (previous_state.board.left_player, previous_state.games_won[0]) \ if previous_state.board.get_status() == Board.LEFT_PLAYER \ else (previous_state.board.right_player, self.games_won[1]) self._game_won = entities.TextSprite(text=winner.name + " wins! ", color=config.SCORE_COLOR, size=26) self._play_again = entities.TextSprite(text="Play again? Y/N", color=config.SCORE_COLOR, size=18) self._game_won.set_center(previous_state.board.bounds.center) below_won_prompt = make_vector( self._game_won.rect.centerx, self._game_won.rect.bottom + self._play_again.rect.height) self._play_again.set_center(below_won_prompt) # track display time self._finished = False self._next_state = None # play long victory (if appropriate) sound = config.VICTORY_LONG if winner is previous_state.board.right_player else config.FAILURE_LONG if sound is not None: sound.play()
def __init__(self, input_state, previous_state=None): super().__init__(input_state, previous_state) self._game = PlayGame(input_state, previous_state) self._display_time = 0 str_messages = \ ["Game starts in " + str(x) for x in reversed(range(1, config.COUNTDOWN_BEGIN + 1))] str_messages.append("Begin!") self._countdown_messages = [ entities.TextSprite(text=x, color=config.SCORE_COLOR) for x in str_messages ] board_center = make_vector(config.WINDOW_SIZE.width * 0.5, config.WINDOW_SIZE.height * 0.5) for msg in self._countdown_messages: center = board_center - make_vector(msg.rect.width * 0.5, -msg.rect.height * 1.5) msg.set_position(center) game_text = entities.TextSprite(text="Game " + str(sum(self.games_won) + 1), color=config.SCORE_COLOR) game_text.set_center(board_center - make_vector(0, game_text.rect.height * 2)) # determine how many more points each player needs to win left_points_needed = BeginGame.calc_points_needed( self.points[0], self.points[1]) right_points_needed = BeginGame.calc_points_needed( self.points[1], self.points[0]) left_points_text = entities.TextSprite(text=str(left_points_needed) + " points to go!", color=config.SCORE_COLOR) right_points_text = entities.TextSprite(text=str(right_points_needed) + " points to go!", color=config.SCORE_COLOR) # display how many games each player has won left_player_wins_text = entities.TextSprite( text=str(self.games_won[0]) + " wins", color=config.SCORE_COLOR) right_player_wins_text = entities.TextSprite( text=str(self.games_won[1]) + " wins", color=config.SCORE_COLOR) quarter_offset = make_vector(config.WINDOW_SIZE.width * 0.25, 0) left_points_text.set_center(board_center - quarter_offset) right_points_text.set_center(board_center + quarter_offset) quarter_offset.y = -(left_points_text.rect.height + left_player_wins_text.rect.height) left_player_wins_text.set_center(board_center - quarter_offset) quarter_offset.y *= -1 right_player_wins_text.set_center(board_center + quarter_offset) self._text_group = pygame.sprite.Group(left_player_wins_text, left_points_text, right_player_wins_text, right_points_text, game_text)
def update(self, elapsed_seconds, paddles): delta_position = self.velocity * elapsed_seconds ball_start = self.position ball_end = ball_start + delta_position # ball is on field, determine whether it has collided with any paddles old_rect = self.rect.copy() # temporarily update rect to make use of spritecollide self.rect.center = ball_end colliding_paddles = pygame.sprite.spritecollide(self, paddles, dokill=False) self.rect = old_rect if any([ Ball._intersects_paddle(ball_end, paddle) for paddle in colliding_paddles ]): # at least one collision has occurred; determine closest intersection point all_intersections = [] for paddle in colliding_paddles: for segment in paddle.get_line_segments(): closest_point = helper.closest_point_on_line( segment[0], segment[1], self.position) intersections = helper.line_line_intersection( ball_start, closest_point, segment[0], segment[1]) if len(intersections) > 0: all_intersections.extend([ (x, segment[0], segment[1], paddle, closest_point) for x in intersections if x is not None ]) if len(all_intersections) > 0: all_intersections.sort( key=self._sort_by_least_distance_squared) # get the segment that we were closest to nearest_segment = all_intersections[0] segment_start, segment_end = nearest_segment[ 1], nearest_segment[2] # find closest point on this segment to the ball closest_point = helper.closest_point_on_line( segment_start, segment_end, ball_end) # adjust position of ball such that this distance is the ball's radius segment_dir = (segment_end - segment_start).normalize() segment_normal = make_vector(segment_dir.y, -segment_dir.x) projected_dir = (self.velocity.dot(segment_normal) / (segment_normal.dot(segment_normal)) * segment_normal) # projected_dir now tells us how to move towards the nearest segment; we can use this to # ensure we're at least {radius} units away if self.velocity.magnitude() > 0: self.position = closest_point + -projected_dir.normalize( ) * config.BALL_RADIUS delta_position = pygame.Vector2( ) # overwrote delta for this frame Ball.play_sound() # adjust velocity based on the segment that was hit paddle = all_intersections[0][3] up = make_vector(0, 1) is_vertical = True if abs( up.dot(segment_dir)) > 0.98 else False if is_vertical: self.velocity.x = -self.velocity.x self.velocity.y += paddle.velocity.y * elapsed_seconds * config.PADDLE_BALL_VELOCITY_MODIFIER else: self.velocity.x += paddle.velocity.x * elapsed_seconds * config.PADDLE_BALL_VELOCITY_MODIFIER self.velocity.y = -self.velocity.y self.position += delta_position self.rect.center = self.position
def get_position(self): return make_vector(self.rect.left, self.rect.top)
class MovementDirection: LEFT = make_vector(-1, 0) RIGHT = make_vector(1, 0) UP = make_vector(0, -1) DOWN = make_vector(0, 1) STOP = make_vector()