python - python多处理和整洁python的奇怪行为
问题描述
我正在开发一个使用整洁python的俄罗斯方块游戏 AI,我遇到了一个非常奇怪的错误。
以下是所发生情况的摘要:
- 将人口规模设置为较大 (>150)
- 开始“训练”(进化)过程
- 游戏因某种原因超出范围而崩溃
我检查了我的 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
,这意味着我的测试方法错误或涉及黑魔法。
很想知道到底发生了什么。
解决方案
经过一些急需的睡眠后,我想我已经想通了。仅具有其所属游戏对象的签名或 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)
...
推荐阅读
- math - 虚数和负面积
- javascript - JavaScript AND / OR 操作数
- python - 如何绘制像素蒙版并获取像素坐标
- assembly - 在 x64 程序集中订购 .data?
- symfony - TYPO3 和 Symfony 问题(非 Composer 模式)/ CLI cronjob 问题
- javascript - React 自定义钩子与 useReducer
- c - 从另一个字符串创建一个字符串,在 C 中的每个第 n 个字符之后插入一个字符
- javascript - 使用 React + Redux 时的重定向
- angular - 响应式表单上的输入数组
- visual-studio-code - FreeBSD 是否有任何 VS 代码扩展?