14-1 Press P to start a new game: Since Alien Invasion uses a keyboard to control the ship, it’s a good idea to allow the player to start the game using buttons as well. Please add code to start the game when the player presses P. Maybe this will help: Extract some of the check_play_button() code into a function called start_game() and call this function in check_play_button() and check_keydown_events().

game_function.py

def start_game(ai_settings, screen, stats,ship, aliens,bullets) :
    # hide cursor
    pygame.mouse.set_visible(False)
    Reset game info
    stats.reset_stats()
    stats.game_active = True

    # Empty the list of aliens and bullets
    aliens.empty()
    bullets.empty()

    # Create a new group of aliens and center the ship
    create_fleet(ai_settings, screen, ship, aliens)
    ship.center_ship()


def check_play_button(ai_settings, screen, stats, play_button, ship, aliens, bullets, mouse_x, mouse_y) :
    "" start a new game when the player clicks the Play button. ""
    button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y)
    if button_clicked and not stats.game_active:
        start_game(ai_settings, screen, stats, ship, aliens, bullets)


def check_keydown_events(event, ai_settings, screen, stats, ship, aliens, bullets) :
    """ Response button """
    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:
        fire_bullet(ai_settings, screen, ship, bullets)
    elif event.key == pygame.K_p:
        if not stats.game_active:
            start_game(ai_settings, screen, stats, ship, aliens, bullets)
    elif event.key == pygame.K_q:
        sys.exit()


def check_events(ai_settings, screen, stats, play_button, ship, aliens, bullets) :
    """ Respond to button and mouse events """
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            check_keydown_events(event, ai_settings, screen, stats, ship,
                                 aliens,
                                 bullets)
        elif event.type == pygame.KEYUP:
            check_keyup_events(event, ship)
        elif event.type == pygame.MOUSEBUTTONDOWN:
            mouse_x, mouse_y = pygame.mouse.get_pos()
            check_play_button(ai_settings, screen, stats, play_button, ship,
                              aliens, bullets, mouse_x, mouse_y)
Copy the code

alien_invasion.py

while True:
    gf.check_events(ai_settings, screen, stats, play_button, ship, aliens,
             bullets)
Copy the code

Def check_play_button() is a function that takes some stuff out of the check_play_button() function and adjusts the parameters in the callsign appropriately. The parameters in parentheses are arranged in alphabetical order.

14-2 Shooting Exercise: Create a rectangle that moves up and down the right edge of the screen at a fixed speed. Then, create a ship on the left edge of the screen that the player can move up and down and shoot at the rectangular target. Add a Play button to start the game, end the game when the player misses the target three times, and redisplay the Play button so the player can restart the game by clicking it.

14-3 Shooting exercise with some difficulty: Based on the work you did to complete Exercise 14-2, make the target move faster as the game progresses and reset it to its initial value when the player clicks the Play button.

14-4 Highest score in history: The highest score will be reset every time the player closes and restarts alien Invasion. Fix this by writing the highest score to the file before calling sys.exit() and reading it from the file when the highest score is initialized in GameStats.

These questions are a summary of the entire PyGame chapter.

  • This answer uses pure color, no image is selected, it doesn’t look good but different color blocks show different sprites better.

  • Because this game I think is quite difficult, so the bullet set is very large. Of course, this also shows how well a single bullet hits two or more targets.

  • However, I don’t know how to change the color of the remaining bullets in the upper left corner. I haven’t found the method yet, and this is not written in the book. This is not a good keyword search on StackOverflow.

  • This answer is different from other answers on the network. The square on the right does not use a small square but makes a random number of Sprite. I see some answers are to hit a square after a new square, this answer is to hit all after a new square.

Initial screen:

Game screen:

setting.py

