Single file implementation of alien_invasion project

Step 1: Merge the code. After merging, the test can be run directly. There are 405 lines of code in total.
Step 2: Delete duplicate import statements
pygame.font does not need to be introduced separately
The settings, game_stats, button, scoreboard, ship, and alien classes are directly defined in the file and do not need to be introduced again.
The functions in the game_factions class are defined directly in the file and do not need to be introduced. Delete and modify the name when the function is called (remove the alias gf)
There are still 381 lines of code left, and the test runs without any problems.
Step 3: Adjust the order of the following categories
Step 4: Analyze the streamlined code
Add the variable screen_rect and pass it as a parameter to reduce the use of self.screen=screen and self.screen_rect=self.screen.get_rect()
There are still 378 lines of code left, and the test runs without problems.
Step 5: Function part
Adjust functions according to the logical order of program operation, reduce function names and function calls, and reduce function nesting
Step 6: Reduce unnecessary calculation functions. The number of aliens in each row is 9 and the number of rows is 4. There is no need to pass the ship parameter.
Step 7: Use the unchanged value directly to reduce the number of variables

After completion, the code will be within 300 lines and all functions will remain unchanged.
end.

Attach all code

import pygame
from pygame.sprite import Sprite
from pygame.sprite import Group
importsys
from time import sleep

class Settings():
    def initialize_dynamic_settings(self): #Dynamic settings, the value will be increased accordingly after the level is increased
        self.ship_speed_factor = 1.5
        self.bullet_speed_factor = 3
        self.alien_speed_factor = 1
        self.fleet_direction = 1 # 1 means right, -1 means left
        self.alien_points = 50

    def increase_speed(self):
        self.ship_speed_factor *= 1.1
        self.bullet_speed_factor *= 1.1
        self.alien_speed_factor *= 1.1
        self.alien_points = int(self.alien_points * 1.5)

class GameStats():
    def __init__(self):
        self.reset_stats()
        self.game_active = False
        self.high_score = 0
    
    def reset_stats(self):
        self.ships_left = 3
        self.score = 0
        self.level = 1

classButton():
    def __init__(self,msg,screen_rect):
        self.rect = pygame.Rect(0,0,200,50)
        self.rect.center = screen_rect.center
        self.prep_msg(msg)
    
    def prep_msg(self,msg):
        self.msg_image = pygame.font.SysFont(None,48).render(msg,True,(255,255,255),(0,255,0))
        self.msg_image_rect = self.msg_image.get_rect()
        self.msg_image_rect.center = self.rect.center

    def draw_button(self,screen):
        screen.fill((0,255,0),self.rect) # display rectangle
        screen.blit(self.msg_image,self.msg_image_rect) # display text Play

class Scoreboard():
    def __init__(self, ai_settings, stats, screen_rect, screen):
        self.ai_settings = ai_settings
        self.stats = stats

        self.prep_score() # Current score
        self.prep_high_score(screen_rect) # highest score
        self.prep_level() #Current level
        self.prep_ships(screen,screen_rect) # The number of remaining spaceships

    def prep_score(self):
        rounded_score = int(round(self.stats.score,-1)) # -1 means rounding to the left of the decimal point
        score_str = "{:,}".format(rounded_score) # thousands separator
        self.score_image = pygame.font.SysFont(None,48).render(score_str,True,(30,30,30),(230,230,230))
        self.score_rect = self.score_image.get_rect()
        self.score_rect.right = 1180
        self.score_rect.top = 20
    
    def prep_high_score(self,screen_rect):
        high_score = int(round(self.stats.high_score,-1))
        high_score_str = "{:,}".format(high_score)
        self.high_score_image =pygame.font.SysFont(None,48).render(high_score_str,True,(30,30,30),(230,230,230))
        self.high_score_rect = self.high_score_image.get_rect()
        self.high_score_rect.centerx = screen_rect.centerx
        self.high_score_rect.top = 20

    def prep_level(self):
        self.level_image = pygame.font.SysFont(None,48).render(str(self.stats.level),True,(30,30,30),(230,230,230))
        self.level_rect = self.level_image.get_rect()
        self.level_rect.right = 1180
        self.level_rect.top = self.score_rect.bottom + 10

    def prep_ships(self,screen,screen_rect):
        self.ships = Group()
        for ship_number in range(self.stats.ships_left): # Use the number of pictures to represent the number of remaining ships
            ship = Ship(screen,self.ai_settings,screen_rect)
            ship.rect.x = 10 + ship_number * ship.rect.width
            ship.rect.y = 10
            self.ships.add(ship)

    def show_score(self,screen):
        screen.blit(self.score_image,self.score_rect)
        screen.blit(self.high_score_image,self.high_score_rect)
        screen.blit(self.level_image,self.level_rect)
        self.ships.draw(screen)

