/* game.c: * This file should contail all the internal game mechanisms; * things that deal with the way things play, not how the * windows look. */ #include #include /* This SHOULD define lrand48.. * but doesn't for solaris. go figure */ #include #include #include #include #include #include #include "defs.h" #include "externs.h" #include "widgets.h" #include "grades.h" #include "init.h" #include "game.h" #include "convert.h" #include "badguess.h" #include "utils.h" #include "log.h" #include "timeout.h" TRANSLATION lastpicked=NULL;/* we can't "really" auto-initialize this... * set to zero so we can set it * properly later */ TRANSLATION values[NUMBEROFCHOICES]; int truevalue=0; int numberincorrect=0; int totalguessed=0, totalmissed=0; /* "numberincorrect" is total number of UNIQUE kanji missed. * "totalmissed" is number of times player clicked wrong. * "totalguessed" is total number of guesses, right or wrong. * The "total" values are only set in the function guessvalue() */ int choicesmode=GUESS_ENGLISH; int questionmode=GUESS_KANJI; int romajiswitch=0; /* fake kana with romaji? */ Boolean doBell,showinorder; Boolean useUsefile; Boolean switchKanaEnglish; int englishwidth=7; /* unused? */ int englishheight=11; /* unused? */ void SetupGuess(); /* *UseThisKanji() * Returns Boolean value on whether this is a "valid" kanji for us, * based on usefile markings, current grades allowed, * and whether it has english/kana translation available */ Boolean UseThisKanji(int kanjinum) { TRANSLATION kanji = translations[kanjinum]; if(useUsefile){ if(InUsefile(kanjinum) == False){ return False; } } /* nonexistant kanji? */ if(kanji == NULL) return False; /* check against frequency limits */ if(kanji->frequency==0) { /* No frequency means REALLY LOW frequency, * therefore if there's ANY limit, dont use */ if(lowfrequency != 0) return False; } else { /* "1" is highest frequency!! */ if(kanji->frequency < highfrequency) return False; } if(lowfrequency != 0){ if(kanji->frequency >lowfrequency) return False; } /* does the appropriate form to display exist? */ if( switchKanaEnglish || (choicesmode != GUESS_KANA)){ if(kanji->english == NULL) return False; if(kanji->english[0] == '\0') return False; } if( switchKanaEnglish || (choicesmode == GUESS_KANA)) { /* we're supposed to be showing kana.. are there any? */ if(kanji->pronunciation == NULL) return False; } /* only thing left is to check grade level */ switch(kanji->grade_level){ case 1: case 2: case 3: case 4: case 5: case 6: if(gradelevelflags & (1 <grade_level) ){ return True; } else { return False; } default: if(gradelevelflags & (1 <<7) ){ return True; } else { return False; } } } /* CountKanji *This routine gets called a hell of a lot: * When we change grade level, and * when we change kana/english display. * (the secnd being because kanjidic does not always have * english and/or kana listings * * This counts the number of usable (enabled) kanji. * * DO NOT update "numberincorrect" here. It is independant of * whether or not a kanji is active this second or not. */ void CountKanji() { TRANSLATION ktrans; int knum; numberofkanji=0; for(knum=lowestkanji; knum<=highestkanji;knum++){ ktrans = translations[knum]; if(ktrans == NULL) continue; if(UseThisKanji(knum)){ numberofkanji++; } } } /* the "missed" window now has an interesting format: * (unique kanji missed)/(total missed)/(total guessed) * * So make a nice wrapper routine to encapsulate it */ static void updatemisscount(Widget w) { char tempstr[100]; sprintf(tempstr,"%d/%d/%d",numberincorrect,totalmissed,totalguessed); XtVaSetValues(w,XtNstring,tempstr,NULL); return; } /* kinda like CountKanji, but this ONLY updates 'numberincorrect' * This is a brute force util, to recount when/if we have a leak in * our 'numberincorrect' calculations */ void TallyWrong() { int wcount=0; int kcount=0; for(kcount=lowestkanji; kcount<=highestkanji; kcount++){ if(translations[kcount]==NULL) continue; if(translations[kcount]->incorrect>0) wcount++; } numberincorrect=wcount; updatemisscount(kanjiMissed); } /* User wants us to forget our missed kanji count. */ void ClearMissed() { int kcount; for(kcount=lowestkanji; kcount<=highestkanji; kcount++) { if(translations[kcount]==NULL) continue; translations[kcount]->incorrect=0; } numberincorrect=0; updatemisscount(kanjiMissed); } /* pickkanji: * picks an acceptably random kanji index. * NOTE THAT THIS DEPENDS ON "numberofkanji" being correct! */ TRANSLATION pickkanji() { int rand_kanji,count; rand_kanji = lrand48()%numberofkanji; for(count=lowestkanji;count<=highestkanji;count++){ if(UseThisKanji(count)) { rand_kanji--; if(rand_kanji <0) return translations[count]; } } fprintf(stderr,"Internal error: picked kanji out of range\n"); fprintf(stderr,"random pick: %d\n",rand_kanji); fprintf(stderr,"number of kanji: %d\n",numberofkanji); exit(0); } /* pickincorrect: * This would be more accurately named "Pick one you missed previously" * * Start at first incorect kanji after the last one picked,and look * for another missed one. * This hopefully ensures that we don't replay the same incorrect * one too often. * * We are trying to phase this routine out, in favour of * GetRepeat() * */ TRANSLATION pickincorrect() { TRANSLATION currentpick=lastpicked; int count; if(numberincorrect==0){ puts("Internal Error.. pickincorrect called when no incorrect stored"); return pickkanji(); } count = lowestkanji; while(translations[count] != lastpicked) count++; /* Okay, we are now at current display. Search for first incorrect*/ do { /* search for first USABLE.*/ do { count++; if(count > highestkanji) count = lowestkanji; currentpick=translations[count]; } while(!UseThisKanji(count)); if(translations[count] == lastpicked){ if(numberincorrect>1){ /* why didnt we hit a second incorrect one? */ setstatus("Internal error picking repeat"); } return lastpicked; } } while (currentpick->incorrect == 0); return currentpick; } /* picktruevalue() * Similar to pickkanji(). * This calls pickkanji() to generate random kanji when * desired, or the "next" kanji after 'lastpicked' * * Also tries to go back to missed kanji, ONLY when * "random" order is in effect. * * Tries to make sure same kanji isn't called in a row, * based on "lastpicked" * * "lastpicked is SET somewhere ELSE, presumably just before calling * this routine. It is also used by the "Back" command */ TRANSLATION picktruevalue() { int currentpick; TRANSLATION ret_val = lastpicked; if(!showinorder){ /* Should we repeat a kanji they missed earlier? */ /* First check if they have missed any */ if(numberincorrect>0){ if(lrand48()%REPEATFRACTION == 0){ /* repeat what user missed */ /*ret_val = pickincorrect();*/ ret_val=GetRepeat(); } else do { ret_val = pickkanji(); } while (ret_val == lastpicked); } /* This also catches the case where we repeat one * they missed, and it is the current one showing */ while (ret_val == lastpicked){ ret_val = pickkanji(); } } else { /* This is annoying. We have to search through all * kanji to find the "lastpicked" translation. * We then add one, so to speak. */ ret_val = lastpicked; currentpick =lowestkanji; while(lastpicked != translations[currentpick]){ currentpick++; } /* okay, add one */ do { currentpick++; if(currentpick >highestkanji) currentpick = lowestkanji; } while(!UseThisKanji(currentpick)); ret_val = translations[currentpick]; } return ret_val; } /* If player has mis-recognized a kanji, call this routine to * make a record that that kanji has been missed at least once. * * Called by guessvalue() */ void markasmissed(TRANSLATION kanjiP) { if(kanjiP->incorrect ==0) numberincorrect+=1; kanjiP->incorrect += REPEATTIMES; if(kanjiP->incorrect >MAXREPEAT) kanjiP->incorrect = MAXREPEAT; AdjustBadCache(kanjiP); } /*guessvalue(int) * This is what handles user guesses. * Passed in number by EITHER kanji button or english button, * we don't care. * Set status bar to correct/incorrect. Beeps if incorrect * Also sets "incorrect" counter in the translations[], * for the translation wrongly guessed. * ALSO calls routine to set total wrong. * */ /* note that "-1" on guess means "you lose: ran out of time" */ void guessvalue(int guess) { TRANSLATION kanjiP; kanjiP = values[guess];/* for setting "incorrect" flag */ totalguessed+=1; if(guess == truevalue){ ClearCheat(); setstatus("Correct! Now try THIS one..."); switch(kanjiP->incorrect){ case 0: break; case 1: numberincorrect -= 1; /* and fall through */ default: kanjiP->incorrect -= 1; AdjustBadCache(kanjiP); /* TallyWrong();*/ } updatemisscount(kanjiMissed); SetupGuess(); return; } totalmissed+=1; /* YOU GOT IT **WWRRROONNGGG!!!!** */ /* We mark two translation entries because of this; * once for the quiz meaning you missed, * and once for the incorrect kanji you thought it was */ Beep(); setstatus("Incorrect."); /* Note that markasmissed also increments numberincorrect, * *IFF* needed */ if(guess != -1){ markasmissed(kanjiP); } kanjiP = values[truevalue]; markasmissed(kanjiP); updatemisscount(kanjiMissed); /*SetWidgetNumberval... */ } /* setkanjilabel: * Takes widget, and translation struct. * Sets widget label to be kanji string. * Will set font. */ void setkanjilabel(Widget w,TRANSLATION trans) { XtVaSetValues(w, XtNencoding,XawTextEncodingChar2b, XtNfont,largekfont, NULL); XtVaSetValues(w, XtNlabel,trans->kanji, NULL); } /* setenglabel * Given widget, and kanji index, * Sets widget label to appropriate english. * Will change fonts to englishfont. */ void setenglabel(Widget widget,TRANSLATION trans) { XtVaSetValues(widget, XtNencoding,XawTextEncoding8bit, XtNfont,englishfont, XtNlabel,trans->english, NULL); } /* setkanalabel * Given widget, and kanji index, * Sets widget label to kana reading * Will change font to smallkfont */ void setkanalabel(Widget widget,TRANSLATION trans) { if(romajiswitch ==1){ char stringbuff[MAXROMAJI+1]; /*translate all kana into romaji */ /* translate to buffer, then set widget string*/ kanatoromaji(trans->pronunciation, stringbuff); /* stupid X bug workaround... */ XtVaSetValues(widget, XtNlabel,"", NULL); XtVaSetValues(widget, XtNencoding,XawTextEncoding8bit, XtNfont,englishfont, NULL); XtVaSetValues(widget, XtNlabel,stringbuff, NULL); } else { /* This needs to be done separately? */ XtVaSetValues(widget, XtNencoding,XawTextEncodingChar2b, XtNfont,smallkfont, NULL); XtVaSetValues(widget, XtNlabel,trans->pronunciation, NULL); } } /* printquestion() * prints out question button, in appropriate font. */ void printquestion() { switch(questionmode) { case GUESS_ENGLISH: setenglabel(questionWidget,values[truevalue]); break; case GUESS_KANA: setkanalabel(questionWidget,values[truevalue]); break; case GUESS_KANJI: setkanjilabel(questionWidget,values[truevalue]); break; default: fprintf(stderr,"Internal Error in kdrill: printallchoices\n"); exit(0); } } /* printallchoices() * updates all "choices" labels... * have to handle english, kana, or kanji! */ void printallchoices() { int i; for(i=0;iincorrect>0) setstatus("You missed this one before"); StartTimeout(); }