Реализация отмены и повтора с помощью pygame и лямбда-выражений

Я пытаюсь реализовать отмену и повтор в приложении pygame с использованием лямбда-выражений, но что-то, связанное со ссылками, или мое понимание реализации list.remove() вызывает сбой моей программы. Код, который создает действия, которые невозможно выполнить, следующий:

elif MOUSEBUTTONUP == event.type:
    x, y = pygame.mouse.get_pos()
    if leftClick:
        if len( objects ) > 0 and objects[ -1 ].is_open():
            actions.do( \
                [ lambda: objects[ -1 ].add( Point( x, y, 0 ) ) ], \
                [ lambda: objects[ -1 ].remove( Point( x, y, 0 ) ) ] \
            )
        else:
            actions.do( \
                [ lambda: objects.append( Polygon( ( 255, 255, 255 ) ).add( Point( x, y, 0 ) ) ) ],
                [ lambda: objects.pop() ] \
            )

где объекты - это список Polygons, которые определены как

class Polygon:
    def __init__( self, colour, width = 0 ):
        self._points = []
        self._colour = colour
        self._isopen = True
        self._width  = width

    def get_colour( self ):
        return self._colour

    def add( self, point ):
        self._points.append( point )
        return self

    def remove( self, point ):
        print "List contains " + str( self._points )
        print "Trying to remove " + str( point )
        self._points.remove( point )
        return self

    def num_points( self ):
        return len( self._points )

    def get_points( self ):
        """ Returns a copy of the points in this vector as a list. """
        return self._points[:]

    def open( self ):
        self._isopen = True
        return self

    def close( self ):
        self._isopen = False
        return self

    def is_open( self ):
        return self._isopen

    def set_width( self, width ):
        self._width = width
        return self

    def get_width( self ):
        return self._width

    def is_filled( self ):
        return self._filled

а добавленные точки определяются как

class Point:
    def __init__( self, x, y, z ):
        self.x = x
        self.y = y
        self.z = z

    def rel_to( self, point ):
        x = self.move( point.z, point.x, self.z, self.x )
        y = self.move( point.z, point.y, self.z, self.y )
        return ( x, y )

    def move( self, viewer_d, displacement, object_d, object_h ):
        over  = object_h * viewer_d + object_d * displacement
        under = object_d + viewer_d + 1
        return over / under

    def __str__( self ):
        return "(%d, %d, %d)" % ( self.x, self.y, self.z )

    def __eq__( self, other ):
        return self.x == other.x and self.y == other.y and self.z == other.z

    def __ne__( self, other ):
        return not ( self == other )

actions в первом фрагменте является экземпляром Action, определение которого следует

class Actions:
    #TODO implement immutability where possible
    def __init__( self ):
        self.undos = []
        self.redos = []

    def do( self, do_steps, undo_steps ):
        for do_step in do_steps:
            do_step()
        self.undos.append( ( do_steps, undo_steps ) )
        self.redos = []

    def can_undo( self ):
        return len( self.undos ) > 0

    def undo( self ):
        if self.can_undo():
            action = self.undos.pop()
            _, undo_steps = action
            for undo_step in undo_steps:
                undo_step()
            self.redos.append( action )

    def can_redo( self ):
        return len( self.redos ) > 0

    def redo( self ):
        if self.can_redo():
            action = self.redos.pop()
            redo_steps, _ = action
            for redo_step in redo_steps:
                redo_step()
            self.undos.append( action )

Итак, проблема в том, что когда я нажимаю более двух раз и пытаюсь вызвать actions.undo(), я получаю исключение с list.remove(x), оно говорит, что x отсутствует в списке, я думаю, потому что он пытается дважды удалить одну и ту же точку. Причина, по которой этого не происходит до первых двух кликов, заключается в том, что первая отмена пытается удалить самую последнюю точку, тогда как вторая отмена просто выталкивает полигон из стека объектов. Мой вопрос: почему Point, что actions.undo(), пытается дважды удалить одну и ту же точку, хотя первая точка должна была быть извлечена из стека self.undos и помещена в стек self.redos? Большое спасибо за любой отзыв.


person Sean Kelleher    schedule 27.05.2012    source источник


Ответы (1)


Кажется, я разобрался, все равно работает так, как ожидалось. Теория, которую я вывел, заключается в том, что лямбда-действия создавали замыкания над x, y и z параметра Point(), поэтому вместо копирования значений этих переменных при создании анонимной функции вместо этого сохранялся указатель на их значения. Таким образом, я мог удалить самую последнюю точку, так как x, y и z все еще ссылались на существующую точку, но не удалось выполнить вторую отмену, поскольку точки больше не существовало. Я использовал лист из учебника по отмене/возврату stackp по адресу http://stackp.online.fr/?cat=8, и передал параметр анонимным функциям в виде списка в action.do(), что означало, что параметры оценивались при выполнении функции do(), и поэтому правильные аргументы были отправлены в do(). Извините за длинный вопрос и ответ.

person Sean Kelleher    schedule 28.05.2012