Blob Blame Raw

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

#include <helianthus.h>


#include "data-h/sound/begin.ogg.h"
#include "data-h/sound/win.ogg.h"



#define BASE_TIME 1.0  // 1000.0 - to disable timeout
//#define EXTRA_TASKS

enum {
	ACTION_SUM = 0,
	ACTION_SUB = 1,
	ACTION_MUL = 2,
	ACTION_DIV = 3,
  ACTIONS_COUNT = 4,

	MASK_SUM = 1 << ACTION_SUM,
	MASK_SUB = 1 << ACTION_SUB,
	MASK_MUL = 1 << ACTION_MUL,
	MASK_DIV = 1 << ACTION_DIV,
	MASK_ALL = ~0,

	TASKS_COUNT = 20,

	TASK_MODE_NONE = 0,
	TASK_MODE_ERROR = 1,
	TASK_MODE_TIMEOUT = 2,
	TASK_MODE_DONE = 3,

  MODE_MENU = 0,
  MODE_BEGIN = 1,
	MODE_PLAYING = 2,
	MODE_FINISHED = 3,
};

const char *signs[] = {"+", "-", "·", ":"};

typedef struct {
	char question[1024];
	char answer[1024];
	char userAnswer[1024];
	double time;
	int mode;
	double x, y, vx, vy;
} Task;

typedef struct {
  char name[1024];
	double time;
	int actions;
	int minA, maxA;
	int minB, maxB;
	int minC, maxC;
	int advanced;
} Level;



Level levels[] = {
	{ "Счёт до 10", 5*BASE_TIME, MASK_SUM | MASK_SUB,  1,  9,  1,  9,  2,  10, 0 },
	{ "Число 10",   3*BASE_TIME, MASK_SUM | MASK_SUB,  1,  9,  1,  9, 10,  10, 0 },
	{ "Сложение и вычитание однозначных чисел",
                  7*BASE_TIME, MASK_SUM | MASK_SUB,  1,  9,  1,  9,  2,  18, 0 },
	{ "Сложение и вычитание двузначных чисел",
                 20*BASE_TIME, MASK_SUM | MASK_SUB, 10, 99, 10, 99, 20, 100, 0 },
  {}, {}, {}, {}, {}, {}, {}, {}, // Умножение на 2..9
  #ifdef EXTRA_TASKS
  {}, {}, {}, {}, {}, {}, {}, {}, // Деление на 2..9
  {}, {}, {}, {}, {}, {}, {}, {}, // Умножение и деление на 2..9
  #endif
	{ "Умножение однозначных чисел",
                  8*BASE_TIME, MASK_MUL,             2,  9,  2,  9,  0, 100, 0 },
	{ "Деление однозначных чисел",
                 12*BASE_TIME, MASK_DIV,             2,  9,  2,  9,  0, 100, 0 },
	{ "Умножение и деление однозначных чисел",
	               12*BASE_TIME, MASK_MUL | MASK_DIV,  2,  9,  2,  9,  0, 100, 0 },
	{ "Умножение и деление больших чисел",
	               24*BASE_TIME, 0,                    0,  0,  0,  0,  0,   0, 1 }
};

int levelsCount = sizeof(levels)/sizeof(*levels);
int currentLevel = 0;

Task tasks[TASKS_COUNT];
int currentTask = 0;
int errorsCount = 0;
int timeoutsCount = 0;
double taskStartTime = 0;

int mode = MODE_MENU;


double regularSize = 18;
double bigSize = 24;
double lineSpace = 7;

double ladderStep = 18 + 5;
double ladderCurrent = 0;
double ladderTarget = 0;
double ladderSpeed = 5;


Sound soundBegin;
Sound soundWin;


