Ошибка со спрайтами pygame 'GameStats' object has no attribute 'sprites'

Начал изучение python по книге Эрик Метиз "Изучаем Python". Сейчас на этапе разработке игры по данной книге "Alien Invasion" столкнулся с проблемой:

Traceback (most recent call last):
  File "c:\python\alien_invasion\alien_invasion.py", line 40, in <module>
    run_game()
    ^^^^^^^^^^
  File "c:\python\alien_invasion\alien_invasion.py", line 37, in run_game
    gf.update_aliens(ai_settings, stats, screen, bullets, aliens, ship)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\python\alien_invasion\game_functions.py", line 213, in update_aliens
    check_fleet_edges(ai_settings, aliens)
  File "c:\python\alien_invasion\game_functions.py", line 182, in check_fleet_edges
    for alien in aliens.sprites():
                 ^^^^^^^^^^^^^^
AttributeError: 'GameStats' object has no attribute 'sprites'

Вот полный код:

alien_invasion.py

    import pygame
    from settings import Settings
    from ship import Ship
    from alien import Alien
    from game_stats import GameStats
    from pygame.sprite import Group
    import game_functions as gf
    
    
    
    def run_game():
        #Инициализирует игру и создает объект экрана
        pygame.init()
        ai_settings = Settings()
        screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))
        pygame.display.set_caption("Alien Invasion")
        #Создание корабля, группы пуль и пришельцев 
        stats = GameStats(ai_settings)
        ship = Ship(ai_settings, screen)
        bullets = Group()
        aliens = Group()
        drops = Group()
    
        #Создание флота пришельцев
        gf.create_rain(ai_settings, screen, drops)
        gf.create_fleet(ai_settings, screen, ship, aliens)
    
    
        #Запуск основного цикла игры
        while True:
            gf.check_ivents(ai_settings, screen, ship, bullets)
            if stats.game_active:
                ship.update()
                gf.update_bullets(ai_settings, screen, ship, aliens, bullets)
                gf.update_drops(ai_settings, screen, drops)
                gf.update_aliens(ai_settings, stats, screen, bullets, aliens, ship)  
            gf.update_screen(ai_settings, screen, ship, aliens, drops, bullets)
    
    run_game()

    

game_functions.py

import sys
import pygame
from ship import Ship
from bullet import Bullet
from alien import Alien
from random import randint
from drops import Drop
from stars import Stars
from time import sleep
from game_stats import GameStats


def get_number_drops(ai_settings, drop_height):
    """ Определяет количество рядов, помещающихся на экране """
    available_space_y = (ai_settings.screen_height - 
                         (3 * drop_height) - drop_height)
    number_drop_rows = int(available_space_y / (2 * drop_height))
    return number_drop_rows

def get_number_drop_x(ai_settings, drop_width):
    """ Вычисляет количество капель в ряду """
    available_space_x = ai_settings.screen_width - 2 * drop_width
    number_drop_x = int(available_space_x / (2 * drop_width))
    return number_drop_x

def create_drop(ai_settings, screen, drops, drop_number, row_number):
    """ Создает капли и размещает его в ряду """
    drop = Drop(ai_settings, screen)
    drop_width = drop.rect.width
    drop.x = drop_width + 2 * drop_width * drop_number
    drop.rect.x = drop.x
    drop.rect.y = drop.rect.height + 2 * drop.rect.height * row_number
    drops.add(drop)

def create_rain(ai_settings, screen, drops):
    '''Создает дождь'''
    drop = Drop(ai_settings, screen)
    number_drop_x = get_number_drop_x(ai_settings, drop.rect.width)
    number_drop_rows = get_number_drops(ai_settings, drop.rect.height)
    #Создание первого ряда капель
    for row_number in range(number_drop_rows):
        for drop_number in range(number_drop_x):
            #Создание дождя и размещение его в ряду
            create_drop(ai_settings, screen, drops, drop_number, row_number)


def get_number_rows(ai_settings, ship_height, alien_height):
    """ Определяет количество рядов, помещающихся на экране """
    available_space_y = (ai_settings.screen_height - 
                         (3 * alien_height) - ship_height)
    number_rows = int(available_space_y / (2 * alien_height))
    return number_rows

def get_number_aliens_x(ai_settings, alien_width):
    """ Вычисляет количество пришельцев в ряду """
    available_space_x = ai_settings.screen_width - 2 * alien_width
    number_aliens_x = int(available_space_x / (2 * alien_width))
    return number_aliens_x

