201 lines
8.0 KiB
C
201 lines
8.0 KiB
C
#include <cmoc.h>
|
|
#include <coco.h>
|
|
#include <Keyboard.h>
|
|
#include <vcc/gime.h>
|
|
#include "quiz.h"
|
|
#include "ui.h"
|
|
static char line[54],answer[ANSWER_SIZE];
|
|
static U8 mode,spelling_attempt;
|
|
/* Named phases make read-only debugger snapshots useful without altering play. */
|
|
U8 phase, current_state;
|
|
static void present(void)
|
|
{
|
|
if(!gime_present()) {gime_close();setHighSpeed(FALSE);printf("Graphics timer stopped.\n");exit(1);}
|
|
}
|
|
static U8 choose(const char *allowed)
|
|
{
|
|
U8 c;release_keys();
|
|
do {c=next_key();if(c>='a' && c<='z')c-='a'-'A';}while(!strchr(allowed,c));
|
|
return c;
|
|
}
|
|
static void credits(void)
|
|
{
|
|
phase=5;frame("CREDITS");
|
|
text(8,19,"A MAP FOR EVERY STATE",6);
|
|
text(8,39,"Map (c) Fla-shop.com",7);
|
|
text(8,51,"https://www.fla-shop.com/svg/usa/",2);
|
|
text(8,65,"Creative Commons Attribution 4.0",7);
|
|
text(8,77,"https://creativecommons.org/licenses/by/4.0/",2);
|
|
text(8,94,"Adapted: rasterized, reduced, recolored;",7);
|
|
text(8,105,"state masks and enlarged detail views added.",7);
|
|
text(8,124,"Font: Pierre Sarrazin, public domain.",2);
|
|
text(8,138,"C / CMOC; VCC Plus GIME and keyboard APIs.",2);
|
|
text(8,158,"Full sources and notices: assets/CREDITS.md",7);
|
|
text(8,180,"SPACE: back",6);present();choose(" ");
|
|
}
|
|
static U8 title(void)
|
|
{
|
|
U8 c;
|
|
do {
|
|
phase=0;frame("50 STATES");
|
|
text(8,19,"HOW WELL DO YOU KNOW YOUR CAPITALS?",6);
|
|
show_map(49);
|
|
/* Right panel is replaced by the mode selector. */
|
|
gime_fill_rect(209,32,111,111,0);
|
|
text(212,40,"CHOOSE A MODE",6);
|
|
text(212,58,"1 MULTIPLE",7);text(224,69,"CHOICE",7);
|
|
text(212,89,"2 TYPE THE",7);text(224,100,"CAPITAL",7);
|
|
text(212,123,"C CREDITS",2);text(212,136,"Q QUIT",2);
|
|
text(8,149,"Choice: 1-4. Type: letters, spaces, Enter.",7);
|
|
text(8,160,"Backspace edits. Near spelling: retry/reveal.",2);
|
|
text(8,171,"50 questions. Release keys between presses.",2);
|
|
text(8,182,"Choice: auto next. Typed: SPACE to continue.",6);
|
|
present();c=choose("12CQ");if(c=='C')credits();
|
|
}while(c=='C');
|
|
return c;
|
|
}
|
|
static void question_screen(void)
|
|
{
|
|
phase=1;frame(mode==1?"MULTIPLE CHOICE":"TYPE THE CAPITAL");
|
|
sprintf(line,"Question %u of 50 Score %u",(unsigned)question+1,(unsigned)score);
|
|
text(8,19,line,7);
|
|
show_map(current_state);
|
|
gime_fill_rect(0,143,320,49,1);
|
|
sprintf(line,"%s: what is its capital?",states[current_state].name);
|
|
text(8,145,line,6);
|
|
}
|
|
static U8 multiple_choice(void)
|
|
{
|
|
U8 i,c;
|
|
quiz_options(current_state);question_screen();
|
|
for(i=0;i<4;++i) {
|
|
sprintf(line,"%u %s",(unsigned)i+1,states[options[i]].capital);
|
|
text(8+(i%2)*156,157+(i/2)*12,line,7);
|
|
}
|
|
text(8,183,"Press 1, 2, 3 or 4",2);present();c=choose("1234")-'1';
|
|
strcpy(answer,states[options[c]].capital);
|
|
return options[c]==current_state;
|
|
}
|
|
static void input_screen(U8 too_long)
|
|
{
|
|
gime_fill_rect(0,155,320,37,1);text(8,158,answer,7);
|
|
text(8+strlen(answer)*6,158,"_",6);
|
|
text(8,171,too_long?"39 characters maximum. Backspace to edit.":"Enter submits. Backspace edits. Esc reveals.",2);
|
|
text(8,183,"Case and extra spaces do not matter.",2);present();
|
|
}
|
|
static void input_edit(U8 old_n,U8 new_n)
|
|
{
|
|
U8 p,first=old_n<new_n?old_n:new_n;
|
|
/* Both hidden/displayed pages receive the same tiny edit. Static help and
|
|
the map are already drawn, so normal typing never repaints the map. */
|
|
for(p=0;p<2;++p) {
|
|
gime_fill_rect(8+(unsigned)first*6,158,12,8,1);
|
|
text(8+(unsigned)first*6,158,answer+first,7);
|
|
text(8+(unsigned)new_n*6,158,"_",6);present();
|
|
}
|
|
}
|
|
static U8 typed_answer(void)
|
|
{
|
|
U8 c,n=0,result,old_n;
|
|
answer[0]=0;spelling_attempt=0;
|
|
question_screen();input_screen(0);question_screen();input_screen(0);release_keys();
|
|
for(;;) {
|
|
c=next_key();release_keys();
|
|
if(c==3 || c==27) {strcpy(answer,"[revealed]");return 0;}
|
|
if(c==8 || c==127) {old_n=n;if(n)answer[--n]=0;input_edit(old_n,n);}
|
|
else if(c==13 || c==10) {
|
|
result=classify(current_state,answer);
|
|
if(result==2)return 1;
|
|
if(result==0)return 0;
|
|
question_screen();phase=2;
|
|
text(8,158,"That looks close. Check your spelling",6);
|
|
text(8,169,"and try again. R: retry V: reveal",7);
|
|
text(8,182,"No point awarded yet.",2);present();
|
|
c=choose("RV");
|
|
if(c=='V')return 0;
|
|
spelling_attempt=1;
|
|
question_screen();input_screen(0);question_screen();input_screen(0);release_keys();
|
|
} else if((c>='a' && c<='z') || (c>='A' && c<='Z') || c==' ' || c=='.' || c=='\t') {
|
|
if(n<ANSWER_SIZE-1){old_n=n;answer[n++]=c;answer[n]=0;input_edit(old_n,n);}
|
|
else {input_screen(1);input_screen(1);}
|
|
}
|
|
}
|
|
}
|
|
static void answer_sound(U8 right)
|
|
{
|
|
/* Color BASIC SOUND uses built-in audio and normal-speed timing.
|
|
Keep the graphics page visible while playing a rising or falling pair. */
|
|
setHighSpeed(FALSE);
|
|
if(right) {sound(170,2);sound(210,3);}
|
|
else {sound(100,3);sound(60,4);}
|
|
setHighSpeed(TRUE);
|
|
}
|
|
static void feedback(U8 right)
|
|
{
|
|
question_screen();phase=3;
|
|
text(8,158,right?"CORRECT!":"NOT THIS TIME.",right?6:5);
|
|
sprintf(line,"The capital is %s.",states[current_state].capital);text(8,170,line,7);
|
|
text(8,183,mode==1?"Next question follows automatically.":"Release your key, then SPACE to continue.",2);
|
|
present();answer_sound(right);
|
|
if(mode==1) delay(90); /* 1.5 seconds to read the answer after the tone. */
|
|
else choose(" ");
|
|
}
|
|
static U8 results(void)
|
|
{
|
|
U8 c,i,page,start,missed=50-score;
|
|
for(;;) {
|
|
phase=4;frame("YOUR RESULTS");
|
|
text(8,19,"JOURNEY COMPLETE",6);
|
|
sprintf(line,"%u / 50 correct %u%%",(unsigned)score,(unsigned)score*2);text(8,48,line,7);
|
|
sprintf(line,"%u correct after spelling correction",(unsigned)corrections);text(8,70,line,2);
|
|
sprintf(line,"%u states to revisit",(unsigned)missed);text(8,92,line,7);
|
|
text(8,123,"R REVIEW MISSED STATES",6);
|
|
text(8,143,"P PLAY AGAIN",7);text(8,163,"Q QUIT TO BASIC",7);
|
|
text(8,183,"Every state asked once. Keep exploring!",2);present();c=choose("RPQ");
|
|
if(c!='R')return c;
|
|
page=0;start=0;
|
|
for(;;) {
|
|
frame("MISSED STATES");
|
|
sprintf(line,"Review page %u",(unsigned)page+1);text(8,19,line,6);
|
|
i=start;c=0;
|
|
while(i<50 && c<3) {
|
|
if(!correct[order[i]]) {
|
|
text(8,39+c*43,states[order[i]].name,6);
|
|
sprintf(line,"You: %s",responses[order[i]][0]?(const char *)responses[order[i]]:"[blank]");text(8,50+c*43,line,7);
|
|
sprintf(line,"Capital: %s",states[order[i]].capital);text(8,61+c*43,line,2);++c;
|
|
}
|
|
++i;
|
|
}
|
|
while(i<50 && correct[order[i]])++i;
|
|
if(!missed)text(8,60,"A perfect trip! No missed states.",7);
|
|
text(8,180,i<50?"N NEXT PAGE B BACK TO RESULTS":"B BACK TO RESULTS",6);present();
|
|
c=choose(i<50?"NB":"B");if(c=='B')break;
|
|
start=i;++page;
|
|
}
|
|
}
|
|
}
|
|
int main(void)
|
|
{
|
|
U8 c,right;unsigned seed;
|
|
if(!gime_open()){printf("Requires 512 KB CoCo 3 in BASIC.\n");return 1;}
|
|
setHighSpeed(TRUE);
|
|
gime_set_palette(0,1);gime_set_palette(1,3);gime_set_palette(2,56);
|
|
gime_set_palette(3,28);gime_set_palette(4,14);gime_set_palette(5,39);
|
|
gime_set_palette(6,54);gime_set_palette(7,63);
|
|
for(;;) {
|
|
c=title();if(c=='Q')break;mode=c-'0';
|
|
seed=entropy ^ getTimer();
|
|
#ifdef TEST_SEED
|
|
seed=TEST_SEED;
|
|
#endif
|
|
quiz_start(seed);
|
|
for(question=0;question<50;++question) {
|
|
current_state=order[question];spelling_attempt=0;
|
|
right=mode==1?multiple_choice():typed_answer();
|
|
quiz_record(current_state,answer,right,spelling_attempt);feedback(right);
|
|
}
|
|
if(results()=='Q')break;
|
|
}
|
|
release_keys();close_input();gime_close();setHighSpeed(FALSE);printf("Thanks for playing Capital Quest!\n");return 0;
|
|
}
|