class Ship(Sprite):
    def __init__(self,screen,ai_settings,screen_rect):
        super().__init__()
        self.screen = screen
        self.ai_settings = ai_settings

        self.image = pygame.image.load("images/ship.bmp")
        self.rect = self.image.get_rect()
        self.rect.centerx = screen_rect.centerx
        self.rect.bottom = screen_rect.bottom
        self.center = float(self.rect.centerx)

        self.moving_right = False
        self.moving_left = False

    def update(self):
        if self.moving_right and self.rect.right < 1200:
            self.center + = self.ai_settings.ship_speed_factor
        if self.moving_left and self.rect.left > 0:
            self.center -= self.ai_settings.ship_speed_factor

        self.rect.centerx = self.center

    def blitme(self):
        self.screen.blit(self.image,self.rect)
    
    def center_ship(self,screen_rect):
        self.center = screen_rect.centerx

class Bullet(Sprite):
    def __init__(self,ai_settings,ship):
        super().__init__()
        self.rect = pygame.Rect(0,0,3,15)
        self.rect.centerx = ship.rect.centerx
        self.rect.top = ship.rect.top
        self.y = float(self.rect.y)
        self.speed_factor = ai_settings.bullet_speed_factor

    def update(self):
        self.y -= self.speed_factor
        self.rect.y = self.y

    def draw_bullet(self,screen):
        pygame.draw.rect(screen,(60,60,60),self.rect)

class Alien(Sprite):
    def __init__(self,ai_settings):
        super().__init__()
        self.ai_settings = ai_settings

        self.image = pygame.image.load("images/alien.bmp")
        self.rect = self.image.get_rect()
        self.rect.x = self.rect.width
        self.rect.y = self.rect.height
        self.x = float(self.rect.x)

    def update(self):
        self.x + = self.ai_settings.alien_speed_factor * self.ai_settings.fleet_direction
        self.rect.x = self.x

def check_events(ship,ai_settings,bullets,play_button,stats,aliens,sb,screen_rect,screen): #Event loop
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys. exit()
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RIGHT:
                ship.moving_right = True
            elif event.key == pygame.K_LEFT:
                ship.moving_left = True
            elif event.key == pygame.K_SPACE:
                if len(bullets) < 3: # Fire, fire bullets and join the group
                    new_bullet = Bullet(ai_settings,ship)
                    bullets.add(new_bullet)
            elif event.key == pygame.K_q:
                sys. exit()
        elif event.type == pygame.KEYUP:
            if event.key == pygame.K_RIGHT:
                ship.moving_right = False
            elif event.key == pygame.K_LEFT:
                ship.moving_left = False
        elif event.type == pygame.MOUSEBUTTONDOWN:
            mouse_x, mouse_y = pygame. mouse. get_pos()
            check_play_button(play_button,mouse_x,mouse_y,stats,aliens,bullets,ai_settings,ship,sb,screen_rect,screen)

def check_play_button(play_button,mouse_x,mouse_y,stats,aliens,bullets,ai_settings,ship,sb,screen_rect,screen):
    button_clicked = play_button.rect.collidepoint(mouse_x,mouse_y)
    if button_clicked and not stats.game_active: # Click the Play button
        ai_settings.initialize_dynamic_settings()
        pygame.mouse.set_visible(False) #Hide the mouse in the game
        stats.reset_stats()
        stats.game_active = True

        sb.prep_score()
        sb. prep_high_score(screen_rect)
        sb.prep_level()
        sb. prep_ships(screen, screen_rect)

        aliens.empty()
        bullets.empty()

        create_fleet(ai_settings, aliens)
        ship. center_ship(screen_rect)
    
