Commit ecd6089d by Иван Кубота

multiple and single questions with image

parent 352493fa
Pipeline #525 canceled with stage
......@@ -15,6 +15,7 @@ const options = yargs
.option("v", { alias: "verbose", describe: "print log", type: "boolean"})
.option("n", { alias: "nolog", describe: "no text log in object", type: "boolean"})
.option("d", { alias: "dir", describe: "database dir", type: "string"})
.option("f", { alias: "fake", describe: "add fake images", type: "boolean"})
.argv;
......@@ -47,9 +48,26 @@ const sources = [
"js/controller/quizGenerator.js",
"js/controller/quizBits/checkbox.js",
"js/controller/quizBits/radio.js"
"js/controller/quizBits/checkboxPhoto.js",
"js/controller/quizBits/radio.js",
"js/controller/quizBits/radioPhoto.js"
];
const rand = function(a, b){
if(Array.isArray(a)){
return a[Math.random.seeded()*a.length|0];
}
if(typeof a === 'object' && 'max' in a){
b = a.max;
a = a.min || 0;
}
a = Math.ceil(a);
b = Math.floor(b);
return Math.floor(Math.random.seeded() * (b - a + 1)) + a;
return (r-a)*(b-a)|0;
};
var js = sources.map(a=>'./public/'+a).map(n=>fs.readFileSync(n)+'').join('\n\n');
if(options.dir)
......@@ -58,24 +76,29 @@ const data = require("../db.js");
const body = 'const window = {};'+js+'; return {quizGenerator, initDataProvider, seeded: Math.random.seeded}';
var ctx = new Function('',body)();
//console.log(options);
data.after = function() {
data.after = function(){
ctx.initDataProvider( data );
if( options.fake ){
for(var i = 0; i < 10; i++){
rand(Object.values(data.products)).image = 'https://robohash.org/'+Math.random().toString(36)
}
}
ctx.initDataProvider( data );
if(options.seed)
ctx.seeded.setStringSeed(options.seed);
if( options.seed )
ctx.seeded.setStringSeed( options.seed );
const result = ctx.quizGenerator( options.multiple?'checkbox':'radio', options.photo);
const result = ctx.quizGenerator( options.multiple ? 'checkbox' : 'radio', options.photo );
if(options.verbose){
if( options.verbose ){
console.log( result.log.join( '\n' ) )
}else{
if(options.nolog)
if( options.nolog )
delete result.log;
console.log('--- START ---\n\n'+JSON.stringify(result,null,2)+'\n\n--- END ---')
console.log( '--- START ---\n\n' + JSON.stringify( result, null, 2 ) + '\n\n--- END ---' )
}
if(options.output)
fs.writeFileSync(options.output, JSON.stringify(result,null,2))
if( options.output )
fs.writeFileSync( options.output, JSON.stringify( result, null, 2 ) )
};
......
......@@ -43,7 +43,9 @@
<script src="js/controller/quizGenerator.js"></script>
<script src="js/controller/quizBits/checkbox.js"></script>
<script src="js/controller/quizBits/checkboxPhoto.js"></script>
<script src="js/controller/quizBits/radio.js"></script>
<script src="js/controller/quizBits/radioPhoto.js"></script>
<script src="main.js"></script>
......
......@@ -83,7 +83,7 @@ quizTypes.checkbox = [ ComponentsOfProduct = {
doNotTrim: true
}, log),
componentsWithProducts = qB.getNotUniqComponentsWithProducts(products, 2);
componentsWithProducts = qB.getComponentsWithSharedProducts(products, 2);
log.push('Shared products count: '+componentsWithProducts.length);
......
quizTypes.checkboxPhoto = [
{
questionMinComponentsCount: 2,
probability: 8,
type: 'What components are in it',
answers: {min: 3, max: 6},
correct: {min: 2, max: 4},
wrong: {min: 1, max: 4},
from: qB.prebuild.similarTaggedProductWithPhotoAndComponents.fn,
question(income, log){
return 'Какие ингредиенты входят в продукт, изображенный на картинке?'
},
answer( income, log ){
let corrects = income.allCorrect
.slice(0, rand(this.correct))
.map(a=>new Answer.Correct(textFormat(a.name))),
wrongs = income.allWrong
.slice(0, rand(this.wrong))
.map(a=>new Answer.Wrong(textFormat(a.name)));
return shuffle(
corrects
.concat(wrongs)
.slice(
0,
Math.min(
this.answers.max,
corrects.length+wrongs.length)));
}
},
{
questionMinComponentsCount: 2,
probability: 3000,
type: 'What components are NOT in it',
answers: {min: 3, max: 6},
correct: {min: 2, max: 4},
wrong: {min: 1, max: 4},
from: qB.prebuild.similarTaggedProductWithPhotoAndComponents.fn,
question(income, log){
return 'Какие ингредиенты НЕ входят в продукт, изображенный на картинке?'
},
answer( income, log ){
let corrects = income.allWrong
.slice(0, rand(this.correct))
.map(a=>new Answer.Correct(textFormat(a.name))),
wrongs = income.allCorrect
.slice(0, rand(this.wrong))
.map(a=>new Answer.Wrong(textFormat(a.name)));
return shuffle(
corrects
.concat(wrongs)
.slice(
0,
Math.min(
this.answers.max,
corrects.length+wrongs.length)));
}
}
];
\ No newline at end of file
......@@ -5,7 +5,8 @@ const qB = {
single,
connectedByTags,
minSimilarTags,
doNotTrim
doNotTrim,
withPhoto
}, log) {
let filtered = Object.values(dP.products);
......@@ -15,6 +16,9 @@ const qB = {
log.push(`Have >= ${minComponents} components: `+filtered.length);
if(!filtered.length)
return;
if(single){
amount = 1;
}
......@@ -112,6 +116,9 @@ const qB = {
},
getUniqComponents: function(products) {
return Object.values(qB.getUniqComponentsHash(products));
},
getUniqComponentsHash: function(products) {
const hash = {};
products.forEach(
p=>
......@@ -122,11 +129,17 @@ const qB = {
)
);
return Object.values(hash);
return hash;
},
getNotUniqComponentsWithProducts: function(products, minSharedProductCount) {
getComponentsWithSharedProducts: function(
products,
minSharedProductCount,
maxSharedProductCount
) {
const hash = {};
minSharedProductCount = minSharedProductCount || 0;
maxSharedProductCount = maxSharedProductCount || Infinity;
products.forEach(
p=>
p
......@@ -139,7 +152,10 @@ const qB = {
);
return Object
.values(hash)
.filter(i => i.length >= minSharedProductCount)
.filter(i =>
i.length >= minSharedProductCount &&
i.length <= maxSharedProductCount
)
.map(i=>({component: i[0].c, products: i.map(({p})=>p)}));
},
......@@ -152,7 +168,107 @@ const qB = {
);
},
getProductsWithTags: function({
products,
tags,
minMatch
}){
if(minMatch === void 0){
minMatch = tags.length;
}
const tHash = tags.reduce((s, t)=>{s[t.id] = t; return s;}, {});
products = products || Object.values( dP.products );
return products
.filter(
p => p.getTags()
.filter(t=>t.id in tHash).length >= minMatch
);
},
prebuild: {
similarTaggedProductWithPhotoAndComponents: {
fn: function(log) {
this.getEmAll = true;
this.products = {min: 2};
const possibilities = shuffle(
qB.prebuild.similarTaggedProductWithPhoto.fn.call(this, log)
);
if(!possibilities || !possibilities.length)
return false;
for( let i = 0, _i = possibilities.length; i < _i; i++ ){
const possibility = possibilities[ i ];
const components = possibility.correct.getComponents();
const other = qB.getUniqComponentsHash(possibility.all.filter( sP => sP !== possibility.correct ))
components.forEach(c=>delete other[normalizeText(c.name)]);
if(Object.keys(other).length >= this.answers.min){
log.push('Take '+possibility.baseProduct.title+' with uniq components:');
components.forEach(c=>
log.push(` > ${c.name}`)
);
log.push('');
log.push('Other components in group tagged as '+
possibility.baseProduct.getTags().map(t=>t.name).join(', ')+':'
);
Object.values( other ).forEach(c=>
log.push(` > ${c.name}`)
);
const c = rand( components )
return {
allCorrect: components,
baseProduct: possibility.baseProduct,
correct: c,
wrong: shuffle( Object.values( other ) ).slice( 0, rand( this.answers.min - 1, this.answers.max - 1 ) ),
allWrong: Object.values( other )
};
}
}
return false;
}
},
similarTaggedProductWithPhoto: {
questionMinComponentsCount: Number,
products: {min: Number, max: Number},
fn: function(log) {
let allProducts = Object.values(dP.products);
let products = Object.values(dP.products)
.filter(p=>p.image)
.filter(p=>p.getComponents().length>=this.questionMinComponentsCount)
log.push( `Products with image and >= ${this.questionMinComponentsCount} components count: ${products.length}` );
if(products.length === 0){
rand(Object.values(dP.products)).image = 'https://robohash.org/'+Math.random().toString(36)
return false;
}
const possibility = products.map(p=>{
const similar = qB.getProductsWithTags({
products: allProducts,
tags: p.getTags(),
minMatch: 1
});
if(similar.length+1 >= this.products.min ){
return {
correct: p,
baseProduct: p,
wrong: similar.filter( sP => sP !== p ).slice(0,rand( this.products.min - 1,this.products.max - 1)),
all: similar
};
}else{
return false;
}
})
.filter(Boolean);
log.push( `Possible count: ${possibility.length}` );
return this.getEmAll ? possibility : rand(possibility);
}
},
similarTaggedProducts: {
// minimal components in product
questionCmpAmount: 4,
......@@ -172,8 +288,11 @@ const qB = {
}, log);
for( let i = 0, _i = products.length; i < _i; i++ ){
const product = products[ i ],
cmps = product
const product = products[ i ];
/* if(this.withPhoto && !product.image)
continue;*/
const cmps = product
.getComponents()
.filter(cmp => products.filter(p=>p.containsComponent(cmp)).length === 1)
......
quizTypes.radioPhoto = [
{
questionMinComponentsCount: 0,
probability: 10,
type: 'What is it',
products: {min: 4, max: 6},
from: qB.prebuild.similarTaggedProductWithPhoto.fn,
question(income, log){
return 'Какой продукт изображен на картинке?'
},
answer( income, log ){
return shuffle(
[new Answer.Correct(textFormat(income.correct.title))]
.concat(income.wrong.map(p=>new Answer.Wrong(textFormat(p.title)))))
}
},
{
questionMinComponentsCount: 0,
probability: 8,
type: 'What describes it better',
products: {min: 3, max: 5},
from: qB.prebuild.similarTaggedProductWithPhoto.fn,
question(income, log){
return 'Какое из этих описаний относится к продукту, изображенному на картинке?'
},
answer( income, log ){
return shuffle(
[new Answer.Correct(textFormat(income.correct.description))]
.concat(income.wrong.map(p=>new Answer.Wrong(textFormat(p.description)))))
}
},
{
questionMinComponentsCount: 2,
probability: 3,
type: 'What component is in it',
answers: {min: 4, max: 6},
from: qB.prebuild.similarTaggedProductWithPhotoAndComponents.fn,
question(income, log){
return 'Выберите один ингредиент, который входит в продукт, изображенный на картинке'
},
answer( income, log ){
return shuffle(
[new Answer.Correct(textFormat(income.correct.name))]
.concat(income.wrong.map(p=>new Answer.Wrong(textFormat(p.name)))))
}
}
];
\ No newline at end of file
......@@ -98,7 +98,7 @@ const quizGenerator = function(type, photo, subType) {
productId: source.baseProduct.id,
question: cfg.question.call(cfg, source, log),
answers,
image: !photo?null:true,
image: !photo?null:source.baseProduct.image,
log,
......
......@@ -19,6 +19,7 @@ view.page.Generate = function() {
const result = quizGenerator( type, photo );
title.innerHTML = textFormat( result.question, true );
image.innerHTML = result.image ? `<img src="${result.image}" alt="img"/>` : '';
debug.value = result.log.join( '\n' )
D.removeChildren( answers );
D.appendChild( answers, result.answers.map( ( a ) => view.cmp.Answer( a, type ) ) );
......@@ -28,7 +29,7 @@ view.page.Generate = function() {
}
};
let title, answers, debug, seedInput, setSeed;
let title, answers, debug, seedInput, image;
this.dom =
div({cls: 'generate-panel'},
......@@ -65,6 +66,7 @@ view.page.Generate = function() {
div({cls: 'generate-example'},
title = div({cls: 'generate-title'}),
image = div({cls: 'generate-image'}),
answers = div({cls: 'generate-answers'}),
debug = D.textarea({cls: 'generate-debug'})
)
......
......@@ -228,4 +228,12 @@ textarea.generate-debug {
}
.generate-seed-label {
margin: 0 8px 0 32px
}
.generate-example {
position: relative;
}
.generate-image {
right: 0;
position: absolute;
top: -100px;
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment