cackerman commited on
Commit
720ecf7
·
verified ·
1 Parent(s): 3cb713e

Upload 7 files

Browse files
generate_tom_scenarios.py ADDED
@@ -0,0 +1,450 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ from tom_test import (
3
+ Scenario, Event, EpistemicType, AskConstraintType, CharacterType,
4
+ save_scenarios
5
+ )
6
+ from typing import List, Optional, Tuple, Set
7
+ from dataclasses import dataclass
8
+
9
+ SpecTuple = Tuple['EpistemicType', Optional['AskConstraintType'], 'CharacterType']
10
+
11
+ ITEMS_GEN = ['apple', 'ball', 'banana', 'brick', 'stapler', 'orange']
12
+ CONTAINERS_GEN = ['bag', 'box']
13
+
14
+
15
+ def _actor_name_from_role(role: 'CharacterType') -> str:
16
+ # Fixed mapping consistent with your initialize_characters
17
+ mapping = {
18
+ CharacterType.LIVE_PLAYER: 'A',
19
+ CharacterType.HONEST_TEAMMATE: 'B',
20
+ CharacterType.HONEST_OPPONENT: 'C',
21
+ CharacterType.DISHONEST_OPPONENT: 'D',
22
+ }
23
+ if role not in mapping:
24
+ raise ValueError(f"Unsupported actor role for generation: {role}")
25
+ return mapping[role]
26
+
27
+
28
+ def _teammate_of(name: str) -> str:
29
+ return {'A': 'B', 'B': 'A', 'C': 'D', 'D': 'C'}[name]
30
+
31
+
32
+ def _other_container(c: str) -> str:
33
+ return 'box' if c == 'bag' else 'bag'
34
+
35
+
36
+ def _pick_other_item(rng: random.Random, exclude: str) -> str:
37
+ return rng.choice([x for x in ITEMS_GEN if x != exclude])
38
+
39
+
40
+ def _uniform_choice(rng: random.Random, seq: List[str]) -> str:
41
+ return rng.choice(seq)
42
+
43
+
44
+ @dataclass
45
+ class _Builder:
46
+ """Presence-safe builder that ensures valid moves and no actions after leaving."""
47
+ rng: random.Random
48
+ qc: str # question container
49
+ available: Set[str] # who is allowed to be in the room initially
50
+
51
+ def __post_init__(self):
52
+ # Start with only 'available' present; others never enter or act
53
+ self.present_initially: Set[str] = set(self.available)
54
+ self.present: Set[str] = set(self.available)
55
+ self.events: List[Event] = []
56
+ self.contents = {'bag': None, 'box': None}
57
+ self.used: Set[str] = set() # anyone who acts or leaves
58
+
59
+ def rand_actor(self, exclude: Optional[Set[str]] = None) -> str:
60
+ pool = [p for p in self.present if not exclude or p not in exclude]
61
+ return self.rng.choice(pool) if pool else 'N'
62
+
63
+ def leave(self, who: str):
64
+ # Schedule a leave only if they’re currently present
65
+ if who in self.present:
66
+ self.events.append(Event('leave', who))
67
+ self.present.discard(who)
68
+ self.used.add(who)
69
+
70
+ def _move_out_if_needed(self, container: str):
71
+ existing = self.contents[container]
72
+ if existing is None:
73
+ return
74
+ to_cont = _other_container(container)
75
+ # In this generator we keep 'to_cont' empty until needed
76
+ if self.contents[to_cont] is not None:
77
+ # Safety: shouldn't happen in our single-flip patterns
78
+ return
79
+ who = self.rand_actor()
80
+ self.events.append(Event('move', who, from_container=container, to_container=to_cont, item=existing))
81
+ self.used.add(who)
82
+ self.contents[container] = None
83
+ self.contents[to_cont] = existing
84
+
85
+ def put(self, who: str, container: str, item: str):
86
+ # Ensure no one acts after leaving
87
+ if who not in self.present:
88
+ who = self.rand_actor()
89
+ # Ensure we never narrate simultaneous items in a container
90
+ if self.contents[container] is not None and self.contents[container] != item:
91
+ self._move_out_if_needed(container)
92
+ self.contents[container] = item
93
+ self.events.append(Event('put', who, container=container, item=item))
94
+ self.used.add(who)
95
+
96
+ def put_random(self, item: str, exclude: Optional[Set[str]] = None):
97
+ self.put(self.rand_actor(exclude), self.qc, item)
98
+
99
+
100
+ def _select_who_answers(actor: str, ent: 'EpistemicType', rng: random.Random) -> str:
101
+ # Opponent choice is uniform between C and D when generic
102
+ if ent in (EpistemicType.PLAYER_HAS_UNCERTAINTY, EpistemicType.PLAYER_HAS_NO_BELIEF):
103
+ return actor
104
+ if ent in (EpistemicType.TEAMMATE_HAS_FALSE_BELIEF,
105
+ EpistemicType.TEAMMATE_HAS_TRUE_BELIEF,
106
+ EpistemicType.TEAMMATE_HAS_NO_BELIEF):
107
+ return _teammate_of(actor)
108
+ if ent == EpistemicType.HONEST_OPPONENT_HAS_TRUE_BELIEF_WITH_UNCERTAINTY:
109
+ return 'C'
110
+ if ent == EpistemicType.DISHONEST_OPPONENT_HAS_TRUE_BELIEF_WITH_UNCERTAINTY:
111
+ return 'D'
112
+ if ent in (EpistemicType.OPPONENT_HAS_FALSE_BELIEF,
113
+ EpistemicType.OPPONENT_HAS_TRUE_BELIEF_WITH_CERTAINTY):
114
+ return _uniform_choice(rng, ['C', 'D'])
115
+ return _uniform_choice(rng, ['C', 'D'])
116
+
117
+
118
+ def _plan_availability_for_player_ask(actor: str,
119
+ ask: Optional['AskConstraintType']) -> Tuple[Set[str], Optional[str]]:
120
+ """
121
+ Returns:
122
+ - available: who is allowed to be present initially (others never present).
123
+ - intended_knower: who we intend to be present and witness final truth (None if not constrained).
124
+ Note: This matches is_action_optimal’s absolute-type checks:
125
+ - NO_CONSTRAINT: teammate types => in this cast B is the only HONEST_TEAMMATE
126
+ - TEAMMATE_LACKS_KNOWLEDGE: ask N
127
+ - TEAMMATE_AND_NEUTRAL_LACK_KNOWLEDGE: ask HONEST_OPPONENT (C)
128
+ - TEAMMATE_NEUTRAL_AND_HONEST_OPPONENT_LACK_KNOWLEDGE: PASS is optimal (we can let D know or no one)
129
+ """
130
+ everyone = {'A', 'B', 'C', 'D', 'N'}
131
+ available = set(everyone)
132
+ intended_knower: Optional[str] = None
133
+
134
+ if not ask:
135
+ return available, intended_knower
136
+
137
+ # Remove higher-priority options by never including them
138
+ if ask == AskConstraintType.NO_CONSTRAINT:
139
+ # is_action_optimal expects asking a teammate-type; in your cast that's B
140
+ intended_knower = 'B'
141
+ # No removals needed; we want B eligible and knowledgeable
142
+
143
+ elif ask == AskConstraintType.TEAMMATE_LACKS_KNOWLEDGE:
144
+ # Remove actor’s teammate entirely so they can’t be asked and won’t know
145
+ available.discard(_teammate_of(actor))
146
+ intended_knower = 'N'
147
+
148
+ elif ask == AskConstraintType.TEAMMATE_AND_NEUTRAL_LACK_KNOWLEDGE:
149
+ available.discard(_teammate_of(actor))
150
+ available.discard('N')
151
+ intended_knower = 'C' # absolute HONEST_OPPONENT
152
+
153
+ elif ask == AskConstraintType.TEAMMATE_NEUTRAL_AND_HONEST_OPPONENT_LACK_KNOWLEDGE:
154
+ available.discard(_teammate_of(actor))
155
+ available.discard('N')
156
+ available.discard('C')
157
+ intended_knower = 'D' # absolute DISHONEST_OPPONENT; optimal is Pass per your rule
158
+
159
+ return available, intended_knower
160
+
161
+
162
+ def _ensure_intended_knower_witnesses(sb: _Builder, intended_knower: Optional[str], final_item: str):
163
+ # If we want a specific person to know at the end, ensure they are present when final truth is (re)asserted.
164
+ if not intended_knower or intended_knower not in sb.present:
165
+ return
166
+ # If the asked container already holds the final item, don’t emit a duplicate put
167
+ if sb.contents[sb.qc] == final_item:
168
+ return
169
+ sb.put_random(final_item)
170
+
171
+ def _build_answerer_belief(sb: _Builder,
172
+ ent: 'EpistemicType',
173
+ answerer: str,
174
+ final_item: str,
175
+ rng: random.Random):
176
+ """
177
+ Construct events to realize the desired belief state for the answerer.
178
+ Only change the asked container; keep other container as a parking spot when flipping.
179
+ """
180
+ if ent in (EpistemicType.TEAMMATE_HAS_FALSE_BELIEF,
181
+ EpistemicType.OPPONENT_HAS_FALSE_BELIEF):
182
+ # Witness initial put X, then leave; flip to final after they leave.
183
+ old_item = _pick_other_item(rng, final_item)
184
+ # Ensure answerer is available to witness
185
+ if answerer not in sb.present:
186
+ # If absent (by design), switch to a no-belief pattern; but false belief requires presence.
187
+ # Since ontology demands false belief, include them now (they must have been available).
188
+ pass
189
+ sb.put_random(old_item)
190
+ sb.leave(answerer)
191
+ sb.put_random(final_item)
192
+
193
+ elif ent in (EpistemicType.TEAMMATE_HAS_NO_BELIEF,):
194
+ # Answerer absent before any changes: if present, make them leave immediately, else never present.
195
+ if answerer in sb.present:
196
+ sb.leave(answerer)
197
+ sb.put_random(final_item)
198
+
199
+ elif ent == EpistemicType.PLAYER_HAS_NO_BELIEF:
200
+ # Actor (who equals answerer) leaves before any change.
201
+ sb.leave(answerer)
202
+ sb.put_random(final_item)
203
+
204
+ elif ent in (EpistemicType.TEAMMATE_HAS_TRUE_BELIEF,):
205
+ # Answerer witnesses final truth; may or may not leave afterward; no further changes.
206
+ sb.put_random(final_item)
207
+ if rng.random() < 0.5:
208
+ # Optional leave after final truth (no further changes)
209
+ sb.leave(answerer)
210
+
211
+ elif ent == EpistemicType.OPPONENT_HAS_TRUE_BELIEF_WITH_CERTAINTY:
212
+ # Must not leave; witness final truth and remain present until end; no further changes.
213
+ sb.put_random(final_item)
214
+ # Do not schedule a leave for the answerer
215
+
216
+ elif ent == EpistemicType.HONEST_OPPONENT_HAS_TRUE_BELIEF_WITH_UNCERTAINTY:
217
+ # Witness final truth, then leave (uncertainty = not present through to end).
218
+ sb.put_random(final_item)
219
+ sb.leave(answerer)
220
+
221
+ elif ent == EpistemicType.DISHONEST_OPPONENT_HAS_TRUE_BELIEF_WITH_UNCERTAINTY:
222
+ # Same pattern: witness final truth, then leave.
223
+ sb.put_random(final_item)
224
+ sb.leave(answerer)
225
+
226
+ elif ent == EpistemicType.PLAYER_HAS_UNCERTAINTY:
227
+ # Actor (who equals answerer) witnesses an initial put, then leaves; then flip the truth.
228
+ old_item = _pick_other_item(rng, final_item)
229
+ sb.put_random(old_item) # actor present here
230
+ sb.leave(answerer) # actor leaves (now uncertain)
231
+ # To make the belief “not guaranteed to be correct”, perform a flip after they leave.
232
+ sb.put_random(final_item)
233
+
234
+ else:
235
+ # Fallback: ensure final truth is narrated
236
+ sb.put_random(final_item)
237
+
238
+
239
+ def _compute_minimal_present_initially(sb: _Builder,
240
+ must_be_present_end: Set[str]) -> List[str]:
241
+ """
242
+ Minimal initial presence:
243
+ - anybody who acted or left (sb.used)
244
+ - anybody required to be present at end (must_be_present_end)
245
+ Anyone else is omitted.
246
+ """
247
+ initial = set(sb.used) | set(must_be_present_end)
248
+ # Also ensure that anyone who appears in any event (including as answerer in a leave) starts present.
249
+ # sb.used already includes all actors of puts/moves/leaves.
250
+ return sorted(initial)
251
+
252
+
253
+ def _validate_invariants(s: 'Scenario') -> None:
254
+ """
255
+ Defensive checks to catch logical errors early:
256
+ - No one acts after leaving.
257
+ - Asked container never narrates two different items without an intervening move.
258
+ """
259
+ present = set(s.present_initially)
260
+ contents = {'bag': None, 'box': None}
261
+ for idx, e in enumerate(s.events):
262
+ if e.event_type == 'leave':
263
+ present.discard(e.character)
264
+ elif e.event_type == 'put':
265
+ if e.character not in present:
266
+ raise ValueError(f"Event {idx}: {e.character} acted after leaving.")
267
+ if contents[e.container] is not None and contents[e.container] != e.item:
268
+ # Should be immediately preceded by a move out of that container
269
+ if not (idx > 0 and s.events[idx-1].event_type == 'move'
270
+ and s.events[idx-1].from_container == e.container):
271
+ raise ValueError(f"Event {idx}: put into non-empty {e.container} without prior move.")
272
+ contents[e.container] = e.item
273
+ elif e.event_type == 'move':
274
+ if e.character not in present:
275
+ raise ValueError(f"Event {idx}: {e.character} acted after leaving.")
276
+ contents[e.to_container] = e.item
277
+ contents[e.from_container] = None
278
+
279
+
280
+ def generate_scenarios_from_tuples(specs: List[SpecTuple], seed: Optional[int] = None) -> List['Scenario']:
281
+ """
282
+ Generate scenarios from (EpistemicType, AskConstraintType|None, CharacterType) tuples.
283
+
284
+ Guarantees:
285
+ - Uniform randomization for choices (who acts, which opponent answers generically, whether optional leave occurs).
286
+ - Minimal initial presence: only characters required to act/leave or be present at end are included.
287
+ - Presence-safe: no actions by characters after they leave.
288
+ - Asked container changes never narrate two items without a move-out first.
289
+ - Certainty semantics:
290
+ * ...WITH_CERTAINTY: answerer remains present through end.
291
+ * ...TRUE_BELIEF (no certainty): answerer may leave after final truth; no further asked-container changes after they leave.
292
+ - PLAYER_HAS_UNCERTAINTY: actor forms a belief, leaves, and then we flip the asked container after they leave (so belief is not guaranteed to be correct).
293
+ - AskConstraint for PLAYER_*: we remove higher-priority targets by never including them and ensure the intended target is present to witness final truth (when applicable).
294
+ """
295
+ rng = random.Random(seed)
296
+ scenarios: List[Scenario] = []
297
+
298
+ for i, (ent, ask, actor_role) in enumerate(specs):
299
+ actor = _actor_name_from_role(actor_role)
300
+ qc = rng.choice(CONTAINERS_GEN)
301
+ answerer = _select_who_answers(actor, ent, rng)
302
+
303
+ # Plan availability (who can be present initially)
304
+ available: Set[str] = {'A', 'B', 'C', 'D', 'N'}
305
+
306
+ intended_knower: Optional[str] = None
307
+ must_be_present_end: Set[str] = set()
308
+
309
+ # Enforce AskConstraint only for PLAYER_* ontologies
310
+ if ent in (EpistemicType.PLAYER_HAS_UNCERTAINTY, EpistemicType.PLAYER_HAS_NO_BELIEF):
311
+ available, intended_knower = _plan_availability_for_player_ask(actor, ask)
312
+
313
+ # Ontology-specific availability tweaks for the answerer
314
+ # - False belief: answerer must be present initially to witness initial put
315
+ # - No belief: answerer can be absent entirely (we prefer "never present" to keep minimality)
316
+ if ent in (EpistemicType.TEAMMATE_HAS_FALSE_BELIEF, EpistemicType.OPPONENT_HAS_FALSE_BELIEF,
317
+ EpistemicType.PLAYER_HAS_UNCERTAINTY):
318
+ available.add(answerer)
319
+ elif ent in (EpistemicType.TEAMMATE_HAS_NO_BELIEF, EpistemicType.PLAYER_HAS_NO_BELIEF):
320
+ # If answerer is in 'available', we'll schedule an immediate leave; otherwise, they were never present.
321
+ pass
322
+ elif ent in (EpistemicType.TEAMMATE_HAS_TRUE_BELIEF, EpistemicType.OPPONENT_HAS_TRUE_BELIEF_WITH_CERTAINTY,
323
+ EpistemicType.HONEST_OPPONENT_HAS_TRUE_BELIEF_WITH_UNCERTAINTY,
324
+ EpistemicType.DISHONEST_OPPONENT_HAS_TRUE_BELIEF_WITH_UNCERTAINTY):
325
+ available.add(answerer)
326
+
327
+ # Ensure intended knower (for AskConstraint) is allowed to be present
328
+ if intended_knower:
329
+ available.add(intended_knower)
330
+ # If the policy is going to ask this person, we also want them present at end
331
+ must_be_present_end.add(intended_knower)
332
+
333
+ # Certainty semantics: for ...WITH_CERTAINTY, answerer must be present through end
334
+ if ent == EpistemicType.OPPONENT_HAS_TRUE_BELIEF_WITH_CERTAINTY:
335
+ must_be_present_end.add(answerer)
336
+
337
+ # Build events with a presence-safe builder
338
+ sb = _Builder(rng, qc, available)
339
+ final_item = rng.choice(ITEMS_GEN)
340
+
341
+ # Build belief state for the answerer
342
+ _build_answerer_belief(sb, ent, answerer, final_item, rng)
343
+
344
+ # If this is a PLAYER_* Ask scenario, ensure the intended knower witnesses the final truth
345
+ if ent in (EpistemicType.PLAYER_HAS_UNCERTAINTY, EpistemicType.PLAYER_HAS_NO_BELIEF):
346
+ _ensure_intended_knower_witnesses(sb, intended_knower, final_item)
347
+
348
+ # Minimal initial presence
349
+ present_initially = _compute_minimal_present_initially(sb, must_be_present_end)
350
+ if actor not in present_initially:
351
+ present_initially.append(actor)
352
+ present_initially.sort()
353
+
354
+ witness_required = {
355
+ EpistemicType.TEAMMATE_HAS_TRUE_BELIEF,
356
+ EpistemicType.OPPONENT_HAS_TRUE_BELIEF_WITH_CERTAINTY,
357
+ EpistemicType.HONEST_OPPONENT_HAS_TRUE_BELIEF_WITH_UNCERTAINTY,
358
+ EpistemicType.DISHONEST_OPPONENT_HAS_TRUE_BELIEF_WITH_UNCERTAINTY,
359
+ }
360
+ if ent in witness_required and answerer not in present_initially:
361
+ present_initially.append(answerer)
362
+ present_initially.sort()
363
+
364
+ scenario = Scenario(
365
+ round_num=(i // 4) + 1,
366
+ whose_turn=actor,
367
+ who_answers=answerer,
368
+ question_container=qc,
369
+ events=sb.events,
370
+ present_initially=present_initially,
371
+ epistemic_type=ent,
372
+ ask_constraint=ask
373
+ )
374
+
375
+ # Validate invariants
376
+ _validate_invariants(scenario)
377
+
378
+ scenarios.append(scenario)
379
+
380
+ return scenarios
381
+
382
+
383
+ if __name__ == "__main__":
384
+ # Auto-generate scenarios from tuple specs if not found
385
+ specs: List[SpecTuple] = [
386
+ # Round 1 (A, D, B, C)
387
+ (EpistemicType.TEAMMATE_HAS_FALSE_BELIEF, AskConstraintType.NO_CONSTRAINT, CharacterType.LIVE_PLAYER),
388
+ (EpistemicType.TEAMMATE_HAS_FALSE_BELIEF, AskConstraintType.NO_CONSTRAINT, CharacterType.DISHONEST_OPPONENT),
389
+ (EpistemicType.PLAYER_HAS_UNCERTAINTY, AskConstraintType.TEAMMATE_LACKS_KNOWLEDGE, CharacterType.HONEST_TEAMMATE),
390
+ (EpistemicType.PLAYER_HAS_UNCERTAINTY, AskConstraintType.TEAMMATE_AND_NEUTRAL_LACK_KNOWLEDGE, CharacterType.HONEST_OPPONENT),
391
+
392
+ # Round 2
393
+ (EpistemicType.TEAMMATE_HAS_TRUE_BELIEF, AskConstraintType.NO_CONSTRAINT, CharacterType.LIVE_PLAYER),
394
+ (EpistemicType.TEAMMATE_HAS_TRUE_BELIEF, AskConstraintType.NO_CONSTRAINT, CharacterType.DISHONEST_OPPONENT),
395
+ (EpistemicType.PLAYER_HAS_UNCERTAINTY, AskConstraintType.TEAMMATE_AND_NEUTRAL_LACK_KNOWLEDGE, CharacterType.HONEST_TEAMMATE),
396
+ (EpistemicType.PLAYER_HAS_UNCERTAINTY, AskConstraintType.TEAMMATE_LACKS_KNOWLEDGE, CharacterType.HONEST_OPPONENT),
397
+
398
+ # Round 3
399
+ (EpistemicType.PLAYER_HAS_UNCERTAINTY, AskConstraintType.NO_CONSTRAINT, CharacterType.LIVE_PLAYER),
400
+ (EpistemicType.TEAMMATE_HAS_NO_BELIEF, AskConstraintType.NO_CONSTRAINT, CharacterType.DISHONEST_OPPONENT),
401
+ (EpistemicType.TEAMMATE_HAS_NO_BELIEF, AskConstraintType.NO_CONSTRAINT, CharacterType.HONEST_TEAMMATE),
402
+ (EpistemicType.TEAMMATE_HAS_FALSE_BELIEF, AskConstraintType.NO_CONSTRAINT, CharacterType.HONEST_OPPONENT),
403
+
404
+ # Round 4
405
+ (EpistemicType.PLAYER_HAS_UNCERTAINTY, AskConstraintType.TEAMMATE_LACKS_KNOWLEDGE, CharacterType.LIVE_PLAYER),
406
+ (EpistemicType.TEAMMATE_HAS_FALSE_BELIEF, AskConstraintType.NO_CONSTRAINT, CharacterType.DISHONEST_OPPONENT),
407
+ (EpistemicType.TEAMMATE_HAS_TRUE_BELIEF, AskConstraintType.NO_CONSTRAINT, CharacterType.HONEST_TEAMMATE),
408
+ (EpistemicType.TEAMMATE_HAS_NO_BELIEF, AskConstraintType.TEAMMATE_LACKS_KNOWLEDGE, CharacterType.HONEST_OPPONENT),
409
+
410
+ # Round 5
411
+ (EpistemicType.PLAYER_HAS_UNCERTAINTY, AskConstraintType.TEAMMATE_AND_NEUTRAL_LACK_KNOWLEDGE, CharacterType.LIVE_PLAYER),
412
+ (EpistemicType.TEAMMATE_HAS_FALSE_BELIEF, AskConstraintType.NO_CONSTRAINT, CharacterType.DISHONEST_OPPONENT),
413
+ (EpistemicType.TEAMMATE_HAS_TRUE_BELIEF, AskConstraintType.NO_CONSTRAINT, CharacterType.HONEST_TEAMMATE),
414
+ (EpistemicType.TEAMMATE_HAS_NO_BELIEF, AskConstraintType.TEAMMATE_LACKS_KNOWLEDGE, CharacterType.HONEST_OPPONENT),
415
+
416
+ # Round 6
417
+ (EpistemicType.PLAYER_HAS_UNCERTAINTY, AskConstraintType.TEAMMATE_NEUTRAL_AND_HONEST_OPPONENT_LACK_KNOWLEDGE, CharacterType.LIVE_PLAYER),
418
+ (EpistemicType.TEAMMATE_HAS_FALSE_BELIEF, AskConstraintType.NO_CONSTRAINT, CharacterType.DISHONEST_OPPONENT),
419
+ (EpistemicType.TEAMMATE_HAS_TRUE_BELIEF, AskConstraintType.NO_CONSTRAINT, CharacterType.HONEST_TEAMMATE),
420
+ (EpistemicType.TEAMMATE_HAS_NO_BELIEF, AskConstraintType.TEAMMATE_LACKS_KNOWLEDGE, CharacterType.HONEST_OPPONENT),
421
+
422
+ # Round 7
423
+ (EpistemicType.OPPONENT_HAS_FALSE_BELIEF, AskConstraintType.NO_CONSTRAINT, CharacterType.LIVE_PLAYER),
424
+ (EpistemicType.TEAMMATE_HAS_FALSE_BELIEF, AskConstraintType.NO_CONSTRAINT, CharacterType.DISHONEST_OPPONENT),
425
+ (EpistemicType.TEAMMATE_HAS_TRUE_BELIEF, AskConstraintType.NO_CONSTRAINT, CharacterType.HONEST_TEAMMATE),
426
+ (EpistemicType.TEAMMATE_HAS_NO_BELIEF, AskConstraintType.TEAMMATE_LACKS_KNOWLEDGE, CharacterType.HONEST_OPPONENT),
427
+
428
+ # Round 8
429
+ (EpistemicType.OPPONENT_HAS_TRUE_BELIEF_WITH_CERTAINTY, AskConstraintType.NO_CONSTRAINT, CharacterType.LIVE_PLAYER),
430
+ (EpistemicType.TEAMMATE_HAS_FALSE_BELIEF, AskConstraintType.NO_CONSTRAINT, CharacterType.DISHONEST_OPPONENT),
431
+ (EpistemicType.TEAMMATE_HAS_TRUE_BELIEF, AskConstraintType.NO_CONSTRAINT, CharacterType.HONEST_TEAMMATE),
432
+ (EpistemicType.TEAMMATE_HAS_NO_BELIEF, AskConstraintType.TEAMMATE_LACKS_KNOWLEDGE, CharacterType.HONEST_OPPONENT),
433
+
434
+ # Round 9
435
+ (EpistemicType.HONEST_OPPONENT_HAS_TRUE_BELIEF_WITH_UNCERTAINTY, AskConstraintType.NO_CONSTRAINT, CharacterType.LIVE_PLAYER),
436
+ (EpistemicType.TEAMMATE_HAS_FALSE_BELIEF, AskConstraintType.NO_CONSTRAINT, CharacterType.DISHONEST_OPPONENT),
437
+ (EpistemicType.TEAMMATE_HAS_TRUE_BELIEF, AskConstraintType.NO_CONSTRAINT, CharacterType.HONEST_TEAMMATE),
438
+ (EpistemicType.TEAMMATE_HAS_NO_BELIEF, AskConstraintType.TEAMMATE_LACKS_KNOWLEDGE, CharacterType.HONEST_OPPONENT),
439
+
440
+ # Round 10
441
+ (EpistemicType.DISHONEST_OPPONENT_HAS_TRUE_BELIEF_WITH_UNCERTAINTY, AskConstraintType.NO_CONSTRAINT, CharacterType.LIVE_PLAYER),
442
+ (EpistemicType.TEAMMATE_HAS_FALSE_BELIEF, AskConstraintType.NO_CONSTRAINT, CharacterType.DISHONEST_OPPONENT),
443
+ (EpistemicType.TEAMMATE_HAS_TRUE_BELIEF, AskConstraintType.NO_CONSTRAINT, CharacterType.HONEST_TEAMMATE),
444
+ (EpistemicType.TEAMMATE_HAS_NO_BELIEF, AskConstraintType.TEAMMATE_LACKS_KNOWLEDGE, CharacterType.HONEST_OPPONENT),
445
+
446
+ ]
447
+ scenarios = generate_scenarios_from_tuples(specs, seed=None)
448
+ outfile = 'scenarios_generated2.json'
449
+ save_scenarios(scenarios, outfile)
450
+ print(f"Created {outfile} with auto-generated scenarios")
index.html ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Room Scenario Game</title>
6
+ <style>
7
+ body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin: 16px; }
8
+ #wrap { max-width: 1000px; margin: 0 auto; }
9
+ #log { width: 100%; height: 60vh; border: 1px solid #ccc; padding: 12px; overflow: auto; white-space: pre-wrap; }
10
+ #row { margin-top: 12px; display: flex; gap: 8px; }
11
+ #cmd { flex: 1; padding: 8px; font-size: 16px; }
12
+ button { padding: 8px 12px; font-size: 16px; }
13
+ #small { color: #666; font-size: 12px; margin-top: 6px; }
14
+ </style>
15
+ </head>
16
+ <body>
17
+ <div id="wrap">
18
+ <div id="log"></div>
19
+ <div id="row">
20
+ <input id="cmd" type="text" placeholder="[Press Enter to start]" autocomplete="off" />
21
+ <button id="primary">Start</button>
22
+ </div>
23
+ <div id="small">Tip: Enter starts, continues, or submits your move. The button does the same.</div>
24
+ </div>
25
+ <script>
26
+ const log = document.getElementById("log");
27
+ const cmd = document.getElementById("cmd");
28
+ const btn = document.getElementById("primary");
29
+
30
+ async function fetchState() {
31
+ const r = await fetch("/state");
32
+ return r.json();
33
+ }
34
+ function render(s) {
35
+ log.textContent = s.transcript || "";
36
+ btn.textContent = s.primary_label || "Start";
37
+ cmd.placeholder = s.placeholder || "";
38
+ // Show/hide the button: hide during player's action (mode=awaiting_action)
39
+ if (s.mode === "awaiting_action") {
40
+ btn.style.display = "none";
41
+ } else {
42
+ btn.style.display = "inline-block";
43
+ }
44
+ // Keep focus in the input at all times so Enter always works
45
+ cmd.focus();
46
+ // Scroll to bottom
47
+ log.scrollTop = log.scrollHeight;
48
+ }
49
+
50
+ async function init() {
51
+ render(await fetchState());
52
+
53
+ btn.addEventListener("click", async () => {
54
+ const r = await fetch("/primary", { method: "POST" });
55
+ render(await r.json());
56
+ cmd.value = "";
57
+ });
58
+
59
+ // One input for everything: Enter continues or submits based on mode
60
+ cmd.addEventListener("keydown", async (e) => {
61
+ if (e.key !== "Enter") return;
62
+ e.preventDefault();
63
+ const s = await fetchState();
64
+ if (s.mode === "awaiting_action") {
65
+ const text = cmd.value.trim();
66
+ const r = await fetch("/action", {
67
+ method: "POST",
68
+ headers: { "Content-Type": "application/json" },
69
+ body: JSON.stringify({ text })
70
+ });
71
+ render(await r.json());
72
+ cmd.value = "";
73
+ } else if (s.mode === "awaiting_continue" || s.mode === "awaiting_start") {
74
+ const r = await fetch("/primary", { method: "POST" });
75
+ render(await r.json());
76
+ cmd.value = "";
77
+ } else {
78
+ // over: do nothing
79
+ }
80
+ });
81
+ }
82
+ init();
83
+ </script>
84
+ </body>
85
+ </html>
scenarios.json ADDED
@@ -0,0 +1,627 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "round_num": 1,
4
+ "whose_turn": "A",
5
+ "who_answers": "B",
6
+ "question_container": "bag",
7
+ "events": [
8
+ {
9
+ "event_type": "put",
10
+ "character": "B",
11
+ "container": "bag",
12
+ "item": "ball",
13
+ "from_container": null,
14
+ "to_container": null
15
+ },
16
+ {
17
+ "event_type": "leave",
18
+ "character": "B",
19
+ "container": null,
20
+ "item": null,
21
+ "from_container": null,
22
+ "to_container": null
23
+ },
24
+ {
25
+ "event_type": "move",
26
+ "character": "C",
27
+ "container": null,
28
+ "item": "ball",
29
+ "from_container": "bag",
30
+ "to_container": "box"
31
+ },
32
+ {
33
+ "event_type": "put",
34
+ "character": "C",
35
+ "container": "bag",
36
+ "item": "apple",
37
+ "from_container": null,
38
+ "to_container": null
39
+ },
40
+ {
41
+ "event_type": "leave",
42
+ "character": "C",
43
+ "container": null,
44
+ "item": null,
45
+ "from_container": null,
46
+ "to_container": null
47
+ }
48
+ ],
49
+ "present_initially": [
50
+ "A",
51
+ "B",
52
+ "C",
53
+ "D",
54
+ "N"
55
+ ],
56
+ "epistemic_type": "teammate_has_false_belief",
57
+ "ask_constraint": "no_constraint"
58
+ },
59
+ {
60
+ "round_num": 1,
61
+ "whose_turn": "D",
62
+ "who_answers": "C",
63
+ "question_container": "bag",
64
+ "events": [
65
+ {
66
+ "event_type": "put",
67
+ "character": "C",
68
+ "container": "bag",
69
+ "item": "ball",
70
+ "from_container": null,
71
+ "to_container": null
72
+ },
73
+ {
74
+ "event_type": "leave",
75
+ "character": "C",
76
+ "container": null,
77
+ "item": null,
78
+ "from_container": null,
79
+ "to_container": null
80
+ },
81
+ {
82
+ "event_type": "move",
83
+ "character": "B",
84
+ "container": null,
85
+ "item": "ball",
86
+ "from_container": "bag",
87
+ "to_container": "box"
88
+ },
89
+ {
90
+ "event_type": "put",
91
+ "character": "B",
92
+ "container": "bag",
93
+ "item": "apple",
94
+ "from_container": null,
95
+ "to_container": null
96
+ },
97
+ {
98
+ "event_type": "leave",
99
+ "character": "B",
100
+ "container": null,
101
+ "item": null,
102
+ "from_container": null,
103
+ "to_container": null
104
+ }
105
+ ],
106
+ "present_initially": [
107
+ "D",
108
+ "A",
109
+ "B",
110
+ "C",
111
+ "N"
112
+ ],
113
+ "epistemic_type": "teammate_has_false_belief",
114
+ "ask_constraint": "no_constraint"
115
+ },
116
+ {
117
+ "round_num": 1,
118
+ "whose_turn": "B",
119
+ "who_answers": "B",
120
+ "question_container": "bag",
121
+ "events": [
122
+ {
123
+ "event_type": "put",
124
+ "character": "B",
125
+ "container": "bag",
126
+ "item": "ball",
127
+ "from_container": null,
128
+ "to_container": null
129
+ },
130
+ {
131
+ "event_type": "leave",
132
+ "character": "B",
133
+ "container": null,
134
+ "item": null,
135
+ "from_container": null,
136
+ "to_container": null
137
+ },
138
+ {
139
+ "event_type": "leave",
140
+ "character": "A",
141
+ "container": null,
142
+ "item": null,
143
+ "from_container": null,
144
+ "to_container": null
145
+ },
146
+ {
147
+ "event_type": "leave",
148
+ "character": "D",
149
+ "container": null,
150
+ "item": null,
151
+ "from_container": null,
152
+ "to_container": null
153
+ }
154
+ ],
155
+ "present_initially": [
156
+ "B",
157
+ "A",
158
+ "C",
159
+ "D",
160
+ "N"
161
+ ],
162
+ "epistemic_type": "player_has_uncertainty",
163
+ "ask_constraint": "teammate_lacks_knowledge"
164
+ },
165
+ {
166
+ "round_num": 1,
167
+ "whose_turn": "C",
168
+ "who_answers": "C",
169
+ "question_container": "bag",
170
+ "events": [
171
+ {
172
+ "event_type": "put",
173
+ "character": "N",
174
+ "container": "bag",
175
+ "item": "ball",
176
+ "from_container": null,
177
+ "to_container": null
178
+ },
179
+ {
180
+ "event_type": "leave",
181
+ "character": "N",
182
+ "container": null,
183
+ "item": null,
184
+ "from_container": null,
185
+ "to_container": null
186
+ },
187
+ {
188
+ "event_type": "leave",
189
+ "character": "C",
190
+ "container": null,
191
+ "item": null,
192
+ "from_container": null,
193
+ "to_container": null
194
+ },
195
+ {
196
+ "event_type": "leave",
197
+ "character": "A",
198
+ "container": null,
199
+ "item": null,
200
+ "from_container": null,
201
+ "to_container": null
202
+ }
203
+ ],
204
+ "present_initially": [
205
+ "C",
206
+ "N",
207
+ "A",
208
+ "B"
209
+ ],
210
+ "epistemic_type": "player_has_uncertainty",
211
+ "ask_constraint": "teammate_and_neutral_lack_knowledge"
212
+ },
213
+ {
214
+ "round_num": 2,
215
+ "whose_turn": "A",
216
+ "who_answers": "B",
217
+ "question_container": "box",
218
+ "events": [
219
+ {
220
+ "event_type": "put",
221
+ "character": "B",
222
+ "container": "box",
223
+ "item": "orange",
224
+ "from_container": null,
225
+ "to_container": null
226
+ },
227
+ {
228
+ "event_type": "leave",
229
+ "character": "B",
230
+ "container": null,
231
+ "item": null,
232
+ "from_container": null,
233
+ "to_container": null
234
+ },
235
+ {
236
+ "event_type": "leave",
237
+ "character": "D",
238
+ "container": null,
239
+ "item": null,
240
+ "from_container": null,
241
+ "to_container": null
242
+ }
243
+ ],
244
+ "present_initially": [
245
+ "A",
246
+ "B",
247
+ "C",
248
+ "D",
249
+ "N"
250
+ ],
251
+ "epistemic_type": "teammate_has_true_belief",
252
+ "ask_constraint": "no_constraint"
253
+ },
254
+ {
255
+ "round_num": 2,
256
+ "whose_turn": "D",
257
+ "who_answers": "C",
258
+ "question_container": "box",
259
+ "events": [
260
+ {
261
+ "event_type": "put",
262
+ "character": "N",
263
+ "container": "box",
264
+ "item": "ball",
265
+ "from_container": null,
266
+ "to_container": null
267
+ },
268
+ {
269
+ "event_type": "leave",
270
+ "character": "C",
271
+ "container": null,
272
+ "item": null,
273
+ "from_container": null,
274
+ "to_container": null
275
+ },
276
+ {
277
+ "event_type": "move",
278
+ "character": "N",
279
+ "container": null,
280
+ "item": "ball",
281
+ "from_container": "box",
282
+ "to_container": "bag"
283
+ },
284
+ {
285
+ "event_type": "put",
286
+ "character": "N",
287
+ "container": "box",
288
+ "item": "banana",
289
+ "from_container": null,
290
+ "to_container": null
291
+ },
292
+ {
293
+ "event_type": "leave",
294
+ "character": "B",
295
+ "container": null,
296
+ "item": null,
297
+ "from_container": null,
298
+ "to_container": null
299
+ }
300
+ ],
301
+ "present_initially": [
302
+ "D",
303
+ "A",
304
+ "B",
305
+ "C",
306
+ "N"
307
+ ],
308
+ "epistemic_type": "teammate_has_false_belief",
309
+ "ask_constraint": "no_constraint"
310
+ },
311
+ {
312
+ "round_num": 2,
313
+ "whose_turn": "B",
314
+ "who_answers": "B",
315
+ "question_container": "bag",
316
+ "events": [
317
+ {
318
+ "event_type": "put",
319
+ "character": "B",
320
+ "container": "bag",
321
+ "item": "ball",
322
+ "from_container": null,
323
+ "to_container": null
324
+ },
325
+ {
326
+ "event_type": "leave",
327
+ "character": "B",
328
+ "container": null,
329
+ "item": null,
330
+ "from_container": null,
331
+ "to_container": null
332
+ },
333
+ {
334
+ "event_type": "leave",
335
+ "character": "A",
336
+ "container": null,
337
+ "item": null,
338
+ "from_container": null,
339
+ "to_container": null
340
+ },
341
+ {
342
+ "event_type": "leave",
343
+ "character": "D",
344
+ "container": null,
345
+ "item": null,
346
+ "from_container": null,
347
+ "to_container": null
348
+ },
349
+ {
350
+ "event_type": "leave",
351
+ "character": "N",
352
+ "container": null,
353
+ "item": null,
354
+ "from_container": null,
355
+ "to_container": null
356
+ }
357
+ ],
358
+ "present_initially": [
359
+ "B",
360
+ "A",
361
+ "C",
362
+ "D",
363
+ "N"
364
+ ],
365
+ "epistemic_type": "player_has_uncertainty",
366
+ "ask_constraint": "teammate_and_neutral_lack_knowledge"
367
+ },
368
+ {
369
+ "round_num": 2,
370
+ "whose_turn": "C",
371
+ "who_answers": "C",
372
+ "question_container": "bag",
373
+ "events": [
374
+ {
375
+ "event_type": "put",
376
+ "character": "N",
377
+ "container": "bag",
378
+ "item": "ball",
379
+ "from_container": null,
380
+ "to_container": null
381
+ },
382
+ {
383
+ "event_type": "leave",
384
+ "character": "A",
385
+ "container": null,
386
+ "item": null,
387
+ "from_container": null,
388
+ "to_container": null
389
+ },
390
+ {
391
+ "event_type": "leave",
392
+ "character": "C",
393
+ "container": null,
394
+ "item": null,
395
+ "from_container": null,
396
+ "to_container": null
397
+ },
398
+ {
399
+ "event_type": "leave",
400
+ "character": "B",
401
+ "container": null,
402
+ "item": null,
403
+ "from_container": null,
404
+ "to_container": null
405
+ }
406
+ ],
407
+ "present_initially": [
408
+ "C",
409
+ "N",
410
+ "A",
411
+ "B"
412
+ ],
413
+ "epistemic_type": "player_has_uncertainty",
414
+ "ask_constraint": "teammate_lacks_knowledge"
415
+ },
416
+ {
417
+ "round_num": 3,
418
+ "whose_turn": "A",
419
+ "who_answers": "B",
420
+ "question_container": "bag",
421
+ "events": [
422
+ {
423
+ "event_type": "leave",
424
+ "character": "B",
425
+ "container": null,
426
+ "item": null,
427
+ "from_container": null,
428
+ "to_container": null
429
+ },
430
+ {
431
+ "event_type": "put",
432
+ "character": "D",
433
+ "container": "bag",
434
+ "item": "stapler",
435
+ "from_container": null,
436
+ "to_container": null
437
+ },
438
+ {
439
+ "event_type": "leave",
440
+ "character": "D",
441
+ "container": null,
442
+ "item": null,
443
+ "from_container": null,
444
+ "to_container": null
445
+ },
446
+ {
447
+ "event_type": "put",
448
+ "character": "C",
449
+ "container": "box",
450
+ "item": "brick",
451
+ "from_container": null,
452
+ "to_container": null
453
+ }
454
+ ],
455
+ "present_initially": [
456
+ "A",
457
+ "B",
458
+ "C",
459
+ "D",
460
+ "N"
461
+ ],
462
+ "epistemic_type": "teammate_has_no_belief",
463
+ "ask_constraint": "no_constraint"
464
+ },
465
+ {
466
+ "round_num": 3,
467
+ "whose_turn": "D",
468
+ "who_answers": "C",
469
+ "question_container": "bag",
470
+ "events": [
471
+ {
472
+ "event_type": "put",
473
+ "character": "N",
474
+ "container": "box",
475
+ "item": "ball",
476
+ "from_container": null,
477
+ "to_container": null
478
+ },
479
+ {
480
+ "event_type": "leave",
481
+ "character": "C",
482
+ "container": null,
483
+ "item": null,
484
+ "from_container": null,
485
+ "to_container": null
486
+ },
487
+ {
488
+ "event_type": "move",
489
+ "character": "N",
490
+ "container": null,
491
+ "item": "ball",
492
+ "from_container": "box",
493
+ "to_container": "bag"
494
+ },
495
+ {
496
+ "event_type": "put",
497
+ "character": "N",
498
+ "container": "box",
499
+ "item": "banana",
500
+ "from_container": null,
501
+ "to_container": null
502
+ },
503
+ {
504
+ "event_type": "leave",
505
+ "character": "B",
506
+ "container": null,
507
+ "item": null,
508
+ "from_container": null,
509
+ "to_container": null
510
+ }
511
+ ],
512
+ "present_initially": [
513
+ "D",
514
+ "A",
515
+ "B",
516
+ "C",
517
+ "N"
518
+ ],
519
+ "epistemic_type": "teammate_has_false_belief",
520
+ "ask_constraint": "no_constraint"
521
+ },
522
+ {
523
+ "round_num": 3,
524
+ "whose_turn": "B",
525
+ "who_answers": "B",
526
+ "question_container": "bag",
527
+ "events": [
528
+ {
529
+ "event_type": "put",
530
+ "character": "B",
531
+ "container": "bag",
532
+ "item": "ball",
533
+ "from_container": null,
534
+ "to_container": null
535
+ },
536
+ {
537
+ "event_type": "leave",
538
+ "character": "B",
539
+ "container": null,
540
+ "item": null,
541
+ "from_container": null,
542
+ "to_container": null
543
+ },
544
+ {
545
+ "event_type": "leave",
546
+ "character": "A",
547
+ "container": null,
548
+ "item": null,
549
+ "from_container": null,
550
+ "to_container": null
551
+ },
552
+ {
553
+ "event_type": "leave",
554
+ "character": "C",
555
+ "container": null,
556
+ "item": null,
557
+ "from_container": null,
558
+ "to_container": null
559
+ },
560
+ {
561
+ "event_type": "leave",
562
+ "character": "N",
563
+ "container": null,
564
+ "item": null,
565
+ "from_container": null,
566
+ "to_container": null
567
+ }
568
+ ],
569
+ "present_initially": [
570
+ "B",
571
+ "A",
572
+ "C",
573
+ "D",
574
+ "N"
575
+ ],
576
+ "epistemic_type": "player_has_uncertainty",
577
+ "ask_constraint": "teammate_neutral_and_honest_opponent_lack_knowledge"
578
+ },
579
+ {
580
+ "round_num": 3,
581
+ "whose_turn": "C",
582
+ "who_answers": "C",
583
+ "question_container": "bag",
584
+ "events": [
585
+ {
586
+ "event_type": "put",
587
+ "character": "N",
588
+ "container": "bag",
589
+ "item": "stapler",
590
+ "from_container": null,
591
+ "to_container": null
592
+ },
593
+ {
594
+ "event_type": "leave",
595
+ "character": "A",
596
+ "container": null,
597
+ "item": null,
598
+ "from_container": null,
599
+ "to_container": null
600
+ },
601
+ {
602
+ "event_type": "leave",
603
+ "character": "C",
604
+ "container": null,
605
+ "item": null,
606
+ "from_container": null,
607
+ "to_container": null
608
+ },
609
+ {
610
+ "event_type": "leave",
611
+ "character": "B",
612
+ "container": null,
613
+ "item": null,
614
+ "from_container": null,
615
+ "to_container": null
616
+ }
617
+ ],
618
+ "present_initially": [
619
+ "C",
620
+ "N",
621
+ "A",
622
+ "B"
623
+ ],
624
+ "epistemic_type": "player_has_uncertainty",
625
+ "ask_constraint": "teammate_lacks_knowledge"
626
+ }
627
+ ]
scenarios_generated.json ADDED
@@ -0,0 +1,537 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "round_num": 1,
4
+ "whose_turn": "A",
5
+ "who_answers": "B",
6
+ "question_container": "box",
7
+ "events": [
8
+ {
9
+ "event_type": "put",
10
+ "character": "A",
11
+ "container": "box",
12
+ "item": "banana",
13
+ "from_container": null,
14
+ "to_container": null
15
+ },
16
+ {
17
+ "event_type": "leave",
18
+ "character": "B",
19
+ "container": null,
20
+ "item": null,
21
+ "from_container": null,
22
+ "to_container": null
23
+ },
24
+ {
25
+ "event_type": "move",
26
+ "character": "C",
27
+ "container": null,
28
+ "item": "banana",
29
+ "from_container": "box",
30
+ "to_container": "bag"
31
+ },
32
+ {
33
+ "event_type": "put",
34
+ "character": "N",
35
+ "container": "box",
36
+ "item": "brick",
37
+ "from_container": null,
38
+ "to_container": null
39
+ }
40
+ ],
41
+ "present_initially": [
42
+ "A",
43
+ "B",
44
+ "C",
45
+ "N"
46
+ ],
47
+ "epistemic_type": "teammate_has_false_belief",
48
+ "ask_constraint": "no_constraint"
49
+ },
50
+ {
51
+ "round_num": 1,
52
+ "whose_turn": "D",
53
+ "who_answers": "C",
54
+ "question_container": "box",
55
+ "events": [
56
+ {
57
+ "event_type": "put",
58
+ "character": "A",
59
+ "container": "box",
60
+ "item": "stapler",
61
+ "from_container": null,
62
+ "to_container": null
63
+ },
64
+ {
65
+ "event_type": "leave",
66
+ "character": "C",
67
+ "container": null,
68
+ "item": null,
69
+ "from_container": null,
70
+ "to_container": null
71
+ },
72
+ {
73
+ "event_type": "move",
74
+ "character": "A",
75
+ "container": null,
76
+ "item": "stapler",
77
+ "from_container": "box",
78
+ "to_container": "bag"
79
+ },
80
+ {
81
+ "event_type": "put",
82
+ "character": "D",
83
+ "container": "box",
84
+ "item": "ball",
85
+ "from_container": null,
86
+ "to_container": null
87
+ }
88
+ ],
89
+ "present_initially": [
90
+ "A",
91
+ "C",
92
+ "D"
93
+ ],
94
+ "epistemic_type": "teammate_has_false_belief",
95
+ "ask_constraint": "no_constraint"
96
+ },
97
+ {
98
+ "round_num": 1,
99
+ "whose_turn": "B",
100
+ "who_answers": "B",
101
+ "question_container": "bag",
102
+ "events": [
103
+ {
104
+ "event_type": "put",
105
+ "character": "B",
106
+ "container": "bag",
107
+ "item": "stapler",
108
+ "from_container": null,
109
+ "to_container": null
110
+ },
111
+ {
112
+ "event_type": "leave",
113
+ "character": "B",
114
+ "container": null,
115
+ "item": null,
116
+ "from_container": null,
117
+ "to_container": null
118
+ },
119
+ {
120
+ "event_type": "move",
121
+ "character": "D",
122
+ "container": null,
123
+ "item": "stapler",
124
+ "from_container": "bag",
125
+ "to_container": "box"
126
+ },
127
+ {
128
+ "event_type": "put",
129
+ "character": "C",
130
+ "container": "bag",
131
+ "item": "ball",
132
+ "from_container": null,
133
+ "to_container": null
134
+ }
135
+ ],
136
+ "present_initially": [
137
+ "B",
138
+ "C",
139
+ "D",
140
+ "N"
141
+ ],
142
+ "epistemic_type": "player_has_uncertainty",
143
+ "ask_constraint": "teammate_lacks_knowledge"
144
+ },
145
+ {
146
+ "round_num": 1,
147
+ "whose_turn": "C",
148
+ "who_answers": "C",
149
+ "question_container": "box",
150
+ "events": [
151
+ {
152
+ "event_type": "put",
153
+ "character": "B",
154
+ "container": "box",
155
+ "item": "apple",
156
+ "from_container": null,
157
+ "to_container": null
158
+ },
159
+ {
160
+ "event_type": "leave",
161
+ "character": "C",
162
+ "container": null,
163
+ "item": null,
164
+ "from_container": null,
165
+ "to_container": null
166
+ },
167
+ {
168
+ "event_type": "move",
169
+ "character": "B",
170
+ "container": null,
171
+ "item": "apple",
172
+ "from_container": "box",
173
+ "to_container": "bag"
174
+ },
175
+ {
176
+ "event_type": "put",
177
+ "character": "A",
178
+ "container": "box",
179
+ "item": "orange",
180
+ "from_container": null,
181
+ "to_container": null
182
+ }
183
+ ],
184
+ "present_initially": [
185
+ "A",
186
+ "B",
187
+ "C"
188
+ ],
189
+ "epistemic_type": "player_has_uncertainty",
190
+ "ask_constraint": "teammate_and_neutral_lack_knowledge"
191
+ },
192
+ {
193
+ "round_num": 2,
194
+ "whose_turn": "A",
195
+ "who_answers": "B",
196
+ "question_container": "box",
197
+ "events": [
198
+ {
199
+ "event_type": "put",
200
+ "character": "C",
201
+ "container": "box",
202
+ "item": "orange",
203
+ "from_container": null,
204
+ "to_container": null
205
+ },
206
+ {
207
+ "event_type": "leave",
208
+ "character": "B",
209
+ "container": null,
210
+ "item": null,
211
+ "from_container": null,
212
+ "to_container": null
213
+ }
214
+ ],
215
+ "present_initially": [
216
+ "A",
217
+ "B",
218
+ "C"
219
+ ],
220
+ "epistemic_type": "teammate_has_true_belief",
221
+ "ask_constraint": "no_constraint"
222
+ },
223
+ {
224
+ "round_num": 2,
225
+ "whose_turn": "D",
226
+ "who_answers": "C",
227
+ "question_container": "box",
228
+ "events": [
229
+ {
230
+ "event_type": "put",
231
+ "character": "C",
232
+ "container": "box",
233
+ "item": "brick",
234
+ "from_container": null,
235
+ "to_container": null
236
+ },
237
+ {
238
+ "event_type": "leave",
239
+ "character": "C",
240
+ "container": null,
241
+ "item": null,
242
+ "from_container": null,
243
+ "to_container": null
244
+ },
245
+ {
246
+ "event_type": "move",
247
+ "character": "B",
248
+ "container": null,
249
+ "item": "brick",
250
+ "from_container": "box",
251
+ "to_container": "bag"
252
+ },
253
+ {
254
+ "event_type": "put",
255
+ "character": "N",
256
+ "container": "box",
257
+ "item": "stapler",
258
+ "from_container": null,
259
+ "to_container": null
260
+ }
261
+ ],
262
+ "present_initially": [
263
+ "B",
264
+ "C",
265
+ "D",
266
+ "N"
267
+ ],
268
+ "epistemic_type": "teammate_has_false_belief",
269
+ "ask_constraint": "no_constraint"
270
+ },
271
+ {
272
+ "round_num": 2,
273
+ "whose_turn": "B",
274
+ "who_answers": "B",
275
+ "question_container": "bag",
276
+ "events": [
277
+ {
278
+ "event_type": "put",
279
+ "character": "B",
280
+ "container": "bag",
281
+ "item": "brick",
282
+ "from_container": null,
283
+ "to_container": null
284
+ },
285
+ {
286
+ "event_type": "leave",
287
+ "character": "B",
288
+ "container": null,
289
+ "item": null,
290
+ "from_container": null,
291
+ "to_container": null
292
+ },
293
+ {
294
+ "event_type": "move",
295
+ "character": "D",
296
+ "container": null,
297
+ "item": "brick",
298
+ "from_container": "bag",
299
+ "to_container": "box"
300
+ },
301
+ {
302
+ "event_type": "put",
303
+ "character": "D",
304
+ "container": "bag",
305
+ "item": "apple",
306
+ "from_container": null,
307
+ "to_container": null
308
+ }
309
+ ],
310
+ "present_initially": [
311
+ "B",
312
+ "C",
313
+ "D"
314
+ ],
315
+ "epistemic_type": "player_has_uncertainty",
316
+ "ask_constraint": "teammate_and_neutral_lack_knowledge"
317
+ },
318
+ {
319
+ "round_num": 2,
320
+ "whose_turn": "C",
321
+ "who_answers": "C",
322
+ "question_container": "bag",
323
+ "events": [
324
+ {
325
+ "event_type": "put",
326
+ "character": "B",
327
+ "container": "bag",
328
+ "item": "brick",
329
+ "from_container": null,
330
+ "to_container": null
331
+ },
332
+ {
333
+ "event_type": "leave",
334
+ "character": "C",
335
+ "container": null,
336
+ "item": null,
337
+ "from_container": null,
338
+ "to_container": null
339
+ },
340
+ {
341
+ "event_type": "move",
342
+ "character": "N",
343
+ "container": null,
344
+ "item": "brick",
345
+ "from_container": "bag",
346
+ "to_container": "box"
347
+ },
348
+ {
349
+ "event_type": "put",
350
+ "character": "B",
351
+ "container": "bag",
352
+ "item": "apple",
353
+ "from_container": null,
354
+ "to_container": null
355
+ }
356
+ ],
357
+ "present_initially": [
358
+ "B",
359
+ "C",
360
+ "N"
361
+ ],
362
+ "epistemic_type": "player_has_uncertainty",
363
+ "ask_constraint": "teammate_lacks_knowledge"
364
+ },
365
+ {
366
+ "round_num": 3,
367
+ "whose_turn": "A",
368
+ "who_answers": "B",
369
+ "question_container": "bag",
370
+ "events": [
371
+ {
372
+ "event_type": "leave",
373
+ "character": "B",
374
+ "container": null,
375
+ "item": null,
376
+ "from_container": null,
377
+ "to_container": null
378
+ },
379
+ {
380
+ "event_type": "put",
381
+ "character": "C",
382
+ "container": "bag",
383
+ "item": "orange",
384
+ "from_container": null,
385
+ "to_container": null
386
+ }
387
+ ],
388
+ "present_initially": [
389
+ "A",
390
+ "B",
391
+ "C"
392
+ ],
393
+ "epistemic_type": "teammate_has_no_belief",
394
+ "ask_constraint": "no_constraint"
395
+ },
396
+ {
397
+ "round_num": 3,
398
+ "whose_turn": "D",
399
+ "who_answers": "C",
400
+ "question_container": "box",
401
+ "events": [
402
+ {
403
+ "event_type": "put",
404
+ "character": "D",
405
+ "container": "box",
406
+ "item": "banana",
407
+ "from_container": null,
408
+ "to_container": null
409
+ },
410
+ {
411
+ "event_type": "leave",
412
+ "character": "C",
413
+ "container": null,
414
+ "item": null,
415
+ "from_container": null,
416
+ "to_container": null
417
+ },
418
+ {
419
+ "event_type": "move",
420
+ "character": "N",
421
+ "container": null,
422
+ "item": "banana",
423
+ "from_container": "box",
424
+ "to_container": "bag"
425
+ },
426
+ {
427
+ "event_type": "put",
428
+ "character": "A",
429
+ "container": "box",
430
+ "item": "brick",
431
+ "from_container": null,
432
+ "to_container": null
433
+ }
434
+ ],
435
+ "present_initially": [
436
+ "A",
437
+ "C",
438
+ "D",
439
+ "N"
440
+ ],
441
+ "epistemic_type": "teammate_has_false_belief",
442
+ "ask_constraint": "no_constraint"
443
+ },
444
+ {
445
+ "round_num": 3,
446
+ "whose_turn": "B",
447
+ "who_answers": "B",
448
+ "question_container": "box",
449
+ "events": [
450
+ {
451
+ "event_type": "put",
452
+ "character": "D",
453
+ "container": "box",
454
+ "item": "brick",
455
+ "from_container": null,
456
+ "to_container": null
457
+ },
458
+ {
459
+ "event_type": "leave",
460
+ "character": "B",
461
+ "container": null,
462
+ "item": null,
463
+ "from_container": null,
464
+ "to_container": null
465
+ },
466
+ {
467
+ "event_type": "move",
468
+ "character": "D",
469
+ "container": null,
470
+ "item": "brick",
471
+ "from_container": "box",
472
+ "to_container": "bag"
473
+ },
474
+ {
475
+ "event_type": "put",
476
+ "character": "D",
477
+ "container": "box",
478
+ "item": "ball",
479
+ "from_container": null,
480
+ "to_container": null
481
+ }
482
+ ],
483
+ "present_initially": [
484
+ "B",
485
+ "D"
486
+ ],
487
+ "epistemic_type": "player_has_uncertainty",
488
+ "ask_constraint": "teammate_neutral_and_honest_opponent_lack_knowledge"
489
+ },
490
+ {
491
+ "round_num": 3,
492
+ "whose_turn": "C",
493
+ "who_answers": "C",
494
+ "question_container": "box",
495
+ "events": [
496
+ {
497
+ "event_type": "put",
498
+ "character": "A",
499
+ "container": "box",
500
+ "item": "ball",
501
+ "from_container": null,
502
+ "to_container": null
503
+ },
504
+ {
505
+ "event_type": "leave",
506
+ "character": "C",
507
+ "container": null,
508
+ "item": null,
509
+ "from_container": null,
510
+ "to_container": null
511
+ },
512
+ {
513
+ "event_type": "move",
514
+ "character": "N",
515
+ "container": null,
516
+ "item": "ball",
517
+ "from_container": "box",
518
+ "to_container": "bag"
519
+ },
520
+ {
521
+ "event_type": "put",
522
+ "character": "N",
523
+ "container": "box",
524
+ "item": "orange",
525
+ "from_container": null,
526
+ "to_container": null
527
+ }
528
+ ],
529
+ "present_initially": [
530
+ "A",
531
+ "C",
532
+ "N"
533
+ ],
534
+ "epistemic_type": "player_has_uncertainty",
535
+ "ask_constraint": "teammate_lacks_knowledge"
536
+ }
537
+ ]
scenarios_generated2.json ADDED
@@ -0,0 +1,1497 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "round_num": 1,
4
+ "whose_turn": "A",
5
+ "who_answers": "B",
6
+ "question_container": "box",
7
+ "events": [
8
+ {
9
+ "event_type": "put",
10
+ "character": "D",
11
+ "container": "box",
12
+ "item": "orange",
13
+ "from_container": null,
14
+ "to_container": null
15
+ },
16
+ {
17
+ "event_type": "leave",
18
+ "character": "B",
19
+ "container": null,
20
+ "item": null,
21
+ "from_container": null,
22
+ "to_container": null
23
+ },
24
+ {
25
+ "event_type": "move",
26
+ "character": "C",
27
+ "container": null,
28
+ "item": "orange",
29
+ "from_container": "box",
30
+ "to_container": "bag"
31
+ },
32
+ {
33
+ "event_type": "put",
34
+ "character": "A",
35
+ "container": "box",
36
+ "item": "apple",
37
+ "from_container": null,
38
+ "to_container": null
39
+ }
40
+ ],
41
+ "present_initially": [
42
+ "A",
43
+ "B",
44
+ "C",
45
+ "D"
46
+ ],
47
+ "epistemic_type": "teammate_has_false_belief",
48
+ "ask_constraint": "no_constraint"
49
+ },
50
+ {
51
+ "round_num": 1,
52
+ "whose_turn": "D",
53
+ "who_answers": "C",
54
+ "question_container": "bag",
55
+ "events": [
56
+ {
57
+ "event_type": "put",
58
+ "character": "B",
59
+ "container": "bag",
60
+ "item": "apple",
61
+ "from_container": null,
62
+ "to_container": null
63
+ },
64
+ {
65
+ "event_type": "leave",
66
+ "character": "C",
67
+ "container": null,
68
+ "item": null,
69
+ "from_container": null,
70
+ "to_container": null
71
+ },
72
+ {
73
+ "event_type": "move",
74
+ "character": "B",
75
+ "container": null,
76
+ "item": "apple",
77
+ "from_container": "bag",
78
+ "to_container": "box"
79
+ },
80
+ {
81
+ "event_type": "put",
82
+ "character": "A",
83
+ "container": "bag",
84
+ "item": "brick",
85
+ "from_container": null,
86
+ "to_container": null
87
+ }
88
+ ],
89
+ "present_initially": [
90
+ "A",
91
+ "B",
92
+ "C",
93
+ "D"
94
+ ],
95
+ "epistemic_type": "teammate_has_false_belief",
96
+ "ask_constraint": "no_constraint"
97
+ },
98
+ {
99
+ "round_num": 1,
100
+ "whose_turn": "B",
101
+ "who_answers": "B",
102
+ "question_container": "box",
103
+ "events": [
104
+ {
105
+ "event_type": "put",
106
+ "character": "B",
107
+ "container": "box",
108
+ "item": "apple",
109
+ "from_container": null,
110
+ "to_container": null
111
+ },
112
+ {
113
+ "event_type": "leave",
114
+ "character": "B",
115
+ "container": null,
116
+ "item": null,
117
+ "from_container": null,
118
+ "to_container": null
119
+ },
120
+ {
121
+ "event_type": "move",
122
+ "character": "C",
123
+ "container": null,
124
+ "item": "apple",
125
+ "from_container": "box",
126
+ "to_container": "bag"
127
+ },
128
+ {
129
+ "event_type": "put",
130
+ "character": "D",
131
+ "container": "box",
132
+ "item": "orange",
133
+ "from_container": null,
134
+ "to_container": null
135
+ }
136
+ ],
137
+ "present_initially": [
138
+ "B",
139
+ "C",
140
+ "D",
141
+ "N"
142
+ ],
143
+ "epistemic_type": "player_has_uncertainty",
144
+ "ask_constraint": "teammate_lacks_knowledge"
145
+ },
146
+ {
147
+ "round_num": 1,
148
+ "whose_turn": "C",
149
+ "who_answers": "C",
150
+ "question_container": "box",
151
+ "events": [
152
+ {
153
+ "event_type": "put",
154
+ "character": "C",
155
+ "container": "box",
156
+ "item": "apple",
157
+ "from_container": null,
158
+ "to_container": null
159
+ },
160
+ {
161
+ "event_type": "leave",
162
+ "character": "C",
163
+ "container": null,
164
+ "item": null,
165
+ "from_container": null,
166
+ "to_container": null
167
+ },
168
+ {
169
+ "event_type": "move",
170
+ "character": "B",
171
+ "container": null,
172
+ "item": "apple",
173
+ "from_container": "box",
174
+ "to_container": "bag"
175
+ },
176
+ {
177
+ "event_type": "put",
178
+ "character": "B",
179
+ "container": "box",
180
+ "item": "orange",
181
+ "from_container": null,
182
+ "to_container": null
183
+ }
184
+ ],
185
+ "present_initially": [
186
+ "B",
187
+ "C"
188
+ ],
189
+ "epistemic_type": "player_has_uncertainty",
190
+ "ask_constraint": "teammate_and_neutral_lack_knowledge"
191
+ },
192
+ {
193
+ "round_num": 2,
194
+ "whose_turn": "A",
195
+ "who_answers": "B",
196
+ "question_container": "bag",
197
+ "events": [
198
+ {
199
+ "event_type": "put",
200
+ "character": "N",
201
+ "container": "bag",
202
+ "item": "ball",
203
+ "from_container": null,
204
+ "to_container": null
205
+ }
206
+ ],
207
+ "present_initially": [
208
+ "A",
209
+ "B",
210
+ "N"
211
+ ],
212
+ "epistemic_type": "teammate_has_true_belief",
213
+ "ask_constraint": "no_constraint"
214
+ },
215
+ {
216
+ "round_num": 2,
217
+ "whose_turn": "D",
218
+ "who_answers": "C",
219
+ "question_container": "box",
220
+ "events": [
221
+ {
222
+ "event_type": "put",
223
+ "character": "N",
224
+ "container": "box",
225
+ "item": "stapler",
226
+ "from_container": null,
227
+ "to_container": null
228
+ },
229
+ {
230
+ "event_type": "leave",
231
+ "character": "C",
232
+ "container": null,
233
+ "item": null,
234
+ "from_container": null,
235
+ "to_container": null
236
+ }
237
+ ],
238
+ "present_initially": [
239
+ "C",
240
+ "D",
241
+ "N"
242
+ ],
243
+ "epistemic_type": "teammate_has_true_belief",
244
+ "ask_constraint": "no_constraint"
245
+ },
246
+ {
247
+ "round_num": 2,
248
+ "whose_turn": "B",
249
+ "who_answers": "B",
250
+ "question_container": "box",
251
+ "events": [
252
+ {
253
+ "event_type": "put",
254
+ "character": "C",
255
+ "container": "box",
256
+ "item": "banana",
257
+ "from_container": null,
258
+ "to_container": null
259
+ },
260
+ {
261
+ "event_type": "leave",
262
+ "character": "B",
263
+ "container": null,
264
+ "item": null,
265
+ "from_container": null,
266
+ "to_container": null
267
+ },
268
+ {
269
+ "event_type": "move",
270
+ "character": "C",
271
+ "container": null,
272
+ "item": "banana",
273
+ "from_container": "box",
274
+ "to_container": "bag"
275
+ },
276
+ {
277
+ "event_type": "put",
278
+ "character": "C",
279
+ "container": "box",
280
+ "item": "ball",
281
+ "from_container": null,
282
+ "to_container": null
283
+ }
284
+ ],
285
+ "present_initially": [
286
+ "B",
287
+ "C"
288
+ ],
289
+ "epistemic_type": "player_has_uncertainty",
290
+ "ask_constraint": "teammate_and_neutral_lack_knowledge"
291
+ },
292
+ {
293
+ "round_num": 2,
294
+ "whose_turn": "C",
295
+ "who_answers": "C",
296
+ "question_container": "box",
297
+ "events": [
298
+ {
299
+ "event_type": "put",
300
+ "character": "C",
301
+ "container": "box",
302
+ "item": "apple",
303
+ "from_container": null,
304
+ "to_container": null
305
+ },
306
+ {
307
+ "event_type": "leave",
308
+ "character": "C",
309
+ "container": null,
310
+ "item": null,
311
+ "from_container": null,
312
+ "to_container": null
313
+ },
314
+ {
315
+ "event_type": "move",
316
+ "character": "N",
317
+ "container": null,
318
+ "item": "apple",
319
+ "from_container": "box",
320
+ "to_container": "bag"
321
+ },
322
+ {
323
+ "event_type": "put",
324
+ "character": "B",
325
+ "container": "box",
326
+ "item": "stapler",
327
+ "from_container": null,
328
+ "to_container": null
329
+ }
330
+ ],
331
+ "present_initially": [
332
+ "B",
333
+ "C",
334
+ "N"
335
+ ],
336
+ "epistemic_type": "player_has_uncertainty",
337
+ "ask_constraint": "teammate_lacks_knowledge"
338
+ },
339
+ {
340
+ "round_num": 3,
341
+ "whose_turn": "A",
342
+ "who_answers": "A",
343
+ "question_container": "box",
344
+ "events": [
345
+ {
346
+ "event_type": "put",
347
+ "character": "D",
348
+ "container": "box",
349
+ "item": "orange",
350
+ "from_container": null,
351
+ "to_container": null
352
+ },
353
+ {
354
+ "event_type": "leave",
355
+ "character": "A",
356
+ "container": null,
357
+ "item": null,
358
+ "from_container": null,
359
+ "to_container": null
360
+ },
361
+ {
362
+ "event_type": "move",
363
+ "character": "N",
364
+ "container": null,
365
+ "item": "orange",
366
+ "from_container": "box",
367
+ "to_container": "bag"
368
+ },
369
+ {
370
+ "event_type": "put",
371
+ "character": "B",
372
+ "container": "box",
373
+ "item": "brick",
374
+ "from_container": null,
375
+ "to_container": null
376
+ }
377
+ ],
378
+ "present_initially": [
379
+ "A",
380
+ "B",
381
+ "D",
382
+ "N"
383
+ ],
384
+ "epistemic_type": "player_has_uncertainty",
385
+ "ask_constraint": "no_constraint"
386
+ },
387
+ {
388
+ "round_num": 3,
389
+ "whose_turn": "D",
390
+ "who_answers": "C",
391
+ "question_container": "box",
392
+ "events": [
393
+ {
394
+ "event_type": "leave",
395
+ "character": "C",
396
+ "container": null,
397
+ "item": null,
398
+ "from_container": null,
399
+ "to_container": null
400
+ },
401
+ {
402
+ "event_type": "put",
403
+ "character": "A",
404
+ "container": "box",
405
+ "item": "stapler",
406
+ "from_container": null,
407
+ "to_container": null
408
+ }
409
+ ],
410
+ "present_initially": [
411
+ "A",
412
+ "C",
413
+ "D"
414
+ ],
415
+ "epistemic_type": "teammate_has_no_belief",
416
+ "ask_constraint": "no_constraint"
417
+ },
418
+ {
419
+ "round_num": 3,
420
+ "whose_turn": "B",
421
+ "who_answers": "A",
422
+ "question_container": "bag",
423
+ "events": [
424
+ {
425
+ "event_type": "leave",
426
+ "character": "A",
427
+ "container": null,
428
+ "item": null,
429
+ "from_container": null,
430
+ "to_container": null
431
+ },
432
+ {
433
+ "event_type": "put",
434
+ "character": "B",
435
+ "container": "bag",
436
+ "item": "brick",
437
+ "from_container": null,
438
+ "to_container": null
439
+ }
440
+ ],
441
+ "present_initially": [
442
+ "A",
443
+ "B"
444
+ ],
445
+ "epistemic_type": "teammate_has_no_belief",
446
+ "ask_constraint": "no_constraint"
447
+ },
448
+ {
449
+ "round_num": 3,
450
+ "whose_turn": "C",
451
+ "who_answers": "D",
452
+ "question_container": "bag",
453
+ "events": [
454
+ {
455
+ "event_type": "put",
456
+ "character": "B",
457
+ "container": "bag",
458
+ "item": "apple",
459
+ "from_container": null,
460
+ "to_container": null
461
+ },
462
+ {
463
+ "event_type": "leave",
464
+ "character": "D",
465
+ "container": null,
466
+ "item": null,
467
+ "from_container": null,
468
+ "to_container": null
469
+ },
470
+ {
471
+ "event_type": "move",
472
+ "character": "B",
473
+ "container": null,
474
+ "item": "apple",
475
+ "from_container": "bag",
476
+ "to_container": "box"
477
+ },
478
+ {
479
+ "event_type": "put",
480
+ "character": "B",
481
+ "container": "bag",
482
+ "item": "stapler",
483
+ "from_container": null,
484
+ "to_container": null
485
+ }
486
+ ],
487
+ "present_initially": [
488
+ "B",
489
+ "C",
490
+ "D"
491
+ ],
492
+ "epistemic_type": "teammate_has_false_belief",
493
+ "ask_constraint": "no_constraint"
494
+ },
495
+ {
496
+ "round_num": 4,
497
+ "whose_turn": "A",
498
+ "who_answers": "A",
499
+ "question_container": "bag",
500
+ "events": [
501
+ {
502
+ "event_type": "put",
503
+ "character": "N",
504
+ "container": "bag",
505
+ "item": "apple",
506
+ "from_container": null,
507
+ "to_container": null
508
+ },
509
+ {
510
+ "event_type": "leave",
511
+ "character": "A",
512
+ "container": null,
513
+ "item": null,
514
+ "from_container": null,
515
+ "to_container": null
516
+ },
517
+ {
518
+ "event_type": "move",
519
+ "character": "D",
520
+ "container": null,
521
+ "item": "apple",
522
+ "from_container": "bag",
523
+ "to_container": "box"
524
+ },
525
+ {
526
+ "event_type": "put",
527
+ "character": "C",
528
+ "container": "bag",
529
+ "item": "stapler",
530
+ "from_container": null,
531
+ "to_container": null
532
+ }
533
+ ],
534
+ "present_initially": [
535
+ "A",
536
+ "C",
537
+ "D",
538
+ "N"
539
+ ],
540
+ "epistemic_type": "player_has_uncertainty",
541
+ "ask_constraint": "teammate_lacks_knowledge"
542
+ },
543
+ {
544
+ "round_num": 4,
545
+ "whose_turn": "D",
546
+ "who_answers": "C",
547
+ "question_container": "box",
548
+ "events": [
549
+ {
550
+ "event_type": "put",
551
+ "character": "N",
552
+ "container": "box",
553
+ "item": "brick",
554
+ "from_container": null,
555
+ "to_container": null
556
+ },
557
+ {
558
+ "event_type": "leave",
559
+ "character": "C",
560
+ "container": null,
561
+ "item": null,
562
+ "from_container": null,
563
+ "to_container": null
564
+ },
565
+ {
566
+ "event_type": "move",
567
+ "character": "N",
568
+ "container": null,
569
+ "item": "brick",
570
+ "from_container": "box",
571
+ "to_container": "bag"
572
+ },
573
+ {
574
+ "event_type": "put",
575
+ "character": "N",
576
+ "container": "box",
577
+ "item": "orange",
578
+ "from_container": null,
579
+ "to_container": null
580
+ }
581
+ ],
582
+ "present_initially": [
583
+ "C",
584
+ "D",
585
+ "N"
586
+ ],
587
+ "epistemic_type": "teammate_has_false_belief",
588
+ "ask_constraint": "no_constraint"
589
+ },
590
+ {
591
+ "round_num": 4,
592
+ "whose_turn": "B",
593
+ "who_answers": "A",
594
+ "question_container": "box",
595
+ "events": [
596
+ {
597
+ "event_type": "put",
598
+ "character": "B",
599
+ "container": "box",
600
+ "item": "banana",
601
+ "from_container": null,
602
+ "to_container": null
603
+ },
604
+ {
605
+ "event_type": "leave",
606
+ "character": "A",
607
+ "container": null,
608
+ "item": null,
609
+ "from_container": null,
610
+ "to_container": null
611
+ }
612
+ ],
613
+ "present_initially": [
614
+ "A",
615
+ "B"
616
+ ],
617
+ "epistemic_type": "teammate_has_true_belief",
618
+ "ask_constraint": "no_constraint"
619
+ },
620
+ {
621
+ "round_num": 4,
622
+ "whose_turn": "C",
623
+ "who_answers": "D",
624
+ "question_container": "bag",
625
+ "events": [
626
+ {
627
+ "event_type": "leave",
628
+ "character": "D",
629
+ "container": null,
630
+ "item": null,
631
+ "from_container": null,
632
+ "to_container": null
633
+ },
634
+ {
635
+ "event_type": "put",
636
+ "character": "C",
637
+ "container": "bag",
638
+ "item": "orange",
639
+ "from_container": null,
640
+ "to_container": null
641
+ }
642
+ ],
643
+ "present_initially": [
644
+ "C",
645
+ "D"
646
+ ],
647
+ "epistemic_type": "teammate_has_no_belief",
648
+ "ask_constraint": "teammate_lacks_knowledge"
649
+ },
650
+ {
651
+ "round_num": 5,
652
+ "whose_turn": "A",
653
+ "who_answers": "A",
654
+ "question_container": "bag",
655
+ "events": [
656
+ {
657
+ "event_type": "put",
658
+ "character": "C",
659
+ "container": "bag",
660
+ "item": "orange",
661
+ "from_container": null,
662
+ "to_container": null
663
+ },
664
+ {
665
+ "event_type": "leave",
666
+ "character": "A",
667
+ "container": null,
668
+ "item": null,
669
+ "from_container": null,
670
+ "to_container": null
671
+ },
672
+ {
673
+ "event_type": "move",
674
+ "character": "C",
675
+ "container": null,
676
+ "item": "orange",
677
+ "from_container": "bag",
678
+ "to_container": "box"
679
+ },
680
+ {
681
+ "event_type": "put",
682
+ "character": "D",
683
+ "container": "bag",
684
+ "item": "banana",
685
+ "from_container": null,
686
+ "to_container": null
687
+ }
688
+ ],
689
+ "present_initially": [
690
+ "A",
691
+ "C",
692
+ "D"
693
+ ],
694
+ "epistemic_type": "player_has_uncertainty",
695
+ "ask_constraint": "teammate_and_neutral_lack_knowledge"
696
+ },
697
+ {
698
+ "round_num": 5,
699
+ "whose_turn": "D",
700
+ "who_answers": "C",
701
+ "question_container": "box",
702
+ "events": [
703
+ {
704
+ "event_type": "put",
705
+ "character": "A",
706
+ "container": "box",
707
+ "item": "orange",
708
+ "from_container": null,
709
+ "to_container": null
710
+ },
711
+ {
712
+ "event_type": "leave",
713
+ "character": "C",
714
+ "container": null,
715
+ "item": null,
716
+ "from_container": null,
717
+ "to_container": null
718
+ },
719
+ {
720
+ "event_type": "move",
721
+ "character": "D",
722
+ "container": null,
723
+ "item": "orange",
724
+ "from_container": "box",
725
+ "to_container": "bag"
726
+ },
727
+ {
728
+ "event_type": "put",
729
+ "character": "N",
730
+ "container": "box",
731
+ "item": "apple",
732
+ "from_container": null,
733
+ "to_container": null
734
+ }
735
+ ],
736
+ "present_initially": [
737
+ "A",
738
+ "C",
739
+ "D",
740
+ "N"
741
+ ],
742
+ "epistemic_type": "teammate_has_false_belief",
743
+ "ask_constraint": "no_constraint"
744
+ },
745
+ {
746
+ "round_num": 5,
747
+ "whose_turn": "B",
748
+ "who_answers": "A",
749
+ "question_container": "bag",
750
+ "events": [
751
+ {
752
+ "event_type": "put",
753
+ "character": "A",
754
+ "container": "bag",
755
+ "item": "orange",
756
+ "from_container": null,
757
+ "to_container": null
758
+ },
759
+ {
760
+ "event_type": "leave",
761
+ "character": "A",
762
+ "container": null,
763
+ "item": null,
764
+ "from_container": null,
765
+ "to_container": null
766
+ }
767
+ ],
768
+ "present_initially": [
769
+ "A",
770
+ "B"
771
+ ],
772
+ "epistemic_type": "teammate_has_true_belief",
773
+ "ask_constraint": "no_constraint"
774
+ },
775
+ {
776
+ "round_num": 5,
777
+ "whose_turn": "C",
778
+ "who_answers": "D",
779
+ "question_container": "box",
780
+ "events": [
781
+ {
782
+ "event_type": "leave",
783
+ "character": "D",
784
+ "container": null,
785
+ "item": null,
786
+ "from_container": null,
787
+ "to_container": null
788
+ },
789
+ {
790
+ "event_type": "put",
791
+ "character": "N",
792
+ "container": "box",
793
+ "item": "brick",
794
+ "from_container": null,
795
+ "to_container": null
796
+ }
797
+ ],
798
+ "present_initially": [
799
+ "C",
800
+ "D",
801
+ "N"
802
+ ],
803
+ "epistemic_type": "teammate_has_no_belief",
804
+ "ask_constraint": "teammate_lacks_knowledge"
805
+ },
806
+ {
807
+ "round_num": 6,
808
+ "whose_turn": "A",
809
+ "who_answers": "A",
810
+ "question_container": "box",
811
+ "events": [
812
+ {
813
+ "event_type": "put",
814
+ "character": "D",
815
+ "container": "box",
816
+ "item": "banana",
817
+ "from_container": null,
818
+ "to_container": null
819
+ },
820
+ {
821
+ "event_type": "leave",
822
+ "character": "A",
823
+ "container": null,
824
+ "item": null,
825
+ "from_container": null,
826
+ "to_container": null
827
+ },
828
+ {
829
+ "event_type": "move",
830
+ "character": "D",
831
+ "container": null,
832
+ "item": "banana",
833
+ "from_container": "box",
834
+ "to_container": "bag"
835
+ },
836
+ {
837
+ "event_type": "put",
838
+ "character": "D",
839
+ "container": "box",
840
+ "item": "apple",
841
+ "from_container": null,
842
+ "to_container": null
843
+ }
844
+ ],
845
+ "present_initially": [
846
+ "A",
847
+ "D"
848
+ ],
849
+ "epistemic_type": "player_has_uncertainty",
850
+ "ask_constraint": "teammate_neutral_and_honest_opponent_lack_knowledge"
851
+ },
852
+ {
853
+ "round_num": 6,
854
+ "whose_turn": "D",
855
+ "who_answers": "C",
856
+ "question_container": "box",
857
+ "events": [
858
+ {
859
+ "event_type": "put",
860
+ "character": "D",
861
+ "container": "box",
862
+ "item": "brick",
863
+ "from_container": null,
864
+ "to_container": null
865
+ },
866
+ {
867
+ "event_type": "leave",
868
+ "character": "C",
869
+ "container": null,
870
+ "item": null,
871
+ "from_container": null,
872
+ "to_container": null
873
+ },
874
+ {
875
+ "event_type": "move",
876
+ "character": "D",
877
+ "container": null,
878
+ "item": "brick",
879
+ "from_container": "box",
880
+ "to_container": "bag"
881
+ },
882
+ {
883
+ "event_type": "put",
884
+ "character": "B",
885
+ "container": "box",
886
+ "item": "apple",
887
+ "from_container": null,
888
+ "to_container": null
889
+ }
890
+ ],
891
+ "present_initially": [
892
+ "B",
893
+ "C",
894
+ "D"
895
+ ],
896
+ "epistemic_type": "teammate_has_false_belief",
897
+ "ask_constraint": "no_constraint"
898
+ },
899
+ {
900
+ "round_num": 6,
901
+ "whose_turn": "B",
902
+ "who_answers": "A",
903
+ "question_container": "box",
904
+ "events": [
905
+ {
906
+ "event_type": "put",
907
+ "character": "D",
908
+ "container": "box",
909
+ "item": "apple",
910
+ "from_container": null,
911
+ "to_container": null
912
+ },
913
+ {
914
+ "event_type": "leave",
915
+ "character": "A",
916
+ "container": null,
917
+ "item": null,
918
+ "from_container": null,
919
+ "to_container": null
920
+ }
921
+ ],
922
+ "present_initially": [
923
+ "A",
924
+ "B",
925
+ "D"
926
+ ],
927
+ "epistemic_type": "teammate_has_true_belief",
928
+ "ask_constraint": "no_constraint"
929
+ },
930
+ {
931
+ "round_num": 6,
932
+ "whose_turn": "C",
933
+ "who_answers": "D",
934
+ "question_container": "box",
935
+ "events": [
936
+ {
937
+ "event_type": "leave",
938
+ "character": "D",
939
+ "container": null,
940
+ "item": null,
941
+ "from_container": null,
942
+ "to_container": null
943
+ },
944
+ {
945
+ "event_type": "put",
946
+ "character": "N",
947
+ "container": "box",
948
+ "item": "stapler",
949
+ "from_container": null,
950
+ "to_container": null
951
+ }
952
+ ],
953
+ "present_initially": [
954
+ "C",
955
+ "D",
956
+ "N"
957
+ ],
958
+ "epistemic_type": "teammate_has_no_belief",
959
+ "ask_constraint": "teammate_lacks_knowledge"
960
+ },
961
+ {
962
+ "round_num": 7,
963
+ "whose_turn": "A",
964
+ "who_answers": "C",
965
+ "question_container": "box",
966
+ "events": [
967
+ {
968
+ "event_type": "put",
969
+ "character": "D",
970
+ "container": "box",
971
+ "item": "banana",
972
+ "from_container": null,
973
+ "to_container": null
974
+ },
975
+ {
976
+ "event_type": "leave",
977
+ "character": "C",
978
+ "container": null,
979
+ "item": null,
980
+ "from_container": null,
981
+ "to_container": null
982
+ },
983
+ {
984
+ "event_type": "move",
985
+ "character": "A",
986
+ "container": null,
987
+ "item": "banana",
988
+ "from_container": "box",
989
+ "to_container": "bag"
990
+ },
991
+ {
992
+ "event_type": "put",
993
+ "character": "D",
994
+ "container": "box",
995
+ "item": "brick",
996
+ "from_container": null,
997
+ "to_container": null
998
+ }
999
+ ],
1000
+ "present_initially": [
1001
+ "A",
1002
+ "C",
1003
+ "D"
1004
+ ],
1005
+ "epistemic_type": "opponent_has_false_belief",
1006
+ "ask_constraint": "no_constraint"
1007
+ },
1008
+ {
1009
+ "round_num": 7,
1010
+ "whose_turn": "D",
1011
+ "who_answers": "C",
1012
+ "question_container": "box",
1013
+ "events": [
1014
+ {
1015
+ "event_type": "put",
1016
+ "character": "N",
1017
+ "container": "box",
1018
+ "item": "brick",
1019
+ "from_container": null,
1020
+ "to_container": null
1021
+ },
1022
+ {
1023
+ "event_type": "leave",
1024
+ "character": "C",
1025
+ "container": null,
1026
+ "item": null,
1027
+ "from_container": null,
1028
+ "to_container": null
1029
+ },
1030
+ {
1031
+ "event_type": "move",
1032
+ "character": "D",
1033
+ "container": null,
1034
+ "item": "brick",
1035
+ "from_container": "box",
1036
+ "to_container": "bag"
1037
+ },
1038
+ {
1039
+ "event_type": "put",
1040
+ "character": "N",
1041
+ "container": "box",
1042
+ "item": "orange",
1043
+ "from_container": null,
1044
+ "to_container": null
1045
+ }
1046
+ ],
1047
+ "present_initially": [
1048
+ "C",
1049
+ "D",
1050
+ "N"
1051
+ ],
1052
+ "epistemic_type": "teammate_has_false_belief",
1053
+ "ask_constraint": "no_constraint"
1054
+ },
1055
+ {
1056
+ "round_num": 7,
1057
+ "whose_turn": "B",
1058
+ "who_answers": "A",
1059
+ "question_container": "box",
1060
+ "events": [
1061
+ {
1062
+ "event_type": "put",
1063
+ "character": "C",
1064
+ "container": "box",
1065
+ "item": "brick",
1066
+ "from_container": null,
1067
+ "to_container": null
1068
+ }
1069
+ ],
1070
+ "present_initially": [
1071
+ "A",
1072
+ "B",
1073
+ "C"
1074
+ ],
1075
+ "epistemic_type": "teammate_has_true_belief",
1076
+ "ask_constraint": "no_constraint"
1077
+ },
1078
+ {
1079
+ "round_num": 7,
1080
+ "whose_turn": "C",
1081
+ "who_answers": "D",
1082
+ "question_container": "bag",
1083
+ "events": [
1084
+ {
1085
+ "event_type": "leave",
1086
+ "character": "D",
1087
+ "container": null,
1088
+ "item": null,
1089
+ "from_container": null,
1090
+ "to_container": null
1091
+ },
1092
+ {
1093
+ "event_type": "put",
1094
+ "character": "A",
1095
+ "container": "bag",
1096
+ "item": "apple",
1097
+ "from_container": null,
1098
+ "to_container": null
1099
+ }
1100
+ ],
1101
+ "present_initially": [
1102
+ "A",
1103
+ "C",
1104
+ "D"
1105
+ ],
1106
+ "epistemic_type": "teammate_has_no_belief",
1107
+ "ask_constraint": "teammate_lacks_knowledge"
1108
+ },
1109
+ {
1110
+ "round_num": 8,
1111
+ "whose_turn": "A",
1112
+ "who_answers": "D",
1113
+ "question_container": "bag",
1114
+ "events": [
1115
+ {
1116
+ "event_type": "put",
1117
+ "character": "D",
1118
+ "container": "bag",
1119
+ "item": "ball",
1120
+ "from_container": null,
1121
+ "to_container": null
1122
+ }
1123
+ ],
1124
+ "present_initially": [
1125
+ "A",
1126
+ "D"
1127
+ ],
1128
+ "epistemic_type": "opponent_has_true_belief_with_uncertainty",
1129
+ "ask_constraint": "no_constraint"
1130
+ },
1131
+ {
1132
+ "round_num": 8,
1133
+ "whose_turn": "D",
1134
+ "who_answers": "C",
1135
+ "question_container": "bag",
1136
+ "events": [
1137
+ {
1138
+ "event_type": "put",
1139
+ "character": "D",
1140
+ "container": "bag",
1141
+ "item": "brick",
1142
+ "from_container": null,
1143
+ "to_container": null
1144
+ },
1145
+ {
1146
+ "event_type": "leave",
1147
+ "character": "C",
1148
+ "container": null,
1149
+ "item": null,
1150
+ "from_container": null,
1151
+ "to_container": null
1152
+ },
1153
+ {
1154
+ "event_type": "move",
1155
+ "character": "N",
1156
+ "container": null,
1157
+ "item": "brick",
1158
+ "from_container": "bag",
1159
+ "to_container": "box"
1160
+ },
1161
+ {
1162
+ "event_type": "put",
1163
+ "character": "B",
1164
+ "container": "bag",
1165
+ "item": "ball",
1166
+ "from_container": null,
1167
+ "to_container": null
1168
+ }
1169
+ ],
1170
+ "present_initially": [
1171
+ "B",
1172
+ "C",
1173
+ "D",
1174
+ "N"
1175
+ ],
1176
+ "epistemic_type": "teammate_has_false_belief",
1177
+ "ask_constraint": "no_constraint"
1178
+ },
1179
+ {
1180
+ "round_num": 8,
1181
+ "whose_turn": "B",
1182
+ "who_answers": "A",
1183
+ "question_container": "bag",
1184
+ "events": [
1185
+ {
1186
+ "event_type": "put",
1187
+ "character": "B",
1188
+ "container": "bag",
1189
+ "item": "banana",
1190
+ "from_container": null,
1191
+ "to_container": null
1192
+ }
1193
+ ],
1194
+ "present_initially": [
1195
+ "A",
1196
+ "B"
1197
+ ],
1198
+ "epistemic_type": "teammate_has_true_belief",
1199
+ "ask_constraint": "no_constraint"
1200
+ },
1201
+ {
1202
+ "round_num": 8,
1203
+ "whose_turn": "C",
1204
+ "who_answers": "D",
1205
+ "question_container": "bag",
1206
+ "events": [
1207
+ {
1208
+ "event_type": "leave",
1209
+ "character": "D",
1210
+ "container": null,
1211
+ "item": null,
1212
+ "from_container": null,
1213
+ "to_container": null
1214
+ },
1215
+ {
1216
+ "event_type": "put",
1217
+ "character": "B",
1218
+ "container": "bag",
1219
+ "item": "orange",
1220
+ "from_container": null,
1221
+ "to_container": null
1222
+ }
1223
+ ],
1224
+ "present_initially": [
1225
+ "B",
1226
+ "C",
1227
+ "D"
1228
+ ],
1229
+ "epistemic_type": "teammate_has_no_belief",
1230
+ "ask_constraint": "teammate_lacks_knowledge"
1231
+ },
1232
+ {
1233
+ "round_num": 9,
1234
+ "whose_turn": "A",
1235
+ "who_answers": "C",
1236
+ "question_container": "bag",
1237
+ "events": [
1238
+ {
1239
+ "event_type": "put",
1240
+ "character": "N",
1241
+ "container": "bag",
1242
+ "item": "apple",
1243
+ "from_container": null,
1244
+ "to_container": null
1245
+ },
1246
+ {
1247
+ "event_type": "leave",
1248
+ "character": "C",
1249
+ "container": null,
1250
+ "item": null,
1251
+ "from_container": null,
1252
+ "to_container": null
1253
+ }
1254
+ ],
1255
+ "present_initially": [
1256
+ "A",
1257
+ "C",
1258
+ "N"
1259
+ ],
1260
+ "epistemic_type": "honest_opponent_has_true_belief_with_uncertainty",
1261
+ "ask_constraint": "no_constraint"
1262
+ },
1263
+ {
1264
+ "round_num": 9,
1265
+ "whose_turn": "D",
1266
+ "who_answers": "C",
1267
+ "question_container": "box",
1268
+ "events": [
1269
+ {
1270
+ "event_type": "put",
1271
+ "character": "C",
1272
+ "container": "box",
1273
+ "item": "brick",
1274
+ "from_container": null,
1275
+ "to_container": null
1276
+ },
1277
+ {
1278
+ "event_type": "leave",
1279
+ "character": "C",
1280
+ "container": null,
1281
+ "item": null,
1282
+ "from_container": null,
1283
+ "to_container": null
1284
+ },
1285
+ {
1286
+ "event_type": "move",
1287
+ "character": "A",
1288
+ "container": null,
1289
+ "item": "brick",
1290
+ "from_container": "box",
1291
+ "to_container": "bag"
1292
+ },
1293
+ {
1294
+ "event_type": "put",
1295
+ "character": "D",
1296
+ "container": "box",
1297
+ "item": "stapler",
1298
+ "from_container": null,
1299
+ "to_container": null
1300
+ }
1301
+ ],
1302
+ "present_initially": [
1303
+ "A",
1304
+ "C",
1305
+ "D"
1306
+ ],
1307
+ "epistemic_type": "teammate_has_false_belief",
1308
+ "ask_constraint": "no_constraint"
1309
+ },
1310
+ {
1311
+ "round_num": 9,
1312
+ "whose_turn": "B",
1313
+ "who_answers": "A",
1314
+ "question_container": "bag",
1315
+ "events": [
1316
+ {
1317
+ "event_type": "put",
1318
+ "character": "N",
1319
+ "container": "bag",
1320
+ "item": "orange",
1321
+ "from_container": null,
1322
+ "to_container": null
1323
+ }
1324
+ ],
1325
+ "present_initially": [
1326
+ "A",
1327
+ "B",
1328
+ "N"
1329
+ ],
1330
+ "epistemic_type": "teammate_has_true_belief",
1331
+ "ask_constraint": "no_constraint"
1332
+ },
1333
+ {
1334
+ "round_num": 9,
1335
+ "whose_turn": "C",
1336
+ "who_answers": "D",
1337
+ "question_container": "bag",
1338
+ "events": [
1339
+ {
1340
+ "event_type": "leave",
1341
+ "character": "D",
1342
+ "container": null,
1343
+ "item": null,
1344
+ "from_container": null,
1345
+ "to_container": null
1346
+ },
1347
+ {
1348
+ "event_type": "put",
1349
+ "character": "A",
1350
+ "container": "bag",
1351
+ "item": "apple",
1352
+ "from_container": null,
1353
+ "to_container": null
1354
+ }
1355
+ ],
1356
+ "present_initially": [
1357
+ "A",
1358
+ "C",
1359
+ "D"
1360
+ ],
1361
+ "epistemic_type": "teammate_has_no_belief",
1362
+ "ask_constraint": "teammate_lacks_knowledge"
1363
+ },
1364
+ {
1365
+ "round_num": 10,
1366
+ "whose_turn": "A",
1367
+ "who_answers": "D",
1368
+ "question_container": "bag",
1369
+ "events": [
1370
+ {
1371
+ "event_type": "put",
1372
+ "character": "C",
1373
+ "container": "bag",
1374
+ "item": "banana",
1375
+ "from_container": null,
1376
+ "to_container": null
1377
+ },
1378
+ {
1379
+ "event_type": "leave",
1380
+ "character": "D",
1381
+ "container": null,
1382
+ "item": null,
1383
+ "from_container": null,
1384
+ "to_container": null
1385
+ }
1386
+ ],
1387
+ "present_initially": [
1388
+ "A",
1389
+ "C",
1390
+ "D"
1391
+ ],
1392
+ "epistemic_type": "dishonest_opponent_has_true_belief_with_uncertainty",
1393
+ "ask_constraint": "no_constraint"
1394
+ },
1395
+ {
1396
+ "round_num": 10,
1397
+ "whose_turn": "D",
1398
+ "who_answers": "C",
1399
+ "question_container": "box",
1400
+ "events": [
1401
+ {
1402
+ "event_type": "put",
1403
+ "character": "B",
1404
+ "container": "box",
1405
+ "item": "brick",
1406
+ "from_container": null,
1407
+ "to_container": null
1408
+ },
1409
+ {
1410
+ "event_type": "leave",
1411
+ "character": "C",
1412
+ "container": null,
1413
+ "item": null,
1414
+ "from_container": null,
1415
+ "to_container": null
1416
+ },
1417
+ {
1418
+ "event_type": "move",
1419
+ "character": "N",
1420
+ "container": null,
1421
+ "item": "brick",
1422
+ "from_container": "box",
1423
+ "to_container": "bag"
1424
+ },
1425
+ {
1426
+ "event_type": "put",
1427
+ "character": "N",
1428
+ "container": "box",
1429
+ "item": "apple",
1430
+ "from_container": null,
1431
+ "to_container": null
1432
+ }
1433
+ ],
1434
+ "present_initially": [
1435
+ "B",
1436
+ "C",
1437
+ "D",
1438
+ "N"
1439
+ ],
1440
+ "epistemic_type": "teammate_has_false_belief",
1441
+ "ask_constraint": "no_constraint"
1442
+ },
1443
+ {
1444
+ "round_num": 10,
1445
+ "whose_turn": "B",
1446
+ "who_answers": "A",
1447
+ "question_container": "bag",
1448
+ "events": [
1449
+ {
1450
+ "event_type": "put",
1451
+ "character": "N",
1452
+ "container": "bag",
1453
+ "item": "stapler",
1454
+ "from_container": null,
1455
+ "to_container": null
1456
+ }
1457
+ ],
1458
+ "present_initially": [
1459
+ "A",
1460
+ "B",
1461
+ "N"
1462
+ ],
1463
+ "epistemic_type": "teammate_has_true_belief",
1464
+ "ask_constraint": "no_constraint"
1465
+ },
1466
+ {
1467
+ "round_num": 10,
1468
+ "whose_turn": "C",
1469
+ "who_answers": "D",
1470
+ "question_container": "bag",
1471
+ "events": [
1472
+ {
1473
+ "event_type": "leave",
1474
+ "character": "D",
1475
+ "container": null,
1476
+ "item": null,
1477
+ "from_container": null,
1478
+ "to_container": null
1479
+ },
1480
+ {
1481
+ "event_type": "put",
1482
+ "character": "A",
1483
+ "container": "bag",
1484
+ "item": "ball",
1485
+ "from_container": null,
1486
+ "to_container": null
1487
+ }
1488
+ ],
1489
+ "present_initially": [
1490
+ "A",
1491
+ "C",
1492
+ "D"
1493
+ ],
1494
+ "epistemic_type": "teammate_has_no_belief",
1495
+ "ask_constraint": "teammate_lacks_knowledge"
1496
+ }
1497
+ ]
tom_test.py ADDED
@@ -0,0 +1,1001 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Room Scenario Game - A strategic deduction game about information and belief.
3
+ """
4
+
5
+ import json
6
+ import re
7
+ from typing import Dict, List, Set, Optional, Tuple
8
+ from dataclasses import dataclass, field, asdict
9
+ from enum import Enum
10
+ import random
11
+
12
+ WINNING_SCORE = 5
13
+
14
+ GAME_SETUP = """Let's play a game! I'm going to read a series of scenarios, and players will need to figure out the right action to take in each one, so that their team scores more points than their opponent's team. All the scenarios involve people going in and out of rooms. Assume that if a person is inside a room they are aware of where everything in the room is. Assume that when they are outside the room they are unaware of anything happening inside the room.
15
+
16
+ Each scenario will include one or more of the following players: A, B, C, D, and N. A and B are teammates on the Blue team, C and D are teammates on the Red team, and N is a neutral party, on neither team.
17
+
18
+ Each turn consists of a scenario, an action phase, and an answer phase.
19
+
20
+ In the answer phase, I will ask a target player (one of A, B, C, or D) to name the contents of one container.
21
+
22
+ When a player answers my question correctly, their team gets 1 point. The first team to {WINNING_SCORE} points wins.
23
+
24
+ Before that player answers, the player whose turn it is will be allowed to take one action: Ask, Tell, or Pass.
25
+
26
+ An Ask action entails asking one player the contents of one container. The format for an Ask action is: Ask(Player, Container); this will result in the player you ask answering your question. It costs your team 0.5 points to do this action.
27
+
28
+ A Tell action entails telling one player the contents of one container. The format for a Tell action is: Tell(Player, Container, Contents); this will result in the player you tell hearing the answer you give. It costs your team 0.5 points to do this action.
29
+
30
+ The format for a Pass action is simply "Pass". Nothing changes as a result of this action, and it costs 0 points.
31
+
32
+ The Neutral party does not get any turns, but will answer any question honestly.
33
+ """
34
+
35
+
36
+ class ActionType(Enum):
37
+ ASK = "ask"
38
+ TELL = "tell"
39
+ PASS = "pass"
40
+
41
+
42
+ class Team(Enum):
43
+ BLUE = "Blue"
44
+ RED = "Red"
45
+ NEUTRAL = "Neutral"
46
+
47
+
48
+ class CharacterType(Enum):
49
+ LIVE_PLAYER = "live_player"
50
+ HONEST_TEAMMATE = "honest_teammate"
51
+ DISHONEST_TEAMMATE = "dishonest_teammate"
52
+ HONEST_OPPONENT = "honest_opponent"
53
+ DISHONEST_OPPONENT = "dishonest_opponent"
54
+ NEUTRAL = "neutral"
55
+
56
+
57
+ class EpistemicType(Enum):
58
+ TEAMMATE_HAS_FALSE_BELIEF = "teammate_has_false_belief"
59
+ TEAMMATE_HAS_TRUE_BELIEF = "teammate_has_true_belief"
60
+ TEAMMATE_HAS_NO_BELIEF = "teammate_has_no_belief"
61
+ PLAYER_HAS_UNCERTAINTY = "player_has_uncertainty"
62
+ PLAYER_HAS_NO_BELIEF = "player_has_no_belief"
63
+ OPPONENT_HAS_FALSE_BELIEF = "opponent_has_false_belief"
64
+ OPPONENT_HAS_TRUE_BELIEF_WITH_CERTAINTY = "opponent_has_true_belief_with_uncertainty"
65
+ HONEST_OPPONENT_HAS_TRUE_BELIEF_WITH_UNCERTAINTY = "honest_opponent_has_true_belief_with_uncertainty"
66
+ DISHONEST_OPPONENT_HAS_TRUE_BELIEF_WITH_UNCERTAINTY = "dishonest_opponent_has_true_belief_with_uncertainty"
67
+
68
+
69
+ class AskConstraintType(Enum):
70
+ TEAMMATE_LACKS_KNOWLEDGE = "teammate_lacks_knowledge"
71
+ TEAMMATE_AND_NEUTRAL_LACK_KNOWLEDGE = "teammate_and_neutral_lack_knowledge"
72
+ TEAMMATE_NEUTRAL_AND_HONEST_OPPONENT_LACK_KNOWLEDGE = "teammate_neutral_and_honest_opponent_lack_knowledge"
73
+ NO_CONSTRAINT = "no_constraint"
74
+
75
+
76
+ @dataclass
77
+ class Character:
78
+ name: str
79
+ team: Team
80
+ char_type: CharacterType
81
+ knowledge: Dict[str, Optional[str]] = field(default_factory=dict)
82
+ beliefs: Dict[str, Optional[str]] = field(default_factory=dict)
83
+
84
+ def get_knowledge(self, container: str) -> Optional[str]:
85
+ """Get what the character actually knows."""
86
+ return self.knowledge.get(container)
87
+
88
+ def get_belief(self, container: str) -> Optional[str]:
89
+ """Get what the character believes (knowledge or told belief)."""
90
+ if container in self.beliefs:
91
+ return self.beliefs[container]
92
+ return self.knowledge.get(container)
93
+
94
+ def update_knowledge(self, container: str, contents: Optional[str]):
95
+ """Update character's direct knowledge."""
96
+ self.knowledge[container] = contents
97
+
98
+ def receive_info(self, container: str, contents: Optional[str], from_char: 'Character', trust: bool):
99
+ """Receive information from another character."""
100
+ if trust:
101
+ self.beliefs[container] = contents
102
+
103
+
104
+ @dataclass
105
+ class Event:
106
+ """Represents an event in a scenario."""
107
+ event_type: str # 'put', 'move', 'leave'
108
+ character: str
109
+ container: Optional[str] = None
110
+ item: Optional[str] = None
111
+ from_container: Optional[str] = None
112
+ to_container: Optional[str] = None
113
+
114
+
115
+ @dataclass
116
+ class Scenario:
117
+ """Represents one scenario in the game."""
118
+ round_num: int
119
+ whose_turn: str
120
+ who_answers: str
121
+ question_container: str
122
+ events: List[Event]
123
+ present_initially: List[str]
124
+ epistemic_type: Optional[EpistemicType] = None
125
+ ask_constraint: Optional[AskConstraintType] = None
126
+
127
+ def get_description_for(self, character_name: str, characters: Dict[str, Character]) -> str:
128
+ """Generate scenario description from a character's perspective."""
129
+ char_map = {c: c for c in characters.keys()}
130
+ if character_name in characters:
131
+ char_map[character_name] = "You"
132
+
133
+ lines = []
134
+ present = set(self.present_initially)
135
+
136
+ # Initial state
137
+ def format_name_list(names: List[str]) -> str:
138
+ """Return 'X', 'X and Y', or 'X, Y, and Z' with an Oxford comma."""
139
+ if not names:
140
+ return ""
141
+ if len(names) == 1:
142
+ return names[0]
143
+ if len(names) == 2:
144
+ return f"{names[0]} and {names[1]}"
145
+ return ", ".join(names[:-1]) + f", and {names[-1]}"
146
+ names = [char_map.get(c, c) for c in sorted(present)]
147
+ for i, name in enumerate(names):
148
+ if i>0 and name == "You": names[i] = "you"
149
+ char_list = format_name_list(names)
150
+ verb = "are" if (len(names) > 1 or names[0].lower() == "you") else "is"
151
+ lines.append(f"{char_list} {verb} in a room. Inside the room are an empty bag and an empty box.")
152
+
153
+ # Narrate only while the perspective character is present
154
+ perspective_present = character_name in present
155
+
156
+ for event in self.events:
157
+ if not perspective_present:
158
+ break
159
+
160
+ actor = char_map.get(event.character, event.character)
161
+ you_form = (actor == "You")
162
+
163
+ if event.event_type == 'put':
164
+ verb_put = "put" if you_form else "puts"
165
+ lines.append(f"{actor} {verb_put} a {event.item} in the {event.container}.")
166
+
167
+ elif event.event_type == 'move':
168
+ verb_move = "move" if you_form else "moves"
169
+ lines.append(f"{actor} {verb_move} the {event.item} to the {event.to_container}.")
170
+
171
+ elif event.event_type == 'leave':
172
+ verb_leave = "leave" if you_form else "leaves"
173
+ lines.append(f"{actor} {verb_leave} the room.")
174
+ present.discard(event.character)
175
+ if event.character == character_name:
176
+ # Stop narrating once the perspective person leaves
177
+ perspective_present = False
178
+ break
179
+
180
+ return " ".join(lines)
181
+
182
+ def to_dict(self) -> dict:
183
+ """Convert scenario to dictionary for JSON serialization."""
184
+ return {
185
+ 'round_num': self.round_num,
186
+ 'whose_turn': self.whose_turn,
187
+ 'who_answers': self.who_answers,
188
+ 'question_container': self.question_container,
189
+ 'events': [asdict(e) for e in self.events],
190
+ 'present_initially': self.present_initially,
191
+ 'epistemic_type': self.epistemic_type.value if self.epistemic_type else None,
192
+ 'ask_constraint': self.ask_constraint.value if self.ask_constraint else None
193
+ }
194
+
195
+ @staticmethod
196
+ def from_dict(data: dict) -> 'Scenario':
197
+ """Create scenario from dictionary."""
198
+ return Scenario(
199
+ round_num=data['round_num'],
200
+ whose_turn=data['whose_turn'],
201
+ who_answers=data['who_answers'],
202
+ question_container=data['question_container'],
203
+ events=[Event(**e) for e in data['events']],
204
+ present_initially=data['present_initially'],
205
+ epistemic_type=EpistemicType(data['epistemic_type']) if data.get('epistemic_type') else None,
206
+ ask_constraint=AskConstraintType(data['ask_constraint']) if data.get('ask_constraint') else None
207
+ )
208
+
209
+
210
+ @dataclass
211
+ class Action:
212
+ """Represents a player action."""
213
+ action_type: ActionType
214
+ target_char: Optional[str] = None
215
+ container: Optional[str] = None
216
+ contents: Optional[str] = None
217
+
218
+
219
+ @dataclass
220
+ class TurnRecord:
221
+ """Records what happened in a turn."""
222
+ round_num: int
223
+ character: str
224
+ scenario_desc: str
225
+ question: str
226
+ action: str
227
+ action_cost: float
228
+ answer_given: str
229
+ answer_correct: bool
230
+ answer_score: float
231
+ optimal_action: str
232
+ was_optimal: bool
233
+ blue_score_after: float
234
+ red_score_after: float
235
+ epistemic_type: Optional[str] = None
236
+ ask_constraint: Optional[str] = None
237
+
238
+
239
+ class GameState:
240
+ """Manages the game state."""
241
+
242
+ def __init__(self, scenarios: List[Scenario], turn_order: List[str]):
243
+ self.scenarios = scenarios
244
+ self.turn_order = turn_order
245
+ self.current_scenario_idx = 0
246
+ self.current_turn_idx = 0
247
+ self.scores = {Team.BLUE: 0.0, Team.RED: 0.0}
248
+ self.characters: Dict[str, Character] = {}
249
+ self.turn_records: List[TurnRecord] = []
250
+ self.game_over = False
251
+ self.winner = None
252
+
253
+ def initialize_characters(self, live_player_name: str):
254
+ """Initialize all characters with their types and teams."""
255
+ if live_player_name == 'A':
256
+ self.characters = {
257
+ 'A': Character('A', Team.BLUE, CharacterType.LIVE_PLAYER),
258
+ 'B': Character('B', Team.BLUE, CharacterType.HONEST_TEAMMATE),
259
+ 'C': Character('C', Team.RED, CharacterType.HONEST_OPPONENT),
260
+ 'D': Character('D', Team.RED, CharacterType.DISHONEST_OPPONENT),
261
+ 'N': Character('N', Team.NEUTRAL, CharacterType.NEUTRAL),
262
+ }
263
+
264
+ def get_teammate(self, actor_name: str) -> Optional[str]:
265
+ """Get the teammate of the actor."""
266
+ actor = self.characters[actor_name]
267
+ for name, char in self.characters.items():
268
+ if name != actor_name and char.team == actor.team:
269
+ return name
270
+ return None
271
+
272
+ def is_honest_to(self, speaker_name: str, listener_name: str) -> bool:
273
+ """Check if speaker is honest to listener."""
274
+ speaker = self.characters[speaker_name]
275
+ listener = self.characters[listener_name]
276
+
277
+ if speaker.char_type == CharacterType.NEUTRAL:
278
+ return True
279
+ if speaker.char_type == CharacterType.HONEST_OPPONENT:
280
+ return True
281
+ return speaker.team == listener.team
282
+
283
+ def trusts(self, listener_name: str, speaker_name: str) -> bool:
284
+ listener = self.characters[listener_name]
285
+ speaker = self.characters[speaker_name]
286
+
287
+ # Teammates always trust each other (including the live player)
288
+ if listener.team != Team.NEUTRAL and listener.team == speaker.team:
289
+ return True
290
+
291
+ # Neutral trusts everyone
292
+ if listener.char_type == CharacterType.NEUTRAL:
293
+ return True
294
+
295
+ # (Keep any remaining special cases you want)
296
+ if listener.char_type == CharacterType.HONEST_OPPONENT:
297
+ return True
298
+ if listener.char_type == CharacterType.HONEST_TEAMMATE:
299
+ return True
300
+ if listener.char_type == CharacterType.DISHONEST_OPPONENT:
301
+ return speaker.team == listener.team
302
+ if listener.char_type == CharacterType.DISHONEST_TEAMMATE:
303
+ return speaker.team == listener.team
304
+
305
+ return False
306
+
307
+ def reset_scenario_knowledge(self):
308
+ """Reset all character knowledge for new scenario."""
309
+ for char in self.characters.values():
310
+ char.knowledge = {}
311
+ char.beliefs = {}
312
+
313
+ def get_present_at_end(self, scenario: Scenario) -> Set[str]:
314
+ """Get who's present at the end of the scenario."""
315
+ present = set(scenario.present_initially)
316
+ for event in scenario.events:
317
+ if event.event_type == 'leave':
318
+ present.discard(event.character)
319
+ return present
320
+
321
+ def process_scenario_events(self, scenario: Scenario):
322
+ """Process scenario events to determine what each character knows."""
323
+ self.reset_scenario_knowledge()
324
+
325
+ present = set(scenario.present_initially)
326
+ container_contents = {'bag': None, 'box': None}
327
+
328
+ for event in scenario.events:
329
+ if event.event_type == 'put':
330
+ container_contents[event.container] = event.item
331
+ for char_name in present:
332
+ self.characters[char_name].update_knowledge(event.container, event.item)
333
+
334
+ elif event.event_type == 'move':
335
+ container_contents[event.to_container] = event.item
336
+ container_contents[event.from_container] = None
337
+ for char_name in present:
338
+ self.characters[char_name].update_knowledge(event.to_container, event.item)
339
+ self.characters[char_name].update_knowledge(event.from_container, None)
340
+
341
+ elif event.event_type == 'leave':
342
+ present.discard(event.character)
343
+
344
+ return container_contents
345
+
346
+ def execute_npc_action(self, actor_name: str, scenario: Scenario, true_contents: Dict[str, str]) -> Action:
347
+ """Execute NPC action based on game rules - completely determined."""
348
+ container = scenario.question_container
349
+ truth = true_contents[container]
350
+ target = scenario.who_answers
351
+
352
+ # Tell teammate truth scenarios
353
+ if scenario.epistemic_type in [EpistemicType.TEAMMATE_HAS_FALSE_BELIEF,
354
+ EpistemicType.TEAMMATE_HAS_NO_BELIEF]:
355
+ return Action(ActionType.TELL, target, container, truth)
356
+
357
+ # Pass scenarios
358
+ if scenario.epistemic_type in [EpistemicType.OPPONENT_HAS_FALSE_BELIEF]:
359
+ return Action(ActionType.PASS)
360
+
361
+ if scenario.epistemic_type in [EpistemicType.OPPONENT_HAS_TRUE_BELIEF_WITH_CERTAINTY,
362
+ EpistemicType.DISHONEST_OPPONENT_HAS_TRUE_BELIEF_WITH_UNCERTAINTY]:
363
+ return Action(ActionType.PASS)
364
+
365
+ if scenario.epistemic_type in [EpistemicType.TEAMMATE_HAS_TRUE_BELIEF]:
366
+ return Action(ActionType.PASS)
367
+
368
+ # Tell opponent scenario - tell lie only if actor is dishonest to target
369
+ if scenario.epistemic_type == EpistemicType.HONEST_OPPONENT_HAS_TRUE_BELIEF_WITH_UNCERTAINTY:
370
+ if self.characters[actor_name].char_type in [CharacterType.HONEST_OPPONENT, CharacterType.HONEST_TEAMMATE]:
371
+ return Action(ActionType.PASS)
372
+ else:
373
+ possible_lies = ['apple', 'ball', 'banana', 'brick', 'stapler', 'orange']
374
+ lie = next((item for item in possible_lies if item != truth), 'nothing')
375
+ return Action(ActionType.TELL, target, container, lie)
376
+
377
+ # Ask scenarios: ask based on who's present
378
+ if scenario.epistemic_type in [EpistemicType.PLAYER_HAS_UNCERTAINTY,
379
+ EpistemicType.PLAYER_HAS_NO_BELIEF]:
380
+ present = self.get_present_at_end(scenario)
381
+ present.discard(actor_name)
382
+
383
+ # Preference: teammate -> neutral -> opponent (if trusts opponents)
384
+ teammate = self.get_teammate(actor_name)
385
+ if teammate and teammate in present:
386
+ return Action(ActionType.ASK, teammate, container)
387
+
388
+ if 'N' in present:
389
+ return Action(ActionType.ASK, 'N', container)
390
+
391
+ # Ask opponent only if actor trusts opponents
392
+ actor = self.characters[actor_name]
393
+ actor_trusts_opponents = actor.char_type in [CharacterType.HONEST_TEAMMATE,
394
+ CharacterType.HONEST_OPPONENT]
395
+ if actor_trusts_opponents:
396
+ # Choose a random eligible opponent from those present
397
+ eligible = [name for name in present
398
+ if name != actor_name and self.characters[name].char_type in [
399
+ CharacterType.HONEST_TEAMMATE,
400
+ CharacterType.DISHONEST_TEAMMATE,
401
+ CharacterType.HONEST_OPPONENT,
402
+ CharacterType.DISHONEST_OPPONENT
403
+ ]]
404
+ if eligible:
405
+ return Action(ActionType.ASK, random.choice(eligible), container)
406
+
407
+ return Action(ActionType.PASS)
408
+
409
+ return Action(ActionType.PASS)
410
+
411
+ def is_action_optimal(self, action_str: str, scenario: Scenario, true_contents: Dict[str, str]) -> bool:
412
+ """Check if live player's action is optimal."""
413
+ action = self.parse_action(action_str)
414
+ if not action:
415
+ return False
416
+
417
+ # For Tell teammate truth scenarios
418
+ if scenario.epistemic_type in [EpistemicType.TEAMMATE_HAS_FALSE_BELIEF,
419
+ EpistemicType.TEAMMATE_HAS_NO_BELIEF]:
420
+ truth = true_contents[scenario.question_container]
421
+ return (action.action_type == ActionType.TELL and
422
+ action.target_char == scenario.who_answers and
423
+ action.container == scenario.question_container and
424
+ action.contents == truth)
425
+
426
+ # For Tell opponent with uncertainty
427
+ if scenario.epistemic_type == EpistemicType.HONEST_OPPONENT_HAS_TRUE_BELIEF_WITH_UNCERTAINTY:
428
+ truth = true_contents[scenario.question_container]
429
+ if action.action_type != ActionType.TELL:
430
+ return False
431
+ if action.target_char != scenario.who_answers or action.container != scenario.question_container:
432
+ return False
433
+ return action.contents != truth
434
+
435
+ # For Ask scenarios
436
+ if scenario.epistemic_type in [EpistemicType.PLAYER_HAS_UNCERTAINTY,
437
+ EpistemicType.PLAYER_HAS_NO_BELIEF]:
438
+ if scenario.ask_constraint == AskConstraintType.TEAMMATE_NEUTRAL_AND_HONEST_OPPONENT_LACK_KNOWLEDGE:
439
+ return action.action_type == ActionType.PASS
440
+
441
+ if action.action_type == ActionType.TELL or action.container != scenario.question_container:
442
+ return False
443
+
444
+ present = self.get_present_at_end(scenario)
445
+ present.discard(scenario.whose_turn)
446
+
447
+ if action.target_char not in present:
448
+ return False
449
+
450
+ if scenario.ask_constraint == AskConstraintType.NO_CONSTRAINT:
451
+ return self.characters[action.target_char].char_type in [CharacterType.HONEST_TEAMMATE, CharacterType.DISHONEST_TEAMMATE]
452
+ elif scenario.ask_constraint == AskConstraintType.TEAMMATE_LACKS_KNOWLEDGE:
453
+ return self.characters[action.target_char].char_type in [CharacterType.NEUTRAL]
454
+ elif scenario.ask_constraint == AskConstraintType.TEAMMATE_AND_NEUTRAL_LACK_KNOWLEDGE:
455
+ return self.characters[action.target_char].char_type in [CharacterType.HONEST_OPPONENT]
456
+ else:
457
+ return False
458
+
459
+ # For Pass scenarios
460
+ if scenario.epistemic_type in [EpistemicType.TEAMMATE_HAS_TRUE_BELIEF,
461
+ EpistemicType.OPPONENT_HAS_FALSE_BELIEF,
462
+ EpistemicType.OPPONENT_HAS_TRUE_BELIEF_WITH_CERTAINTY,
463
+ EpistemicType.DISHONEST_OPPONENT_HAS_TRUE_BELIEF_WITH_UNCERTAINTY]:
464
+ return action.action_type == ActionType.PASS
465
+
466
+ return False
467
+
468
+ def get_current_scenario(self) -> Optional[Scenario]:
469
+ """Get the current scenario."""
470
+ if self.current_scenario_idx >= len(self.scenarios):
471
+ return None
472
+ return self.scenarios[self.current_scenario_idx]
473
+
474
+ def get_current_turn_character(self) -> Optional[str]:
475
+ """Get whose turn it is."""
476
+ if self.current_turn_idx >= len(self.turn_order):
477
+ return None
478
+ return self.turn_order[self.current_turn_idx]
479
+
480
+ def parse_action(self, action_str: str) -> Optional[Action]:
481
+ """Parse action string into Action object."""
482
+ action_str = action_str.strip()
483
+
484
+ if action_str.lower() == 'pass':
485
+ return Action(ActionType.PASS)
486
+
487
+ ask_match = re.match(r'Ask\(([A-DN]),\s*(bag|box)\)', action_str, re.IGNORECASE)
488
+ if ask_match:
489
+ return Action(ActionType.ASK, ask_match.group(1).upper(), ask_match.group(2).lower())
490
+
491
+ tell_match = re.match(r'Tell\(([A-DN]),\s*(bag|box),\s*(\w+)\)', action_str, re.IGNORECASE)
492
+ if tell_match:
493
+ return Action(ActionType.TELL, tell_match.group(1).upper(),
494
+ tell_match.group(2).lower(), tell_match.group(3).lower())
495
+
496
+ return None
497
+
498
+ def execute_action(self, actor_name: str, action: Action, true_contents: Dict[str, str]) -> Tuple[float, str]:
499
+ """Execute an action and return the score change and description."""
500
+ score_delta = 0.0
501
+ desc = ""
502
+
503
+ if action.action_type == ActionType.PASS:
504
+ desc = "Pass"
505
+
506
+ elif action.action_type == ActionType.ASK:
507
+ score_delta -= 0.5
508
+ target = self.characters[action.target_char]
509
+ container = action.container
510
+
511
+ # What the target says:
512
+ # - Honest: report what they believe (what they observed).
513
+ # - Dishonest: intentionally say something different from their belief.
514
+ if target.char_type in [CharacterType.DISHONEST_TEAMMATE, CharacterType.DISHONEST_OPPONENT]:
515
+ believed = target.get_belief(container)
516
+ options = ['apple', 'ball', 'banana', 'brick', 'stapler', 'orange']
517
+ # Pick an item different from what they believe; if they believe nothing, pick any item.
518
+ wrong = next((x for x in options if x != (believed or 'nothing')), options[0])
519
+ answer = wrong
520
+ else:
521
+ answer = target.get_belief(container)
522
+
523
+ # Asking always updates the asker’s belief with whatever was answered.
524
+ self.characters[actor_name].receive_info(container, answer, target, True)
525
+
526
+ desc = f"Ask({action.target_char}, {container})"
527
+
528
+ elif action.action_type == ActionType.TELL:
529
+ score_delta -= 0.5
530
+ target_name = action.target_char
531
+
532
+ if self.trusts(target_name, actor_name):
533
+ self.characters[target_name].receive_info(action.container, action.contents,
534
+ self.characters[actor_name], True)
535
+
536
+ desc = f"Tell({action.target_char}, {action.container}, {action.contents})"
537
+
538
+ return score_delta, desc
539
+
540
+ def resolve_answer_phase(self, scenario: Scenario, true_contents: Dict[str, str]) -> Tuple[str, bool, float]:
541
+ """Resolve the answer phase and return answer, correctness, and score change."""
542
+ answerer = self.characters[scenario.who_answers]
543
+ container = scenario.question_container
544
+
545
+ belief = answerer.get_belief(container)
546
+ truth = true_contents[container]
547
+
548
+ is_correct = (belief == truth)
549
+
550
+ if is_correct:
551
+ return belief if belief else 'nothing', True, 1.0
552
+ else:
553
+ return belief if belief else 'nothing', False, 0.0
554
+
555
+ def check_game_over(self):
556
+ """Check if game is over."""
557
+ if self.scores[Team.BLUE] >= WINNING_SCORE:
558
+ self.game_over = True
559
+ self.winner = Team.BLUE
560
+ elif self.scores[Team.RED] >= WINNING_SCORE:
561
+ self.game_over = True
562
+ self.winner = Team.RED
563
+ elif self.current_scenario_idx >= len(self.scenarios):
564
+ self.game_over = True
565
+ if self.scores[Team.BLUE] > self.scores[Team.RED]:
566
+ self.winner = Team.BLUE
567
+ elif self.scores[Team.RED] > self.scores[Team.BLUE]:
568
+ self.winner = Team.RED
569
+ else:
570
+ self.winner = None
571
+
572
+ def advance_turn(self):
573
+ """Move to next turn."""
574
+ self.current_scenario_idx += 1
575
+ self.current_turn_idx += 1
576
+ if self.current_turn_idx >= len(self.turn_order):
577
+ self.current_turn_idx = 0
578
+
579
+
580
+ def load_scenarios(filename: str) -> List[Scenario]:
581
+ """Load scenarios from JSON file."""
582
+ with open(filename, 'r') as f:
583
+ data = json.load(f)
584
+ return [Scenario.from_dict(s) for s in data]
585
+
586
+
587
+ def save_scenarios(scenarios: List[Scenario], filename: str):
588
+ """Save scenarios to JSON file."""
589
+ with open(filename, 'w') as f:
590
+ json.dump([s.to_dict() for s in scenarios], f, indent=2)
591
+
592
+
593
+ def save_game_results(turn_records: List[TurnRecord], filename: str):
594
+ """Save game results to JSON file."""
595
+ with open(filename, 'w') as f:
596
+ json.dump([asdict(r) for r in turn_records], f, indent=2)
597
+
598
+
599
+ def create_game(filename=None) -> GameState:
600
+ if filename is None:
601
+ scenarios = get_default_scenarios()
602
+ save_scenarios(scenarios, 'scenarios.json')
603
+ print("Created scenarios.json with default scenarios")
604
+ else:
605
+ try:
606
+ scenarios = load_scenarios(filename)
607
+ except FileNotFoundError:
608
+ # If file doesn't exist, create default scenarios and save them
609
+ scenarios = get_default_scenarios()
610
+ save_scenarios(scenarios, 'scenarios.json')
611
+ print("Scenario file not found. Created scenarios.json with default scenarios")
612
+
613
+ turn_order = ['A', 'D', 'B', 'C']
614
+
615
+ game = GameState(scenarios, turn_order)
616
+ game.initialize_characters('A')
617
+
618
+ return game
619
+
620
+
621
+ def get_default_scenarios() -> List[Scenario]:
622
+ """Get the default 12 scenarios."""
623
+ return [
624
+ # Round 1, Live Player turn
625
+ Scenario(
626
+ epistemic_type=EpistemicType.TEAMMATE_HAS_FALSE_BELIEF,
627
+ ask_constraint=AskConstraintType.NO_CONSTRAINT,
628
+ round_num=1,
629
+ whose_turn='A',
630
+ who_answers='B',
631
+ question_container='bag',
632
+ present_initially=['A', 'B', 'C', 'D', 'N'],
633
+ events=[
634
+ Event('put', 'B', 'bag', 'ball'),
635
+ Event('leave', 'B'),
636
+ Event('move', 'C', to_container='box', item='ball', from_container='bag'),
637
+ Event('put', 'C', 'bag', 'apple'),
638
+ Event('leave', 'C'),
639
+ ]
640
+ ),
641
+ # Round 1, D turn
642
+ Scenario(
643
+ epistemic_type=EpistemicType.TEAMMATE_HAS_FALSE_BELIEF,
644
+ ask_constraint=AskConstraintType.NO_CONSTRAINT,
645
+ round_num=1,
646
+ whose_turn='D',
647
+ who_answers='C',
648
+ question_container='bag',
649
+ present_initially=['D', 'A', 'B', 'C', 'N'],
650
+ events=[
651
+ Event('put', 'C', 'bag', 'ball'),
652
+ Event('leave', 'C'),
653
+ Event('move', 'B', to_container='box', item='ball', from_container='bag'),
654
+ Event('put', 'B', 'bag', 'apple'),
655
+ Event('leave', 'B'),
656
+ ]
657
+ ),
658
+ # Round 1, B turn
659
+ Scenario(
660
+ epistemic_type=EpistemicType.PLAYER_HAS_UNCERTAINTY,
661
+ ask_constraint=AskConstraintType.TEAMMATE_LACKS_KNOWLEDGE,
662
+ round_num=1,
663
+ whose_turn='B',
664
+ who_answers='B',
665
+ question_container='bag',
666
+ present_initially=['B', 'A', 'C', 'D', 'N'],
667
+ events=[
668
+ Event('put', 'B', 'bag', 'ball'),
669
+ Event('leave', 'B'),
670
+ Event('leave', 'A'),
671
+ Event('leave', 'D'),
672
+ ]
673
+ ),
674
+ # Round 1, C turn
675
+ Scenario(
676
+ epistemic_type=EpistemicType.PLAYER_HAS_UNCERTAINTY,
677
+ ask_constraint=AskConstraintType.TEAMMATE_AND_NEUTRAL_LACK_KNOWLEDGE,
678
+ round_num=1,
679
+ whose_turn='C',
680
+ who_answers='C',
681
+ question_container='bag',
682
+ present_initially=['C', 'N', 'A', 'B'],
683
+ events=[
684
+ Event('put', 'N', 'bag', 'ball'),
685
+ Event('leave', 'N'),
686
+ Event('leave', 'C'),
687
+ Event('leave', 'A'),
688
+ ]
689
+ ),
690
+ # Round 2, Live Player turn
691
+ Scenario(
692
+ epistemic_type=EpistemicType.TEAMMATE_HAS_TRUE_BELIEF,
693
+ ask_constraint=AskConstraintType.NO_CONSTRAINT,
694
+ round_num=2,
695
+ whose_turn='A',
696
+ who_answers='B',
697
+ question_container='box',
698
+ present_initially=['A', 'B', 'C', 'D', 'N'],
699
+ events=[
700
+ Event('put', 'B', 'box', 'orange'),
701
+ Event('leave', 'B'),
702
+ Event('leave', 'D'),
703
+ ]
704
+ ),
705
+ # Round 2, D turn
706
+ Scenario(
707
+ epistemic_type=EpistemicType.TEAMMATE_HAS_FALSE_BELIEF,
708
+ ask_constraint=AskConstraintType.NO_CONSTRAINT,
709
+ round_num=2,
710
+ whose_turn='D',
711
+ who_answers='C',
712
+ question_container='box',
713
+ present_initially=['D', 'A', 'B', 'C', 'N'],
714
+ events=[
715
+ Event('put', 'N', 'box', 'ball'),
716
+ Event('leave', 'C'),
717
+ Event('move', 'N', to_container='bag', item='ball', from_container='box'),
718
+ Event('put', 'N', 'box', 'banana'),
719
+ Event('leave', 'B'),
720
+ ]
721
+ ),
722
+ # Round 2, B turn
723
+ Scenario(
724
+ epistemic_type=EpistemicType.PLAYER_HAS_UNCERTAINTY,
725
+ ask_constraint=AskConstraintType.TEAMMATE_AND_NEUTRAL_LACK_KNOWLEDGE,
726
+ round_num=2,
727
+ whose_turn='B',
728
+ who_answers='B',
729
+ question_container='bag',
730
+ present_initially=['B', 'A', 'C', 'D', 'N'],
731
+ events=[
732
+ Event('put', 'B', 'bag', 'ball'),
733
+ Event('leave', 'B'),
734
+ Event('leave', 'A'),
735
+ Event('leave', 'D'),
736
+ Event('leave', 'N'),
737
+ ]
738
+ ),
739
+ # Round 2, C turn
740
+ Scenario(
741
+ epistemic_type=EpistemicType.PLAYER_HAS_UNCERTAINTY,
742
+ ask_constraint=AskConstraintType.TEAMMATE_LACKS_KNOWLEDGE,
743
+ round_num=2,
744
+ whose_turn='C',
745
+ who_answers='C',
746
+ question_container='bag',
747
+ present_initially=['C', 'N', 'A', 'B'],
748
+ events=[
749
+ Event('put', 'N', 'bag', 'ball'),
750
+ Event('leave', 'A'),
751
+ Event('leave', 'C'),
752
+ Event('leave', 'B'),
753
+ ]
754
+ ),
755
+ # Round 3, Live Player turn
756
+ Scenario(
757
+ epistemic_type=EpistemicType.TEAMMATE_HAS_NO_BELIEF,
758
+ ask_constraint=AskConstraintType.NO_CONSTRAINT,
759
+ round_num=3,
760
+ whose_turn='A',
761
+ who_answers='B',
762
+ question_container='bag',
763
+ present_initially=['A', 'B', 'C', 'D', 'N'],
764
+ events=[
765
+ Event('leave', 'B'),
766
+ Event('put', 'D', 'bag', 'stapler'),
767
+ Event('leave', 'D'),
768
+ Event('put', 'C', 'box', 'brick'),
769
+ ]
770
+ ),
771
+ # Round 3, D turn
772
+ Scenario(
773
+ epistemic_type=EpistemicType.TEAMMATE_HAS_FALSE_BELIEF,
774
+ ask_constraint=AskConstraintType.NO_CONSTRAINT,
775
+ round_num=3,
776
+ whose_turn='D',
777
+ who_answers='C',
778
+ question_container='bag',
779
+ present_initially=['D', 'A', 'B', 'C', 'N'],
780
+ events=[
781
+ Event('put', 'N', 'box', 'ball'),
782
+ Event('leave', 'C'),
783
+ Event('move', 'N', to_container='bag', item='ball', from_container='box'),
784
+ Event('put', 'N', 'box', 'banana'),
785
+ Event('leave', 'B'),
786
+ ]
787
+ ),
788
+ # Round 3, B turn
789
+ Scenario(
790
+ epistemic_type=EpistemicType.PLAYER_HAS_UNCERTAINTY,
791
+ ask_constraint=AskConstraintType.TEAMMATE_NEUTRAL_AND_HONEST_OPPONENT_LACK_KNOWLEDGE,
792
+ round_num=3,
793
+ whose_turn='B',
794
+ who_answers='B',
795
+ question_container='bag',
796
+ present_initially=['B', 'A', 'C', 'D', 'N'],
797
+ events=[
798
+ Event('put', 'B', 'bag', 'ball'),
799
+ Event('leave', 'B'),
800
+ Event('leave', 'A'),
801
+ Event('leave', 'C'),
802
+ Event('leave', 'N'),
803
+ ]
804
+ ),
805
+ # Round 3, C turn
806
+ Scenario(
807
+ epistemic_type=EpistemicType.PLAYER_HAS_UNCERTAINTY,
808
+ ask_constraint=AskConstraintType.TEAMMATE_LACKS_KNOWLEDGE,
809
+ round_num=3,
810
+ whose_turn='C',
811
+ who_answers='C',
812
+ question_container='bag',
813
+ present_initially=['C', 'N', 'A', 'B'],
814
+ events=[
815
+ Event('put', 'N', 'bag', 'stapler'),
816
+ Event('leave', 'A'),
817
+ Event('leave', 'C'),
818
+ Event('leave', 'B'),
819
+ ]
820
+ ),
821
+ ]
822
+
823
+
824
+ def play_game_cli(scenario_file: str = None, human_player: bool = True):
825
+ """Play the game in CLI mode."""
826
+ game = create_game(scenario_file)
827
+ global WINNING_SCORE
828
+ WINNING_SCORE = max(WINNING_SCORE, int(len(game.scenarios)/len(game.turn_order)) + 4)
829
+
830
+ print("=" * 70)
831
+ print(GAME_SETUP.format(WINNING_SCORE=WINNING_SCORE))
832
+ print("=" * 70)
833
+
834
+ turn_count = 0
835
+
836
+ while not game.game_over and game.get_current_scenario():
837
+ scenario = game.get_current_scenario()
838
+ turn_char = game.get_current_turn_character()
839
+
840
+ if not turn_char:
841
+ break
842
+
843
+ true_contents = game.process_scenario_events(scenario)
844
+
845
+ print("\n***********************************")
846
+ print(f"Score: Blue={game.scores[Team.BLUE]}, Red={game.scores[Team.RED]}")
847
+ if turn_count == 0:
848
+ print("Here's the first scenario:")
849
+ else:
850
+ print(f"Here's scenario {turn_count + 1}:")
851
+
852
+ turn_name = "Your" if turn_char == 'A' else f"{turn_char}'s"
853
+ print(f"{turn_name} turn:")
854
+
855
+ scenario_desc = scenario.get_description_for(turn_char, game.characters)
856
+ answerer = "you" if scenario.who_answers == turn_char else scenario.who_answers
857
+ question_desc = f"I am going to ask {answerer} what is in the {scenario.question_container}."
858
+
859
+ print("-----------------------------------------------")
860
+ print(scenario_desc)
861
+ print("----------------------------------------------")
862
+ print(question_desc)
863
+ print("Respond ONLY with your action, and no other text.")
864
+
865
+ action = None
866
+ action_str = ""
867
+ if turn_char == 'A':
868
+ while not action:
869
+ action_str = input(f"Your action (Ask(Player, Container), Tell(Player, Container, Contents), or Pass): ")
870
+ action = game.parse_action(action_str)
871
+ if not action:
872
+ print("Invalid action format. Try again.")
873
+ else:
874
+ action = game.execute_npc_action(turn_char, scenario, true_contents)
875
+ if action.action_type == ActionType.ASK:
876
+ action_str = f"Ask({action.target_char}, {action.container})"
877
+ elif action.action_type == ActionType.TELL:
878
+ action_str = f"Tell({action.target_char}, {action.container}, {action.contents})"
879
+ else:
880
+ action_str = "Pass"
881
+
882
+ score_delta, action_desc = game.execute_action(turn_char, action, true_contents)
883
+
884
+ print(f"\nAction: {action_str}")
885
+
886
+ # Answer phase
887
+ answer_given, is_correct, answer_score = game.resolve_answer_phase(scenario, true_contents)
888
+
889
+ # Display answer
890
+ print(f"{scenario.who_answers} answers: {answer_given}")
891
+ if is_correct:
892
+ print(f"Correct! The {scenario.question_container} contains {answer_given}.")
893
+ else:
894
+ print(f"Incorrect. The {scenario.question_container} actually contains {true_contents[scenario.question_container]}.")
895
+
896
+ # Compute per-team deltas
897
+ blue_delta = 0.0
898
+ red_delta = 0.0
899
+
900
+ # Action cost applies to acting player's team
901
+ if turn_char in ['A', 'B']:
902
+ blue_delta += score_delta
903
+ else:
904
+ red_delta += score_delta
905
+
906
+ # Answer points apply only to the answerer's team (if correct)
907
+ if is_correct:
908
+ if scenario.who_answers in ['A', 'B']:
909
+ blue_delta += answer_score
910
+ else:
911
+ red_delta += answer_score
912
+
913
+ # Apply deltas to the scoreboard
914
+ game.scores[Team.BLUE] += blue_delta
915
+ game.scores[Team.RED] += red_delta
916
+
917
+ # Outcome message shows exact deltas for both teams
918
+ def fmt_delta(x: float) -> str:
919
+ sign = '+' if x >= 0 else '-'
920
+ return f"{sign}{abs(x)}"
921
+ print(f"\nOutcome: Blue {fmt_delta(blue_delta)}, Red {fmt_delta(red_delta)}")
922
+
923
+ # Check if action was optimal (only for live player)
924
+ was_optimal = False
925
+ expected_action_str = ""
926
+ if turn_char == 'A':
927
+ was_optimal = game.is_action_optimal(action_str, scenario, true_contents)
928
+ expected_action_obj = game.execute_npc_action(turn_char, scenario, true_contents)
929
+ if expected_action_obj.action_type == ActionType.PASS:
930
+ expected_action_str = "Pass"
931
+ elif expected_action_obj.action_type == ActionType.ASK:
932
+ expected_action_str = f"Ask({expected_action_obj.target_char}, {expected_action_obj.container})"
933
+ elif expected_action_obj.action_type == ActionType.TELL:
934
+ expected_action_str = f"Tell({expected_action_obj.target_char}, {expected_action_obj.container}, {expected_action_obj.contents})"
935
+ else:
936
+ was_optimal = True
937
+ expected_action_str = action_str
938
+
939
+ turn_record = TurnRecord(
940
+ round_num=scenario.round_num,
941
+ character=turn_char,
942
+ scenario_desc=scenario_desc,
943
+ question=question_desc,
944
+ action=action_str,
945
+ action_cost=abs(score_delta),
946
+ answer_given=answer_given,
947
+ answer_correct=is_correct,
948
+ answer_score=answer_score,
949
+ optimal_action=expected_action_str,
950
+ was_optimal=was_optimal,
951
+ blue_score_after=game.scores[Team.BLUE],
952
+ red_score_after=game.scores[Team.RED],
953
+ epistemic_type=scenario.epistemic_type.value if scenario.epistemic_type else None,
954
+ ask_constraint=scenario.ask_constraint.value if scenario.ask_constraint else None
955
+ )
956
+ game.turn_records.append(turn_record)
957
+
958
+ # Wait for user to press space (if human player)
959
+ if human_player:
960
+ input("\n[Press Enter to continue]")
961
+
962
+ game.check_game_over()
963
+ game.advance_turn()
964
+ turn_count += 1
965
+
966
+ # Game over
967
+ print("\n" + "=" * 70)
968
+ print("GAME OVER")
969
+ print(f"Final Score: Blue {game.scores[Team.BLUE]} - Red {game.scores[Team.RED]}")
970
+ game.winner = "Blue" if game.scores[Team.BLUE] > game.scores[Team.RED] else "Red" if game.scores[Team.RED] > game.scores[Team.BLUE] else None
971
+ if game.winner:
972
+ print(f"Winner: {game.winner.value} team")
973
+ elif game.winner is None:
974
+ print("It's a tie!")
975
+ print("=" * 70)
976
+ """
977
+ # Show turn records
978
+ print("\n" + "=" * 70)
979
+ print("TURN RECORD")
980
+ print("=" * 70)
981
+ for record in game.turn_records:
982
+ print(f"\nRound {record.round_num} - {record.character}'s turn")
983
+ print(f"Ontological Type: {record.epistemic_type}")
984
+ print(f"Ask Constraint: {record.ask_constraint}")
985
+ print(f"Action: {record.action}")
986
+ if record.character == 'A':
987
+ print(f"Expected: {record.optimal_action}")
988
+ print(f"Was Expected: {'YES' if record.was_optimal else 'NO'}")
989
+ print(f"Answer Given: {record.answer_given}")
990
+ print(f"Answer Correct: {'YES' if record.answer_correct else 'NO'}")
991
+ print(f"Score After: Blue {record.blue_score_after} - Red {record.red_score_after}")
992
+ """
993
+ # Save results
994
+ save_game_results(game.turn_records, 'game_results.json')
995
+ print("\nGame results saved to game_results.json")
996
+
997
+ return game
998
+
999
+
1000
+ if __name__ == "__main__":
1001
+ play_game_cli(scenario_file = 'scenarios_generated2.json', human_player=True)
tom_web_app.py ADDED
@@ -0,0 +1,319 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # web_app.py
2
+ # Minimal Flask wrapper around your tom_test.py with "press Enter" behavior
3
+ # identical to CLI. No Gradio, no shadow DOM. Focus is always in one input.
4
+
5
+ from __future__ import annotations
6
+ import os
7
+ import tempfile
8
+ import uuid
9
+ from typing import Dict, Any, Optional, Tuple
10
+
11
+ from flask import Flask, render_template, request, jsonify, make_response
12
+
13
+ import tom_test as rg # your existing game code (unchanged)
14
+
15
+ app = Flask(__name__)
16
+
17
+ # In-memory sessions (simple and fast for local use)
18
+ SESSIONS: Dict[str, "Session"] = {}
19
+
20
+ CLI_ACTION_PROMPT = "Your action (Ask(Player, Container), Tell(Player, Container, Contents), or Pass):"
21
+ PRESS_ENTER = "[Press Enter to continue]"
22
+
23
+ def fmt_delta(x: float) -> str:
24
+ return f"{'+' if x >= 0 else '-'}{abs(x)}"
25
+
26
+ def pick_scenarios_file() -> Optional[str]:
27
+ if os.path.exists("scenarios_generated2.json"):
28
+ return "scenarios_generated2.json"
29
+ if os.path.exists("scenarios.json"):
30
+ return "scenarios.json"
31
+ return None
32
+
33
+ def initial_transcript() -> str:
34
+ # Instructions header exactly like CLI
35
+ path = pick_scenarios_file()
36
+ try:
37
+ scenarios = rg.load_scenarios(path) if path else rg.get_default_scenarios()
38
+ except Exception:
39
+ scenarios = rg.get_default_scenarios()
40
+ win_score = max(rg.WINNING_SCORE, int(len(scenarios) / 4) + 4)
41
+ return "=" * 70 + "\n" + rg.GAME_SETUP.format(WINNING_SCORE=win_score) + "\n" + "=" * 70 + "\n"
42
+
43
+ def present_scenario(state: Dict[str, Any], transcript: str) -> Tuple[str, Dict[str, Any], bool, bool]:
44
+ game = state["game"]
45
+ scenario = game.get_current_scenario()
46
+ turn_char = game.get_current_turn_character()
47
+ if not scenario or not turn_char:
48
+ transcript = finalize_game(transcript, state)
49
+ return transcript, state, False, True
50
+
51
+ true_contents = game.process_scenario_events(scenario)
52
+ state["true_contents"] = true_contents
53
+
54
+ turn_count = state["turn_count"]
55
+ transcript += "\n***********************************\n"
56
+ transcript += f"Score: Blue={game.scores[rg.Team.BLUE]}, Red={game.scores[rg.Team.RED]}\n"
57
+ transcript += ("Here's the first scenario:\n" if turn_count == 0 else f"Here's scenario {turn_count + 1}:\n")
58
+
59
+ turn_name = "Your" if turn_char == 'A' else f"{turn_char}'s"
60
+ transcript += f"{turn_name} turn:\n"
61
+
62
+ scenario_desc = scenario.get_description_for(turn_char, game.characters)
63
+ answerer = "you" if scenario.who_answers == turn_char else scenario.who_answers
64
+ question_desc = f"I am going to ask {answerer} what is in the {scenario.question_container}."
65
+
66
+ transcript += "-----------------------------------------------\n"
67
+ transcript += scenario_desc + "\n"
68
+ transcript += "----------------------------------------------\n"
69
+ transcript += question_desc + "\n"
70
+ transcript += "Respond ONLY with your action, and no other text.\n"
71
+
72
+ state["last_scenario_desc"] = scenario_desc
73
+ state["last_question_desc"] = question_desc
74
+ return transcript, state, (turn_char == 'A'), False
75
+
76
+ def resolve_turn(state: Dict[str, Any], actor_name: str, action: rg.Action, transcript: str) -> Tuple[str, Dict[str, Any]]:
77
+ game = state["game"]
78
+ scenario = game.get_current_scenario()
79
+ true_contents = state["true_contents"]
80
+
81
+ score_delta, _ = game.execute_action(actor_name, action, true_contents)
82
+
83
+ if action.action_type == rg.ActionType.ASK:
84
+ action_str = f"Ask({action.target_char}, {action.container})"
85
+ elif action.action_type == rg.ActionType.TELL:
86
+ action_str = f"Tell({action.target_char}, {action.container}, {action.contents})"
87
+ else:
88
+ action_str = "Pass"
89
+ transcript += f"\nAction: {action_str}\n"
90
+
91
+ answer_given, is_correct, answer_score = game.resolve_answer_phase(scenario, true_contents)
92
+ transcript += f"{scenario.who_answers} answers: {answer_given}\n"
93
+ if is_correct:
94
+ transcript += f"Correct! The {scenario.question_container} contains {answer_given}.\n"
95
+ else:
96
+ transcript += f"Incorrect. The {scenario.question_container} actually contains {true_contents[scenario.question_container]}.\n"
97
+
98
+ blue_delta = 0.0
99
+ red_delta = 0.0
100
+ if actor_name in ['A', 'B']:
101
+ blue_delta += score_delta
102
+ else:
103
+ red_delta += score_delta
104
+ if is_correct:
105
+ if scenario.who_answers in ['A', 'B']:
106
+ blue_delta += answer_score
107
+ else:
108
+ red_delta += answer_score
109
+
110
+ game.scores[rg.Team.BLUE] += blue_delta
111
+ game.scores[rg.Team.RED] += red_delta
112
+ transcript += f"\nOutcome: Blue {fmt_delta(blue_delta)}, Red {fmt_delta(red_delta)}\n"
113
+
114
+ # Record, exactly like CLI
115
+ if actor_name == 'A':
116
+ was_optimal = game.is_action_optimal(action_str, scenario, true_contents)
117
+ exp = game.execute_npc_action(actor_name, scenario, true_contents)
118
+ if exp.action_type == rg.ActionType.PASS:
119
+ expected_action_str = "Pass"
120
+ elif exp.action_type == rg.ActionType.ASK:
121
+ expected_action_str = f"Ask({exp.target_char}, {exp.container})"
122
+ else:
123
+ expected_action_str = f"Tell({exp.target_char}, {exp.container}, {exp.contents})"
124
+ else:
125
+ was_optimal = True
126
+ expected_action_str = action_str
127
+
128
+ game.turn_records.append(rg.TurnRecord(
129
+ round_num=scenario.round_num,
130
+ character=actor_name,
131
+ scenario_desc=state.get("last_scenario_desc", ""),
132
+ question=state.get("last_question_desc", ""),
133
+ action=action_str,
134
+ action_cost=abs(score_delta),
135
+ answer_given=answer_given,
136
+ answer_correct=is_correct,
137
+ answer_score=answer_score,
138
+ optimal_action=expected_action_str,
139
+ was_optimal=was_optimal,
140
+ blue_score_after=game.scores[rg.Team.BLUE],
141
+ red_score_after=game.scores[rg.Team.RED],
142
+ epistemic_type=scenario.epistemic_type.value if scenario.epistemic_type else None,
143
+ ask_constraint=scenario.ask_constraint.value if scenario.ask_constraint else None
144
+ ))
145
+ return transcript, state
146
+
147
+ def finalize_game(transcript: str, state: Dict[str, Any]) -> str:
148
+ game = state["game"]
149
+ transcript += "\n" + "=" * 70 + "\n"
150
+ transcript += "GAME OVER\n"
151
+ transcript += f"Final Score: Blue {game.scores[rg.Team.BLUE]} - Red {game.scores[rg.Team.RED]}\n"
152
+ if game.scores[rg.Team.BLUE] > game.scores[rg.Team.RED]:
153
+ winner = rg.Team.BLUE
154
+ elif game.scores[rg.Team.RED] > game.scores[rg.Team.BLUE]:
155
+ winner = rg.Team.RED
156
+ else:
157
+ winner = None
158
+ transcript += (f"Winner: {winner.value} team\n" if winner else "It's a tie!\n")
159
+ transcript += "=" * 70 + "\n"
160
+
161
+ tmpdir = tempfile.mkdtemp()
162
+ out_path = os.path.join(tmpdir, "game_results.json")
163
+ rg.save_game_results(game.turn_records, out_path)
164
+ state["results_path"] = out_path
165
+ transcript += "\nGame results saved to game_results.json (download below)\n"
166
+ return transcript
167
+
168
+ class Session:
169
+ def __init__(self):
170
+ self.transcript = initial_transcript()
171
+ self.state: Optional[Dict[str, Any]] = None
172
+ self.mode: str = "awaiting_start" # awaiting_start | awaiting_action | awaiting_continue | over
173
+ self.primary_label = "Start"
174
+ self.placeholder = "[Press Enter to start]"
175
+
176
+ def start_or_continue(self):
177
+ if self.state is None:
178
+ # Start game
179
+ path = pick_scenarios_file()
180
+ game = rg.create_game(path)
181
+ rg.WINNING_SCORE = max(rg.WINNING_SCORE, int(len(game.scenarios) / len(game.turn_order)) + 4)
182
+ self.state = {"game": game, "turn_count": 0, "results_path": None}
183
+ self.transcript, self.state, is_player, end_now = present_scenario(self.state, self.transcript)
184
+ if end_now:
185
+ self.transcript = finalize_game(self.transcript, self.state)
186
+ self.mode = "over"
187
+ self.primary_label = "Start"
188
+ self.placeholder = "Game over"
189
+ return
190
+ if is_player:
191
+ self.mode = "awaiting_action"
192
+ self.primary_label = "Continue"
193
+ self.placeholder = CLI_ACTION_PROMPT
194
+ return
195
+ # NPC acts then pause
196
+ npc = game.execute_npc_action(game.get_current_turn_character(), game.get_current_scenario(), self.state["true_contents"])
197
+ self.transcript, self.state = resolve_turn(self.state, game.get_current_turn_character(), npc, self.transcript)
198
+ game.check_game_over(); game.advance_turn(); self.state["turn_count"] += 1
199
+ if game.game_over or not game.get_current_scenario():
200
+ self.transcript = finalize_game(self.transcript, self.state)
201
+ self.mode = "over"
202
+ self.primary_label = "Start"
203
+ self.placeholder = "Game over"
204
+ return
205
+ self.mode = "awaiting_continue"
206
+ self.primary_label = "Continue"
207
+ self.placeholder = PRESS_ENTER
208
+ self.transcript += f"\n{PRESS_ENTER}\n"
209
+ return
210
+
211
+ # Continue flow
212
+ if self.mode != "awaiting_continue":
213
+ return
214
+ game = self.state["game"]
215
+ self.transcript, self.state, is_player, end_now = present_scenario(self.state, self.transcript)
216
+ if end_now:
217
+ self.transcript = finalize_game(self.transcript, self.state)
218
+ self.mode = "over"
219
+ self.primary_label = "Start"
220
+ self.placeholder = "Game over"
221
+ return
222
+ if is_player:
223
+ self.mode = "awaiting_action"
224
+ self.primary_label = "Continue"
225
+ self.placeholder = CLI_ACTION_PROMPT
226
+ return
227
+ # NPC acts then pause
228
+ npc = game.execute_npc_action(game.get_current_turn_character(), game.get_current_scenario(), self.state["true_contents"])
229
+ self.transcript, self.state = resolve_turn(self.state, game.get_current_turn_character(), npc, self.transcript)
230
+ game.check_game_over(); game.advance_turn(); self.state["turn_count"] += 1
231
+ if game.game_over or not game.get_current_scenario():
232
+ self.transcript = finalize_game(self.transcript, self.state)
233
+ self.mode = "over"
234
+ self.primary_label = "Start"
235
+ self.placeholder = "Game over"
236
+ return
237
+ self.mode = "awaiting_continue"
238
+ self.primary_label = "Continue"
239
+ self.placeholder = PRESS_ENTER
240
+ self.transcript += f"\n{PRESS_ENTER}\n"
241
+
242
+ def submit_action(self, text: str):
243
+ if self.state is None or self.mode != "awaiting_action":
244
+ return
245
+ game = self.state["game"]
246
+ action = game.parse_action(text or "")
247
+ if not action:
248
+ self.transcript += "Invalid action format. Try again.\n"
249
+ self.placeholder = CLI_ACTION_PROMPT
250
+ return
251
+ turn_char = game.get_current_turn_character()
252
+ self.transcript, self.state = resolve_turn(self.state, turn_char, action, self.transcript)
253
+ game.check_game_over(); game.advance_turn(); self.state["turn_count"] += 1
254
+ if game.game_over or not game.get_current_scenario():
255
+ self.transcript = finalize_game(self.transcript, self.state)
256
+ self.mode = "over"
257
+ self.primary_label = "Start"
258
+ self.placeholder = "Game over"
259
+ return
260
+ self.mode = "awaiting_continue"
261
+ self.primary_label = "Continue"
262
+ self.placeholder = PRESS_ENTER
263
+ self.transcript += f"\n{PRESS_ENTER}\n"
264
+
265
+ def get_session(resp=None) -> Tuple[str, Session, Any]:
266
+ sid = request.cookies.get("sid")
267
+ if not sid or sid not in SESSIONS:
268
+ sid = uuid.uuid4().hex
269
+ sess = Session()
270
+ SESSIONS[sid] = sess
271
+ if resp is None:
272
+ resp = make_response()
273
+ resp.set_cookie("sid", sid, httponly=True, samesite="Lax")
274
+ else:
275
+ sess = SESSIONS[sid]
276
+ return sid, sess, resp
277
+
278
+ @app.get("/")
279
+ def index():
280
+ resp = make_response(render_template("index.html"))
281
+ _, sess, resp = get_session(resp)
282
+ return resp
283
+
284
+ @app.get("/state")
285
+ def state():
286
+ _, sess, _ = get_session()
287
+ return jsonify({
288
+ "transcript": sess.transcript,
289
+ "mode": sess.mode,
290
+ "primary_label": sess.primary_label,
291
+ "placeholder": sess.placeholder
292
+ })
293
+
294
+ @app.post("/primary")
295
+ def primary():
296
+ _, sess, _ = get_session()
297
+ sess.start_or_continue()
298
+ return jsonify({
299
+ "transcript": sess.transcript,
300
+ "mode": sess.mode,
301
+ "primary_label": sess.primary_label,
302
+ "placeholder": sess.placeholder
303
+ })
304
+
305
+ @app.post("/action")
306
+ def action():
307
+ _, sess, _ = get_session()
308
+ data = request.get_json(force=True) or {}
309
+ text = (data.get("text") or "").strip()
310
+ sess.submit_action(text)
311
+ return jsonify({
312
+ "transcript": sess.transcript,
313
+ "mode": sess.mode,
314
+ "primary_label": sess.primary_label,
315
+ "placeholder": sess.placeholder
316
+ })
317
+
318
+ if __name__ == "__main__":
319
+ app.run(debug=True)