/* 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 <stdio.h>
#include <stdlib.h> /* This SHOULD define lrand48..
* but doesn't for solaris. go figure
*/
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <Intrinsic.h>
#include <StringDefs.h>
#include <Xaw/Command.h>
#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 <<kanji->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;i<NUMBEROFCHOICES;i++) {
/* Used to call UnhighlightButton() on each, to make sure
* 'cheat' effect went away.
* Now, guessvalue() calls ClearCheat() if correct
* value has been clicked on.
*/
switch(choicesmode) {
case GUESS_ENGLISH:
setenglabel(choicesWidgets[i],values[i]);
break;
case GUESS_KANA:
setkanalabel(choicesWidgets[i],values[i]);
break;
case GUESS_KANJI:
setkanjilabel(choicesWidgets[i],values[i]);
break;
default:
fprintf(stderr,"Internal Error in kdrill: printallchoices\n");
exit(0);
}
}
}
/* SetupGuess
*
******************************************************
*
* called by guessvalue(), (or init) once user has clicked the CORRECT answer.
* sets up new question and triggers button label refresh
*
*/
void SetupGuess()
{
Boolean doloop;
int valuecount;
StopTimeout();
lastpicked = values[truevalue];
/* first determine which of the buttons will be the "true" choice */
truevalue = lrand48() %NUMBEROFCHOICES;
/* Now randomly assign characters for each choice button */
for(valuecount=0;valuecount<NUMBEROFCHOICES;valuecount++){
if(valuecount == truevalue){
values[valuecount] = picktruevalue();
} else {
values[valuecount] = pickkanji();
}
}
/* now weed out duplicates..
* This is messy.
* We compare everything to everything else, and change
* the "current" value if there is a conflict...
* UNLESS current value is the "truevalue".
* We then do a full compare again if there was a conflict
*/
do {
int startcount=0;
doloop = False;
do {
if(startcount == truevalue)
continue;
for(valuecount=0;valuecount<NUMBEROFCHOICES;valuecount++){
if(valuecount == startcount)
continue;
if(values[startcount]== values[valuecount] ){
doloop = True;
values[startcount] = pickkanji();
}
}
} while(++startcount<NUMBEROFCHOICES);
} while(doloop== True);
DescribeCurrent(values[truevalue]);
printquestion(); /* update labels */
printallchoices(); /* likewise */
printgrades(); /* stupid buttons needs updating manually,
* EVERY durn time. arrg */
if(values[truevalue]->incorrect>0)
setstatus("You missed this one before");
StartTimeout();
}
syntax highlighted by Code2HTML, v. 0.9.1