def update_bullets(bullets,aliens,ai_settings,stats,sb,screen_rect):
    bullets.update() # Update bullet position
    for bullet in bullets.copy(): # Detect bullets that reach the top of the screen and delete them
        if bullet.rect.bottom <= 0:
            bullets.remove(bullet)
    
    collisions = pygame.sprite.groupcollide(bullets,aliens,False,True) # Bullet and alien collision detection
    if collisions:
        for aliens in collisions. values():
            stats.score + = ai_settings.alien_points * len(aliens)
            sb.prep_score()

        if stats.score > stats.high_score: # Check the highest score
            stats.high_score = stats.score
            sb. prep_high_score(screen_rect)

    if len(aliens) == 0:
        bullets.empty()
        ai_settings.increase_speed()
        stats.level + = 1
        sb.prep_level()
        create_fleet(ai_settings,aliens)

def create_fleet(ai_settings,aliens): # Create an alien group
    for row_number in range(4):
        for alien_number in range(9):
            alien = Alien(ai_settings)
            alien_width = alien.rect.width
            alien.x = alien_width + 2 * alien_width * alien_number
            alien.rect.x = alien.x
            alien.rect.y = alien.rect.height + 2 * alien.rect.height * row_number
            aliens.add(alien)

def ship_hit(stats,aliens,bullets,ship,ai_settings,sb,screen,screen_rect): # Reset the game after a ship collision
    if stats.ships_left > 0:
        stats.ships_left -= 1
        sb.prep_ships(screen,screen_rect)
        aliens. empty()
        bullets. empty()
        create_fleet(ai_settings,aliens)
        ship.center_ship(screen_rect)
        sleep(0.5)
    else:
        stats.game_active = False
        pygame.mouse.set_visible(True)

def update_aliens(aliens,ai_settings,ship,stats,bullets,screen_rect,sb,screen):
    for alien in aliens.sprites(): # When reaching the edge of the screen, move downward and change the direction of movement
        if alien.rect.right >= 1200 or alien.rect.left <= 0:
            for alien in aliens.sprites():
                alien.rect.y += 10
            ai_settings.fleet_direction *= -1
            break
    
    aliens. update()

    if pygame.sprite.spritecollideany(ship,aliens): # Collision detection between ship and aliens
        ship_hit(stats, aliens, bullets, ship, ai_settings, sb, screen, screen_rect)
    
    for alien in aliens.sprites():
        if alien.rect.bottom >= screen_rect.bottom:
            ship_hit(stats, aliens, bullets, ship, ai_settings, sb, screen, screen_rect)
            break

def run_game():
    pygame.init()
    ai_settings = Settings()
    screen = pygame.display.set_mode((1200,800))
    screen_rect = screen. get_rect()
    pygame.display.set_caption("Alien Invasion")
    
    stats = GameStats()
    play_button = Button("Play",screen_rect)
    sb = Scoreboard(ai_settings,stats,screen_rect,screen)
    ship = Ship(screen,ai_settings,screen_rect)
    bullets = Group()
    aliens = Group()

    create_fleet(ai_settings, aliens)

    while True:
        screen.fill((230,230,230))
       
        check_events(ship, ai_settings, bullets, play_button, stats, aliens, sb, screen_rect, screen)
        
        if stats. game_active:
            ship. update()
            update_bullets(bullets, aliens, ai_settings, stats, sb, screen_rect)
            update_aliens(aliens, ai_settings, ship, stats, bullets, screen_rect, sb, screen)
                
        for bullet in bullets. sprites():
            bullet. draw_bullet(screen)

        ship. blitme()
        aliens.draw(screen)
        sb. show_score(screen)

        if not stats. game_active:
            play_button. draw_button(screen)
       
        pygame.display.flip()

run_game()