import os from pathlib import Path import sys,runpy,json,re sys.path.insert(0,str(Path(__file__).parent));from framebuffer import Screen,ROOT,shuffle,EVIDENCE c=runpy.run_path(str(Path(__file__).with_name('playtest_support.py')));run=json.load(open(os.environ['CAPITAL_TEST_STATE']))['run']['run_id'] rows=[x.split('\t') for x in (ROOT/'data/states.tsv').read_text().splitlines()] order,_=shuffle();evidence=[];score=0;pos=[0]*4 c['key'](run,'1',8,600) for q,state in enumerate(order): name=f'mc-q{q+1:02d}-{rows[state][0]}' meta=c['capture'](run,name);scr=Screen(str(EVIDENCE)+'/'+name+'.png') heading=scr.text(8,19,45);prompt=scr.text(8,145,50) assert heading==f'Question {q+1} of 50 Score {score}',(q,heading,score) assert prompt==f'{rows[state][1]}: what is its capital?',(q,prompt,rows[state]) opts=[scr.text(8+(i%2)*156,157+(i//2)*12,25)[2:] for i in range(4)] assert len(set(opts))==4 and rows[state][2] in opts,(q,opts) correctpos=opts.index(rows[state][2]);pos[correctpos]+=1 # Confirm selected state's actual framebuffer mask has the expected gold fill. src=(ROOT/'src/map_data.c').read_text();d=list(map(int,re.search(r'national_spans\[\] = \{\s*([\d,\s]+)',src)[1].replace('\n','').split(',')));off=list(map(int,re.search(r'national_offsets\[51\] = \{([\d,]+)',src)[1].split(','))) points=[(6+x,33+y) for k in range(off[state],off[state+1],3) for y,x0,n in [d[k:k+3]] for x in range(x0,x0+n)] gold=sum(scr.guest(x,y)==(255,255,0) for x,y in points) assert gold>=len(points)*.8,(q,rows[state][0],gold,len(points)) right=q%7!=0;selection=correctpos if right else (correctpos+1)%4 if right:score+=1 # Read the feedback as soon as it appears, before its automatic timeout. held=[str(selection+1)] if q==0 else [] c['inp'](run,[dict(keys=[str(selection+1)],fields=8),dict(keys=held,fields=80)]) for attempt in range(35): feedback=c['capture'](run,name+'-feedback');fs=Screen(str(EVIDENCE)+'/'+name+'-feedback.png') if fs.text(8,158,24) in ('CORRECT!','NOT THIS TIME.'):break c['inp'](run,[dict(keys=held,fields=20)]) assert fs.text(8,158,24)==('CORRECT!' if right else 'NOT THIS TIME.'),(q,fs.text(8,158,24)) assert fs.text(8,170,50)==f'The capital is {rows[state][2]}.',(q,fs.text(8,170,50)) assert fs.text(8,183,48)=='Next question follows automatically.' evidence.append(dict(question=q+1,code=rows[state][0],state=rows[state][1],options=opts,answer=opts[selection],correct=right,score=score,gold_pixels=gold,expected_state_pixels=len(points),field=meta['field_clock'])) # No Space/Enter/answer input advances feedback: elapsed guest fields do it. c['inp'](run,[dict(keys=held,fields=600)]) if q==0: c['inp'](run,[dict(keys=held,fields=600)]) c['capture'](run,'held-key-next-question');hs=Screen(str(EVIDENCE)+'/held-key-next-question.png') assert hs.text(8,19,45)==f'Question 2 of 50 Score {score}' assert hs.text(8,183,48)=='Press 1, 2, 3 or 4' c['inp'](run,[dict(keys=[],fields=8)]) print(q+1,rows[state][0],score,flush=True) result=c['capture'](run,'mc-results');s=Screen(str(EVIDENCE)+'/mc-results.png') assert s.text(8,48,45)==f'{score} / 50 correct {score*2}%',s.text(8,48,45) assert s.text(8,70,45)=='0 correct after spelling correction',s.text(8,70,45) assert score==42 and all(pos) # Review all eight misses over three pages, preserving answers. c['key'](run,'r',8,500) misses=[x for x in evidence if not x['correct']] for page in range(3): c['capture'](run,f'mc-review-{page+1}');s=Screen(f'{EVIDENCE}/mc-review-{page+1}.png') assert s.text(8,19,30)==f'Review page {page+1}' for i,m in enumerate(misses[page*3:page*3+3]): assert s.text(8,39+i*43,45)==m['state'] assert s.text(8,50+i*43,48)==f"You: {m['answer']}" cap=next(x[2] for x in rows if x[0]==m['code']) assert s.text(8,61+i*43,48)==f'Capital: {cap}' if page<2:c['key'](run,'n',8,500) c['key'](run,'b',8,500);c['key'](run,'p',8,500);c['capture'](run,'replay-title');c['key'](run,'q',8,200);final=c['capture'](run,'mc-exit') assert 'screen_text' in final or final.get('screen_text_unavailable') is None,final json.dump(dict(score=score,positions=pos,questions=evidence,run_id=run,artifact=result['artifact_id'],exit=final),open(str(EVIDENCE)+'/mc-report.json','w'),indent=2) print('PASS complete multiple-choice quiz, all 50 masks, automatic feedback advance without Space, held-key protection, 42/50, review, replay, exit.',flush=True) c['v'].close()