Blob Blame Raw

#include <helianthus.h>
#include <string.h>
#include <stdio.h>


#define MAXREC     1000
#define ANSWERS       4

#define MAXQUESTS    10


typedef struct {
  char text[128];
} Date;

typedef struct {
  char text[1024];
} Text;

typedef struct {
  char text[128];
} Part;


typedef struct {
  Date date;
  Text text;
  Part part;
} BaseRecord;

typedef struct {
  Date *date;
  Text *text;
  Part *part;
  int error;
} Record;

typedef struct {
  Date *date;
  int error;
} Answer;


BaseRecord recordsSrc[MAXREC];
Record records[MAXREC];
Part *parts[MAXREC];
Date *dates[MAXREC];
int partCount;
int dateCount;
int recCount;


int mode;
int errorCount;
int curError;
int curRec;
double questionFade;
Answer answers[ANSWERS];


void load(const char *filename) {
  recCount = 0;
  partCount = 0;
  dateCount = 0;
  memset(recordsSrc, 0, sizeof(recordsSrc));
  memset(records, 0, sizeof(records));
  memset(parts, 0, sizeof(parts));
  memset(dates, 0, sizeof(dates));

  FILE *f = fopen(filename, "r");
  if (!f) {
    printf("cannot open file for read: %s\n", filename);
    return;
  }

  int mode = 0;
  BaseRecord *rs = recordsSrc;
  char *p = rs->date.text;
  char *e = p + sizeof(rs->date.text);
  while(1) {
    int c = fgetc(f);
    if (c <= 0 || c >= 256) break;
    if (c == '\t') {
      if (mode == 0) {
        mode = 1; p = rs->text.text; e = p + sizeof(rs->text.text);
      } else
      if (mode == 1) {
        mode = 2; p = rs->part.text; e = p + sizeof(rs->part.text);
      } else
      if (mode == 2) {
        mode = 3;
      }
    } else
    if (c == '\n') {
        if (rs->date.text[0] || rs->text.text[0] || rs->part.text[0]) {
          ++recCount; ++rs;
          if (recCount >= MAXREC) break;
        }
        mode = 0; p = rs->date.text; e = p + sizeof(rs->date.text);
    } else
    if (c != '\r' && p+1 < e) {
      *p++ = c;
    }
  }
  fclose(f);

  if (recCount < MAXREC)
    if (rs->date.text[0] || rs->text.text[0] || rs->part.text[0])
      ++recCount;

  for(int i = 0; i < recCount; ++i) {
    Record *r = &records[i];
    BaseRecord *rs = &recordsSrc[i];
    for(int j = 0; j <= i; ++j) {
      BaseRecord *rrs = &recordsSrc[j];
      if (!r->date && !strcmp(rs->date.text, rrs->date.text)) r->date = &rrs->date;
      if (!r->text && !strcmp(rs->text.text, rrs->text.text)) r->text = &rrs->text;
      if (!r->part && !strcmp(rs->part.text, rrs->part.text)) r->part = &rrs->part;
    }
    if (r->part == &rs->part)
      parts[partCount++] = r->part;
    if (r->date == &rs->date)
      dates[dateCount++] = r->date;
  }

  for(int i = 0; i < 2*recCount; ++i) {
    int a = randomNumber(0, recCount - 1);
    int b = randomNumber(0, recCount - 1);
    if (a == b) continue;
    Record r;
    memcpy(&r, &records[a], sizeof(r));
    memcpy(&records[a], &records[b], sizeof(r));
    memcpy(&records[b], &r, sizeof(r));
  }
  if (recCount > MAXQUESTS)
    recCount = MAXQUESTS;
}


