# shared_bots.py from otree.api import Bot, Submission from constants import Round_FirstTask, Round_Per_Gen # ───────────────────────────────────────────────────────────────────────────── # Shared helpers # ───────────────────────────────────────────────────────────────────────────── _HIGH_BUCKETS = [3, 6, 9, 12, 15, 18, 21, 24, 27, 30] _LOW_BUCKETS = [3, 6, 9, 12, 15] def _endowment(bot): return bot.participant.vars.get('Endowment', 15) def _endowment_type(bot): return bot.participant.vars.get('EndowmentType', 1) def _my_order(bot): return bot.participant.vars.get('my_order', 1) def _safe_contrib(bot): return 15 if _endowment_type(bot) == 2 else 7 def _pun_fields_for(prefix, endowment): buckets = _HIGH_BUCKETS if endowment == 30 else _LOW_BUCKETS return {f'{prefix}_{b}': 3 for b in buckets} def _punishment_fields(bot): order = _my_order(bot) e1 = bot.participant.vars.get('Edmt_Mmb1', 15) e2 = bot.participant.vars.get('Edmt_Mmb2', 15) e3 = bot.participant.vars.get('Edmt_Mmb3', 15) if order == 1: return {**_pun_fields_for('mmb2', e2), **_pun_fields_for('mmb3', e3)} elif order == 2: return {**_pun_fields_for('mmb1', e1), **_pun_fields_for('mmb3', e3)} else: return {**_pun_fields_for('mmb1', e1), **_pun_fields_for('mmb2', e2)} def _belief_fields(bot): if _endowment(bot) == 30: return {f'belief_high_{b}': 5 for b in _HIGH_BUCKETS} return {f'belief_low_{b}': 5 for b in _LOW_BUCKETS} def _fair_fields(bot): if _endowment(bot) == 30: return {f'fair_high_{b}': 4 for b in _HIGH_BUCKETS} return {f'fair_low_{b}': 4 for b in _LOW_BUCKETS} # ───────────────────────────────────────────────────────────────────────────── # AA_SetUpApp # ───────────────────────────────────────────────────────────────────────────── def play_AA_SetUpApp(bot): import AA_SetUpApp as app # ArrivalPage: shown to all, round 1 only yield Submission(app.ArrivalPage, check_html=False) # ManualDrop_G1: only Gen1, round 1, state=="active" # Gen2 bots skip this — their generation==2 so is_displayed returns False if bot.participant.vars.get('generation') == 1: yield Submission(app.ManualDrop_G1, dict(manual_entry=''), check_html=False) # Gen1RedirectToFirstTask: only Gen1, round 1 # Never visually displayed (is_displayed returns True but page has no content # — it immediately fires app_after_this_page). Bots must still yield it so # oTree can execute app_after_this_page and route the participant forward. if bot.participant.vars.get('generation') == 1: yield Submission(app.Gen1RedirectToFirstTask, check_html=False) # SessionFullPage_G1: dead-end for closed Gen1 — active bots never reach it # so we intentionally omit it here. # ───────────────────────────────────────────────────────────────────────────── # A_Endowment_Creation_Gen1_D # NUM_ROUNDS = Round_FirstTask (20) # Each round contains a coin-flip half and a token-grab half, # ordered by first_task_order (0 = coin first, 1 = token first). # ───────────────────────────────────────────────────────────────────────────── def play_A_Endowment_Creation_Gen1_D(bot): import A_Endowment_Creation_Gen1_D as app rn = bot.round_number half = Round_FirstTask // 2 # 10 first_order = bot.participant.vars.get('first_task_order', 0) flip_list = bot.participant.vars.get('flip_result_list', ['Heads'] * Round_FirstTask) # ── FirstTask: shown only at round 1 ────────────────────────────────── if rn == 1: yield Submission(app.FirstTask, check_html=False) # ── FirstTask_CoinFlip_Intro ─────────────────────────────────────────── # is_displayed: (rn==1 and first_order==0) OR (rn==half+1 and first_order==1) if (rn == 1 and first_order == 0) or (rn == half + 1 and first_order == 1): yield Submission(app.FirstTask_CoinFlip_Intro, check_html=False) # ── FirstTask_CoinFlip ──────────────────────────────────────────────── # is_displayed: # order==0: rounds 1..half # order==1: rounds half+1..Round_FirstTask coin_flip_active = ( (first_order == 0 and 1 <= rn <= half) or (first_order == 1 and half + 1 <= rn <= Round_FirstTask) ) if coin_flip_active: # Derive the index into flip_result_list for this round if first_order == 0: idx = rn - 1 else: idx = rn - (half + 1) idx = max(0, min(idx, len(flip_list) - 1)) yield Submission( app.FirstTask_CoinFlip, dict(flip_result=flip_list[idx]), check_html=False, ) # ── FirstTask_TokenGrab_Intro ───────────────────────────────────────── # is_displayed: (rn==half+1 and first_order==0) OR (rn==1 and first_order==1) if (rn == half + 1 and first_order == 0) or (rn == 1 and first_order == 1): yield Submission(app.FirstTask_TokenGrab_Intro, check_html=False) # ── FirstTask_TokenGrab ─────────────────────────────────────────────── # is_displayed: # order==0: rounds half+1..Round_FirstTask # order==1: rounds 1..half token_grab_active = ( (first_order == 0 and half + 1 <= rn <= Round_FirstTask) or (first_order == 1 and 1 <= rn <= half) ) if token_grab_active: yield Submission( app.FirstTask_TokenGrab, dict(take_token=True), check_html=False, ) # ── EndofPart1: shown only at last round ────────────────────────────── if rn == Round_FirstTask: yield Submission(app.EndofPart1, check_html=False) # ───────────────────────────────────────────────────────────────────────────── # B_PGGame_UQ_Gen1_D # NUM_ROUNDS = Round_FirstTask (20); all pages shown only at round == NUM_ROUNDS # DemoRound_High_Endowment shown when first_practice_order == 0 # DemoRound_High_Endowment_2 shown when first_practice_order == 1 # DemoRound_Low_Endowment shown every time (no practice-order filter) # ───────────────────────────────────────────────────────────────────────────── def play_B_PGGame_UQ_Gen1_D(bot): import B_PGGame_UQ_Gen1_D as app rn = bot.round_number fpo = bot.participant.vars.get('first_practice_order', 0) # All pages in this app are gated on round_number == NUM_ROUNDS (== Round_FirstTask) if rn != Round_FirstTask: return # WaitForUQ_G1 — WaitPage, never yield yield Submission(app.UnderstandingQ0_G1, check_html=False) yield Submission(app.UnderstandingQ1_G1, dict(UQ1=1, UQ2=1, UQ3=2, UQ4=1), check_html=False) yield Submission(app.UnderstandingQ2_G1, dict(UQ5=2, UQ6=2), check_html=False) yield Submission(app.UnderstandingQ3_G1, dict(UQ7=2, UQ8=1), check_html=False) yield Submission(app.DemoRound_Intro_G1, check_html=False) # DemoRound_High_Endowment: is_displayed requires first_practice_order == 0 if fpo == 0: yield Submission(app.DemoRound_High_Endowment, dict(practice_contrib_high=15), check_html=False) # DemoRound_Low_Endowment: always shown (no practice-order filter) yield Submission(app.DemoRound_Low_Endowment, dict(practice_contrib_low=8), check_html=False) # DemoRound_High_Endowment_2: is_displayed requires first_practice_order == 1 if fpo == 1: yield Submission(app.DemoRound_High_Endowment_2, dict(practice_contrib_high=15), check_html=False) # DemoRound_Calculation — WaitPage, never yield yield Submission(app.DemoRound_Outro, check_html=False) # ───────────────────────────────────────────────────────────────────────────── # C_FamilyGroupings_Gen1_D (NUM_ROUNDS = 1, all pages round 1 only) # ───────────────────────────────────────────────────────────────────────────── def play_C_FamilyGroupings_Gen1_D(bot): import C_FamilyGroupings_Gen1_D as app yield Submission(app.Intro_startDT, check_html=False) # WaitForinfoRevl1 — WaitPage, never yield yield Submission(app.GennFamily0, check_html=False) yield Submission(app.GennFamily1, check_html=False) yield Submission(app.GennFamily2, check_html=False) yield Submission(app.GennFamily3, check_html=False) yield Submission(app.GennFamily3_withbtn, dict(Att1=1, Att1_attempts=1), check_html=False) # ───────────────────────────────────────────────────────────────────────────── # D_PGGame_Gen1_D # NUM_ROUNDS = Round_Per_Gen (16) # Belief/Fair pages: round 1 only # Punishment/Contribution/Results: every round # PostGame pages + EndofPG: last round (Round_Per_Gen) only # ───────────────────────────────────────────────────────────────────────────── def play_D_PGGame_Gen1_D(bot): import D_PGGame_Gen1_D as app rn = bot.round_number # ── Round 1 only: belief and fairness elicitation ───────────────────── if rn == 1: yield Submission(app.BeliefIntro, check_html=False) yield Submission(app.BeliefElicitation, _belief_fields(bot), check_html=False) yield Submission(app.FairDisapproval, _fair_fields(bot), check_html=False) yield Submission(app.BeliefOutro, check_html=False) # ── Every round ─────────────────────────────────────────────────────── # WaitForPG_G1 — WaitPage, never yield yield Submission(app.Punishment_G1, _punishment_fields(bot), check_html=False) yield Submission(app.PGGamep1_G1, dict(contribution=_safe_contrib(bot)), check_html=False) # WaitForGeneration1 — WaitPage, never yield yield Submission(app.PGGamep2_G1, check_html=False) # ── Last round only ─────────────────────────────────────────────────── if rn == Round_Per_Gen: yield Submission(app.PostGameFairIntro, check_html=False) yield Submission(app.PostGameFairDisapproval, _fair_fields(bot), check_html=False) yield Submission(app.EndofPG_G1, check_html=False) # ───────────────────────────────────────────────────────────────────────────── # E_PGGame_UQ_Gen2_D (NUM_ROUNDS = 1) # ManualDrop_G2: Gen2 only # DemoRound_High_Endowment: first_practice_order == 0 # DemoRound_High_Endowment_2: first_practice_order == 1 # DemoRound_Low_Endowment: always shown # Hypo0_G2 is in page_sequence but Hypo_G2 (with fields) follows it; # note the source file's page_sequence omits Hypo0_G2 — we match what's # actually in page_sequence. # ───────────────────────────────────────────────────────────────────────────── def play_E_PGGame_UQ_Gen2_D(bot): import E_PGGame_UQ_Gen2_D as app fpo = bot.participant.vars.get('first_practice_order', 0) # ManualDrop_G2: Gen2 active only yield Submission(app.ManualDrop_G2, dict(manual_entry=''), check_html=False) # WaitForGen2 — WaitPage, never yield # Gen2RedirectOrContinue — is_displayed always False, oTree skips it # SessionFullPage_G2 — dead-end for closed; active bots never reach it yield Submission(app.UnderstandingQ0_G2, check_html=False) yield Submission(app.UnderstandingQ1_G2, dict(UQ1=1, UQ2=1, UQ3=2, UQ4=1), check_html=False) yield Submission(app.UnderstandingQ2_G2, dict(UQ5=2, UQ6=2), check_html=False) yield Submission(app.UnderstandingQ3_G2, dict(UQ7=2, UQ8=1), check_html=False) # Hypo0_G2 is in page_sequence (intro, no form) yield Submission(app.Hypo0_G2, check_html=False) yield Submission(app.Hypo_G2, dict(PQ_hp_token=5, PQ_hp_coin=5), check_html=False) yield Submission(app.DemoRound_Intro_G2, check_html=False) # DemoRound_High_Endowment: first_practice_order == 0 if fpo == 0: yield Submission(app.DemoRound_High_Endowment, dict(practice_contrib_high=15), check_html=False) # DemoRound_Low_Endowment: always shown yield Submission(app.DemoRound_Low_Endowment, dict(practice_contrib_low=8), check_html=False) # DemoRound_High_Endowment_2: first_practice_order == 1 if fpo == 1: yield Submission(app.DemoRound_High_Endowment_2, dict(practice_contrib_high=15), check_html=False) # DemoRound_Calculation_G2 — WaitPage, never yield yield Submission(app.DemoRound_Outro, check_html=False) # ───────────────────────────────────────────────────────────────────────────── # F_JointTask_Gen12_D (NUM_ROUNDS = 1) # Gen1RedirectToQuestionnaire: Gen1 only # DropJointTask: state=="end" only — active bots skip # WaitPages: never yield # ───────────────────────────────────────────────────────────────────────────── def play_F_JointTask_Gen12_D(bot): import F_JointTask_Gen12_D as app gen = bot.participant.vars.get('generation') # WaitForSelection_G12 — WaitPage, never yield yield Submission(app.Introduction2, check_html=False) # WaitForIntroduction_G12 — WaitPage, never yield yield Submission(app.Selection_G12, check_html=False) yield Submission(app.JointTask_Intro_G12, check_html=False) # WaitForStart1 — WaitPage, never yield yield Submission(app.JointTask_R1p1, check_html=False) yield Submission(app.JointTask_R1p2, check_html=False) # WaitForStart2 — WaitPage, never yield yield Submission(app.JointTask_R2p1, check_html=False) yield Submission(app.JointTask_R2p2, check_html=False) # WaitForStart3 — WaitPage, never yield yield Submission(app.JointTask_R3p1, check_html=False) yield Submission(app.JointTask_R3p2, check_html=False) # Gen1RedirectToQuestionnaire: is_displayed requires generation == 1 if gen == 1: yield Submission(app.Gen1RedirectToQuestionnaire, check_html=False) # DropJointTask: is_displayed requires state == "end" — active bots skip # ───────────────────────────────────────────────────────────────────────────── # G_Endowment_History_Gen12_D (NUM_ROUNDS = 1) # All pages: Gen2, round 1, state=="active" # GennFamily2_G2_p2_withbtn has no form fields — submit empty # ───────────────────────────────────────────────────────────────────────────── def play_G_Endowment_History_Gen12_D(bot): import G_Endowment_History_Gen12_D as app yield Submission(app.Q_oneness0, check_html=False) yield Submission(app.Q_oneness1, dict(Oneness1='D'), check_html=False) yield Submission(app.Q_oneness2, dict(Oneness2='D'), check_html=False) yield Submission(app.Q_oneness3, dict(Oneness3='D'), check_html=False) # WaitForinfoRevl2 — WaitPage, never yield yield Submission(app.GennFamily0_G2, check_html=False) yield Submission(app.GennFamily1_G2, check_html=False) yield Submission(app.GennFamily2_G2_p1, check_html=False) yield Submission(app.GennFamily2_G2_p1_withbtn, dict(Att1=1, Att1_attempts=1), check_html=False) yield Submission(app.GennFamily2_G2_p2, check_html=False) yield Submission(app.GennFamily2_G2_p2_withbtn, check_html=False) # ───────────────────────────────────────────────────────────────────────────── # H_PGGame_Gen2_D # NUM_ROUNDS = Round_Per_Gen (16) # Belief/Fair pages: round 1 only # Punishment/Contribution/Results: every round # PostGame pages: last round (Round_Per_Gen) only # Gen2RedirectToQuestionnaire: last round only — fires app_after_this_page # ───────────────────────────────────────────────────────────────────────────── def play_H_PGGame_Gen2_D(bot): import H_PGGame_Gen2_D as app rn = bot.round_number # ── Round 1 only ────────────────────────────────────────────────────── if rn == 1: yield Submission(app.BeliefIntro_G2, check_html=False) yield Submission(app.BeliefElicitation_G2, _belief_fields(bot), check_html=False) yield Submission(app.FairDisapproval_G2, _fair_fields(bot), check_html=False) yield Submission(app.BeliefOutro_G2, check_html=False) # ── Every round ─────────────────────────────────────────────────────── yield Submission(app.Punishment_G2, _punishment_fields(bot), check_html=False) yield Submission(app.PGGamep1_G2, dict(contribution=_safe_contrib(bot)), check_html=False) # WaitForGeneration2 — WaitPage, never yield yield Submission(app.PGGamep2_G2, check_html=False) # ── Last round only ─────────────────────────────────────────────────── if rn == Round_Per_Gen: yield Submission(app.PostGameFairIntro_G2, check_html=False) yield Submission(app.PostGameFairDisapproval_G2, _fair_fields(bot), check_html=False) # Gen2RedirectToQuestionnaire fires app_after_this_page → I_Post_Questionnaire_D yield Submission(app.Gen2RedirectToQuestionnaire, check_html=False) # ───────────────────────────────────────────────────────────────────────────── # I_Post_Questionnaire_D (NUM_ROUNDS = 1) # ClosedExit: state=="closed" only — active bots skip # All PQ pages: state not in ("inactive", "closed") # ───────────────────────────────────────────────────────────────────────────── def play_I_Post_Questionnaire_D(bot): import I_Post_Questionnaire_D as app state = bot.participant.vars.get('state', 'active') # ClosedExit: only for closed participants — active bots skip if state == 'closed': yield Submission(app.ClosedExit, check_html=False) return # closed participants see nothing else # All remaining pages are gated on state not in ("inactive", "closed") yield Submission(app.PQ0_G1, check_html=False) yield Submission(app.PQ1_G1, dict(PQ_uyr=2, PQ_gd=2, PQ_race=1, PQ_course='Economics'), check_html=False) yield Submission(app.PQ3_G1, dict(PQ_ust=4, PQ_stdpps='Load-test bot.'), check_html=False) yield Submission(app.PQ4_G1, dict(PQ_wor_coinmst=3, PQ_wor_coinlst=3, PQ_wor_tkmst=3, PQ_wor_tklst=3), check_html=False) yield Submission(app.PQ5_G1, dict(PQ_dsv_coinmst=3, PQ_dsv_coinlst=3, PQ_dsv_tkmst=3, PQ_dsv_tklst=3), check_html=False) yield Submission(app.PQ6_G1, dict(PQ_stf_edmt='Bot answer.', PQ_stf_tmop='Bot answer.'), check_html=False) yield Submission(app.PQ7_G1, dict(PQ_sttmt1=3, PQ_sttmt2=3, PQ_sttmt3=3, PQ_sttmt4=3), check_html=False) yield Submission(app.PQ8_G1, dict(PQ_disadv1=3, PQ_disadv2=3, PQ_disadv3=3, PQ_disadv4=3, PQ_disadv5=3), check_html=False) yield Submission(app.PQ9_G1, dict(PQ_disadv6=3, PQ_disadv7=3, PQ_disadv8=3, PQ_disadv9=3, PQ_disadv10=3), check_html=False) yield Submission(app.PQ10_G1, dict(PQ_adv1=3, PQ_adv2=3, PQ_adv3=3, PQ_adv4=3, PQ_adv5=3), check_html=False) yield Submission(app.PQ11_G1, dict(PQ_adv6=3, PQ_adv7=3, PQ_adv8=3, PQ_adv9=3, PQ_adv10=3), check_html=False) yield Submission(app.PQ12_G1, dict(PQ_unclear_yn=False, PQ_unclear='', PQ_issue_yn=False, PQ_issue=''), check_html=False) yield Submission(app.Pmt, check_html=False)