# coding: UTF-8
# 《外星人入侵》运行的所有函数

import sys
import pygame

from bullet import Bullet
from alien import Alien
from time import sleep
import json
import music


def play_bg_music():
    """背景音乐"""
    if not pygame.mixer.music.get_busy():
        pygame.mixer.music.play()


def check_keydown_events(event, ai_settings, screen, ship, bullets, game_stats, scoreb, aliens):
    """"
        响应鼠标按下操作
    """
    # 按右箭头飞船向右移动
    if event.key == pygame.K_RIGHT:
        ship.moving_right = True
    # 按左箭头飞船向左移动
    elif event.key == pygame.K_LEFT:
        ship.moving_left = True

    # 按空格键创建一颗子弹并将其加入到编组bullets中
    elif event.key == pygame.K_SPACE:
        fire_bullet(ai_settings, screen, ship, bullets)

    # 按p开始游戏
    elif event.key == pygame.K_p:
        start_game(ai_settings, screen, ship, bullets, game_stats, scoreb,
                   aliens)

    # 按z暂停游戏3s
    elif event.key == pygame.K_z:
        sleep(2)

    # 按q退出游戏并把最高分写入文件
    elif event.key == pygame.K_q:
        with open('Max_score.json', 'w', encoding='UTF-8') as file:
            json.dump(game_stats.high_score, file)
        sys.exit()


def fire_bullet(ai_settings, screen, ship, bullets):
    """
        按照要求发射子弹数量
    """
    if len(bullets) < ai_settings.bullet_allowed:
        music.bullet_biu()                              # 发射子弹的声音
        new_bullet = Bullet(ai_settings, screen, ship)  # 如果还没有到达限制，就发射一颗子弹
        bullets.add(new_bullet)                         # 创建一颗子弹，并将其加入到编组bullets中


def start_game(ai_settings, screen, ship, bullets, game_stats, scoreb,
               aliens):
    """
        P264中动手试一试14-1的练习：让玩家按p开始游戏
    """
    # 重置游戏统计信息
    game_stats.reset_stats()
    game_stats.game_active = True

    # 重置记分牌图像
    scoreb.prep_score()
    scoreb.prep_high_score()
    scoreb.prep_level()
    scoreb.prep_ships()

    # 清空外星人列表和子弹列表
    aliens.empty()
    bullets.empty()

    # 创建一群新的外星人，并让飞船居中
    create_fleet(ai_settings, screen, ship, aliens)
    ship.center_ship()

    # 暂停让用户反应一会
    sleep(0.5)


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


def check_play_button(ai_settings, screen, game_stats, scoreb, play_button, ship,
                      aliens, bullets, mouse_x, mouse_y):
    """
        在玩家单击Play按钮时开始游戏
    """
    button_clicked = play_button.button_rect.collidepoint(mouse_x, mouse_y)
    # 单击Play按钮且游戏处于非活跃状态时----避免了游戏处于活跃状态下单击到了Play按钮区域重启游戏
    if button_clicked and not game_stats.game_active:
        """如果鼠标单击位置在msg_image_rect范围内则将游戏置于活跃状态"""

        # # 重置游戏设置
        ai_settings.initialize_dynamic_settings()

        # 游戏开始后Play按钮隐藏起来
        pygame.mouse.set_visible(False)

        start_game(ai_settings, screen, ship, bullets, game_stats, scoreb, aliens)


def check_events(ai_settings, screen, game_stats, scoreb, play_button, ship, aliens, bullets):
    """
        响应鼠标和键盘事件
    """
    # 游戏退出并把最高分写入文件
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            with open('Max_score.json', 'w', encoding='UTF-8') as file:
                json.dump(game_stats.high_score, file)
            sys.exit()

        # 响应Play按钮操作
        elif event.type == pygame.MOUSEBUTTONDOWN:
            mouse_x, mouse_y = pygame.mouse.get_pos()   # 该函数返回一个元组--获取鼠标单击Play按钮时的位置
            check_play_button(ai_settings, screen, game_stats, scoreb, play_button, ship,
                              aliens, bullets, mouse_x, mouse_y)

        # 判读飞船向左还是向右移动
        elif event.type == pygame.KEYDOWN:
            check_keydown_events(event, ai_settings, screen, ship, bullets, game_stats, scoreb, aliens)

        # 飞船停下
        elif event.type == pygame.KEYUP:
            check_keyup_events(event, ship)


def update_screen(ai_settings, screen, game_stats, scoreb, ship, aliens, bullets, play_button):
    """
        更新屏幕上的图像并切换到新屏幕
    """
    # 每次循环时都重绘屏幕
    screen.fill(ai_settings.bg_color)   # 先填充背景后绘制飞船
    ship.blitem()
    aliens.draw(screen)

    # 在飞船后重绘所有子弹
    for bullet in bullets.sprites():
        bullet.draw_bullet()
    # 显示得分
    scoreb.show_score()

    # 如果游戏处于非活动状态就绘制Play按钮
    if not game_stats.game_active:
        play_button.draw_button()

    # 让最近绘制的屏幕可见
    pygame.display.flip()


def start_new_level(ai_settings, screen, game_stats, scoreb, ship, aliens, bullets):
    """
        当消灭干净外星人提高一个等级
    """
    if len(aliens) == 0:
        # 删除现有子弹
        bullets.empty()

        # 逐渐提高速度加快游戏节奏
        ai_settings.increase_speed()

        # 当前屏幕外星人全部被消灭就提高一个等级
        game_stats.level += 1
        scoreb.prep_level()

        # 创建一群新的外星人
        create_fleet(ai_settings, screen, ship, aliens)  # 调用创建外星人群的函数