void init() {
  background(colorByName("#4c4c4c"));

  soundBegin = createSoundFromMemory(data_sound_begin_ogg, data_sound_begin_ogg_len);
  soundWin = createSoundFromMemory(data_sound_win_ogg, data_sound_win_ogg_len);

  for(int i = 2; i < 10; ++i) {
    Level *level = &levels[4 + i - 2];
    sprintf(level->name, "Умножение на %d",i);
    level->time = 5*BASE_TIME;
    level->actions = MASK_MUL;
    level->minA = level->maxA = i;
    level->minB = 2; level->maxB = 9;
    level->minC = 0; level->maxC = 100;

    #ifdef EXTRA_TASKS
    Level *levelD = &levels[4 + 8 + i - 2];
    memcpy(levelD, level, sizeof(Level));
    sprintf(levelD->name, "Деление на %d",i);
    levelD->time = 7*BASE_TIME;
    levelD->actions = MASK_DIV;

    Level *levelMD = &levels[4 + 8 + 8 + i - 2];
    memcpy(levelMD, level, sizeof(Level));
    sprintf(levelMD->name, "Умножение и деление на %d",i);
    levelMD->time = 7*BASE_TIME;
    levelMD->actions = MASK_MUL | MASK_DIV;
    #endif
  }
}


void generateAdvancedTask(Level *level, Task *task) {
  memset(task, 0, sizeof(*task));

  int a = randomNumber(2, 20);
  int b = randomNumber(2, 10);
	if (randomNumber(0, 100) < 20) a *= 10;
	if (randomNumber(0, 100) < 20) b *= 10;
	if (a < 10 && b < 10) {
		if (randomNumber(0, 1))
      a *= 10; else b *= 10;
  }
  if (randomNumber(0, 1))
    { int x = a; a = b; b = x; }
	int c = a * b;

  if (randomNumber(0, 100) < 25) {
    sprintf(task->question, "%d %s %d = ", c, signs[ACTION_DIV], a);
    sprintf(task->answer, "%d", b);
	} else {
    sprintf(task->question, "%d %s %d = ", a, signs[ACTION_MUL], b);
    sprintf(task->answer, "%d", c);
	}
  task->time = level->time;
}


void generateTask(Level *level, Task *task) {
  if (level->advanced)
    { generateAdvancedTask(level, task); return; }

  memset(task, 0, sizeof(*task));

	int action = 0;
	while(1) {
    action = randomNumber(0, ACTIONS_COUNT-1);
    if (level->actions & (1 << action)) break;
	}

	int a = 0, b = 0, c = 0;
	while(1) {
    a = randomNumber(level->minA, level->maxA);
    b = randomNumber(level->minB, level->maxB);
		if (action == ACTION_SUM || action == ACTION_SUB)
			c = a + b; else c = a * b;
		if (c >= level->minC && c <= level->maxC)
			break;
  }

	if (randomNumber(0, 1))
    { int x = a; a = b; b = x; }

	if (action == ACTION_SUM || action == ACTION_MUL) {
    sprintf(task->question, "%d %s %d = ", a, signs[action], b);
    sprintf(task->answer, "%d", c);
	} else {
    sprintf(task->question, "%d %s %d = ", c, signs[action], a);
    sprintf(task->answer, "%d", b);
	}
  task->time = level->time;
}


void nextTask() {
  int remains = 0;
  for(int i = 0; i < TASKS_COUNT; ++i)
    if (tasks[i].mode != TASK_MODE_DONE) ++remains;

  if (!remains) {
    ladderTarget = -100;
    mode = MODE_FINISHED;
    soundPlay(soundWin, FALSE);
    return;
  }

  ladderTarget = remains - 1;

  while(1) {
    currentTask = randomNumber(0, TASKS_COUNT-1);
    if (tasks[currentTask].mode != TASK_MODE_DONE) break;
  }

  Task *task = &tasks[currentTask];
  task->userAnswer[0] = 0;
  task->mode = TASK_MODE_NONE;
  task->x = 0;
  task->y = (remains - 1)*ladderStep;
  task->vx = 0;
  task->vy = 0;
  taskStartTime = windowGetSeconds();
}


