Как управлять рекурсией обработчика событий в python turtle? - PullRequest
0 голосов
/ 20 января 2019

Я работал над игрой в понг, используя модуль черепахи в Python. Ниже мой код:

from turtle import Turtle, _Screen, TurtleScreen
from random import choice, randrange, randint
from tkinter import *
from tkinter import messagebox

class Field(_Screen):
    def __init__(self, width = 1024, height = 600):
        # Get __init__ from _Screen
        super().__init__()
        # Get __init__ from TurtleScreen (parent class of _Screen)
        TurtleScreen.__init__(self, self._canvas)
        if Turtle._screen is None:
            Turtle._screen = self
        self.width = width
        self.height = height
        self.setup(self.width+100,self.height+50)
        self.screensize(self.width,self.height)
        self.title("Pong")
        self.bgcolor("black")
        # Define size of score bar, above the play field
        self.score_height = self.height/10
        # Offset 0 axis line, due to score bar
        self.yzero = -(self.height/2 - (self.height-self.score_height)/2)

class Ball(Turtle):
    def __init__(self, velocity = 5, size = 1, color = "white"):
        super().__init__(shape="circle",visible=False)
        self.color(color)
        self.speed(0)
        self.penup()
        self.shapesize(size,size)
        self.setposition(0,field.yzero)
        self.st()
        self.velocity = velocity
        self.dirrection = 0

    def player_collision(self,player):
        bx,by = self.position()
        px,py = player.position()
        x_off = ball.shapesize()[1]*10 + player.shapesize()[0]*10
        y_off = ball.shapesize()[0]*10 + player.shapesize()[1]*10
        if px > 0:
            if (bx > px-x_off and by <= py+y_off and by >= py-y_off):
                return True
        elif px < 0:
            if (bx < px+x_off and by <= py+y_off and by >= py-y_off):
                return True
        return False

    def court_collision(self,court):
        if (ball.ycor() >= ((field.height/2)-
                            court.score_height-self.shapesize()[0]*10)
            or ball.ycor() <= -court.height/2+10): return True
        return False

    def out_left(self,court):
        if self.xcor() <= -court.width/2: return True
        return False

    def out_right(self,court):
        if self.xcor() >= court.width/2: return True
        return False        

class Player(Turtle):
    def __init__(self, x=0, y=0, color="white", up=None, down=None):
        super().__init__(shape="square",visible=False)
        self.color(color)
        self.speed(0)
        self.penup()
        # setup player paddle
        self.shapesize(1,10)
        # Rotate turtle, to allow the use of forward method
        self.setheading(90)
        self.setposition(x,y)
        self.st()
        self.score = 0
        self.height = self.shapesize()[1]*10
        self.velocity = 50
        self.ondrag(self.drag)
        self.upkey = up
        self.downkey = down

    def drag(self,x,y):
        self.ondrag(None) # Disable event handler to avoid recursion
        if y >= (field.height/2-field.score_height) - self.height:
            y = (field.height/2-field.score_height) - self.height
        if y <= -field.height/2+self.height:
            y = -field.height/2+self.height
        self.goto(self.xcor(),y)
        self.ondrag(self.drag) # Reactivate event handler

    def up(self):
        #field.onkeypress(None, self.upkey)
        if (self.ycor()+self.height <=
            (field.height-field.score_height)/2+field.yzero):
            self.forward(self.velocity)
        #field.onkeypress(self.up, self.upkey)

    def down(self):
        #field.onkeypress(None, self.downkey)
        if self.ycor()-self.height >= -field.height/2:
            self.forward(-self.velocity)
        #field.onkeypress(self.down, self.downkey)

class Score(Turtle):
    def __init__(self):
        super().__init__(visible=False)
        self.speed(0)
        self.color("white")
        self.pensize(3)
        # Draw lower border
        self.penup()
        self.goto(-field.width,-field.height/2)
        self.pendown()
        self.goto(field.width,-field.height/2)
        # Draw upper border
        self.penup()
        self.goto(-field.width,field.height/2-field.score_height)
        self.pendown()
        self.goto(field.width,field.height/2-field.score_height)
        self.penup()
        # Draw score
        self.goto(-100,field.height/2-field.score_height)
        self.write(player2.score,font=("Monospace",50,"bold")) 
        self.goto(100,field.height/2-field.score_height)
        self.write(player1.score,font=("Monospace",50,"bold"))

    def update(self):
        # Clear the previous score
        for i in range(3):
            self.undo()
        # And write the new one
        self.write(player2.score,font=("Monospace",50,"bold")) 
        self.goto(100,field.height/2-field.score_height)
        self.write(player1.score,font=("Monospace",50,"bold"))

class Game:
    def __init__(self,court,difficulty=0):
        # Difficulty = increase in ball speed
        self.difficulty = difficulty
        # Setup event handlers
        court.onkeypress(self.qt, "Escape")
        court.onkeypress(player1.up, player1.upkey)
        court.onkeypress(player1.down, player1.downkey)
        court.onkeypress(player2.up, player2.upkey)
        court.onkeypress(player2.down, player2.downkey)
        court.onkey(self.pause, "p")
        court.listen()
        # Try to implement game pause. Not working, for the moment
        #self.pause = False
        #self.pause_count = 0

    def reset(self):
        ball.setposition(0,field.yzero)
        player1.setposition(player1.xcor(),field.yzero)
        player2.setposition(player2.xcor(),field.yzero)
        ball.dirrection = choice([0,180])   # Left or right
        ball.setheading(ball.dirrection+randrange(-80,80))

    def restart(self):
        self.reset()
        self.player1_score = 0
        self.player2_score = 0
        self.difficulty = 0

    def qt(self):
        prompt = Tk()
        prompt.eval('tk::PlaceWindow %s center' % prompt.winfo_toplevel())
        prompt.withdraw()
        answer = messagebox.askyesno("Quit", "Are you sure you want to quit?")
        if answer == True:
            field.bye()
        return

    # Not currently working
    def pause(self):
        if self.pause_count % 2 == 0:
            self.pause == True
        else:
            self.pause = False