def check_bullet_alien_collisions(ai_settings, screen, game_stats, scoreb, ship, aliens, bullets):
    """子弹与外星人相撞后的操作"""
    # 检查是否有子弹击中外星人
    # 如果击中了就删除相应的子弹与外星人
    collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
    """方法sprite.groupcollide()检查两个编组的成员之间的碰撞（碰撞是指游戏元素重叠在一起）：
    将每颗子弹同每个外星人的rect作比较 并返回一个字典 字典中的键为子弹  值为被击中的外星人"""

    # 每消灭一个外星人都将加一个points且显示最新得分的图像
    if collisions:
        music.voice_small()                 # 子弹与外星人相撞的声音
        for aliens in collisions.values():  # aliens指被同一颗子弹击中的外星人---是一个列表
            game_stats.score += ai_settings.alien_points * len(aliens)
            scoreb.prep_score()
        check_high_score(game_stats, scoreb)

    start_new_level(ai_settings, screen, game_stats, scoreb, ship, aliens, bullets)


def update_bullets(ai_settings, screen, game_stats, scoreb, ship, aliens, bullets):
    """
        更新子弹位置并删除已消失的子弹
    """
    # 更新子弹位置
    bullets.update()

    # 删除已消失的子弹
    for bullet in bullets.copy():
        if bullet.rect.bottom <= 0:  # 当子弹底部越过屏幕顶部（0）时删除子弹
            bullets.remove(bullet)
    check_bullet_alien_collisions(ai_settings, screen, game_stats, scoreb, ship, aliens, bullets)


def get_number_aliens_X(ai_settings, alien_width):
    """
        计算每行可容纳多少个外星人
    """
    availabble_space_x = ai_settings.screen_width - 2 * alien_width  # 一行可可容纳的水平长度
    number_aliens_x = int(availabble_space_x / (2 * alien_width))  # 一行可容纳多少个外星人
    return number_aliens_x


def get_number_rows(ai_settings, alien_height, ship_height):
    """
        计算屏幕可容纳多少行外星人
    """
    availables_space_y = (ai_settings.screen_height-ship_height-
                          (3*alien_height))
    number_rows = int(availables_space_y / (2 * alien_height))
    return number_rows


def create_alien(ai_settings, screen,  aliens, alien_number, row_number):
    """
        创建一个外星人并将其放在当前行
    """
    alien = Alien(ai_settings, screen)
    alien_width = alien.rect.width
    alien_height = alien.rect.height + 3    # 让外星人之间间隔大些
    alien.x = alien_width + 2 * alien_width * alien_number  # 获取新建外星人所移动的位置
    alien.rect.x = alien.x  # 新建外星人的位置
    alien.rect.y = alien_height + 2 * alien_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, alien.rect.height,
                                  ship.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 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 check_aliens_bottom(ai_settings, game_stats, scoreb, screen, ship, aliens, bullets):
    """检测外星人是否到达屏幕底部"""
    screen_rect = screen.get_rect()
    for alien in aliens.sprites():
        if alien.rect.bottom >= screen_rect.bottom:
            # 外星人触底爆炸声
            music.voice_large()
            # 有外星人到达屏幕后进行像飞船被撞后一样的操作
            ship_aliens_hit(ai_settings, game_stats, scoreb, screen, ship, aliens, bullets)
            break


def ship_aliens_hit(ai_settings, game_stats, scoreb, screen, ship, aliens, bullets):
    """
        响应外星人飞船相撞的后续操作
    """
    # 外星人飞船相撞时 发出碰撞声音 ship_left减1
    if game_stats.ships_left > 0:
        music.voice_large()
        game_stats.ships_left -= 1

        # 更新屏幕上飞船的艘数
        scoreb.prep_ships()

    else:
        game_stats.game_active = False
        ai_settings.increase_wen()      # 游戏结束后alien的速度得回到最初为
        pygame.mouse.set_visible(True)  # 游戏结束后Play按钮显示出来

    # 清空外星人列表和子弹列表
    aliens.empty()
    bullets.empty()

    # 创建一群新的外星人并将飞船重新出现在屏幕在底部中央
    create_fleet(ai_settings, screen, ship, aliens)
    ship.center_ship()

    # 暂停让用户反应一会
    sleep(0.5)


def update_aliens(ai_settings, game_stats, scoreb, screen, ship, aliens, bullets):
    """
        更新外星人群中所有外星人的位置
    """
    check_fleet_edges(ai_settings, aliens)
    aliens.update()

    if pygame.sprite.spritecollideany(ship, aliens):
        """
        接收俩个实参--检测飞船和外星人是否发生碰撞：发生了---往下走；没发生---返回None
        """
        ship_aliens_hit(ai_settings, game_stats, scoreb, screen, ship, aliens, bullets)

    # 检测是否有外星人到达屏幕底部
    check_aliens_bottom(ai_settings, game_stats, scoreb, screen, ship, aliens, bullets)


def check_high_score(game_stats, scoreb):
    """检查是否诞生了新的最高得分"""
    if game_stats.score > game_stats.high_score:
        game_stats.high_score = game_stats.score
        scoreb.prep_high_score()