'use strict'; /* ════════════════════════════════════════════════════════════════════════ Генераторы задач тренажёра — ДАННЫЕ, не код. Таксономия: тема → навык. Приём «корень-вперёд»: выбираем целый корень/множители и коэффициенты, затем ВЫВОДИМ остальное так, чтобы ответ был целым, а задача — решаемой. Поэтому самопроверка движка (verifyRoot) всегда проходит. Шаг решения — { note(подробный текст), tex(одно равенство) }; tex рендерится в KaTeX (exprToLatex). Последний шаг «Проверка» подставляет корень — это и педагогика, и наглядная демонстрация того, как движок проверяет ответ. Виды задач: • solve (деф.) — уравнение lhs = rhs, ученик находит x. Показывается уравнение. • compute (kind:'compute') — вычислительная задача (проценты): на сцене — текстовый prompt из display, а lhs:'x' / rhs:<значение> служат ТОЛЬКО для проверки ответа подстановкой (latex уравнения не показывается). Темы (7 класс, алгебра): Уравнения → Пропорции → Проценты. Дальше (Уровень 1): текстовые задачи через LLM с той же подстановочной верификацией. ════════════════════════════════════════════════════════════════════════ */ (function (global) { var TOPICS = [ { key: 'linear-eq', label: 'Уравнения', subject: 'algebra', grade: 7, order: 1 }, { key: 'proportions', label: 'Пропорции', subject: 'algebra', grade: 7, order: 2 }, { key: 'percents', label: 'Проценты', subject: 'algebra', grade: 7, order: 3 } ]; var GENERATORS = [ /* ═══ Тема: Уравнения ═══ */ /* ax + b = c */ { id: 'lin-basic', topic: 'linear-eq', order: 1, subject: 'algebra', grade: 7, title: 'ax + b = c', pick: { a: [2, 9], b: [1, 20], root: [-9, 9] }, require: 'root != 0', derive: { c: 'a*root + b', cmb: 'a*root' }, lhs: '{a}*x + {b}', rhs: '{c}', display: '{a}x + {b} = {c}', answerVar: 'x', answer: 'root', integerAnswer: true, solution: [ { note: 'Перед нами линейное уравнение. Наша цель — оставить x одного в левой части. Сначала уберём свободное число {b}: перенесём его вправо, поменяв знак.', tex: '{a}x = {c} - {b}' }, { note: 'Выполняем вычитание в правой части.', tex: '{a}x = {cmb}' }, { note: 'Осталось избавиться от множителя при x — делим обе части уравнения на {a}.', tex: 'x = {cmb} / {a}' }, { note: 'Получаем корень уравнения.', tex: 'x = {ans}' }, { note: 'Проверка: подставим найденное значение в исходное уравнение — левая часть должна совпасть с правой.', tex: '{a}*({ans}) + {b} = {c}' } ] }, /* a(x + b) = c */ { id: 'lin-paren', topic: 'linear-eq', order: 2, subject: 'algebra', grade: 7, title: 'a(x + b) = c', pick: { a: [2, 8], b: [1, 12], root: [-9, 9] }, require: 'root != 0', derive: { c: 'a*(root + b)', ca: 'root + b' }, lhs: '{a}*(x + {b})', rhs: '{c}', display: '{a}(x + {b}) = {c}', answerVar: 'x', answer: 'root', integerAnswer: true, solution: [ { note: 'Слева число {a} умножается на скобку. Самый короткий путь — разделить обе части уравнения на {a}, чтобы убрать этот множитель.', tex: 'x + {b} = {ca}' }, { note: 'Справа получилось целое число. Теперь переносим {b} вправо со сменой знака.', tex: 'x = {ca} - {b}' }, { note: 'Получаем корень уравнения.', tex: 'x = {ans}' }, { note: 'Проверка: подставляем корень в скобку исходного уравнения.', tex: '{a}*({ans} + {b}) = {c}' } ] }, /* ax + b = cx + d */ { id: 'lin-both-sides', topic: 'linear-eq', order: 3, subject: 'algebra', grade: 7, title: 'ax + b = cx + d', pick: { a: [3, 9], c: [1, 8], b: [1, 20], root: [-9, 9] }, constraint: 'c < a', require: 'root != 0', derive: { d: '(a - c)*root + b', amc: 'a - c', dmb: '(a - c)*root' }, lhs: '{a}*x + {b}', rhs: '{c}*x + {d}', display: '{a}x + {b} = {c}x + {d}', answerVar: 'x', answer: 'root', integerAnswer: true, solution: [ { note: 'Здесь x есть в обеих частях. Соберём все слагаемые с x слева, а числа — справа. Переносимые слагаемые меняют знак.', tex: '({a} - {c})x = {d} - {b}' }, { note: 'Приводим подобные: вычитаем коэффициенты при x и отдельно числа.', tex: '{amc}x = {dmb}' }, { note: 'Делим обе части на коэффициент при x, то есть на {amc}.', tex: 'x = {dmb} / {amc}' }, { note: 'Получаем корень уравнения.', tex: 'x = {ans}' }, { note: 'Проверка: подставим корень — обе части дадут одно и то же число.', tex: '{a}*({ans}) + {b} = {c}*({ans}) + {d}' } ] }, /* a(x + b) = c(x + d) */ { id: 'lin-paren-both', topic: 'linear-eq', order: 4, subject: 'algebra', grade: 7, title: 'a(x+b) = c(x+d)', pick: { a: [2, 6], c: [2, 6], b: [1, 10], root: [-6, 6] }, constraint: 'a != c', derive: { V: 'a*(root + b)', d: 'V/c - root', ab: 'a*b', cd: 'c*(V/c - root)', amc: 'a - c', diff: 'c*(V/c - root) - a*b' }, require: 'mod(V, c) == 0 && root != 0', lhs: '{a}*(x + {b})', rhs: '{c}*(x + {d})', display: '{a}(x + {b}) = {c}(x + {d})', answerVar: 'x', answer: 'root', integerAnswer: true, solution: [ { note: 'Скобки с двух сторон. Раскрываем их: умножаем множитель перед скобкой на каждое слагаемое внутри.', tex: '{a}x + {ab} = {c}x + {cd}' }, { note: 'Переносим слагаемые с x влево, числа — вправо, и приводим подобные.', tex: '{amc}x = {diff}' }, { note: 'Делим обе части на {amc}.', tex: 'x = {diff} / {amc}' }, { note: 'Получаем корень уравнения.', tex: 'x = {ans}' }, { note: 'Проверка: подставляем корень в обе скобки.', tex: '{a}*({ans} + {b}) = {c}*({ans} + {d})' } ] }, /* x/a + b = c */ { id: 'lin-frac-denom', topic: 'linear-eq', order: 5, subject: 'algebra', grade: 7, title: 'x/a + b = c', pick: { a: [2, 6], k: [-6, 6], b: [1, 12] }, require: 'k != 0', derive: { root: 'a*k', c: 'k + b', cmb: 'k' }, lhs: 'x/{a} + {b}', rhs: '{c}', display: 'x/{a} + {b} = {c}', answerVar: 'x', answer: 'root', integerAnswer: true, solution: [ { note: 'Слева — дробь x/{a} и число {b}. Сначала уберём свободное число: вычтем {b} из обеих частей.', tex: 'x/{a} = {cmb}' }, { note: 'Чтобы избавиться от знаменателя {a}, умножаем обе части уравнения на {a}.', tex: 'x = {cmb} * {a}' }, { note: 'Получаем корень уравнения.', tex: 'x = {ans}' }, { note: 'Проверка: подставляем корень в исходное уравнение.', tex: '{ans}/{a} + {b} = {c}' } ] }, /* (a·x)/b = c */ { id: 'lin-coef-frac', topic: 'linear-eq', order: 6, subject: 'algebra', grade: 7, title: 'ax/b = c', pick: { a: [2, 5], b: [2, 5], m: [-5, 5] }, require: 'm != 0', derive: { root: 'b*m', c: 'a*m', cb: 'a*m*b' }, lhs: '{a}*x/{b}', rhs: '{c}', display: '{a}x/{b} = {c}', answerVar: 'x', answer: 'root', integerAnswer: true, solution: [ { note: 'Слева дробь, в числителе — {a}x. Умножаем обе части уравнения на знаменатель {b}, чтобы избавиться от дроби.', tex: '{a}x = {cb}' }, { note: 'Теперь делим обе части на коэффициент {a}.', tex: 'x = {cb} / {a}' }, { note: 'Получаем корень уравнения.', tex: 'x = {ans}' }, { note: 'Проверка: подставляем корень в исходную дробь.', tex: '{a}*({ans})/{b} = {c}' } ] }, /* (ax + b)/c = d */ { id: 'lin-frac-eq', topic: 'linear-eq', order: 7, subject: 'algebra', grade: 7, title: '(ax + b)/c = d', pick: { a: [2, 6], b: [1, 12], c: [2, 6], root: [-6, 6] }, derive: { prod: 'a*root + b', d: '(a*root + b)/c', cd: 'a*root + b', cdmb: 'a*root' }, require: 'mod(a*root + b, c) == 0 && root != 0', lhs: '({a}*x + {b})/{c}', rhs: '{d}', display: '({a}x + {b})/{c} = {d}', answerVar: 'x', answer: 'root', integerAnswer: true, solution: [ { note: 'Вся левая часть делится на {c}. Умножаем обе части уравнения на {c}, чтобы убрать знаменатель.', tex: '{a}x + {b} = {cd}' }, { note: 'Переносим число {b} в правую часть со сменой знака.', tex: '{a}x = {cdmb}' }, { note: 'Делим обе части на {a}.', tex: 'x = {cdmb} / {a}' }, { note: 'Получаем корень уравнения.', tex: 'x = {ans}' }, { note: 'Проверка: подставляем корень в исходную дробь.', tex: '({a}*({ans}) + {b})/{c} = {d}' } ] }, /* ═══ Тема: Пропорции ═══ */ /* a/b = c/x */ { id: 'prop-x-right', topic: 'proportions', order: 1, subject: 'algebra', grade: 7, title: 'a/b = c/x', pick: { a: [2, 9], b: [2, 9], t: [2, 9] }, derive: { c: 'a*t', root: 'b*t', bc: 'b*a*t' }, lhs: '{a}/{b}', rhs: '{c}/x', display: '{a}/{b} = {c}/x', answerVar: 'x', answer: 'root', integerAnswer: true, solution: [ { note: 'Это пропорция — равенство двух отношений. По основному свойству пропорции произведение крайних членов равно произведению средних (умножаем «крест-накрест»).', tex: '{a}*x = {b} * {c}' }, { note: 'Считаем произведение в правой части.', tex: '{a}x = {bc}' }, { note: 'Делим обе части на {a}, чтобы найти x.', tex: 'x = {bc} / {a}' }, { note: 'Получаем корень.', tex: 'x = {ans}' }, { note: 'Проверка: при найденном x обе дроби равны.', tex: '{a}/{b} = {c}/{ans}' } ] }, /* x/a = b/c */ { id: 'prop-x-left', topic: 'proportions', order: 2, subject: 'algebra', grade: 7, title: 'x/a = b/c', pick: { a: [2, 9], c: [2, 9], s: [2, 9] }, derive: { b: 'c*s', root: 'a*s', ab: 'a*c*s' }, lhs: 'x/{a}', rhs: '{b}/{c}', display: 'x/{a} = {b}/{c}', answerVar: 'x', answer: 'root', integerAnswer: true, solution: [ { note: 'Перед нами пропорция. Перемножаем её члены крест-накрест: числитель левой дроби на знаменатель правой и наоборот.', tex: '{c}*x = {a} * {b}' }, { note: 'Считаем произведение в правой части.', tex: '{c}x = {ab}' }, { note: 'Делим обе части на {c}.', tex: 'x = {ab} / {c}' }, { note: 'Получаем корень.', tex: 'x = {ans}' }, { note: 'Проверка: обе дроби равны.', tex: '{ans}/{a} = {b}/{c}' } ] }, /* a/x = b/c */ { id: 'prop-x-denom', topic: 'proportions', order: 3, subject: 'algebra', grade: 7, title: 'a/x = b/c', pick: { b: [2, 9], c: [2, 9], s: [2, 9] }, derive: { a: 'b*s', root: 'c*s', ac: 'b*s*c' }, lhs: '{a}/x', rhs: '{b}/{c}', display: '{a}/x = {b}/{c}', answerVar: 'x', answer: 'root', integerAnswer: true, solution: [ { note: 'Пропорция, где неизвестное стоит в знаменателе. Перемножаем крест-накрест.', tex: '{b}*x = {a} * {c}' }, { note: 'Считаем произведение в правой части.', tex: '{b}x = {ac}' }, { note: 'Делим обе части на {b}.', tex: 'x = {ac} / {b}' }, { note: 'Получаем корень.', tex: 'x = {ans}' }, { note: 'Проверка: обе дроби равны.', tex: '{a}/{ans} = {b}/{c}' } ] }, /* ═══ Тема: Проценты (вычислительные задачи) ═══ */ /* p% от числа a */ { id: 'pct-of', topic: 'percents', order: 1, subject: 'algebra', grade: 7, kind: 'compute', title: 'p% от числа', pick: { pidx: [2, 10], abase: [1, 15] }, derive: { p: 'pidx*5', a: 'abase*20', val: 'pidx*abase' }, lhs: 'x', rhs: '{p}*{a}/100', display: 'Найдите {p}% от числа {a}', answerVar: 'x', answer: 'val', integerAnswer: true, solution: [ { note: 'Процент — это сотая доля числа. Чтобы найти {p}% от {a}, нужно умножить число на {p} и разделить на 100.', tex: 'x = {a}*{p}/100' }, { note: 'Выполняем умножение и деление — получаем ответ.', tex: 'x = {ans}' } ] }, /* сколько % составляет a от b */ { id: 'pct-what', topic: 'percents', order: 2, subject: 'algebra', grade: 7, kind: 'compute', title: 'Сколько процентов', pick: { pidx: [2, 10], bbase: [1, 8] }, derive: { p: 'pidx*5', b: 'bbase*20', a: 'pidx*bbase' }, lhs: 'x', rhs: '100*{a}/{b}', display: 'Сколько процентов составляет {a} от {b}?', answerVar: 'x', answer: 'p', integerAnswer: true, solution: [ { note: 'Чтобы узнать, какую часть {a} составляет от {b}, делим {a} на {b}. А чтобы перевести эту часть в проценты — умножаем результат на 100.', tex: 'x = {a}/{b}*100' }, { note: 'Считаем — ответ получается в процентах.', tex: 'x = {ans}' } ] }, /* p% числа равны a — найти число */ { id: 'pct-whole', topic: 'percents', order: 3, subject: 'algebra', grade: 7, kind: 'compute', title: 'Число по проценту', pick: { pidx: [2, 10], wbase: [1, 12] }, derive: { p: 'pidx*5', whole: 'wbase*20', a: 'pidx*wbase' }, lhs: 'x', rhs: '100*{a}/{p}', display: '{p}% числа равны {a}. Найдите это число.', answerVar: 'x', answer: 'whole', integerAnswer: true, solution: [ { note: 'Известно, что {p}% некоторого числа равны {a}. Значит само число во столько раз больше: умножаем {a} на 100 и делим на {p}.', tex: 'x = {a}*100/{p}' }, { note: 'Считаем — получаем искомое число.', tex: 'x = {ans}' } ] } ]; function get(id) { for (var i = 0; i < GENERATORS.length; i++) if (GENERATORS[i].id === id) return GENERATORS[i]; return null; } function byTopic(key) { return GENERATORS.filter(function (g) { return g.topic === key; }) .sort(function (a, b) { return (a.order || 0) - (b.order || 0); }); } global.TrainerGenerators = { list: function () { return GENERATORS.slice(); }, topics: function () { return TOPICS.slice(); }, byTopic: byTopic, get: get, GENERATORS: GENERATORS, TOPICS: TOPICS }; })(typeof window !== 'undefined' ? window : globalThis);