比较改动
选两个分支看看改了什么,再发起 PR 把工作带过去。
可以合并。 这两个分支可以自动合并。
已经有一个开放的 PR:#1 Update from shoal-games
1 个提交
+8 −7 kit/room.py 查看文件
| @@ -64,7 +64,7 @@ class Room: | ||
| 64 | 64 | spec = self.seats.get(seat) or {} |
| 65 | 65 | kind = spec.get('kind') |
| 66 | 66 | if kind == 'bot': |
| 67 | - child = self._bot(spec) | |
| 67 | + child = self._bot(spec, seat=seat) | |
| 68 | 68 | move = child.ask(bot if bot is not None else {'obs': obs, 'legal': legal}) if child else None |
| 69 | 69 | move = ok(move) if move is not None else None |
| 70 | 70 | return auto() if move is None else move # a late, broken or illegal bot move: auto plays it |
| @@ -128,16 +128,17 @@ class Room: | ||
| 128 | 128 | if msg.get('act') is not None: |
| 129 | 129 | log(f'ignored act {json.dumps(msg["act"])[:120]}: stale') |
| 130 | 130 | |
| 131 | - def _bot(self, spec): | |
| 132 | - """The child for a bot seat, one per folder (as the checks share one between seats); None if it cannot start.""" | |
| 131 | + def _bot(self, spec, seat=None): | |
| 132 | + """One process per seat and folder: a bot must never accumulate another seat's hidden observations.""" | |
| 133 | 133 | if spec.get('field'): |
| 134 | 134 | folder = os.path.join(self.field, spec['field']) |
| 135 | 135 | else: |
| 136 | 136 | folder = os.path.join(os.environ.get('SS_INPUT', '.'), spec.get('dir') or spec.get('route') or '') |
| 137 | - if folder not in self.bots: | |
| 137 | + key = (seat, folder) | |
| 138 | + if key not in self.bots: | |
| 138 | 139 | try: |
| 139 | - self.bots[folder] = self.start(folder) | |
| 140 | + self.bots[key] = self.start(folder) | |
| 140 | 141 | except (Exception, SystemExit) as e: # a missing bot is played by auto rather than ending the room |
| 141 | 142 | log(f'no bot at {folder} ({e}): auto plays that seat') |
| 142 | - self.bots[folder] = None | |
| 143 | - return self.bots[folder] | |
| 143 | + self.bots[key] = None | |
| 144 | + return self.bots[key] | |
+27 −0 kit/test_room.py 查看文件
| @@ -55,3 +55,30 @@ def test_room(): | ||
| 55 | 55 | if __name__ == '__main__': |
| 56 | 56 | test_room() |
| 57 | 57 | print('room protocol: ok') |
| 58 | + | |
| 59 | + | |
| 60 | +def test_bot_processes_are_private_to_seats(): | |
| 61 | + # Same strategy, different seats: neither process can remember the other seat's hand. | |
| 62 | + from room import Room | |
| 63 | + class Child: | |
| 64 | + def __init__(self): | |
| 65 | + self.seen = [] | |
| 66 | + def ask(self, msg): | |
| 67 | + self.seen.append(msg) | |
| 68 | + return 'a' | |
| 69 | + def close(self): | |
| 70 | + pass | |
| 71 | + room = object.__new__(Room) | |
| 72 | + room.start, room.field, room.bots = lambda folder: Child(), 'field', {} | |
| 73 | + a = room._bot({'field': 'same'}, seat='0') | |
| 74 | + b = room._bot({'field': 'same'}, seat='1') | |
| 75 | + assert a is not b | |
| 76 | + assert room._bot({'field': 'same'}, seat='0') is a | |
| 77 | + a.ask({'hand': 'private'}) | |
| 78 | + assert b.seen == [] | |
| 79 | + room.close() | |
| 80 | + | |
| 81 | + | |
| 82 | +if __name__ == '__main__': | |
| 83 | + test_bot_processes_are_private_to_seats() | |
| 84 | + print('seat process isolation: ok') | |