def create_alien(ai_settings, screen, aliens, alien_number, row_number):
    """ Создает пришельца и размещает его в ряду """
    alien = Alien(ai_settings, screen)
    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 create_fleet(ai_settings, screen, ship, aliens):
    '''Создает флот пришельцев'''
    #Создание пришельца и вычисление количества пришельцев в ряду
    alien = Alien(ai_settings, screen)
    number_aliens_x = get_number_aliens_x(ai_settings, alien.rect.width)
    number_rows = get_number_rows(ai_settings, ship.rect.height, alien.rect.height)
    #Создание первого ряда пришельцев
    for row_number in range(number_rows):
        for alien_number in range(number_aliens_x):
            #Создание пришельца и размещение его в ряду
            create_alien(ai_settings, screen, aliens, alien_number, row_number)
    

def fire_bullet(ai_settings, screen, ship, bullets):
    '''Выпускает пули, если максимум еще не достигнут'''
    #Создание новой пули и включение ее в группу bullets
    if len(bullets) < ai_settings.bullets_allowed:
        new_bullet = Bullet(ai_settings, screen, ship)
        bullets.add(new_bullet)

def check_keydown_events(event, ai_settings, screen, ship, bullets):
    '''Реагирует на нажатие клавиш'''
    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_UP:
        ship.moving_up = True
    elif event.key == pygame.K_DOWN:
        ship.moving_down = True
    elif event.key == pygame.K_SPACE:
        fire_bullet(ai_settings, screen, ship, bullets)
    elif event.key == pygame.K_q:
        sys.exit()

def check_keyup_events(event, ship):
    '''Реагирует на отпускание клавиш'''
    if event.key == pygame.K_RIGHT:
        ship.moving_right = False
    elif event.key == pygame.K_LEFT:
        ship.moving_left = False
    elif event.key == pygame.K_UP:
        ship.moving_up = False
    elif event.key == pygame.K_DOWN:
        ship.moving_down = False
    

def check_ivents(ai_settings, screen, ship, bullets):
    '''Обрабатывает нажатия клавиш и события мыши'''
    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, ship, bullets)
        elif event.type == pygame.KEYUP:
            check_keyup_events(event, ship)

def update_bullets(ai_settings, screen, ship, aliens, bullets):
    '''Обновляет позиции пуль и уничтожает старые пули'''
    #обновление позиции пуль
    bullets.update()
    #Удаление старых пуль, вышедших за край экрана
    for bullet in bullets.copy():
        if bullet.rect.bottom <= 0:
            bullets.remove(bullet)
    #Проверка попаданий в пришельцев
    #При обнаружении попадания удалить пулю и пришельца
    check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets)
    

def check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets):
    """ Обработка коллизий пуль с пришельцами. """
    #Удаление пуль и пришельцев, участвующих в коллизиях
    collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
    if len(aliens) == 0:
        #Уничтожение существующих пуль и создание нового флота
        bullets.empty()
        create_fleet(ai_settings, screen, ship, aliens)

def update_screen(ai_settings, screen, ship, aliens, drops, bullets):
    '''Обновляет изображения на экране и отображает новый экран'''
    screen.fill(ai_settings.bg_color)
    drops.draw(screen)
    ship.blitme()
    aliens.draw(screen)
    #Все пули выводятся спереди изображений корабля и пришельцев
    for bullet in bullets.sprites():
        bullet.draw_bullet()

def check_fleet_edges(ai_settings, aliens):
    """ Реагирует на достижение пришельцем края экрана """
    for alien in aliens.sprites():
        if alien.check_edges():
            change_fleet_direction(ai_settings, aliens)
            break

def change_fleet_direction(ai_settings, aliens):
    """ Опускает весь флот и меняет направление флота """
    for alien in aliens.sprites():
        alien.rect.y += ai_settings.fleet_drop_speed
    ai_settings.fleet_direction *= -1

def ship_hit(ai_settings, stats, screen, ship, aliens, bullets):
    """Обрабатывает столкновение корабля с пришельцем"""
    #Уменьшение списков пришельцев и пуль
    if stats.ships_left > 0:
        stats.ships_left -= 1
        #Очистка списков пришельцев и пуль
        aliens.empty()
        bullets.empty()
        #Создание нового флота и размещение корабля в центре
        create_fleet(ai_settings, screen, ship, aliens)
        ship.center_ship()
        #Пауза
        sleep(0.5)
    else:
        stats.game_active = False


def update_aliens(ai_settings, aliens, ship, stats, screen, bullets):
    """ Проверяет, достиг ли флот края экрана, 
     после чего обновляет позиции всех пришельцев во флоте """
    check_fleet_edges(ai_settings, aliens)
    aliens.update()
    #Проверка коллизий пришелец-корабль
    if pygame.sprite.spritecollideany(ship, aliens):
        ship_hit(ai_settings, stats, screen, ship, aliens, bullets)
        check_aliens_bottom(ai_settings, stats, screen, ship, aliens, bullets)
        print("SHip hit!!!")

