Игра Python на основе Turtle ведет себя неожиданно при повторном воспроизведении

Я создал игру, используя пакет черепахи Python, в котором вы используете клавишу со стрелкой вверх, чтобы помочь черепахе пересечь экран и избежать при этом препятствий. У него есть кнопка повтора, которую вы можете нажать после поражения, чтобы перезагрузить игру и сыграть снова. Однако, если вы проиграете дважды и нажмете кнопку «Повтор», препятствия внезапно начнут двигаться быстрее на экране, а после вашего проигрыша препятствия будут продолжать генерироваться и появляться над сообщением «Игра окончена». Этого не происходит в первые два раза игры.

Я считаю, что это связано с некоторыми функциями tkinter, о которых я не знаю. Несколько недель назад я попытался решить эту проблему с помощью отладчика и заметил следующее в функции Begin_game, когда черепаха сталкивается с препятствием и игра заканчивается:

  1. play_game имеет значение False, потому что if cosmo.distance(c) <= 20 оценивается как True

  2. Условие if Cosmo.reach_finish_line оценивается как False, и оно пропускается.

  3. При достижении условия if play_game отладчик попадает в некоторый код tkinter и в конце его переходит к началу функции Begin_game и начинает ее выполнение. Этого не должно происходить, поскольку для параметра play_game установлено значение False, поэтому метод Begin_game не должен вызываться рекурсивно. Я даже дважды проверил значение play_game перед тем, как будет оценен оператор if, и оно все еще было ложным.

Кто-нибудь знает, почему это может происходить? Почему препятствия внезапно начинают двигаться быстрее, когда игра переигрывается во второй раз? И почему они продолжают появляться на экране, даже когда игрок проигрывает?

Ниже приведена проблемная функция Begin_game.

# Begin game when player hits 'Play' or 'Replay' buttons
def begin_game():
    cosmo.showturtle()
    level_display.show_level()
    play_game = True
    screen.update()

    # Call obstacle_generator methods to create obstacles and move them forward
    obstacle_generator.create_obstacles()
    obstacle_generator.obstacle_move()

    # Detect collision between obstacle and turtle
    for c in obstacle_generator.all_obstacles:
        if cosmo.distance(c) <= 20:
            time.sleep(0.3)
            screen.tracer(0)

            # Iterate over obstacle list, hide turtle, and append to recycle list since this game round is over,
            # and they should not be visible on the end game screen
            for num in range(len(obstacle_generator.all_obstacles)):
                obstacle_generator.all_obstacles[num].hideturtle()
                obstacle_generator.recycle.append(obstacle_generator.all_obstacles[num])
            obstacle_generator.all_obstacles = []

            cosmo.hideturtle()

            # Call function to display end game screen
            end_game()

            play_game = False
            break

    # Detect if player has completed current level
    if cosmo.reach_finish_line():
        level_display.refresh_level()
        obstacle_generator.level_up()

    # Recursively call begin_game function if game is still on
    if play_game:
        screen.ontimer(fun=begin_game, t=100)
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
0
75
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы задаетесь вопросом, как возможно, что препятствия ускоряются... Причина проблемы кроется не в tkinter, а в вашем коде, где функция replay_game() вызывается несколько раз, что приводит к множеству play_game() циклов событий таймера, работающих рядом друг с другом. А если выполняются две или более петель play_game(), препятствия перемещаются каждой из этих петель, что приводит к более быстрому перемещению препятствий.

В вашем коде есть две основные причины наблюдаемой вами проблемы:

  • Первая причина в том, что переменная play_game не является глобальной, но она должна быть глобальной, чтобы вписаться в логику управления игровым кодом, управляемую отдельными функциями.
  • вторая причина в том, что функция replay_game() вызывается несколько раз

Как получается, что функция replay_game() вызывается несколько раз? Причина — использование опции add=True в методе onclick(). Если для этой опции установлено значение True, то будет добавлена ​​дополнительная функция обратного вызова, которая будет запускаться при нажатии. В случае вашего кода это функция replay_game(), которая добавляется в список функций, вызываемых при нажатии кнопки воспроизведения. С каждым завершением игры добавляется еще один вызов replay_game(), что приводит к многократному запуску функции begin_game(). Установка add=False не добавляет еще одну функцию и решает таким образом наблюдаемую проблему.

Исходя из вышеизложенного, решение вашей проблемы следующее:

  • make во всех функциях, используя play_game эту глобальную переменную
  • удалите ненужный play_game = True из begin_game() и установите его везде в коде перед вызовом кода begin_game()
  • измените параметр add для регистрации функций обратного вызова на False, чтобы предотвратить несколько запусков одной и той же функции одним щелчком мыши.

Ниже приведен код «main.py» с минимальным набором изменений, реализующих вышеперечисленные модификации. Этот код решает проблему с ускорением прохождения препятствий при каждом новом перезапуске игры:

import time
from turtle import Screen, Turtle

from obstacle_generator import ObstacleGenerator
from player import Player
from scoreboard import Scoreboard
from welcome_screen import WelcomeScreen


# Complete initial setup of game
def setup(*args):
    screen.setup(width=600, height=600)
    screen.bgcolor("black")

    # Display source of turtle icons
    icon_credit.goto(-240, -285)
    icon_credit.color("white")
    icon_credit.write("Icons by Icon8", move=False, align = "center", font=FONT)

    # Add images used in welcome screen turtle objects to screen's available shapes
    for num in range(1, 5):
        screen.addshape(f"images/welcome_screen{num}.gif")
    screen.addshape("images/next.gif")
    screen.addshape("images/back.gif")
    screen.addshape("images/play.gif")

    # Call welcome_screen object methods to display welcome message and instructions
    welcome_screen.welcome1()

    screen.addshape("images/turtle.gif")
    cosmo.shape("images/turtle.gif")
    cosmo.hideturtle()

    screen.listen()
    screen.onkeypress(fun=cosmo.move_up, key = "Up")

    # Add images used in obstacle turtle objects to screen's available shapes
    for obs_type in obstacle_generator.types:
        screen.addshape(f"images/{obs_type}.gif")
    check_game_trigger()


# Use Turtle's ontimer function to recursively check whether player has pressed 'Play' button for game to begin
def check_game_trigger():
    if welcome_screen.trigger:
        welcome_screen.goto(-1000, -1000)
        welcome_screen.next_button.goto(-1000, -1000)
        welcome_screen.back_button.goto(-1000, -1000)
        begin_game()
    else:
        screen.update()
        screen.ontimer(check_game_trigger, 100)


# Begin game when player hits 'Play' or 'Replay' buttons
def begin_game():
    global play_game ###<<<###
    cosmo.showturtle()

    level_display.show_level()
    # play_game = True ###<<<###
    screen.update()

    # Call obstacle_generator methods to create obstacles and move them forward
    obstacle_generator.create_obstacles()
    obstacle_generator.obstacle_move()

    # Detect collision between obstacle and turtle
    for c in obstacle_generator.all_obstacles:
        if cosmo.distance(c) <= 20:
            time.sleep(0.3)
            screen.tracer(0)

            # Iterate over obstacle list, hide turtle, and append to recycle list since this game round is over,
            # and they should not be visible on the end game screen
            for num in range(len(obstacle_generator.all_obstacles)):
                obstacle_generator.all_obstacles[num].hideturtle()
                obstacle_generator.recycle.append(obstacle_generator.all_obstacles[num])
            obstacle_generator.all_obstacles = []

            cosmo.hideturtle()

            # Call function to display end game screen
            end_game()

            play_game = False
            break

    # Detect if player has completed current level
    if cosmo.reach_finish_line():
        level_display.refresh_level()
        obstacle_generator.level_up()

    # Recursively call begin_game function if game is still on
    if play_game:
        screen.ontimer(fun=begin_game, t=100)


def end_game():
    screen.bgcolor("black")

    # Add end game screen turtle image shapes to screen's available shapes
    screen.addshape("images/game_over_message.gif")
    screen.addshape("images/alien_monster.gif")
    screen.addshape("images/replay.gif")
    screen.addshape("images/exit.gif")

    # Call lose_screen method to display game over message and ask player to replay or exit
    lose_screen.lose_screen()
    lose_screen.exit_button.onclick(fun=exit_screen, add=False) ###<<<###
    lose_screen.replay_button.onclick(fun=replay_game, add=False) ###<<<###
    screen.update()


# Reset screen if player chooses to replay and call the begin_game function
def replay_game(*args):
    global play_game ###<<<###

    lose_screen.goto(-1000, -1000)
    lose_screen.exit_button.goto(-1000, -1000)
    lose_screen.replay_button.goto(-1000, -1000)
    lose_screen.game_over_message.goto(-1000, -1000)
    obstacle_generator.level = 0
    level_display.clear()
    level_display.level = 0
    cosmo.goto(cosmo.starting_pos)

    play_game = True
    begin_game()


# Exit game screen if player clicks on the 'Exit' button
def exit_screen(*args):
    screen.clearscreen()
    screen.bye()


# Initialize objects used in game and call setup function
if __name__ == "__main__":
    screen = Screen()
    screen.tracer(0)
    cosmo = Player()
    welcome_screen = WelcomeScreen()
    obstacle_generator = ObstacleGenerator()
    lose_screen = WelcomeScreen()
    level_display = Scoreboard()
    icon_credit = Turtle()
    icon_credit.hideturtle()
    FONT = ("Courier", 8, "normal")
    setup()
    play_game = True
    screen.mainloop()

Другие вопросы по теме