首页 > 解决方案 > python多处理和整洁python的奇怪行为

问题描述

我正在开发一个使用整洁python的俄罗斯方块游戏 AI,我遇到了一个非常奇怪的错误。

以下是所发生情况的摘要:

我检查了我的 tetromino 碰撞功能,这不是问题。我向我的游戏对象添加了一个列表属性,该属性存储执行的每个动作(即使它失败)并转储它和一些其他有用的对象(当前棋子、“持有”棋子、游戏板、放置在棋盘上的棋子)并发现了一些奇怪的结果:

>>> actions = [...] # All executed actions, the last one is presumably failed
>>> len(actions)
92
>>> actions.count('hold')
87
>>> actions.count('down') # This gets execute only by the game object thread timer
5
>>> 87 + 5
92
>>> current.direction # The default direction for piece is 'up' and there's no 'rotate'
... # in executed actions
'right'
>>> [piece.direction for piece in unbag] # unbag here being a list containing every
... # single piece that was placed/locked/pasted to the game board
['up', 'down', 'up', 'right', 'left']

看着上面的片段,我唯一能想到的可能是一些游戏实例在训练时“感人”,因为我一次评估了 48 个。考虑到这一理论,我为游戏对象添加了一个新属性 uuid。它被设置为str(uuid4())这样__init__我可以将它记录在片段对象中:

class Game:
    def __init__(self, board: np.array=None, network: FFN=None, render=False):

        self.uuid = str(uuid4())

class Mino:
    def __init__(self, type_: str, pos: Tuple[int, int]=None) -> None:
        if type_ not in 'IJLOSTZ':
            raise ValueError(f'invalid tetromino type {repr(type_)}')
        self.type = type_
        if pos is None:
            pos = SPAWN_POSITIONS[type_]
        self.x: int = pos[0]
        self.y: int = pos[1]
        self.direction = 'up'
        self.id_log = set()
...
    def rotate(self) -> None:
        self._log_id()
        ...
...
    def move(self, board, dir_):
        self._log_id()
        ...
...
    def _log_id(self):
        stack = inspect.stack()
        caller_object = stack[2][0].f_locals['self']
        if isinstance(caller_object, Game):
            self.id_log.add(caller_object.uuid)

但是检查我们得到的努力的结果:

>>> current.id_log
{'aa7980ed-c3a6-4ab6-8c41-0938e1b78eb1'}
>>> for p in unbag:
...     print(p.id_log)
...
{'aa7980ed-c3a6-4ab6-8c41-0938e1b78eb1'}
{'aa7980ed-c3a6-4ab6-8c41-0938e1b78eb1'}
{'aa7980ed-c3a6-4ab6-8c41-0938e1b78eb1'}
{'aa7980ed-c3a6-4ab6-8c41-0938e1b78eb1'}
{'aa7980ed-c3a6-4ab6-8c41-0938e1b78eb1'}

如您所见,tetromino 报告它已被 触摸aa7980ed-c3a6-4ab6-8c41-0938e1b78eb1,这意味着我的测试方法错误或涉及黑魔法。

很想知道到底发生了什么。

代码:https ://github.com/yoursred/tetris-ai

标签: pythonpython-3.xmultiprocessing

解决方案


经过一些急需的睡眠后,我想我已经想通了。仅具有其所属游戏对象的签名或 uuid 的片段,同时还显示出并行执行的迹象,这意味着游戏对象本身必须具有并行操作它的东西,它确实做到了。那东西是threading.Timer游戏时钟,这有点道理。如果你的时间恰到好处,游戏时钟不可能在它已经被移动到其他地方时将它向下移动(更准确地说是在碰撞检查之后和实际移动它之前),从而让它离开如果在正常情况下它会锁定该部分,则限制。

考虑到这一点,我提出了这个解决方案:

  • 添加仅由游戏时钟执行的新“重力”动作
  • 在执行其他任何操作之前停止游戏时钟(旋转、向左、向右……)
  • 执行上述动作
  • 重新启动计时器,第一个实例等待最后一个实例离开多长时间

这是代码中的解决方案:

# custom timer class that can report time left
class MyTimer(threading.Timer):
    started_at = None
    def start(self):
        self.started_at = time.time()
        threading.Timer.start(self)
    def elapsed(self):
        return time.time() - self.started_at
    def remaining(self):
        if self.finished.is_set():
            return 0
        return self.interval - self.elapsed()

Game课堂上:

...
    def timetick(self, dt=None):
        if not self.gameover:
            self.timer = MyTimer(self.tickdelay,
                                 self.timetick if dt is not None else dt)
            self.game_step('down')
            self.timer.start()
...
    def game_step(self, cmd='nop'):
        ...
        dt = self.timer.remaining()
        self.timer.cancel()
        ...
        self.timetick(dt)
...

推荐阅读