class Play(Turtle):
    def __init__(self):
        super().__init__(visible=False)
        self.shape("square")
        self.color("white")
        self.speed(0)
        self.penup()
        self.shapesize(2,4)
        self.goto(-field.width/2,field.height/2-field.score_height/2)
        self.write("Play",font=("Monospace",20,"bold"))
        field.onscreenclick(self.click)

    def click(self,x,y):
        print(x,y)
        if (x <= -field.width/2+field.width/2/10 and
            x >= -field.width/2 and
            y >= field.height/2-field.score_height/2 and y <= field.height/2):
            self.color("green")
            self.clear()
            self.write("Play",font=("Monospace",20,"bold"))
            self.color("white")
            self.clear()
            self.write("Play",font=("Monospace",20,"bold"))
            game.reset()
            main()

def main():
    ball.forward(ball.velocity+game.difficulty)
    # Check for paddle collision
    if ball.player_collision(player1) or ball.player_collision(player2):
        ball.setheading(180 - ball.heading())
    # Bounce from upper or lower border
    if ball.court_collision(field):
        ball.setheading(-ball.heading())
    # Check for ball out of field and update player score
    elif ball.out_right(field):
        game.reset()
        player2.score += 1
        score.update()
        game.difficulty += 0.5
    elif ball.out_left(field):
        game.reset()
        player1.score += 1
        score.update()
        game.difficulty += 0.5
    field.ontimer(main)

if __name__ == "__main__":
    field = Field(1280,720)
    ball = Ball()
    player1 = Player(field.width/2,field.yzero,up = "Up", down = "Down")
    player2 = Player(-field.width/2,field.yzero, up = "w", down = "s")
    game = Game(field)
    score = Score()
    play_button = Play()
    #field.mainloop()

Это вроде работает, но если вы используете клавиши для игры, это в конечном итоге вернет ошибку:

RecursionError: максимальная глубина рекурсии превышена при вызове Python объект

Сначала может показаться, что проблема связана с функцией main(), но настоящая проблема связана с обработчиком событий для нажатий клавиш. Если я буду играть только с помощью мыши, игра не выдаст ошибки, она будет просто дрожать.

Я прочитал следующие темы:

превышена максимальная глубина рекурсии

Избегайте ошибок RecursionError в коде краски черепахи

Turtle.onkeypress не работает (Python)

И попытался реализовать найденные там решения. Единственное, что мне подходит, это отключение обработчика событий для функции ondrag(). Если я попытаюсь использовать то же решение на плеере (раскомментируйте строки в up() и down() методах Player), оно будет работать только тогда, когда main() не запущен. Если я запускаю функцию main (), она просто запускается один раз и деактивируется.

Так что мне нужна помощь:

  1. Как избежать максимальной ошибки рекурсии. (это происходит только когда main() активен);
  2. заставить работать функцию ondrag без рывков main();
  3. метод qt() из класса game не работает должным образом, если запущен main().

Так вы, ребята, думаете, что я могу улучшить эти аспекты?

Редактировать: ниже приведен полный трекбек

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\Bogey\AppData\Local\Programs\Python\Python37-32\lib\tkinter\__init__.py", line 1702, in __call__
    return self.func(*args)
  File "C:\Users\Bogey\AppData\Local\Programs\Python\Python37-32\lib\tkinter\__init__.py", line 746, in callit
    func(*args)
  File "C:/Users/Bogey/Desktop/asd.py", line 209, in main
    ball.forward(ball.velocity+game.difficulty)
  File "C:\Users\Bogey\AppData\Local\Programs\Python\Python37-32\lib\turtle.py", line 1637, in forward
    self._go(distance)
  File "C:\Users\Bogey\AppData\Local\Programs\Python\Python37-32\lib\turtle.py", line 1604, in _go
    ende = self._position + self._orient * distance
RecursionError: maximum recursion depth exceeded

1 Ответ

0 голосов
/ 21 января 2019

Основная проблема, которую я вижу, это то, что вы делаете слишком много ненужных вычислений во время игры.Например, рассмотрим метод court_collision(), который вызывается при каждом движении мяча:

def court_collision(self,court):
        if (ball.ycor() >= ((field.height/2)-
                            court.score_height-self.shapesize()[0]*10)
            or ball.ycor() <= -court.height/2+10): return True
        return False

Из всех этих значений меняется только ball.ycor(), остальные должны быть вычислены до начала игры и сохраненычтобы метод выглядел больше как:

def court_collision(self):
        return not self.wall_top_offset > ball.ycor() > self.wall_bottom_offset

То же самое для player_collision(), drag() и т. д.

Функция main() должна быть действительноmove() метод из Ball.

У меня есть другие гниды, но они не имеют никакого отношения к производительности игры.

...