#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;
}