Build Capital Quest for CoCo 3 with maps and emulator tests
This commit is contained in:
commit
9f7d6f4786
44 files changed
+6238
No files matched your search
@@ -0,0 +1,28 @@
|
||||
import json,re
|
||||
from pathlib import Path
|
||||
r=Path(__file__).resolve().parents[1]
|
||||
states=[line.split('\t') for line in (r/'data/states.tsv').read_text().splitlines()]
|
||||
assert len(states)==50 and len({s[0] for s in states})==50
|
||||
assert len({s[1] for s in states})==50 and len({s[2] for s in states})==50
|
||||
assert dict((s[0],s[2]) for s in states)['AK']=='Juneau'
|
||||
assert dict((s[0],s[2]) for s in states)['HI']=='Honolulu'
|
||||
assert all(s[0] not in ('DC','PR') for s in states)
|
||||
m=json.loads((r/'data/map-manifest.json').read_text())
|
||||
s=(r/'src/map_data.c').read_text()
|
||||
for view in ('national','northeast','hawaii'):
|
||||
data=list(map(int,re.search(view+r'_spans\[\] = \{\s*([\d,\s]+)',s)[1].replace('\n','').split(',')))
|
||||
off=list(map(int,re.search(view+r'_offsets\[51\] = \{([\d,]+)',s)[1].split(',')))
|
||||
assert len(off)==51 and off[0]==0 and off[-1]==len(data)
|
||||
w,h=m[view]['size']; occupied=set()
|
||||
for i,state in enumerate(states):
|
||||
area=0
|
||||
for k in range(off[i],off[i+1],3):
|
||||
y,x,n=data[k:k+3];assert 0<=y<h and 0<=x<x+n<=w
|
||||
for xx in range(x,x+n):assert (xx,y) not in occupied;occupied.add((xx,y))
|
||||
area+=n
|
||||
assert area==m[view]['pixels'][state[0]]
|
||||
if view=='national':assert area>0
|
||||
for code in ('CT','DE','ME','MD','MA','NH','NJ','NY','PA','RI','VT'):assert m['northeast']['pixels'][code]>0
|
||||
assert m['hawaii']['pixels']['HI']>400
|
||||
assert all(n==0 for code,n in m['hawaii']['pixels'].items() if code!='HI')
|
||||
print('PASS: 50 unique pairs and geometry IDs, bounded/disjoint spans, all national masks, Northeast and Hawaii detail coverage.')
|
||||
@@ -0,0 +1,65 @@
|
||||
import struct,zlib,re,json,runpy,sys,uuid,os,tempfile
|
||||
from pathlib import Path
|
||||
ROOT=Path(__file__).resolve().parents[1]
|
||||
EVIDENCE=Path(os.environ['CAPITAL_TEST_EVIDENCE'])
|
||||
S=json.load(open(os.environ['CAPITAL_TEST_STATE']))
|
||||
ART=next(Path(tempfile.gettempdir()).glob('vcc-c-project-*/Builds/'+S['artifact']['artifact_id']+'/build/CAPITAL.bin'))
|
||||
mp=ART.with_suffix('.map').read_text()
|
||||
addr=int(re.search(r'Symbol: _font5x8 .*?= ([0-9A-Fa-f]+)',mp)[1],16)
|
||||
blob=ART.read_bytes();memory={};p=0
|
||||
while p<len(blob):
|
||||
t=blob[p];n,a=struct.unpack('>HH',blob[p+1:p+5]);p+=5
|
||||
if t==255:break
|
||||
for i,b in enumerate(blob[p:p+n]):memory[a+i]=b
|
||||
p+=n
|
||||
font=bytes(memory[addr+i] for i in range(768))
|
||||
|
||||
def png(path):
|
||||
b=Path(path).read_bytes();p=8;dat=b''
|
||||
while p<len(b):
|
||||
n=int.from_bytes(b[p:p+4],'big');t=b[p+4:p+8];v=b[p+8:p+8+n];p+=n+12
|
||||
if t==b'IHDR':w,h,depth,ct,*_=struct.unpack('>IIBBBBB',v)
|
||||
if t==b'IDAT':dat+=v
|
||||
assert depth==8 and ct in (2,6),(depth,ct)
|
||||
channels=3 if ct==2 else 4;stride=w*channels;raw=zlib.decompress(dat);out=[];prior=bytearray(stride);p=0
|
||||
for y in range(h):
|
||||
f=raw[p];p+=1;row=bytearray(raw[p:p+stride]);p+=stride
|
||||
for i in range(stride):
|
||||
a=row[i-channels] if i>=channels else 0;b=prior[i];c=prior[i-channels] if i>=channels else 0
|
||||
if f==1:pred=a
|
||||
elif f==2:pred=b
|
||||
elif f==3:pred=(a+b)//2
|
||||
elif f==4:
|
||||
v=a+b-c;pa=abs(v-a);pb=abs(v-b);pc=abs(v-c);pred=a if pa<=pb and pa<=pc else b if pb<=pc else c
|
||||
else:pred=0
|
||||
row[i]=(row[i]+pred)&255
|
||||
out.append(row);prior=row
|
||||
return w,h,channels,out
|
||||
class Screen:
|
||||
def __init__(self,path):self.w,self.h,self.ch,self.rows=png(path)
|
||||
def pixel(self,x,y):return tuple(self.rows[y][x*self.ch:x*self.ch+3])
|
||||
def guest(self,x,y):return self.pixel(x*2,50+y*2)
|
||||
def text(self,x,y,n,color=None):
|
||||
bg=self.guest(x+5,y);out=''
|
||||
for j in range(n):
|
||||
bits=[]
|
||||
for row in range(8):
|
||||
for col in range(5):bits.append(self.guest(x+j*6+col,y+row)==color if color else self.guest(x+j*6+col,y+row)!=bg)
|
||||
scores=[]
|
||||
for c in range(32,127):
|
||||
expected=[not(font[(c-32)*8+r]&(128>>co)) for r in range(8) for co in range(5)]
|
||||
scores.append((sum(a!=b for a,b in zip(bits,expected)),c))
|
||||
d,c=min(scores);out+=chr(c) if d==0 else '?'
|
||||
return out.rstrip()
|
||||
|
||||
def shuffle(seed=2709):
|
||||
rng=seed
|
||||
def rand(n):
|
||||
nonlocal rng
|
||||
while True:
|
||||
x=rng;x^=x<<7;x&=65535;x^=x>>9;x^=x<<8;x&=65535;rng=x;x-=1
|
||||
if x<65535-65535%n:return x%n
|
||||
order=list(range(50))
|
||||
for i in range(49,0,-1):j=rand(i+1);order[i],order[j]=order[j],order[i]
|
||||
return order,rand
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run real guest-input regression tests via an exported public VCC Plus grant.
|
||||
Usage: python3 tests/playtest.py PRIVATE_GRANT --evidence /tmp/quiz-evidence
|
||||
Requires C Assistant's Use Project Makefile and Allow Disposable Playtests.
|
||||
"""
|
||||
import argparse,json,os,runpy,sys,time,uuid
|
||||
from pathlib import Path
|
||||
ROOT=Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0,str(ROOT/'tools'))
|
||||
from vcc_mcp import VCC
|
||||
p=argparse.ArgumentParser(description=__doc__)
|
||||
p.add_argument('grant',type=Path);p.add_argument('--evidence',required=True,type=Path)
|
||||
a=p.parse_args();out=a.evidence.resolve()
|
||||
if out.is_relative_to(ROOT):p.error('Keep evidence outside the watched checkout until the run completes.')
|
||||
out.mkdir(parents=True,exist_ok=True)
|
||||
os.environ.update(VCC_GRANT=str(a.grant.resolve()),CAPITAL_TEST_EVIDENCE=str(out),CAPITAL_TEST_STATE=str(out/'session.json'))
|
||||
v=VCC(a.grant)
|
||||
c=v.call('get_capabilities');assert c.get('owned_playtests'),c
|
||||
b=v.call('build_project',dict(project_id=c['project_id'],revision=c['revision'],action='disk',configuration='Test',request_id=str(uuid.uuid4())))
|
||||
assert 'build_id' in b,b
|
||||
for _ in range(150):
|
||||
receipt=v.call('get_build',dict(build_id=b['build_id']))
|
||||
if receipt.get('status')!='building':break
|
||||
time.sleep(.2)
|
||||
assert receipt.get('status')=='succeeded',receipt
|
||||
(out/'build-receipt.json').write_text(json.dumps(receipt,indent=2))
|
||||
r=v.call('start_playtest',dict(project_id=c['project_id'],revision=c['revision'],artifact_id=receipt['artifact_id'],request_id=str(uuid.uuid4())))
|
||||
assert 'run_id' in r,r
|
||||
state=dict(cap=c,build=b,artifact=receipt,run=r)
|
||||
(out/'session.json').write_text(json.dumps(state))
|
||||
v.call('playtest_input',dict(run_id=r['run_id'],request_id=str(uuid.uuid4()),steps=[dict(keys=[],fields=600)]))
|
||||
v.close()
|
||||
support=runpy.run_path(str(Path(__file__).with_name('playtest_support.py')))
|
||||
support['capture'](r['run_id'],'title');support['v'].close()
|
||||
runpy.run_path(str(Path(__file__).with_name('playtest_mc.py')),run_name='__main__')
|
||||
runpy.run_path(str(Path(__file__).with_name('playtest_typed.py')),run_name='__main__')
|
||||
state=json.loads((out/'session.json').read_text());v=VCC(a.grant)
|
||||
for r in (state['run'],state['typed_run']):
|
||||
result=v.call('stop_playtest',dict(run_id=r['run_id'],request_id=str(uuid.uuid4())))
|
||||
(out/('retained-'+r['run_id']+'.json')).write_text(json.dumps(result,indent=2))
|
||||
v.close();print('PASS: complete multiple-choice and typed regression suite. Evidence:',out)
|
||||
@@ -0,0 +1,53 @@
|
||||
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
|
||||
# Hold the first answer through feedback: it must not advance to question 2.
|
||||
c['key'](run,str(selection+1),600 if q==0 else 8,450)
|
||||
feedback=c['capture'](run,name+'-feedback');fs=Screen(str(EVIDENCE)+'/'+name+'-feedback.png')
|
||||
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))
|
||||
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']))
|
||||
c['key'](run,'space',8,500)
|
||||
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, 42/50, review, held-key feedback, replay, exit.',flush=True)
|
||||
c['v'].close()
|
||||
@@ -0,0 +1,18 @@
|
||||
import sys,json,base64,uuid,os
|
||||
from pathlib import Path
|
||||
sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'tools'))
|
||||
from vcc_mcp import VCC
|
||||
v=VCC(os.environ['VCC_GRANT'])
|
||||
def call(n,a={}):
|
||||
r=v.call(n,a)
|
||||
if r.get('isError'):raise RuntimeError(r)
|
||||
return r
|
||||
def capture(run,name):
|
||||
r=v.request('tools/call',{'name':'capture_playtest','arguments':{'run_id':run}})
|
||||
meta={}
|
||||
for c in r['content']:
|
||||
if c['type']=='image':(Path(os.environ['CAPITAL_TEST_EVIDENCE'])/(name+'.png')).write_bytes(base64.b64decode(c['data']))
|
||||
if c['type']=='text':meta=json.loads(c['text'])
|
||||
(Path(os.environ['CAPITAL_TEST_EVIDENCE'])/(name+'.json')).write_text(json.dumps(meta,indent=2));return meta
|
||||
def inp(run,steps):return call('playtest_input',{'run_id':run,'request_id':str(uuid.uuid4()),'steps':steps})
|
||||
def key(run,k,hold=8,settle=250):return inp(run,[{'keys':[k],'fields':hold},{'keys':[],'fields':settle}])
|
||||
@@ -0,0 +1,81 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import runpy,json,sys,uuid
|
||||
from pathlib import Path
|
||||
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')));state=json.load(open(os.environ['CAPITAL_TEST_STATE']))
|
||||
r=c['call']('start_playtest',dict(project_id=state['cap']['project_id'],revision=state['cap']['revision'],artifact_id=state['artifact']['artifact_id'],request_id=str(uuid.uuid4())))
|
||||
run=r['run_id'];state['typed_run']=r;Path(os.environ['CAPITAL_TEST_STATE']).write_text(json.dumps(state))
|
||||
rows=[x.split('\t') for x in (ROOT/'data/states.tsv').read_text().splitlines()];order,_=shuffle();report=[];score=corrections=0
|
||||
|
||||
def press(k,hold=4,settle=12):return c['key'](run,k,hold,settle)
|
||||
def settle():c['inp'](run,[{'keys':[],'fields':600},{'keys':[],'fields':300}])
|
||||
def cap(name):c['capture'](run,name);return Screen(str(EVIDENCE)+'/'+name+'.png')
|
||||
def type_text(s):
|
||||
steps=[]
|
||||
for ch in s:
|
||||
keys=['space'] if ch==' ' else ['shift',ch.lower()] if ch.isupper() else [ch]
|
||||
steps.extend([dict(keys=keys,fields=4),dict(keys=[],fields=12)])
|
||||
if len(steps)>=60:c['inp'](run,steps);steps=[]
|
||||
if steps:c['inp'](run,steps)
|
||||
def clear_input(n):
|
||||
for start in range(0,n,25):c['inp'](run,[x for _ in range(min(25,n-start)) for x in [dict(keys=['left'],fields=4),dict(keys=[],fields=12)]])
|
||||
settle();c['key'](run,'2',8,600);settle();c['inp'](run,[dict(keys=['shift','0'],fields=8),dict(keys=[],fields=12)])
|
||||
# First alias in mixed case. Capture input before submission to verify the real keys.
|
||||
type_text('sT pAuL');s=cap('typed-alias-input');print('input=',s.text(8,158,45),flush=True)
|
||||
assert s.text(8,158,45)=='sT pAuL_',s.text(8,158,45)
|
||||
press('enter',8,600);s=cap('typed-alias-feedback');assert s.text(8,158,30)=='CORRECT!',s.text(8,158,30)
|
||||
score=1;report.append(dict(question=1,code='MN',answer='sT pAuL',correct=True,correction=False));c['key'](run,'space',8,600);settle()
|
||||
for q in range(1,50):
|
||||
st=order[q];code,name,capital=rows[st];s=cap(f'typed-q{q+1:02d}-{code}')
|
||||
assert s.text(8,19,45)==f'Question {q+1} of 50 Score {score}',(q,s.text(8,19,45),score)
|
||||
assert s.text(8,145,50)==name+': what is its capital?'
|
||||
spelling=False;right=True
|
||||
if q==1 or code=='CO':
|
||||
# Adjacent transpositions, followed by a real Backspace correction.
|
||||
answer='Chalreston' if q==1 else 'Dnever';type_text(answer);press('enter',8,600)
|
||||
s=cap('typed-near-'+code)
|
||||
assert s.text(8,158,48)=='That looks close. Check your spelling'
|
||||
assert s.text(8,182,35)=='No point awarded yet.'
|
||||
assert s.text(8,19,45)==f'Question {q+1} of 50 Score {score}'
|
||||
c['key'](run,'r',8,600);settle();clear_input(len(answer));type_text(capital);spelling=True
|
||||
elif q==2:
|
||||
answer='Austin';type_text(answer);right=False
|
||||
elif q==3:
|
||||
answer='X'*39;type_text('X'*45);settle();s=cap('typed-long-input')
|
||||
assert s.text(8,158,45)=='X'*39+'_',s.text(8,158,45)
|
||||
assert s.text(8,171,48)=='39 characters maximum. Backspace to edit.'
|
||||
right=False
|
||||
elif q==4:
|
||||
answer='Tallhassee';type_text(answer);press('enter',8,600);s=cap('typed-near-reveal')
|
||||
assert s.text(8,158,48)=='That looks close. Check your spelling'
|
||||
c['key'](run,'v',8,600);right=False
|
||||
elif q==5:
|
||||
answer=' bAtOn rOuGe ';type_text(answer)
|
||||
else:
|
||||
answer=capital.lower() if q%3==0 else capital.upper() if q%3==1 else ''.join(ch.lower() if i%2 else ch.upper() for i,ch in enumerate(capital))
|
||||
type_text(answer)
|
||||
if q!=4:press('enter',8,600)
|
||||
s=cap(f'typed-feedback-{q+1:02d}')
|
||||
assert s.text(8,158,30)==('CORRECT!' if right else 'NOT THIS TIME.'),(q,s.text(8,158,30),answer)
|
||||
assert s.text(8,170,50)==f'The capital is {capital}.'
|
||||
if right:score+=1
|
||||
if spelling:corrections+=1
|
||||
report.append(dict(question=q+1,code=code,answer=answer,correct=right,correction=spelling))
|
||||
c['key'](run,'space',8,600)
|
||||
if q!=49:settle()
|
||||
print(q+1,code,score,corrections,flush=True)
|
||||
s=cap('typed-results');assert s.text(8,48,48)==f'{score} / 50 correct {score*2}%',s.text(8,48,48)
|
||||
assert s.text(8,70,48)==f'{corrections} correct after spelling correction'
|
||||
assert score==47 and corrections==2
|
||||
c['key'](run,'r',8,600);s=cap('typed-review')
|
||||
misses=[x for x in report if not x['correct']]
|
||||
for i,m in enumerate(misses):
|
||||
name,capital=next((r[1],r[2]) for r in rows if r[0]==m['code'])
|
||||
assert s.text(8,39+i*43,48)==name
|
||||
assert s.text(8,50+i*43,48)=='You: '+m['answer']
|
||||
assert s.text(8,61+i*43,48)=='Capital: '+capital
|
||||
c['key'](run,'b',8,600);c['key'](run,'q',8,300);final=c['capture'](run,'typed-exit')
|
||||
assert final.get('screen_text_unavailable') is None
|
||||
json.dump(dict(score=score,corrections=corrections,questions=report,run_id=run,exit=final),open(str(EVIDENCE)+'/typed-report.json','w'),indent=2)
|
||||
print('PASS full typed quiz: casing, spaces, alias, transpositions, corrected spelling, wrong capital, long input, reveal, score/review/exit.',flush=True);c['v'].close()
|
||||
@@ -0,0 +1,48 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "quiz.h"
|
||||
static unsigned index_of(const char *code){unsigned i;for(i=0;i<50;++i)if(!strcmp(states[i].code,code))return i;assert(0);return 0;}
|
||||
int main(void)
|
||||
{
|
||||
unsigned seed,i,j,p,positions[4]={0},last[50],changes=0;
|
||||
char a[160],b[ANSWER_SIZE];
|
||||
for(seed=1;seed<=2000;++seed){
|
||||
unsigned seen[50]={0};quiz_start(seed);
|
||||
for(i=0;i<50;++i){
|
||||
assert(order[i]<50);++seen[order[i]];quiz_options(order[i]);p=0;
|
||||
for(j=0;j<4;++j){unsigned k;assert(options[j]<50);if(options[j]==order[i]){++p;++positions[j];}for(k=0;k<j;++k)assert(options[k]!=options[j]);}
|
||||
assert(p==1);if(seed>1 && last[i]!=order[i])++changes;last[i]=order[i];
|
||||
}
|
||||
for(i=0;i<50;++i)assert(seen[i]==1);
|
||||
}
|
||||
for(i=0;i<4;++i)assert(positions[i]>20000);
|
||||
assert(changes>90000);
|
||||
for(i=0;i<50;++i){
|
||||
assert(classify(i,states[i].capital)==2);
|
||||
strcpy(a,states[i].capital);
|
||||
for(j=0;a[j];++j)if(a[j]>='a' && a[j]<='z')a[j]-=32;
|
||||
assert(classify(i,a)==2);
|
||||
for(j=0;a[j];++j)if((j&1) && a[j]>='A' && a[j]<='Z')a[j]+=32;
|
||||
assert(classify(i,a)==2);
|
||||
for(j=0;j<50;++j)if(j!=i)assert(classify(i,states[j].capital)==0);
|
||||
}
|
||||
assert(classify(index_of("MN"),"St. Paul")==2);
|
||||
assert(classify(index_of("MN"),"st paul")==2);
|
||||
assert(classify(index_of("MN")," sAiNt PaUl ")==2);
|
||||
assert(classify(index_of("UT")," salt lake city ")==2);
|
||||
assert(classify(index_of("CO"),"Dnever")==1);
|
||||
assert(classify(index_of("AK"),"Juneu")==1);
|
||||
assert(classify(index_of("MA"),"Austin")==0);
|
||||
assert(classify(index_of("CO"),"")==0);
|
||||
memset(a,'x',159);a[159]=0;assert(classify(0,a)==0);
|
||||
normalize(" \t baton ROUGE \n ",b);assert(!strcmp(b,"baton rouge"));
|
||||
quiz_start(2709);
|
||||
for(i=0;i<50;++i){quiz_record(order[i],i%2?"wrong":states[order[i]].capital,!(i%2),!(i%4));quiz_record(order[i],"duplicate",1,1);}
|
||||
assert(score==25 && corrections==13);
|
||||
for(i=0;i<50;++i)assert(answered[i]==1);
|
||||
quiz_start(2710);assert(score==0 && corrections==0);
|
||||
for(i=0;i<50;++i)assert(!answered[i] && !responses[i][0]);
|
||||
printf("PASS: 2000 shuffles, 100000 option sets, all casing / known-wrong capitals, aliases, distance, bounds, scoring, reset.\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user