from otree.api import Currency as cu from ._builtin import Page from .models import Constants from experience_no_choice.models import mark_slot_taken_for_participant, touch_reservation, reservation_active class AttentionCheck(Page): def is_displayed(self): if not reservation_active(self.session, self.participant): self.participant.vars['expired'] = True return False return self.round_number == 40 def vars_for_template(self): # just read, don't modify; we want history up to Round 39 tg = self.participant.vars.get('tg') or getattr(self.player, 'tg', None) try: tg = int(tg) if tg is not None else None except (TypeError, ValueError): tg = None history = self.participant.vars.get('history', []) return dict(tg=tg, history=history) def error_message(self, values): if not reservation_active(self.session, self.participant): self.participant.vars['expired'] = True return "Your session has expired due to inactivity" def before_next_page(self): if not reservation_active(self.session, self.participant): self.participant.vars['expired'] = True return touch_reservation(self.session, self.participant) class Page1(Page): def is_displayed(self): if not reservation_active(self.session, self.participant): self.participant.vars['expired'] = True return False return True def _get_tg_int(self): tg = self.participant.vars.get('tg') or getattr(self.player, 'tg', None) try: return int(tg) if tg is not None else None except (TypeError, ValueError): return None def _get_info_set_int(self): info_set = self.participant.vars.get('info_set') or getattr(self.player, 'info_set', None) try: return int(info_set) if info_set is not None else None except (TypeError, ValueError): return None def _get_outcome_for_round(self, info_set: int) -> int: seq = Constants.SEQUENCES_BY_INFOSET.get(info_set) if not seq: raise RuntimeError(f"No sequence for info_set {info_set}.") idx = self.subsession.round_number - 1 try: val = int(seq[idx]) except IndexError: raise RuntimeError(f"Sequence for info_set {info_set} is shorter than {self.subsession.round_number} rounds.") if val not in (0, -3, -4): raise RuntimeError(f"Invalid outcome {val} (must be 0, 3, or 5).") return val def vars_for_template(self): tg = self._get_tg_int() info_set = self._get_info_set_int() if info_set is None: raise RuntimeError("info_set not assigned. Set it in the first app.") outcome = self._get_outcome_for_round(info_set) label = {-4: "L1: -4", -3: "L2: -3", 0: "L1: 0"}[outcome] # Preview total if tg==3 (only then do we add the outcome to payoff) added = cu(outcome) / 100 if tg == 3 else cu(0) current_total = self.participant.payoff + added # History line = f"Round {self.subsession.round_number}: {label}" hist = self.participant.vars.setdefault('history', []) history = hist if (hist and hist[-1] == line) else hist + [line] return dict( tg=tg, info_set=info_set, outcome=outcome, label=label, history=history, current_total=current_total, ) def error_message(self, values): if not reservation_active(self.session, self.participant): self.participant.vars['expired'] = True return "Your session has expired due to inactivity" def before_next_page(self, timeout_happened=False): if not reservation_active(self.session, self.participant): self.participant.vars['expired'] = True return tg = self._get_tg_int() info_set = self._get_info_set_int() if info_set is None: raise RuntimeError("info_set not assigned. Set it in the first app.") outcome = self._get_outcome_for_round(info_set) # Add payoff only for tg==3 if tg == 3: self.participant.payoff += cu(outcome) / 100 # Persist history (one line per round) label = {-4: "L1: -4", -3: "L2: -3", 0: "L1: 0"}[outcome] line = f"Round {self.subsession.round_number}: {label}" hist = self.participant.vars.setdefault('history', []) if not hist or hist[-1] != line: hist.append(line) touch_reservation(self.session, self.participant) class Expired(Page): # Show if we’ve already marked them expired, or if the reservation is no longer active. def is_displayed(self): if self.participant.vars.get('expired'): return True if not reservation_active(self.session, self.participant): self.participant.vars['expired'] = True return True return False page_sequence = [ AttentionCheck, Page1, Expired, ]