class Setting() :
    def __init__(self) :
        # Canvas Settings
        self.canvas_width = 1200
        self.canvas_height = 900
        self.canvas_bg_color = (105.105.105)

        # target setting
        self.target_direction = 1
        # self.target_drop_speed = 0.2 in initialize_speed_settings

        # ship set
        # self.ship_speed = 0.5 in initialize_speed_settings

        # set bullets
        self.bullet_width = 15
        self.bullet_height = 300
        self.bullet_color = (139.0.139)
        self.bullet_allowed = 1
        self.bullets_limit = 3
        # self.bullet_speed = 1.2 is placed in initialize_speed_settings

        # Speed up the scale
        self.speedup_scale = 1.1
        self.score_scale = 1.5

        Load initial Settings
        self.initialize_speed_settings()

    def initialize_speed_settings(self) :
        self.target_drop_speed = 0.2
        self.ship_speed = 0.5
        self.bullet_speed = 1.2
        self.target_points = 50

    def increase_speed(self) :
        # Speed up Settings
        self.target_drop_speed *= self.speedup_scale
        # self.ship_speed *= self.speedup_scale
        # self.bullet_speed *= self.speedup_scale

        # Set score after acceleration
        self.target_points = int(self.target_points * self.score_scale)
Copy the code

ship.py

import pygame


class Ship() :
    def __init__(self, canvas) :
        self.canvas = canvas
        self.image = pygame.Surface((40.40))
        pygame.Surface.fill(self.image, (205.133.63))
        self.rect = self.image.get_rect()
        self.canvas_rect = canvas.get_rect()
        # Hold the ticker not loose to move the plane
        self.move_up = False
        self.move_down = False
        # Locate the aircraft
        self.rect.centery = self.canvas_rect.centery
        self.rect.left = self.canvas_rect.left
        # y floating point
        self.center_y = float(self.rect.centery)

    def center_ship(self) :
        # Center the ship
        self.center_y = self.canvas_rect.centery

    def blit_ship(self) :
        # painting craft
        self.canvas.blit(self.image, self.rect)

    def update_ship(self, settings) :
        # Update ship position
        if self.move_up and self.rect.top > 0:
            self.center_y -= settings.ship_speed
        elif self.move_down and self.rect.bottom < self.canvas_rect.bottom:
            self.center_y += settings.ship_speed
        Pass the y-coordinate back to center y
        self.rect.centery = self.center_y
Copy the code

target.py

import pygame
from pygame.sprite import Sprite


class Target(Sprite) :
    def __init__(self, canvas) :
        super(Target, self).__init__()
        # screen Settings
        self.canvas = canvas
        self.image = pygame.Surface((80.50))
        pygame.Surface.fill(self.image, (60.179.113))
        Get the enclosing rectangle
        self.rect = self.image.get_rect()
        self.canvas_rect = canvas.get_rect()
        Set target to the right of the screen
        self.rect.x = self.canvas_rect.width - self.rect.width
        self.rect.y = self.rect.height
        Floating-point ordinate representation
        self.y = float(self.rect.y)

    def check_edge(self) :
        Check if target has moved to the edge of the screen
        if self.rect.bottom >= self.canvas_rect.bottom:
            return True
        elif self.rect.top <= self.canvas_rect.top:
            return True         # both return True

    def update(self, settings, *args, **kwargs) - >None:
        # update target location
        self.y += (settings.target_drop_speed * settings.target_direction)
        # pass the floating-point ordinate back to rect.y
        self.rect.y = self.y
Copy the code

scoreboard.py

import pygame.font
from pygame.sprite import Group
from bullet import Bullet


