...
 
Commits (2)
......@@ -7,7 +7,7 @@ Problem sets are lists of problems which may be used in a quiz. Each set has a l
The center of the generator is a problem bank. Each problem in the bank consists of a Lua script and a JSON file. The JSON file defines the problem and the Lua script generates it. Within the JSON file is an "options" object which is passed into the Lua script to change it's operation. These settings may be overridden by problem sets.
This is still in early development... see TODO.md to see what needs to be done.
This is still in early development.
Developers:
* Aeden McClain (aeden@platypro.net)
QuizGrind TODO
==============
General
* [X] Command-line interface for backend
* [X] Static exercises
Quiz export types
* [X] Network quiz
* [X] raw JSON export
* [X] PDF export
Docs
* [ ] Building and deploying
* [ ] RESTful API
* [ ] Command-line usage
* [ ] Creating problems
* [ ] Web front-end
* [ ] Widget, Lua API, and Exercise reference
Future
* [ ] Exam export (Many of one quiz, with QR codes)
* [ ] Party quiz
......@@ -3,10 +3,16 @@
"calculator" : "banned",
"randMax" : 400,
"preamble" : "Evaluate.",
"options" : {
"variables" : {
"val_1": "-option:max_1:min_1",
"val_2": "-option:max_2:min_2",
},
"options" : {
"operation" : "add",
"max_1" : 20,
"min_1" : 1,
"max_2" : 20,
"min_2" : 1,
"format" : "natural"
},
}
local a = math.random(options["max_1"])
local b = math.random(options["max_2"])
local a = variables["val_1"] + options["min_1"]
local b = variables["val_2"] + options["min_2"]
local ans
function doWidget(ans, name, av, bv)
......
{
"name" : "Periodic Table",
"calculator" : "banned",
"randMax" : 118,
"options" : {
"missing" : "number",
},
"variables" : {
"elementID": "#static:.",
},
"static" : [
{"n":"Hydrogen","s":"H","a":1,"w":1.008},{"n":"Helium","s":"He","a":2,"w":4.0026},{"n":"Lithium","s":"Li","a":3,"w":6.94},{"n":"Beryllium","s":"Be","a":4,"w":9.01218},{"n":"Boron","s":"B","a":5,"w":10.81},{"n":"Carbon","s":"C","a":6,"w":12.011},{"n":"Nitrogen","s":"N","a":7,"w":14.007},{"n":"Oxygen","s":"O","a":8,"w":15.999},{"n":"Fluorine","s":"F","a":9,"w":18.9984},{"n":"Neon","s":"Ne","a":10,"w":20.1798},{"n":"Sodium","s":"Na","a":11,"w":22.9898},{"n":"Magnesium","s":"Mg","a":12,"w":24.305},{"n":"Aluminium","s":"Al","a":13,"w":26.9815},{"n":"Silicon","s":"Si","a":14,"w":28.085},{"n":"Phosphorus","s":"P","a":15,"w":30.9738},{"n":"Sulfur","s":"S","a":16,"w":32.06},{"n":"Chlorine","s":"Cl","a":17,"w":35.45},{"n":"Argon","s":"Ar","a":18,"w":39.9481},{"n":"Potassium","s":"K","a":19,"w":39.0983},{"n":"Calcium","s":"Ca","a":20,"w":40.0784},{"n":"Scandium","s":"Sc","a":21,"w":44.9559},{"n":"Titanium","s":"Ti","a":22,"w":47.8671},{"n":"Vanadium","s":"V","a":23,"w":50.9415},{"n":"Chromium","s":"Cr","a":24,"w":51.9962},{"n":"Manganese","s":"Mn","a":25,"w":54.938},{"n":"Iron","s":"Fe","a":26,"w":55.8452},{"n":"Cobalt","s":"Co","a":27,"w":58.9332},{"n":"Nickel","s":"Ni","a":28,"w":58.6934},{"n":"Copper","s":"Cu","a":29,"w":63.5463},{"n":"Zinc","s":"Zn","a":30,"w":65.382},{"n":"Gallium","s":"Ga","a":31,"w":69.7231},{"n":"Germanium","s":"Ge","a":32,"w":72.6308},{"n":"Arsenic","s":"As","a":33,"w":74.9216},{"n":"Selenium","s":"Se","a":34,"w":78.9718},{"n":"Bromine","s":"Br","a":35,"w":79.904},{"n":"Krypton","s":"Kr","a":36,"w":83.7982},{"n":"Rubidium","s":"Rb","a":37,"w":85.4678},{"n":"Strontium","s":"Sr","a":38,"w":87.621},{"n":"Yttrium","s":"Y","a":39,"w":88.9058},{"n":"Zirconium","s":"Zr","a":40,"w":91.2242},{"n":"Niobium","s":"Nb","a":41,"w":92.9064},{"n":"Molybdenum","s":"Mo","a":42,"w":95.951},{"n":"Technetium","s":"Tc","a":43,"w":98},{"n":"Ruthenium","s":"Ru","a":44,"w":101.072},{"n":"Rhodium","s":"Rh","a":45,"w":102.906},{"n":"Palladium","s":"Pd","a":46,"w":106.421},{"n":"Silver","s":"Ag","a":47,"w":107.868},{"n":"Cadmium","s":"Cd","a":48,"w":112.414},{"n":"Indium","s":"In","a":49,"w":114.818},{"n":"Tin","s":"Sn","a":50,"w":118.711},{"n":"Antimony","s":"Sb","a":51,"w":121.76},{"n":"Tellurium","s":"Te","a":52,"w":127.603},{"n":"Iodine","s":"I","a":53,"w":126.904},{"n":"Xenon","s":"Xe","a":54,"w":131.294},{"n":"Cesium","s":"Cs","a":55,"w":132.905},{"n":"Barium","s":"Ba","a":56,"w":137.328},{"n":"Lanthanum","s":"La","a":57,"w":138.905},{"n":"Cerium","s":"Ce","a":58,"w":140.116},{"n":"Praseodymium","s":"Pr","a":59,"w":140.908},{"n":"Neodymium","s":"Nd","a":60,"w":144.242},{"n":"Promethium","s":"Pm","a":61,"w":145},{"n":"Samarium","s":"Sm","a":62,"w":150.362},{"n":"Europium","s":"Eu","a":63,"w":151.964},{"n":"Gadolinium","s":"Gd","a":64,"w":157.253},{"n":"Terbium","s":"Tb","a":65,"w":158.925},{"n":"Dysprosium","s":"Dy","a":66,"w":162.5},{"n":"Holmium","s":"Ho","a":67,"w":164.93},{"n":"Erbium","s":"Er","a":68,"w":167.259},{"n":"Thulium","s":"Tm","a":69,"w":168.934},{"n":"Ytterbium","s":"Yb","a":70,"w":173.045},{"n":"Lutetium","s":"Lu","a":71,"w":174.967},{"n":"Hafnium","s":"Hf","a":72,"w":178.492},{"n":"Tantalum","s":"Ta","a":73,"w":180.948},{"n":"Tungsten","s":"W","a":74,"w":183.841},{"n":"Rhenium","s":"Re","a":75,"w":186.207},{"n":"Osmium","s":"Os","a":76,"w":190.233},{"n":"Iridium","s":"Ir","a":77,"w":192.217},{"n":"Platinum","s":"Pt","a":78,"w":195.085},{"n":"Gold","s":"Au","a":79,"w":196.967},{"n":"Mercury","s":"Hg","a":80,"w":200.592},{"n":"Thallium","s":"Tl","a":81,"w":204.38},{"n":"Lead","s":"Pb","a":82,"w":207.21},{"n":"Bismuth","s":"Bi","a":83,"w":208.98},{"n":"Polonium","s":"Po","a":84,"w":209},{"n":"Astatine","s":"At","a":85,"w":210},{"n":"Radon","s":"Rn","a":86,"w":222},{"n":"Francium","s":"Fr","a":87,"w":223},{"n":"Radium","s":"Ra","a":88,"w":226},{"n":"Actinium","s":"Ac","a":89,"w":227},{"n":"Thorium","s":"Th","a":90,"w":232.038},{"n":"Protactinium","s":"Pa","a":91,"w":231.036},{"n":"Uranium","s":"U","a":92,"w":238.029},{"n":"Neptunium","s":"Np","a":93,"w":237},{"n":"Plutonium","s":"Pu","a":94,"w":244},{"n":"Americium","s":"Am","a":95,"w":243},{"n":"Curium","s":"Cm","a":96,"w":247},{"n":"Berkelium","s":"Bk","a":97,"w":247},{"n":"Californium","s":"Cf","a":98,"w":251},{"n":"Einsteinium","s":"Es","a":99,"w":252},{"n":"Fermium","s":"Fm","a":100,"w":257},{"n":"Mendelevium","s":"Md","a":101,"w":258},{"n":"Nobelium","s":"No","a":102,"w":259},{"n":"Lawrencium","s":"Lr","a":103,"w":266},{"n":"Rutherfordium","s":"Rf","a":104,"w":267},{"n":"Dubnium","s":"Db","a":105,"w":268},{"n":"Seaborgium","s":"Sg","a":106,"w":269},{"n":"Bohrium","s":"Bh","a":107,"w":270},{"n":"Hassium","s":"Hs","a":108,"w":269},{"n":"Meitnerium","s":"Mt","a":109,"w":278},{"n":"Darmstadtium","s":"Ds","a":110,"w":281},{"n":"Roentgenium","s":"Rg","a":111,"w":282},{"n":"Copernicium","s":"Cn","a":112,"w":285},{"n":"Nihonium","s":"Nh","a":113,"w":286},{"n":"Flerovium","s":"Fl","a":114,"w":289},{"n":"Moscovium","s":"Mc","a":115,"w":289},{"n":"Livermorium","s":"Lv","a":116,"w":293},{"n":"Tennessine","s":"Ts","a":117,"w":294},{"n":"Oganesson","s":"Og","a":118,"w":294},{"n":"Ununennium","s":"Uue","a":119,"w":315}
]
......
thisElement = getStatic(string.format("[%d]", questionID))
thisElement = getStatic(string.format("[%d]", variables.elementID))
if options["missing"] == "number" then
qtext = "What is the atomic number of " .. thisElement.n .. "?"
......
......@@ -70,7 +70,7 @@
"key":"0",
"options": {
"numSelections": "1",
"choices":["$svg:$template<1>", "$svg:$template<2>", "$svg:$template<3>"]
"choices":["$svg:$template:1", "$svg:$template:2", "$svg:$template:3"]
}
}
]
......
<svg
height="100"
width="100">
height="20"
width="20">
<rect x="0" y="0"
height="100"
width="100"
height="20"
width="20"
fill="{{color}}"
stroke="black"
stroke-width="2"/>
......
{
"name": "ptable",
"description": "Generates a question based on the periodic table. This was built to demonstrate static data storage and templates.",
"options": [
{
"name": "missing",
"description": "The missing value from the periodic table.",
"default": "name",
"type": "string",
"enum": ["name", "number"]
}],
"templates": ["default.svg"]
}
{
"name" : "Variable Test",
"calculator" : "banned",
"options" : { },
"variables" : {
"test1": 2,
"test2": 3,
"test3": 4,
},
"static" : [ ]
}
createWidget('textView', 0, {text =
'' .. variables["test1"] .. ' ' .. variables["test2"] .. ' ' .. variables["test3"]})
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
height="100"
width="100">
<rect x="0" y="0"
height="100"
width="100"
id="border"
fill="#fff48d"
stroke="black"
stroke-width="3" />
<text x="2" y="14"
text-anchor= "start"
font-size= "12"
line-height= "12">
{{a}}
</text>
<text x="98" y="14"
text-anchor= "end"
font-size= "12"
line-height= "12">
{{w}}
</text>
<text x="50" y="60"
text-anchor= "middle"
font-size= "36"
line-height= "36">
{{s}}
</text>
<text x="50" y="90"
text-anchor= "middle"
font-size= "12"
line-height= "12">
{{n}}
</text>
</svg>
{
"name" : "Test set",
"questions": [
{
"id":"net.platypro.ptable",
"numQuestions":5
},
{"id":"net.platypro.test.static"},
{
"id":"net.platypro.test.variables",
"numQuestions":100
},
{
"id":"net.platypro.arith",
"options" : {
"operation" : "add",
"max_1" : 100,
"min_1" : 98,
"max_2" : 100,
"min_2" : 80,
},
"numQuestions": 5,
},
]
}
{
"name" : "Test set",
"questions": [{"id":"net.platypro.test.static"}]
}
......@@ -23,4 +23,8 @@ clean:
rm ${INSTALLDIR}/index.js
rm ${INSTALLDIR}/index.php
rm ${INSTALLDIR}/style.css
rm ${BINDIR}/bundle.js
\ No newline at end of file
rm ${BINDIR}/bundle.js
dev:
${WEBPACK} --output-path=${BINDIR} -d
......@@ -5,7 +5,8 @@
"main": "index.js",
"scripts": {
"build": "make build",
"install": "make all"
"install": "make all",
"dev": "make dev",
},
"repository": "https://gitlab.com/platypro/quizgrind",
"author": "Aeden McClain",
......
......@@ -12,6 +12,7 @@ set(SRCS
backend/exercise.c
backend/script.c
backend/specialproperty.c
# Widget sources
widget/widget.c
......
This diff is collapsed.
......@@ -14,9 +14,11 @@
#include "unirand.h"
#include "backend/script.h"
#include "backend/specialproperty.h"
struct PG_State;
struct Widget;
struct SpecialProperty;
#define EXRAND_DEFAULT 1
......@@ -33,27 +35,38 @@ typedef enum CalcType
#define CALCTYPE_SCIENTIFIC_ "Scientific"
#define CALCTYPE_GRAPHING_ "Graphing"
#define STATTYPE_NORMAL_ "normal"
#define STATTYPE_PURE_ "pure"
#define STATTYPE_STATIC_ "static"
typedef struct ExerciseVariable
{
char* name;
uint32_t value;
} EXERCISEVARIABLE;
typedef struct _ExerciseVariable
{
struct _ExerciseVariable* next;
char* name;
char* src;
struct SpecialProperty value;
} _EXERCISEVARIABLE;
typedef struct Question
{
struct Exercise* eref;
struct ExerciseRef* eref;
struct Widget* widgets;
struct Widget* widget_last;
WJElement hints;
struct ExerTemplate* templates;
bool isStatic;
} QUESTION;
typedef struct Exercise
{
struct Exercise* next;
struct Question staticQuestion;
struct Widget* baseWidgets;
struct Widget* baseWidgets_last;
WJElement baseHints;
struct ExerTemplate* baseTemplates;
WJElement staticStore;
WJElement baseOptions;
......@@ -63,8 +76,11 @@ typedef struct Exercise
char* preamble;
char* id;
_EXERCISEVARIABLE* variables;
uint32_t numVariables;
struct unirand_t rand;
bool hasScript;
} EXERCISE;
......@@ -74,6 +90,9 @@ typedef struct ExerciseRef
WJElement options;
uint32_t numInstances;
struct unirand_t rand;
EXERCISEVARIABLE* instancedVariables;
} EXERCISEREF;
typedef struct ProblemSet
......@@ -86,6 +105,8 @@ typedef struct ProblemSet
} PROBLEMSET;
#define DEFAULT_PSET_NAME ""
typedef struct ExerTemplate
{
struct ExerTemplate* next;
......@@ -139,46 +160,30 @@ typedef struct Quiz
} QUIZ;
typedef enum ProcessedString_Type
{
PSTYPE_INVALID,
PSTYPE_TEMPLATE,
PSTYPE_STRING,
} PROCESSEDSTRING_TYPE;
// Problem set functions
typedef enum ProcessedString_Class
{
PSCLASS_NORMAL,
PSCLASS_SVG
} PROCESSEDSTRING_CLASS;
extern bool pset_loadAll(struct PG_State* state);
extern char* pset_loadOne(struct PG_State* state, char* set);
// Exercise functions
typedef struct ProcessedString
{
struct ProcessedString* child;
PROCESSEDSTRING_TYPE type;
PROCESSEDSTRING_CLASS class;
union {
PMUS_BUILDER* template;
char* string;
};
} PROCESSEDSTRING;
extern bool exercise_loadAll(struct PG_State* state);
extern char* exercise_loadOne(struct PG_State* state, char* set);
extern PROBLEMSET* exercise_getSet(struct PG_State* state, char* name);
extern GENMODE exercise_toGenMode(char* name);
extern EXERTEMPLATE* exercise_mkTemplate(struct PG_State* state, QUESTION* q, char* name);
PROCESSEDSTRING exercise_processString(QUESTION* q, char* src);
extern uint32_t exercise_writeJSONcheck(QUESTION* question, WJElement solutions, WJWriter out);
extern bool exercise_writeJSONWidgets(struct PG_State* state, QUESTION* question, WJWriter w);
extern bool exercise_writeJSONSolutions(struct PG_State* state, QUESTION* question, WJWriter w);
extern EXERTEMPLATE* exercise_mkTemplate(struct PG_State* state, struct ExerTemplate** templates, char* exId, char* name);
extern EXERCISEVARIABLE* exercise_getVariable(QUESTION* q, char* id);
extern void exercise_cleanup(struct PG_State* state);
extern QUIZ* exercise_mkQuiz(struct PG_State* state, PROBLEMSET* pset, GENMODE type, uint8_t numQuestions);
extern bool exercise_destroyQuiz(QUIZ* q);
// Quiz Generation Functions
extern QUIZ* quiz_create(struct PG_State* state, PROBLEMSET* pset, GENMODE type, uint8_t numQuestions);
extern bool quiz_destroy(QUIZ* q);
extern QUESTION* question_create(struct PG_State* state, QUIZ* q);
extern bool question_destroy(QUESTION* question);
extern QUESTION* exercise_mkQuestion(struct PG_State* state, QUIZ* q);
extern bool exercise_destroyQuestion(QUESTION* question);
extern bool question_writeWidgets_json(struct PG_State* state, QUESTION* question, WJWriter w);
extern bool question_writeSolutions_json(struct PG_State* state, QUESTION* question, WJWriter w);
extern uint32_t question_checkSolutions_json(QUESTION* question, WJElement solutions, WJWriter out);
#endif /*INCLUDE_EXERCISE_H*/
......
......@@ -13,6 +13,7 @@
#include <libgen.h>
#include <dirent.h>
#include <stdlib.h>
#include <math.h>
#include <widget/widget.h>
#include <pmustache/provider.wje.h>
......@@ -206,13 +207,13 @@ int mkTemplate(lua_State* L)
QUESTION* q = getActiveQuestion(L);
PG_STATE* state = getState(L);
EXERTEMPLATE* prop = exercise_mkTemplate(state, q, (char*) lua_tostring(L, 1));
EXERTEMPLATE* prop = exercise_mkTemplate(state, &q->templates, q->eref->exercise->id, (char*) lua_tostring(L, 1));
prop->builder.baseContext = luaTableToElement(L, NULL, 2, NULL);
prop->builder.escape = NULL;
prop->builder.provider = &WJEMustacheProvider;
char buffer[23];
snprintf(buffer, 23, "$template<%ul>", prop->id);
snprintf(buffer, 23, "$template:%ul", prop->id);
lua_pushstring(L, buffer);
return 1;
......@@ -318,7 +319,7 @@ bool script_unload(PG_STATE* scr, char* id)
return result;
}
bool script_genQuiz(PG_STATE* scr, QUESTION* question, EXERCISEREF* exercise, uint32_t questionID)
bool script_genQuiz(PG_STATE* scr, QUESTION* question, EXERCISEREF* exref, uint32_t questionID)
{
bool result = true;
lua_State* L = scr->exercise.script.L;
......@@ -326,19 +327,37 @@ bool script_genQuiz(PG_STATE* scr, QUESTION* question, EXERCISEREF* exercise, ui
lua_pushlightuserdata(L, question);
lua_setfield(L, LUA_REGISTRYINDEX, LUA_KEY_QUESTION);
lua_pushlightuserdata(L, exercise);
lua_pushlightuserdata(L, exref);
lua_setfield(L, LUA_REGISTRYINDEX, LUA_KEY_EXERCISE);
lua_pushlightuserdata(L, scr);
lua_setfield(L, LUA_REGISTRYINDEX, LUA_KEY_STATE);
//Push random question number
lua_pushnumber(L, questionID);
lua_setglobal(L, GLOB_QUESTION_ID);
//Push question variables
if(exref->exercise->numVariables)
{
uint32_t varat = 0;
uint32_t multiplier = 1;
lua_createtable(L, exref->exercise->numVariables, 0);
while(varat < exref->exercise->numVariables)
{
uint32_t maxval = exref->instancedVariables[varat].value;
uint32_t val = ceil((questionID % (multiplier * maxval)) / multiplier);
if(!val) val = maxval;
multiplier *= maxval;
lua_pushstring(L, exref->instancedVariables[varat].name);
lua_pushnumber(L, val);
lua_settable(L, -3);
varat ++;
}
lua_setglobal(L, KEYLIST(KEY_VARIABLE));
}
//Load function table and copy out the function
lua_getfield(L, LUA_REGISTRYINDEX, LUA_KEY_SCRIPTS);
lua_getfield(L, -1, exercise->exercise->id);
lua_getfield(L, -1, exref->exercise->id);
//Set up scope and map existing globals to it
lua_newtable(L);
......@@ -347,7 +366,7 @@ bool script_genQuiz(PG_STATE* scr, QUESTION* question, EXERCISEREF* exercise, ui
lua_setfield(L, -2, "__index");
lua_setmetatable(L, -2);
lua_pushJSON(L, exercise->options);
lua_pushJSON(L, exref->options);
lua_setfield(L, -2, KEYLIST(KEY_OPTION));
lua_setupvalue(L, -2, 1);
......@@ -357,7 +376,7 @@ bool script_genQuiz(PG_STATE* scr, QUESTION* question, EXERCISEREF* exercise, ui
if(lua_pcall(L, 0, LUA_MULTRET,0) != LUA_OK)
{
printf("%s\n", lua_tostring(L, -1));
printf("Lua Error in exercise %s: %s\n", exref->exercise->id, lua_tostring(L, -1));
result = false;
}
//Pop function table
......
......@@ -25,8 +25,6 @@
#define FUN_GETSTATIC_COUNT "getStaticCount"
#define FUN_MAKETEMPLATE "mkTemplate"
#define GLOB_QUESTION_ID "questionID"
struct ExerciseRef;
struct PG_State;
struct Question;
......
This diff is collapsed.
/* Aeden McClain (c) 2019
* web: https://www.platypro.net
* email: dev@platypro.net
* License info at bottom.
*
* This file is a part of QuizGrind.
*/
#ifndef INC_SPECIALPROPERTY_H
#define INC_SPECIALPROPERTY_H
#include <pmustache/template.h>
#include <wjelement.h>
#include <keys.h>
struct Question;
struct ExerciseRef;
typedef enum ProcessedString_Type
{
PSTYPE_TEMPLATE,
PSTYPE_VARIABLE,
PSTYPE_OPTION,
PSTYPE_OPTION_NUM,
PSTYPE_OPTION_DIFF,
PSTYPE_STATIC,
PSTYPE_STATIC_NUM,
PSTYPE_SVG_TEMPLATE,
PSTYPE_SVG_LITERAL,
PSTYPE_SOFT_TOP, // End of PSTYPE constants with prefixes
PSTYPE_INVALID,
PSTYPE_STRING,
PSTYPE_INTEGER,
} PROCESSEDSTRING_TYPE;
#define PSTYPE_TEMPLATE_ "$" KEY_TEMPLATE ":"
#define PSTYPE_VARIABLE_ "$" KEY_VARIABLE ":"
#define PSTYPE_OPTION_ "$" KEY_OPTION ":"
#define PSTYPE_OPTION_NUM_ "#" KEY_OPTION ":"
#define PSTYPE_OPTION_DIFF_ "-" KEY_OPTION ":"
#define PSTYPE_STATIC_ "$" KEY_STATIC ":"
#define PSTYPE_STATIC_NUM_ "#" KEY_STATIC ":"
#define PSTYPE_SVG_TEMPLATE_ "$" KEY_SVG ":$" KEY_TEMPLATE ":"
#define PSTYPE_SVG_LITERAL_ "$" KEY_SVG ":"
#define SPECIALPROPERTY_IS_SVG(ps) ((ps).type == PSTYPE_SVG_TEMPLATE || (ps).type == PSTYPE_SVG_LITERAL)
typedef union SpecialProperty_Unit
{
char* string;
uint32_t intValue;
} SPECIALPROPERTY_UNIT;
typedef struct SpecialProperty
{
PROCESSEDSTRING_TYPE type;
char* base;
SPECIALPROPERTY_UNIT unit1;
SPECIALPROPERTY_UNIT unit2;
} SPECIALPROPERTY;
typedef union SpecialProperty_Scope_Unit
{
struct ExerciseRef* eref;
struct Question* question;
} SPECIALPROPERTY_SCOPE_UNIT;
typedef enum SpecialProperty_Scope_Type
{
SPECIALPROPERTY_SCOPE_TYPE_EREF,
SPECIALPROPERTY_SCOPE_TYPE_QUESTION
} SPECIALPROPERTY_SCOPE_TYPE;
typedef struct SpecialProperty_Scope
{
SPECIALPROPERTY_SCOPE_TYPE type;
SPECIALPROPERTY_SCOPE_UNIT unit;
} SPECIALPROPERTY_SCOPE;
#define SP_SCOPE_QUESTION(q) &(SPECIALPROPERTY_SCOPE) \
{.type=SPECIALPROPERTY_SCOPE_TYPE_QUESTION, \
.unit.question=(q)}
#define SP_SCOPE_EREF(q) &(SPECIALPROPERTY_SCOPE) \
{.type=SPECIALPROPERTY_SCOPE_TYPE_EREF, \
.unit.eref=(q)}
// String processing
extern bool sp_parse(SPECIALPROPERTY* property, char* src);
extern bool sp_evaluate_number(SPECIALPROPERTY* src,
SPECIALPROPERTY_SCOPE* scope,
uint32_t* number);
// Note, you must call sp_devaluate after this
extern bool sp_evaluate_string(SPECIALPROPERTY* src,
SPECIALPROPERTY_SCOPE* scope,
char** string);
extern bool sp_evaluate_wjw(SPECIALPROPERTY* src,
SPECIALPROPERTY_SCOPE* scope,
WJWriter writer,
char* name);
extern bool sp_devaluate(SPECIALPROPERTY* src,
char* string);
#ifdef _QUIZGRIND_BUILD_PDF
#include <librsvg/rsvg.h>
bool sp_evaluate_rsvg(SPECIALPROPERTY* src,
SPECIALPROPERTY_SCOPE* scope,
RsvgHandle** rsvg);
#endif
#endif /* INC_SPECIALPROPERTY_H */
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
......@@ -57,7 +57,7 @@ int daemon_run(void* data, int argc, char** argv)
printf("Daemon mode enabled\n");
struct termios term;
char c;
exercise_loadAll(state);
pset_loadAll(state);
pcg32_srandom(time(NULL), (intptr_t)&printf);
char link[256];
......@@ -88,7 +88,7 @@ int daemon_run(void* data, int argc, char** argv)
exercise_cleanup(state);
script_cleanup(state);
script_init(state);
exercise_loadAll(state);
pset_loadAll(state);
printf("Reloaded!\n");
break;
default: continue;
......
......@@ -97,9 +97,9 @@ void writePset(PG_STATE* state, WJWriter response)
bool user_nextQuestion(PG_STATE* state, USER* u, WJWriter w)
{
//Generate question
if(u->questionAt) exercise_destroyQuestion(u->questionAt);
if(u->questionAt) question_destroy(u->questionAt);
if(!(u->questionAt = exercise_mkQuestion(state, u->quiz)))
if(!(u->questionAt = question_create(state, u->quiz)))
{
writeError(w, "Could not Generate question!");
return false;
......@@ -110,9 +110,9 @@ bool user_nextQuestion(PG_STATE* state, USER* u, WJWriter w)
u->hintAt = u->questionAt->hints->child;
WJWUInt32(KEYCOUNT(KEY_HINT), u->questionAt->hints->count, w);
} else WJWUInt32(KEYCOUNT(KEY_HINT), 0, w);
WJWString(KEY_CALCULATOR, u->questionAt->eref->calculator, true, w);
WJWString(KEY_NAME, u->questionAt->eref->name, true, w);
exercise_writeJSONWidgets(state, u->questionAt, w);
WJWString(KEY_CALCULATOR, u->questionAt->eref->exercise->calculator, true, w);
WJWString(KEY_NAME, u->questionAt->eref->exercise->name, true, w);
question_writeWidgets_json(state, u->questionAt, w);
return true;
}
......@@ -170,11 +170,11 @@ bool session_common_process(PG_STATE* state, WJElement request, WJWriter respons
PROBLEMSET* _pset = exercise_getSet(state, pset);
SCP_ASSERT(_pset, "Invalid problem set!");
char* genmode = WJEString(request, C_KEY(KEY_GENMODE), WJE_GET, NULL);
exercise_destroyQuiz(u->quiz);
u->quiz = exercise_mkQuiz(state, _pset, exercise_toGenMode(genmode), 5);
GENMODE genmode = exercise_toGenMode(WJEString(request, C_KEY(KEY_GENMODE), WJE_GET, NULL));
quiz_destroy(u->quiz);
u->quiz = quiz_create(state, _pset, genmode, 5);
WJWUInt32(KEYCOUNT(KEY_QUESTION), u->quiz->ordered_rand.top, response);
WJWUInt32(KEYCOUNT(KEY_QUESTION), genmode == QTYPE_NORMAL ? 5 : u->quiz->ordered_rand.top, response);
if(giveFirst && user_nextQuestion(state, u, response))
WJWString(KEY_ACTION, ACTION_QUIZ_NEW, true, response);
}
......@@ -200,7 +200,7 @@ bool session_common_process(PG_STATE* state, WJElement request, WJWriter respons
u->hintAt = u->hintAt->next;
}
else
exercise_writeJSONSolutions(state, u->questionAt, response);
question_writeSolutions_json(state, u->questionAt, response);
}
}
else if(!strcmp(action, ACTION_QUESTION_CHECK))
......@@ -212,23 +212,10 @@ bool session_common_process(PG_STATE* state, WJElement request, WJWriter respons
SCP_ASSERT(usolution, "No answer given!");
WJWString(KEY_ACTION, ACTION_QUESTION_CHECK, true, response);
if(!exercise_writeJSONcheck(u->questionAt, usolution, response))
if(!question_checkSolutions_json(u->questionAt, usolution, response))
WJWString(KEY_RESULT, "correct", true, response);
else
WJWString(KEY_RESULT, "incorrect", true, response);
// if(!exercise_checkAnswer(u->questionAt, &usolution))
// else
// {
// WJWOpenArray(KEY_ERROR, response);
// usolution = usolution->child;
// while(usolution)
// {
// WJWUInt32(NULL, WJEUInt32(usolution, KEY_ID, WJE_GET, 0), response);
// usolution = usolution->next;
// }
// WJWCloseArray(response);
// }
}
}
else if(!strcmp(action, ACTION_QSET_QUERY))
......@@ -253,8 +240,8 @@ void session_common_cleanup(PG_STATE* state)
while(*user)
{
USER* user_next = (*user)->next;
exercise_destroyQuestion((*user)->questionAt);
exercise_destroyQuiz((*user)->quiz);
question_destroy((*user)->questionAt);
quiz_destroy((*user)->quiz);
free(*user);
(*user) = user_next;
}
......
......@@ -50,7 +50,7 @@ bool gen_json(PG_STATE* state, PROBLEMSET* pset)
if(!f) return false;
WJWriter writer = WJWOpenFILEDocument(true, f);
WJWriter solwriter = writer;
QUIZ* quiz = exercise_mkQuiz(state, pset, QTYPE_NORMAL, state->gen.num);
QUIZ* quiz = quiz_create(state, pset, QTYPE_NORMAL, state->gen.num);
QUESTION* q = NULL;
if(state->gen.sfile)
......@@ -61,21 +61,21 @@ bool gen_json(PG_STATE* state, PROBLEMSET* pset)
}
WJWOpenArray(NULL, writer);
while((q = exercise_mkQuestion(state, quiz)))
while((q = question_create(state, quiz)))
{
WJWOpenObject(NULL, writer);
WJWString(KEY_CALCULATOR, q->eref->calculator, true, writer);
WJWString(KEY_NAME, q->eref->name, true, writer);
exercise_writeJSONWidgets(state, q, writer);
exercise_writeJSONSolutions(state, q, state->gen.sfile ? solwriter : writer);
WJWString(KEY_CALCULATOR, q->eref->exercise->calculator, true, writer);
WJWString(KEY_NAME, q->eref->exercise->name, true, writer);
question_writeWidgets_json(state, q, writer);
question_writeSolutions_json(state, q, state->gen.sfile ? solwriter : writer);
WJEWriteDocument(q->hints, writer, KEYLIST(KEY_HINT));
WJWCloseObject(writer);
exercise_destroyQuestion(q);
question_destroy(q);
}
WJWCloseArray(writer);
WJWCloseDocument(writer);
exercise_destroyQuiz(quiz);
quiz_destroy(quiz);
if(sf) fclose(sf);
fclose(f);
return true;
......@@ -161,13 +161,9 @@ int handle_file(void* data, int argc, char** argv)
if(!state->gen.file || !state->gen.pset) return 1;
char* setPath = exercise_loadOne(state, state->gen.pset);
char* setPath = pset_loadOne(state, state->gen.pset);
if(!setPath)
{
printf("Problem set %s not found!", state->gen.pset);
return false;
}
if(!setPath) return false;
PROBLEMSET* pset = exercise_getSet(state, setPath);
......
......@@ -69,31 +69,9 @@ double pdf_set_text(struct WidgetDrawCtx* ctx, char* text, int textLen, int widt
return (extents.height + extents.y) / PANGO_SCALE;
}
RsvgHandle* pdf_loadSvg(PROCESSEDSTRING* str)
{
GError* err = NULL;
GInputStream* stream;
if(str->type == PSTYPE_TEMPLATE)
stream = G_INPUT_STREAM(pmus_input_stream_open(str->template, &err));
else
stream = G_INPUT_STREAM(g_memory_input_stream_new_from_data(str->string, -1, NULL));
RsvgHandle* handle =
rsvg_handle_new_from_stream_sync (
stream,
NULL,
RSVG_HANDLE_FLAG_KEEP_IMAGE_DATA,
NULL,
&err);
g_object_unref(stream);
if(!handle) printf("Error in SVG: %s\n", err->message);
return handle;
}
void pdf_question_begin(struct WidgetDrawCtx* ctx, char* title)
{
char exerNumber[13];
char exerNumber[20];
ctx->surface = cairo_recording_surface_create(CAIRO_CONTENT_ALPHA, NULL);
ctx->cairo = cairo_create(ctx->surface);
......@@ -104,7 +82,7 @@ void pdf_question_begin(struct WidgetDrawCtx* ctx, char* title)
// Print Question Number
if(ctx->sequenceID < 2)
{
snprintf(exerNumber, 13, "%d.", ctx->exerciseAt);
snprintf(exerNumber, 20, "%d.", ctx->exerciseAt);
pango_layout_set_text(ctx->textLayout, exerNumber, -1);
pango_cairo_show_layout(ctx->cairo, ctx->textLayout);
}
......@@ -126,8 +104,19 @@ void pdf_question_begin(struct WidgetDrawCtx* ctx, char* title)
// Print subquestion number (a, b, c, etc)
if(ctx->sequenceID > 0)
{
snprintf(exerNumber, 13, "%c)", ctx->sequenceID + '`');
pango_layout_set_text(ctx->textLayout, exerNumber, -1);
uint32_t newSeq = ctx->sequenceID;
char* exerNumber_at = exerNumber + 19;
exerNumber_at[1] = '\000';
exerNumber_at[0] = ')';
do
{
exerNumber_at --;
*exerNumber_at = (newSeq - 1) % 26;
newSeq = (newSeq - *exerNumber_at) / 26;
*exerNumber_at += 'a';
} while(newSeq);
pango_layout_set_text(ctx->textLayout, exerNumber_at, -1);
pango_cairo_show_layout(ctx->cairo, ctx->textLayout);
ctx->w -= PDF_QNUM_PADDING;
cairo_translate(ctx->cairo, PDF_QNUM_PADDING, 0);
......@@ -329,9 +318,22 @@ void pdf_drawFields(PG_STATE* state, struct WidgetDrawCtx* ctx)
pango_layout_set_alignment(ctx->textLayout, PANGO_ALIGN_LEFT);
}
void pushWidgets(struct Widget* wid,
struct Question* q,
struct WidgetDrawCtx* qCtx,
struct AnswerState* answerState)
{
while(wid)
{
widget_pdf_gen(wid, q, qCtx, answerState);
pdf_pushy(qCtx, PDF_WPADDING_Y);
wid = wid->next;
}
}
bool gen_pdf(PG_STATE* state, PROBLEMSET* pset)
{
QUIZ* quiz = exercise_mkQuiz(state, pset, state->gen.mode, state->gen.num);
QUIZ* quiz = quiz_create(state, pset, state->gen.mode, state->gen.num);
struct WidgetDrawCtx qCtx = {0};
ANSWERSTATE answerState = {
.data = &qCtx,
......@@ -370,26 +372,21 @@ bool gen_pdf(PG_STATE* state, PROBLEMSET* pset)
pdf_title_draw(&qCtx, pset->name);
QUESTION* q;
while((q = exercise_mkQuestion(state, quiz)))
while((q = question_create(state, quiz)))
{
WIDGET* wid = q->widgets;
qCtx.sequenceID = quiz->sequenceID;
pdf_question_begin (&qCtx, q->eref->preamble);
pdf_question_begin (&qCtx, q->eref->exercise->preamble);
while(wid)
{
widget_pdf_gen(wid, q, &qCtx, &answerState);
pdf_pushy(&qCtx, PDF_WPADDING_Y);
wid = wid->next;
}
pushWidgets(q->widgets, q, &qCtx, &answerState);
pushWidgets(q->eref->exercise->baseWidgets, q, &qCtx, &answerState);
pdf_question_end(&qCtx);
exercise_destroyQuestion(q);
question_destroy(q);
if(qCtx.sequenceID < 2)
qCtx.exerciseAt ++;
}
exercise_destroyQuiz(quiz);
quiz_destroy(quiz);
pango_attr_list_unref(qCtx.attr_special);
pango_font_description_free(fontDescription);
......
......@@ -18,7 +18,7 @@
struct PG_State;
struct ProblemSet;
struct ProcessedString;
struct SpecialProperty;
#define DEFAULT_PDF_MARGIN 12
#define DEFAULT_PDF_PAGE_WIDTH 210
......@@ -75,7 +75,7 @@ typedef struct PG_Pdf
// Functions to help with building PDFs
extern double pdf_set_text(struct WidgetDrawCtx* ctx, char* text, int textLen, int width);
extern void pdf_pushy(struct WidgetDrawCtx* ctx, int y);
extern RsvgHandle* pdf_loadSvg(struct ProcessedString* str);
extern RsvgHandle* pdf_loadSvg(struct SpecialProperty* str);
extern int pdf_draw_shortfield(struct WidgetDrawCtx* ctx,
double width,
char* hint, int hintLen,
......
......@@ -38,7 +38,7 @@
#define KEY_SUBS "subs"
#define KEY_SVG "svg"
#define KEY_IMAGE "image"
#define KEY_RAND_MAX "randMax"
#define KEY_VARIABLE "variable"
#define KEY_PREAMBLE "preamble"
// PSet keys
......
......@@ -45,8 +45,14 @@ void unirand_seed(struct unirand_t* rand, uint32_t top)
uint32_t unirand(struct unirand_t* rand, uint32_t* at)
{
uint32_t result = (*at * rand->prime + rand->offset)
% (rand->top);
uint32_t result;
if(rand->top)
{
result = (*at * rand->prime + rand->offset)
% (rand->top);
}
else result = 0;
(*at)++;
return result;
}
......@@ -30,7 +30,8 @@ bool widget_choiceInput_pdf(WIDGET* wid, QUESTION* q, WIDGETDRAWCTX* pdf, ANSWER
while(array)
{
char* choice = WJEString(array, NULL, WJE_GET, NULL);
PROCESSEDSTRING str = exercise_processString(q, choice);
SPECIALPROPERTY str;
if(!sp_parse(&str, choice)) return false;
#ifdef _QUIZGRIND_BUILD_PDF
int choiceHeight = 0;
......@@ -40,37 +41,7 @@ bool widget_choiceInput_pdf(WIDGET* wid, QUESTION* q, WIDGETDRAWCTX* pdf, ANSWER
DEF_CHOICEINPUT_PADDING);
#endif
if(str.class == PSCLASS_NORMAL)
{
switch(str.type)
{
case PSTYPE_TEMPLATE:
choice = mustache_generate_mem(str.template);
break;
case PSTYPE_STRING:
choice = str.string;
break;
default: continue;
}
if(strchr(wid->key, *choiceAt - 'a' + '0') && ans)
{
widget_pushSolution(ans, SOLTYPE_INPUT, "%s %s", choiceAt, choice);
}
#ifdef _QUIZGRIND_BUILD_PDF
if(pdf)
{
choiceHeight = pdf_set_text(pdf, choice, -1, pdf->w);
choiceHeight += DEF_CHOICEINPUT_PADDING * 2;
pango_cairo_show_layout(pdf->cairo, pdf->textLayout);
}
#endif
if(str.type == PSTYPE_TEMPLATE)
free(choice);
}
else if(str.class == PSCLASS_SVG)
if(SPECIALPROPERTY_IS_SVG(str))
{
if(strchr(wid->key, *choiceAt - 'a' + '0') && ans)
{
......@@ -80,7 +51,8 @@ bool widget_choiceInput_pdf(WIDGET* wid, QUESTION* q, WIDGETDRAWCTX* pdf, ANSWER
if(pdf)
{
RsvgDimensionData dimens;
RsvgHandle* svgHandle = pdf_loadSvg(&str);
RsvgHandle* svgHandle;
if(!sp_evaluate_rsvg(&str, SP_SCOPE_QUESTION(q), &svgHandle)) return false;
if(svgHandle)
{
cairo_t* oldCairo = pdf->cairo;
......@@ -96,6 +68,27 @@ bool widget_choiceInput_pdf(WIDGET* wid, QUESTION* q, WIDGETDRAWCTX* pdf, ANSWER
}
#endif
}
else
{
sp_evaluate_string(&str, SP_SCOPE_QUESTION(q), &choice);
if(strchr(wid->key, *choiceAt - 'a' + '0') && ans)
{
widget_pushSolution(ans, SOLTYPE_INPUT, "%s %s", choiceAt, choice);
}
#ifdef _QUIZGRIND_BUILD_PDF
if(pdf)
{
choiceHeight = pdf_set_text(pdf, choice, -1, pdf->w);
choiceHeight += DEF_CHOICEINPUT_PADDING * 2;
pango_cairo_show_layout(pdf->cairo, pdf->textLayout);
}
#endif
if(str.type == PSTYPE_TEMPLATE)
free(choice);
}
#ifdef _QUIZGRIND_BUILD_PDF
if(pdf)
......
......@@ -7,7 +7,7 @@
"name": "choices",
"description": "A 0-based array containing the chices available to the widget. \
This list should be displayed in order to the user. \
If the element is SVG, use the SVG class tag.",
If the element is SVG, use either SVG special string.",
"type": "stringArray"
},
{
......
......@@ -8,11 +8,13 @@
bool widget_imageView_pdf(WIDGET* wid, QUESTION* q, WIDGETDRAWCTX* pdf, ANSWERSTATE* ans)
{
#ifdef _QUIZGRIND_BUILD_PDF
PROCESSEDSTRING src =
exercise_processString(q, WJEString(wid->options, KEY_IMAGEVIEW_SRC, WJE_GET, NULL));
if(src.class == PSCLASS_SVG)
SPECIALPROPERTY src;
sp_parse(&src, WJEString(wid->options, KEY_IMAGEVIEW_SRC, WJE_GET, NULL));
if(SPECIALPROPERTY_IS_SVG(src))
{
RsvgHandle* svgHandle = pdf_loadSvg(&src);
RsvgHandle* svgHandle;
if(!sp_evaluate_rsvg(&src, SP_SCOPE_QUESTION(q), &svgHandle)) return false;
if(svgHandle)
{
RsvgDimensionData dimens;
......
......@@ -15,8 +15,11 @@ bool widget_lineInput_pdf(WIDGET* wid, QUESTION* q, WIDGETDRAWCTX* pdf, ANSWERST
cairo_move_to( pdf->cairo,
0, lineHeight);
cairo_line_to( pdf->cairo,
pdf->w, lineHeight);
pdf->w - PDF_QNUM_PADDING - PDF_QPADDING, lineHeight);
cairo_stroke( pdf->cairo);
widget_pushSolution(ans, SOLTYPE_NORMAL, wid->key);
pdf_pushy( pdf, lineHeight);
length--;
}
......
......@@ -20,25 +20,15 @@ bool widget_textView_pdf(WIDGET* wid, QUESTION* q, WIDGETDRAWCTX* pdf, ANSWERSTA
#ifdef _QUIZGRIND_BUILD_PDF
char* src = WJEString(wid->options, KEY_TEXTVIEW_TEXT, WJE_GET, NULL);
PROCESSEDSTRING ps = exercise_processString(q, src);
switch(ps.type)
{
case PSTYPE_TEMPLATE:
{
src = mustache_generate_mem(ps.template);
break;
}
case PSTYPE_STRING:
{
src = ps.string;
break;
}
default: return false;
}
SPECIALPROPERTY ps;
if(!sp_parse(&ps, src)
|| !sp_evaluate_string(&ps, SP_SCOPE_QUESTION(q), &src))
return false;
int pushy = pdf_set_text( pdf, src, -1, ( pdf->w - PDF_QNUM_PADDING));
pango_cairo_show_layout( pdf->cairo, pdf->textLayout);
pdf_pushy( pdf, pushy);
if(ps.type == PSTYPE_TEMPLATE) free(src);
sp_devaluate(&ps, src);
return widget_textView_answer_show(wid, q, ans);
#else
return true;
......