void verifyTask() {
  Task *task = &tasks[currentTask];
  if (strcmp(task->answer, task->userAnswer) != 0) {
    task->mode = TASK_MODE_ERROR;
    ++errorsCount;
  } else
  if (windowGetSeconds() - taskStartTime > task->time) {
    task->mode = TASK_MODE_TIMEOUT;
    ++timeoutsCount;
  } else {
    task->mode = TASK_MODE_DONE;
  }
  nextTask();
}


void drawTask(Task *task) {
  saveState();
  double dt = windowGetFrameTime();

  noFill();
  stroke(colorByName("white"));
  textSize(regularSize);
  if (task->mode == TASK_MODE_NONE) {
    if (task != &tasks[currentTask]) noStroke();
  } else
  if (task->mode == TASK_MODE_ERROR) {
    task->vy += 100*dt;
    stroke(colorByRGBA(1, 1, 1, 0.25));
  } else
  if (task->mode == TASK_MODE_TIMEOUT) {
    task->vx -= 20*dt;
    stroke(colorByRGBA(1, 1, 1, 0.25));
  }
  task->x += task->vx;
  task->y += task->vy;

  textf(task->x, task->y, "%s%s", task->question, task->userAnswer);
  restoreState();
}


void drawLadder() {
	double d = 5*windowGetFrameTime();
	if (fabs(ladderCurrent - ladderTarget) < d) {
		ladderCurrent = ladderTarget;
	} else
	if (ladderCurrent < ladderTarget) {
		ladderCurrent += d;
	} else {
		ladderCurrent -= d;
  }

  saveState();

  stroke(colorByName("white"));
  strokeWidth(1);

	double w = 30;
	int i0 = -100;
	int i1 = 300;
	for(int i = i0; i <= i1; ++i)
    line(-w, i*ladderStep, w, i*ladderStep);
  line(-w, i0*ladderStep, -w, i1*ladderStep);
  line( w, i0*ladderStep,  w, i1*ladderStep);

  double k = ladderStep;
	double iy = floor(ladderCurrent/2)*2;
	double py = ladderCurrent - iy;
	double ly = iy + (py < 1 ? py : 1)*2;
	double ry = iy + (py < 1 ? 0 : py-1)*2 + 1;
	double yy = (ly + ry)/2;

	strokeWidth(0.6*k);
	point(0, yy*k);
	strokeWidth(1);
	line(0, (yy+0.3)*k,  0, (yy+2)*k);
	line(0, (yy+0.7)*k, -k,     ly*k);
	line(0, (yy+0.7)*k,  k,     ry*k);
	line(0, (yy+2.0)*k, -k, (ly+3)*k);
	line(0, (yy+2.0)*k,  k, (ry+3)*k);
	restoreState();
}


void startLevel() {
  currentTask = 0;
  errorsCount = 0;
  timeoutsCount = 0;
  for(int i = 0; i < TASKS_COUNT; ++i)
    generateTask(&levels[currentLevel], &tasks[i]);
  ladderTarget = ladderCurrent = TASKS_COUNT - 1;
  mode = MODE_BEGIN;
  soundPlay(soundBegin, FALSE);
}


