Logo Search packages:      
Sourcecode: kdrill version File versions  Download package

game.c

/* 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();
}


Generated by  Doxygen 1.6.0   Back to index