void nextQuestion() {
  if (mode != 2) return;
  if (!curError) ++curRec;
  if (curRec >= recCount)
    { mode = 3; return; }

  curError = 0;
  mode = 1;

  int q = randomNumber(curRec, recCount-1);
  if (q != curRec) {
    Record r;
    memcpy(&r, &records[curRec], sizeof(r));
    memcpy(&records[curRec], &records[q], sizeof(r));
    memcpy(&records[q], &r, sizeof(r));
  }

  for(int i = 0; i < ANSWERS; ++i) {
    for(int j = 0; j < 100; ++j) {
      answers[i].date = dates[randomNumber(0, dateCount-1)];
      if (answers[i].date != records[curRec].date) break;
    }
    answers[i].error = 0;
  }
  answers[randomNumber(0, ANSWERS-1)].date = records[curRec].date;
}


void startTest() {
  mode = 2;
  curRec = -1;
  curError = 0;
  errorCount = 0;
  for(int i = 0; i < recCount; ++i)
    records[i].error = 0;
  nextQuestion();
}


void giveAnswer(int i) {
  if (mode != 1) return;
  if (answers[i].date == records[curRec].date) {
    mode = 2;
  } else {
    curError = 1;
    if (!records[curRec].error) {
      records[curRec].error = 1;
      ++errorCount;
    }
    for(int j = 0; j < ANSWERS; ++j)
      if (answers[j].date != records[curRec].date)
        answers[j].error = 1;
  }
}


void init() {
  load("data/history/ancient-dates.txt");
  startTest();
}


void draw() {
  saveState();
  double w = windowGetWidth();
  double h = windowGetHeight();
  translate(w/2, h/2);

  double my = mouseTransformedY();

  saveState();
  if (mode == 1 || mode == 2) {
    noFill();
    strokeWidth(2);
    stroke(COLOR_BLACK);
    textAlign(HALIGN_CENTER, VALIGN_CENTER);
    textf(0, -200, "%d / %d", curRec+1, recCount);
    textSize(32);
    text(0, -100, records[curRec].text->text);
    textSize(24);
    textAlign(HALIGN_LEFT, VALIGN_TOP);
    line(-150, -65, 150, -65);
    double y = -40, dy = 40;
    int hover = -1;
    for(int i = 0; i < ANSWERS; ++i) {
      if (answers[i].error) {
        stroke(COLOR_RED);
      } else
      if (mode == 2) {
        if (answers[i].date == records[curRec].date) {
          circle(-130, y+i*dy+20, 10);
          stroke(COLOR_BLUE);
          fill(COLOR_BLUE);
          circle(-130, y+i*dy+20, 5);
          noFill();
        }
      } else {
        circle(-130, y+i*dy+20, 10);
        if (my > y+i*dy && my < y+(i+1)*dy) {
          hover = i;
          stroke(COLOR_BLUE);
          fill(COLOR_BLUE);
          circle(-130, y+i*dy+20, 5);
          noFill();
        }
      }
      text(-100, y + i*dy, answers[i].date->text);
      stroke(COLOR_BLACK);
    }

    if (mode == 1) {
      questionFade += windowGetFrameTime();
      if (questionFade > 1) questionFade = 1;
      if (hover >= 0 && mouseWentDown("left"))
        giveAnswer(hover);
    } else {
      questionFade -= windowGetFrameTime();
      if (questionFade < 0) {
        questionFade = 0;
        nextQuestion();
      }
    }
  } else
  if (mode == 3) {
    textSize(64);
    textAlign(HALIGN_CENTER, HALIGN_CENTER);
    textf(0, 0, "%d / %d", recCount - errorCount, recCount);
    questionFade += windowGetFrameTime();
    if (questionFade > 1) questionFade = 1;
  }
  restoreState();

  noStroke();
  fill(colorByRGBA(1, 1, 1, 1-questionFade));
  rect(-w/2, -h/2, w, h);

  restoreState();
}


int main() {
  windowSetSize(800, 600);
  windowSetResizable(TRUE);
  windowSetVariableFrameRate();
  windowSetInit(&init);
  windowSetDraw(&draw);
  windowRun();
  return 0;
}