class Scoreboard() :
    def __init__(self, canvas, settings, ship, stats) :
        self.canvas = canvas
        self.canvas_rect = canvas.get_rect()
        self.settings = settings
        self.stats = stats
        # Text color and use the system default font
        self.text_color = (250.235.215)
        self.font = pygame.font.SysFont(None.48)
        Call the function that renders the text as an image
        self.prep_score()
        self.prep_high_score()
        self.prep_level()
        self.prep_bullet(canvas, settings, ship)

    def prep_bullet(self, canvas, settings, ship) :
        # Draw the number of remaining bullets in the upper left corner of the screen
        self.bullets = Group()
        # Walk through the remaining bullets stats.bullets_left
        for bullet_number in range(self.stats.bullets_left):
            # Iterate over one to generate one bullet
            bullet = Bullet(canvas, settings, ship)
            # Locate the x and y coordinates of the generated bullet
            bullet.rect.x = 10 + bullet_number * bullet.rect.width * 5  # For clear display, widen spacing
            bullet.rect.y = 10
            Add the generated bullets to the group
            self.bullets.add(bullet)

    def prep_score(self) :
        Round () The second argument to round() determines the number of decimal places, or, if negative, the exact point (negative) to the decimal point.
        rounded_score = int(round(self.stats.score, -1))
        # Text content to be displayed
        score_str = "Current Score: " + ": {and}".format(rounded_score)
        If the words # current score are written in Chinese, a box is displayed.
        Put a Chinese font file into the project folder:
        Self.font =pygame.font.SysFont(None,45)
        Self.Font =pygame.font.Font(' stzhongs.ttf ',45

        # Render text as picture
        self.score_image = self.font.render(score_str, True, self.text_color,
                                            self.settings.canvas_bg_color)
        Position the text that is rendered into the image
        self.score_rect = self.score_image.get_rect()
        self.score_rect.centerx = self.canvas_rect.centerx
        self.score_rect.top = self.canvas_rect.top + 20

    def prep_high_score(self) :
        # round the high score
        rounded_high_score = int(round(self.stats.high_score, -1))
        # High Score text to be displayed
        high_score_str = "High Score: " + ": {and}".format(rounded_high_score)
        # Render high score
        self.high_score_image = self.font.render(high_score_str, True,
                                                 self.text_color,
                                                 self.settings.canvas_bg_color)
        # Position high score
        self.high_score_rect = self.high_score_image.get_rect()
        self.high_score_rect.centerx = self.canvas_rect.centerx
        self.high_score_rect.bottom = self.canvas_rect.bottom - 20

    def prep_level(self) :
        # Render level text for images
        self.level_image = self.font.render(
            "Current Level: " + str(self.stats.level), True, self.text_color,
            self.settings.canvas_bg_color)
        # locate the position of the level
        self.level_rect = self.level_image.get_rect()
        self.level_rect.centerx = self.canvas_rect.centerx
        self.level_rect.top = self.canvas_rect.top + 60

    def show_score(self) :
        Display text rendered as images on canvas
        self.canvas.blit(self.score_image, self.score_rect)
        self.canvas.blit(self.high_score_image, self.high_score_rect)
        self.canvas.blit(self.level_image, self.level_rect)
        self.bullets.draw(self.canvas)
Copy the code

button.py

import pygame


class Button() :
    # Start button class
    def __init__(self, canvas, msg) :
        self.canvas = canvas
        self.canvas_rect = canvas.get_rect()
        # Size of start button
        self.width = 200
        self.height = 50
        # Start button background color
        self.button_color = (219.112.147)
        # Color of text on start button
        self.text_color = (255.255.255)
        # start button font
        self.font = pygame.font.SysFont(None.48)
        Generate a start button at 0,0
        self.rect = pygame.Rect(0.0, self.width, self.height)
        Move the button to the center of the screen
        self.rect.center = self.canvas_rect.center
        Call prep_msg to render text to image
        self.prep_msg(msg)

    def prep_msg(self, msg) :
        # Render text into images
        self.msg_image = self.font.render(msg, True, self.text_color,
                                          self.button_color)
        self.msg_image_rect = self.msg_image.get_rect()
        self.msg_image_rect.center = self.rect.center

    def draw_button(self) :
        Draw a button filled with color, and then draw the text on the button
        self.canvas.fill(self.button_color, self.rect)
        self.canvas.blit(self.msg_image, self.msg_image_rect)
Copy the code

bullet.py

import pygame
from pygame.sprite import Sprite


class Bullet(Sprite) :
    def __init__(self, canvas, settings, ship) :
        super(Bullet, self).__init__()
        self.canvas = canvas

        [Bug Mc-10899] - # bullet with PyGame.Rect does not draw images of remaining bullets.
        The Rect object has no image
        self.image = pygame.Surface(
            (settings.bullet_width, settings.bullet_height))
        self.rect = self.image.get_rect()
        Position the bullet with the center axis of the ship and the right part of the ship.
        self.rect.centery = ship.rect.centery
        self.rect.right = ship.rect.right
        # Bullet color Settings
        self.color = settings.bullet_color
        Float the bullet's abscissa
        self.x = float(self.rect.x)

    def draw_bullet(self) :
        # Draw bullets on canvas
        pygame.draw.rect(self.canvas, self.color, self.rect)

    def update(self, settings, *args, **kwargs) - >None:
        # Update bullet movement position
        self.x += settings.bullet_speed
        Substitute the float abscissa into rect x
        self.rect.x = self.x
Copy the code

game_stats.py

class GameStats() :
    def __init__(self, settings) :
        self.settings = settings
        # introduce the rest_STATS setting
        self.reset_stats()
        # The screen is still at the beginning
        self.game_active = False
        self.high_score = 0

    def reset_stats(self) :
        self.bullets_left = self.settings.bullets_limit
        self.score = 0
        self.level = 1
Copy the code

game_function.py

import sys
import pygame
from random import randint
from time import sleep
from bullet import Bullet
from target import Target


def update_canvas(canvas, play_button, sb, settings, ship, stats, targets) :
    """ Update the content on the canvas """
    # Fill color
    canvas.fill(settings.canvas_bg_color)
    # show spacecraft
    ship.blit_ship()
    # show targets Sprite
    targets.draw(canvas)
    # show the scoreboard
    sb.show_score()
    If the game is inactive, draw the button
    if not stats.game_active:
        play_button.draw_button()


def create_target(canvas, settings, targets) :
    """ Create target on the right side of the screen ""
    target = Target(canvas)
    target_height = target.rect.height
    # Measure how many lines of target the screen can hold
    num_row = int((settings.canvas_height - 2 * target.rect.height) / (
            2 * target_height))
    Create a random target between 1 and num_row
    for target_number in range(randint(1, num_row)):
        target = Target(canvas)
        # Location of each target
        target.y = target_height + 2 * target_height * target_number
        # assign the float value of target.y to rect.y
        target.rect.y = target.y
        Add the generated target to the sprites group
        targets.add(target)


def check_target_sprite_edges(settings, targets) :
    """ Detect bullet and target impact """
    Check if sprites touch edges
    for target in targets.sprites():
        if target.check_edge():
            If it does, set the target_direction in setting
            settings.target_direction *= -1
            break


def update_targets(settings, targets) :
    "" refresh the target on the right side of the screen ""
    Check if target touches an edge
    check_target_sprite_edges(settings, targets)
    Update the target orientation after hitting the edge
    targets.update(settings)


def update_bullets(bullets, canvas, sb, settings, ship, stats, targets) :
    """ Refresh bullet """
    Draw each bullet on the canvas after holding down space to fire
    for bullet in bullets.sprites():
        bullet.draw_bullet()
    # # Remove bullet out of screen
    # Walk through every bullet in a Bullets replica
    for bullet in bullets.copy():
        # If the bullet's left side exceeds the canvas width
        if bullet.rect.left > settings.canvas_width:
            # Remove this bullet from the pack
            bullets.remove(bullet)
            # Number of bullets in stats reduced by 1
            stats.bullets_left -= 1
            Pause 0.5 seconds for every bullet fired
            sleep(0.5)
            Every time a bullet flies, the remaining bullet in the upper left of the screen will be redrawn
            sb.prep_bullet(canvas, settings, ship)
        # If bullet count is 0
        if stats.bullets_left == 0:
            # Change the game state to false
            stats.game_active = False
            # Redisplay the mouse within the screen
            pygame.mouse.set_visible(True)
    Every time a bullet is fired, the function is called to check if the bullet and target collide right
    check_bullet_target_collision(bullets, canvas, sb, settings, stats,
                                  targets)

    # Update the bullets movement
    bullets.update(settings)


def check_high_score(sb, stats) :
    "" query current grade and best grade """
    # If the current score is greater than the best score
    if stats.score > stats.high_score:
        # Assign the current score to the best score
        stats.high_score = stats.score
        # Redraw the best result on the screen
        sb.prep_high_score()


def check_bullet_target_collision(bullets, canvas, sb, settings, stats, targets) :
    """ Check if bullet and target collide """
    collisions = pygame.sprite.groupcollide(bullets, targets, True.True)

    if collisions:
        # in check_bullet_alien_collisions(), the bullets involved in collisions with aliens are dictionaries
        A key in # collisions; And the value associated with each bullet is a list of what the bullet hit
        # aliens. We run through the dictionary collisions to ensure that each alien extinction point is accounted for:
        # {key: value} is {bullet:[hit alien 1, hit 2, hit 3... hit n]}
        for x in collisions.values():
            stats.score += settings.target_points * len(x)
            sb.prep_score()
        check_high_score(sb, stats)
        # Without the above traversal, it is possible for one bullet to hit two but only show one score. Each bullet in the dictionary is a key, and
        # value is the target it hit. So iterating through the dictionary's values, find out when a bullet hits multiple targets.

    # if all targets are hit, 0 is left
    if len(targets) == 0:
        # Empty the bullets
        bullets.empty()
        # Speed up the game
        settings.increase_speed()
        # level加1
        stats.level += 1
        Redraw the level on the canvas
        sb.prep_level()
        Rebuild a batch of targets
        create_target(canvas, settings, targets)


def check_events(bullets, canvas, play_button, sb, settings, ship, stats, targets) :
    """ Get game events """
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            check_keydown_events(bullets, canvas, event, settings, ship)
        elif event.type == pygame.KEYUP:
            check_keyup_events(event, ship)
        elif event.type == pygame.MOUSEBUTTONDOWN:
            Get the x and y coordinates of the mouse
            mouse_x, mouse_y = pygame.mouse.get_pos()
            # call function
            check_play_button(bullets, canvas, mouse_x, mouse_y, play_button,
                              sb, settings, ship, stats, targets)


def check_play_button(bullets, canvas, mouse_x, mouse_y, play_button, sb, settings, ship, stats, targets) :
    """ Gets the event after the Play button is pressed. ""
    Return True or False to check if play button(generated by button()) intersects mouse's x and y coordinates
    button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y)
    # If there is an intersection (click) and the game is not playing
    if button_clicked and not stats.game_active:
        # Game initialization
        settings.initialize_speed_settings()
        # Mouse hide not visible
        pygame.mouse.set_visible(False)
        Reset stats
        stats.reset_stats()
        # Change the game state to True to start the game
        stats.game_active = True

        # Redraw the scoreboard
        sb.prep_score()
        sb.prep_high_score()
        sb.prep_level()
        sb.prep_bullet(canvas, settings, ship)

        # Empty targets and bullets
        targets.empty()
        bullets.empty()
        Create a set of targets
        create_target(canvas, settings, targets)
        # Center the ship
        ship.center_ship()


def check_keydown_events(bullets, canvas, event, settings, ship) :
    if event.key == pygame.K_DOWN:
        ship.move_down = True
    elif event.key == pygame.K_UP:
        ship.move_up = True
    elif event.key == pygame.K_SPACE:
        fire_bullet(bullets, canvas, settings, ship)


def fire_bullet(bullets, canvas, settings, ship) :
    # After the space is pressed and the number of bullets on the screen does not exceed the limit
    if len(bullets) < settings.bullet_allowed:
        Spawn a new bullet and join the group
        new_bullet = Bullet(canvas, settings, ship)
        bullets.add(new_bullet)


def check_keyup_events(event, ship) :
    if event.key == pygame.K_DOWN:
        ship.move_down = False
    elif event.key == pygame.K_UP:
        ship.move_up = False
Copy the code

main.py

import pygame
from pygame.sprite import Group
from button import Button
from game_stats import GameStats
from scoreboard import Scoreboard
from setting import Setting
from ship import Ship
import game_function as gf


def run_game() :
    pygame.init()
    # import setting
    settings = Setting()
    # generate canvas
    canvas = pygame.display.set_mode(
        (settings.canvas_width, settings.canvas_height))
    pygame.display.set_caption(""Python Programming: Getting Started to Practice"Problem set 14-2,14-3,14-4")
    Import the start button with the word play on it
    play_button = Button(canvas, "Play")
    # import ship
    ship = Ship(canvas)
    # import stat
    stats = GameStats(settings)
    # import score board
    sb = Scoreboard(canvas, settings, ship, stats)

    # group bullet
    bullets = Group()
    Group the target
    targets = Group()
    Create a set of targets
    gf.create_target(canvas, settings, targets)

    while True:

        gf.check_events(bullets, canvas, play_button, sb, settings, ship,
                        stats, targets)

        gf.update_canvas(canvas, play_button, sb, settings, ship, stats,
                         targets)

        if stats.game_active:
            # update ship
            ship.update_ship(settings)

            # update the targets
            gf.update_targets(settings,
                              targets)  # targets. Update () places update_targets under this function

            gf.update_bullets(bullets, canvas, sb, settings, ship, stats,
                              targets)  # bullets. Update () places the update_bullets in this function

        pygame.display.flip()


run_game()
Copy the code