| @@ -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 |
| @@ -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: |
| 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] |