def check_aliens_bottom(ai_settings, stats, screen, ship, aliens, bullets):
    """Проверяет добрались ли пришельцы до нижнего края экрана"""
    screen_rect = screen.get_rect()
    for alien in aliens.get_rect:
        if alien.rect.bottom >= screen_rect.bottom:
            ship_hit(ai_settings, stats, screen, ship, aliens, bullets)
            break

def check_drop_edges(ai_settings, screen, drops):
    """ Проверяет достигнут ли низ """
    flag = False
    for drop in drops.sprites():
        if drop.check_edges():
            change_drop_direction(ai_settings, drops)
            drops.remove(drop)                
            flag = True
    if flag:
        create_rain(ai_settings, screen, drops)
                        
def change_drop_direction(ai_settings, drops):
    """ Капли стекают """
    for drop in drops.sprites():
        drop.rect.y += ai_settings.drop_speed

def update_drops(ai_settings, screen, drops):
    """ Обновляет позиции капель """
    check_drop_edges(ai_settings, screen, drops)
    drops.update(ai_settings)

    #Отображение последнего прорисованного экрана
    pygame.display.flip()

game_stats.py

    class GameStats():
        """ Отслеживание статистики для игры Alien Invasion """
        def __init__(self, ai_settings):
            """Инициализирует статистику"""
            self.ai_settings = ai_settings
            self.reset_stats()
            self.game_active = True
    
        def reset_stats(self):
            """Инициализирует статистику изменяющуюся в ходе игры"""
            self.ships_left = self.ai_settings.ship_limit
            

alien.py

import pygame
from pygame.sprite import Sprite

class Alien(Sprite):
    '''Класс представляющий одного пришельца'''
    def __init__(self, ai_settings, screen):
        '''Инициализирует пришельца и задает его начальную позицию'''
        super().__init__()
        self.screen = screen
        self.ai_settings = ai_settings

        #Загрузка изображения пришельца и назначение атрибута rect
        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 check_edges(self):
        """ Проверяет True, если пришелец находится у края экрана """
        screen_rect = self.screen.get_rect()
        if self.rect.right >= screen_rect.right:
            return True
        elif self.rect.left <= 0:
            return True

    def update(self):
        """ Перемещает пришельца вправо или влево """
        self.x += (self.ai_settings.alien_speed_factor * 
                   self.ai_settings.fleet_direction)
        self.rect.x = self.x

    def blitme(self):
        '''Выводит пришельца в текущем положении'''
        self.screen.blit(self.image, self.rect)

ship.py

import pygame

class Ship():

def __init__(self, ai_settings, screen):
    '''Инициализирует корабль и задает его начальную позицию'''
    self.screen = screen
    self.ai_settings = ai_settings

    #Загрузка изображения корабля и получение прямоугольника
    self.image = pygame.image.load('images/ship.bmp')
    self.rect = self.image.get_rect()
    self.screen_rect = screen.get_rect()

    #Каждый новый корабль появляется у нижнего края экрана
    self.rect.centerx = self.screen_rect.centerx
    self.rect.bottom = self.screen_rect.bottom
    self.center = float(self.rect.centerx)

    #Флаг перемещения
    self.moving_right = False
    self.moving_left = False

def update(self):
    '''Обновляет позицию корабля с учетом флага'''
    #Обновляется атрибут center, не rect
    if self.moving_right and self.rect.right < self.screen_rect.right:
        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
    #обновление атрибута rect на основании self.center
    self.rect.centerx = self.center

def blitme(self):
    '''Рисует корабль в текущей позиции'''
    self.screen.blit(self.image, self.rect)

def center_ship(self):
    """Размещает корабль в центре нижней стороны"""
    self.center = self.screen_rect.centerx

settings.py

class Settings():
'''Класс для хранения всех настроек игры'''

def __init__(self):
    '''Инициализирует настройки игры'''
    self.screen_width = 1200
    self.screen_height = 800
    self.bg_color = (230, 230, 230)
    self.ship_speed_factor = 0.9
    self.ship_limit = 3

    #Параметры пули
    self.bullet_speed_factor = 0.9
    self.bullet_width = 3
    self.bullet_height = 15
    self.bullet_color = 60, 60, 60
    self.bullets_allowed = 4

    #Настройки пришельцев
    self.alien_speed_factor = 0.7
    self.fleet_drop_speed = 10
    #fleet_direction = 1 обозначает движение вправо; a -1 - влево
    self.fleet_direction = 1

    #Настройки капель
    self.drop_speed = 0.5

Сравнивал мой код с кодом из github https://github.com/bamboo2panda/alien_invasion/tree/master решение проблемы так и не нашел.


Ответы (0 шт):