11"""
2- Instruction presentation: pages through text/instructions_MID.txt (one page per
3- non-blank line) via the shared psyexp_core instruction pager, then waits for the
4- start key. The task owns the pages and how each is drawn; the harness owns the
5- flip + key-polling navigation loop.
2+ Instruction presentation: pages through a hardcoded list of instruction pages
3+ via the shared psyexp_core instruction pager, then waits for the start key.
4+
5+ Each page is text plus an optional cue shape drawn beneath it as a visual aid,
6+ so the pages that describe CIRCLE / SQUARE cues also show the shape. The task
7+ owns the pages and how each is drawn; the harness owns the flip + key-polling
8+ navigation loop.
69"""
710from __future__ import annotations
811
9- from pathlib import Path
12+ from dataclasses import dataclass
1013
1114from psychopy import visual
1215from psychopy .hardware import keyboard
1518from rich .console import Console
1619
1720from mid_det import config
21+ from mid_det .task import display
22+
23+
24+ @dataclass (frozen = True )
25+ class InstructionPage :
26+ """One instruction page: prompt text and an optional example cue as a visual aid.
27+
28+ ``cue`` is a ``(polarity, magnitude)`` pair drawn via ``display.draw_cue`` — the
29+ full cue (shape + magnitude chord + dollar label), matching what participants
30+ see on a real trial — or ``None`` for a text-only page.
31+ """
32+ text : str
33+ cue : tuple [str , int ] | None = None
34+
1835
19- _PROJECT_ROOT = Path (__file__ ).resolve ().parents [3 ] # src/mid_det/task/ -> project root
20- _TEXT_DIR = _PROJECT_ROOT / "text"
36+ # The circle/square explanations are split one-shape-per-page so each page can
37+ # show the cue it describes. An example cue is drawn with a representative
38+ # high ($5) magnitude so the chord (magnitude line) is clearly off-center.
39+ # Navigation / "press to continue" hints are drawn by the harness (instr_first),
40+ # so they are not repeated in the text here.
41+ _EXAMPLE_MAGNITUDE = 5
2142
43+ _PAGES : list [InstructionPage ] = [
44+ InstructionPage (
45+ "In this experiment you will respond as quickly as possible to earn "
46+ "money. You will see a cue indicating how much money you can win or "
47+ "avoid losing. After this cue, a triangle will appear. Hit a button as "
48+ "fast as you can when the triangle appears to win."
49+ ),
50+ InstructionPage (
51+ "Cues are either circles or squares. CIRCLE cues mean that you can EARN "
52+ "that amount if you hit the target." ,
53+ cue = ("gain" , _EXAMPLE_MAGNITUDE ),
54+ ),
55+ InstructionPage (
56+ "SQUARE cues mean that you can AVOID LOSING that amount if you hit the "
57+ "target." ,
58+ cue = ("loss" , _EXAMPLE_MAGNITUDE ),
59+ ),
60+ InstructionPage (
61+ "If you miss a CIRCLE cue, you will NOT GAIN the amount shown in the cue." ,
62+ cue = ("gain" , _EXAMPLE_MAGNITUDE ),
63+ ),
64+ InstructionPage (
65+ "If you miss a SQUARE cue, you will LOSE the amount shown in the cue." ,
66+ cue = ("loss" , _EXAMPLE_MAGNITUDE ),
67+ ),
68+ InstructionPage ("Please HOLD STILL while you are in the scanner." ),
69+ ]
2270
23- def _load_pages (path : Path ) -> list [str ]:
24- """Read an instruction text file into a list of pages (one non-blank line each)."""
25- pages : list [str ] = []
26- with open (path ) as f :
27- for line in f :
28- stripped = line .rstrip ()
29- if stripped :
30- pages .append (stripped )
31- return pages
71+ # On cue pages the prompt is raised so text clears the cue drawn at screen center,
72+ # and the "press to continue" hint is lowered so it clears the dollar label drawn
73+ # just below the cue. Plain pages keep the hint at its default position.
74+ _PROMPT_Y_PLAIN = 0.1
75+ _PROMPT_Y_WITH_CUE = 0.3
76+ _HINT_Y_WITH_CUE = - 0.35
3277
3378
3479def display_instructions (
@@ -37,19 +82,28 @@ def display_instructions(
3782 kb : keyboard .Keyboard ,
3883 rcon : Console ,
3984) -> None :
40- """Display instructions from text/instructions_MID.txt one page at a time."""
41- pages = _load_pages (_TEXT_DIR / "instructions_MID.txt" )
42- if not pages :
43- return
85+ """Display the hardcoded instruction pages one at a time."""
86+ default_hint_y = stimuli .instr_first .pos [1 ]
4487
45- def _draw_page (page : str , _is_last : bool ) -> None :
46- stimuli .instr_prompt .text = page
88+ def _draw_page (page : InstructionPage , _is_last : bool ) -> None :
89+ stimuli .instr_prompt .pos = (
90+ 0 ,
91+ _PROMPT_Y_WITH_CUE if page .cue else _PROMPT_Y_PLAIN ,
92+ )
93+ stimuli .instr_prompt .text = page .text
4794 stimuli .instr_prompt .draw ()
95+ if page .cue :
96+ polarity , magnitude = page .cue
97+ display .draw_cue (stimuli , polarity , magnitude )
98+ stimuli .instr_first .pos = (
99+ 0 ,
100+ _HINT_Y_WITH_CUE if page .cue else default_hint_y ,
101+ )
48102 stimuli .instr_first .draw ()
49103
50104 core_instructions .page_through (
51105 win ,
52- pages ,
106+ _PAGES ,
53107 _draw_page ,
54108 forward_keys = config .INSTRUCTION_KEYS ["forward" ],
55109 back_keys = config .INSTRUCTION_KEYS ["back" ],
0 commit comments