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