void drawLevel(int width, int height) {
  double t = windowGetSeconds();

  Level *level = &levels[currentLevel];

  saveState();
  noFill();
  stroke(colorByName("white"));

  textSize(regularSize);
  textf(0, 0, "Уровень %d", (currentLevel + 1));

  textSize(bigSize);
  text(0, regularSize + lineSpace, level->name);

  textSize(regularSize);
  textf(0, 80, "Ошибки: %d", errorsCount);
  textf(0, 80 + regularSize + lineSpace, "Промедления: %d", timeoutsCount);

  if (mode == MODE_BEGIN || mode == MODE_FINISHED) {
    textSize(bigSize);
    double alpha = (1 + cos(10*t))/4 + 0.5;
    stroke(colorByRGBA(1, 1, 1, alpha));
    textAlign(HALIGN_LEFT, VALIGN_BOTTOM);
    text(0, height, "Нажмите Enter...");
    textAlign(HALIGN_LEFT, VALIGN_TOP);
  }

  if (mode != MODE_BEGIN) {
    saveState();
    translate(width - 300, height - TASKS_COUNT*ladderStep);
    for(int i = 0; i < TASKS_COUNT; ++i)
      drawTask(&tasks[i]);
    restoreState();
  }

  saveState();
  translate(width - 30, height - TASKS_COUNT*ladderStep);
  drawLadder();
  restoreState();

  restoreState();

  if (keyWentDown("escape")) mode = MODE_MENU;

  if (mode == MODE_BEGIN) {
    if (keyWentDown("any return")) { mode = MODE_PLAYING; nextTask(); }
  } else
  if (mode == MODE_PLAYING) {
    Task *task = &tasks[currentTask];
    char *ua = task->userAnswer;
    int i = strlen(ua);
    if (i > 0 && keyWentDown("backspace")) ua[--i] = 0;
    if (i == 1 && *ua == '0') i = 0;
    int ii = i;
    if (i < 10 && keyWentDown("any 0")) ua[i++] = '0';
    if (i < 10 && keyWentDown("any 1")) ua[i++] = '1';
    if (i < 10 && keyWentDown("any 2")) ua[i++] = '2';
    if (i < 10 && keyWentDown("any 3")) ua[i++] = '3';
    if (i < 10 && keyWentDown("any 4")) ua[i++] = '4';
    if (i < 10 && keyWentDown("any 5")) ua[i++] = '5';
    if (i < 10 && keyWentDown("any 6")) ua[i++] = '6';
    if (i < 10 && keyWentDown("any 7")) ua[i++] = '7';
    if (i < 10 && keyWentDown("any 8")) ua[i++] = '8';
    if (i < 10 && keyWentDown("any 9")) ua[i++] = '9';
    if (i > ii) ua[i] = 0;
    if (keyWentDown("any return")) verifyTask();
  } else
  if (mode == MODE_FINISHED) {
    if (keyWentDown("any return")) {
      ++currentLevel;
      if (currentLevel >= levelsCount) {
        currentLevel = levelsCount - 1;
        mode = MODE_MENU;
      } else {
        startLevel();
      }
    }
  }
}


void drawMenu(int width, int height) {
  saveState();
  noFill();
  stroke(colorByName("white"));
  double scrollY = currentLevel*(regularSize+lineSpace) + bigSize + lineSpace - height;
  if (scrollY < 0) scrollY = 0;
  double y = -scrollY;
  for(int i = 0; i < levelsCount; ++i) {
    double size = i == currentLevel ? bigSize : regularSize;
    textSize(size);
    text(0, y, levels[i].name);
    y += size + lineSpace;
  }
  restoreState();

  if (keyWentDown("escape")) windowStop();

  if (keyWentDown("up"  )) --currentLevel;
  if (keyWentDown("down")) ++currentLevel;
  if (keyWentDown("home")) currentLevel = 0;
  if (keyWentDown("end" )) currentLevel = levelsCount - 1;
  if (currentLevel >= levelsCount) currentLevel = levelsCount - 1;
  if (currentLevel < 0) currentLevel = 0;

  if (keyWentDown("any return")) startLevel();
}


void draw() {
  saveState();
  translate(70, 50);
  int width  = windowGetWidth() - 70 - 70;
  int height = windowGetHeight() - 50 - 80;
  if (mode == MODE_MENU)
    drawMenu(width, height);
  else
    drawLevel(width, height);
  restoreState();
}


int main() {
  windowSetTitle("Arithm");
	windowSetVariableFrameRate();
	windowSetSize(1000, 700);
	windowSetInit(&init);
	windowSetDraw(&draw);
	windowRun();
	return 0;
}