feat(trainer): A-добивка символов + C1 новые форматы (choice/verify/estimate)
A-добивка answerSym: суммы прогрессий → S, гипотенуза → c, катет/сторона → b (+6 генераторов; устранена нестыковка ввода и решения). C1 — новые форматы условий (движок + страница + 10 генераторов): - kind 'choice' — выбор из вариантов; дистракторы из generator.distractors (правильный + до 3, перемешиваются детерминированно); страница рисует кнопки, клик = ответ, верный подсвечивается зелёным, выбранный неверный — красным. - kind 'verify' — верно/неверно: claim (булева SimExpr) → две кнопки Верно/Неверно. - kind 'estimate' — прикидка: ответ принимается в допуске tol (_checkEstimate). Движок: problem.choices/tol; самопроверка choice (ровно 1 правильный); estimate через verifyRoot + допуск. Страница: #tr-choices/#tr-choice-next, applyInputMode переключает ввод↔варианты, renderChoices/submitChoice, advance сбрасывает. Генераторы: ch-area-rect/ch-lin-basic/ch-pct-of/ch-pyth-hyp (выбор), vf-frac-compare/vf-divisible/vf-pyth (обратная Пифагора)/vf-eq-root (верно/неверно), est-product/est-percent (прикидка). Итого 199 генераторов. Смоук v41 99634 проверки (choice: ≥2 варианта, ровно 1 правильный; estimate: приём в допуске + отказ далёкого; рендер фигур; шаги→LaTeX); figures-смоук 19289/3180 на 53 геом-генераторах. Inline trainer.html парсится. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -330,6 +330,30 @@
|
|||||||
answer = pair[avs[0]]; // запасной одиночный ответ
|
answer = pair[avs[0]]; // запасной одиночный ответ
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── новые форматы условий (Ф C1) ──
|
||||||
|
// choice — выбор из вариантов; verify — верно/неверно; estimate — ответ в допуске
|
||||||
|
var choices = null, tol = null;
|
||||||
|
if (kind === 'choice') {
|
||||||
|
var corr = answer;
|
||||||
|
var pool = [corr];
|
||||||
|
(gen.distractors || []).forEach(function (d) {
|
||||||
|
var v = evalExpr(d, env);
|
||||||
|
if (gen.integerAnswer) v = Math.round(v);
|
||||||
|
if (isFinite(v) && pool.every(function (x) { return Math.abs(x - v) > 1e-9; })) pool.push(v);
|
||||||
|
});
|
||||||
|
var opts = pool.slice(0, 4); // правильный + до 3 дистракторов
|
||||||
|
for (var oi = opts.length - 1; oi > 0; oi--) { // детерминированное перемешивание (rng)
|
||||||
|
var oj = Math.floor(rng() * (oi + 1)), tmp = opts[oi]; opts[oi] = opts[oj]; opts[oj] = tmp;
|
||||||
|
}
|
||||||
|
choices = opts.map(function (v) { return { label: fmtNum(v), correct: Math.abs(v - corr) < 1e-9 }; });
|
||||||
|
} else if (kind === 'verify') {
|
||||||
|
var claimTrue = truthy(evalExpr(gen.claim, env));
|
||||||
|
answer = claimTrue ? 1 : 0;
|
||||||
|
choices = [{ label: 'Верно', correct: claimTrue }, { label: 'Неверно', correct: !claimTrue }];
|
||||||
|
} else if (kind === 'estimate') {
|
||||||
|
tol = (gen.tol != null) ? Math.abs(evalExpr(String(gen.tol), env)) : Math.max(1, Math.abs(answer) * 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
var lhsExpr = render(gen.lhs || 'x', env);
|
var lhsExpr = render(gen.lhs || 'x', env);
|
||||||
var rhsExpr = render(gen.rhs || 'x', env);
|
var rhsExpr = render(gen.rhs || 'x', env);
|
||||||
var sEnv = assign(env, { ans: answer });
|
var sEnv = assign(env, { ans: answer });
|
||||||
@@ -372,6 +396,8 @@
|
|||||||
figure: gen.figure || null, // спека чертежа (данные) — рисует TrainerFigures по params
|
figure: gen.figure || null, // спека чертежа (данные) — рисует TrainerFigures по params
|
||||||
figurePrompt: gen.figurePrompt || null, // краткое условие для режима «читать с чертежа»
|
figurePrompt: gen.figurePrompt || null, // краткое условие для режима «читать с чертежа»
|
||||||
answerSym: gen.answerSym || null, // обозначение искомой величины (P/S/C/d/…) — только для показа
|
answerSym: gen.answerSym || null, // обозначение искомой величины (P/S/C/d/…) — только для показа
|
||||||
|
choices: choices, // [{label, correct}] для kind choice/verify
|
||||||
|
tol: tol, // допуск для kind estimate
|
||||||
lhsExpr: lhsExpr,
|
lhsExpr: lhsExpr,
|
||||||
rhsExpr: rhsExpr,
|
rhsExpr: rhsExpr,
|
||||||
// система: по умолчанию показываем сами уравнения; но если задан gen.display
|
// система: по умолчанию показываем сами уравнения; но если задан gen.display
|
||||||
@@ -418,6 +444,12 @@
|
|||||||
okSelf = _origIneqHolds(lhsExpr, rhsExpr, gen.dispOp || '<', answerVar, inside) &&
|
okSelf = _origIneqHolds(lhsExpr, rhsExpr, gen.dispOp || '<', answerVar, inside) &&
|
||||||
!_origIneqHolds(lhsExpr, rhsExpr, gen.dispOp || '<', answerVar, outside);
|
!_origIneqHolds(lhsExpr, rhsExpr, gen.dispOp || '<', answerVar, outside);
|
||||||
why = 'неравенство не согласовано с ответом';
|
why = 'неравенство не согласовано с ответом';
|
||||||
|
} else if (kind === 'choice') {
|
||||||
|
okSelf = !!choices && choices.length >= 2 && choices.filter(function (c) { return c.correct; }).length === 1;
|
||||||
|
why = 'некорректный набор вариантов выбора';
|
||||||
|
} else if (kind === 'verify') {
|
||||||
|
okSelf = !!choices && choices.length === 2;
|
||||||
|
why = 'verify без вариантов';
|
||||||
} else if (kind === 'system') {
|
} else if (kind === 'system') {
|
||||||
okSelf = !!(system && system.length) && system.every(function (e) {
|
okSelf = !!(system && system.length) && system.every(function (e) {
|
||||||
var L = evalExpr(e.lhs, pair), R = evalExpr(e.rhs, pair);
|
var L = evalExpr(e.lhs, pair), R = evalExpr(e.rhs, pair);
|
||||||
@@ -469,6 +501,7 @@
|
|||||||
if (problem.kind === 'roots') return _checkMultiRoot(problem, raw);
|
if (problem.kind === 'roots') return _checkMultiRoot(problem, raw);
|
||||||
if (problem.kind === 'inequality') return _checkInequality(problem, raw);
|
if (problem.kind === 'inequality') return _checkInequality(problem, raw);
|
||||||
if (problem.kind === 'system') return _checkSystem(problem, raw);
|
if (problem.kind === 'system') return _checkSystem(problem, raw);
|
||||||
|
if (problem.kind === 'estimate') return _checkEstimate(problem, raw);
|
||||||
|
|
||||||
var c = SE().compile(raw);
|
var c = SE().compile(raw);
|
||||||
if (c.error) {
|
if (c.error) {
|
||||||
@@ -582,6 +615,17 @@
|
|||||||
return { ok: true, reason: null, message: 'Верно!' };
|
return { ok: true, reason: null, message: 'Верно!' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Оценка/прикидка: ответ верен, если попадает в допуск tol вокруг истинного значения. */
|
||||||
|
function _checkEstimate(problem, raw) {
|
||||||
|
var c = SE().compile(raw);
|
||||||
|
if (c.error) return { ok: false, reason: 'parse', value: null, message: 'Не понял ответ: ' + c.error };
|
||||||
|
var val = c.fn({});
|
||||||
|
if (!isFinite(val)) return { ok: false, reason: 'nan', value: val, message: 'Это не число.' };
|
||||||
|
var tol = (typeof problem.tol === 'number' && isFinite(problem.tol)) ? problem.tol : 0;
|
||||||
|
var ok = Math.abs(val - problem.answer) <= tol + 1e-9;
|
||||||
|
return { ok: ok, reason: ok ? null : 'wrong', value: val, message: ok ? 'Верно (в допуске)!' : 'Пока неверно.' };
|
||||||
|
}
|
||||||
|
|
||||||
/* Упрощение: ответ-выражение проверяем на эквивалентность сэмплингом. */
|
/* Упрощение: ответ-выражение проверяем на эквивалентность сэмплингом. */
|
||||||
function _checkEquiv(problem, raw) {
|
function _checkEquiv(problem, raw) {
|
||||||
var c = SE().compile(raw);
|
var c = SE().compile(raw);
|
||||||
|
|||||||
@@ -547,7 +547,7 @@
|
|||||||
/* гипотенуза по катетам (пифагорова тройка m,n) */
|
/* гипотенуза по катетам (пифагорова тройка m,n) */
|
||||||
{
|
{
|
||||||
id: 'pyth-hyp', topic: 'g-pyth', order: 1, subject: 'geometry', grade: 8, kind: 'compute',
|
id: 'pyth-hyp', topic: 'g-pyth', order: 1, subject: 'geometry', grade: 8, kind: 'compute',
|
||||||
title: 'Гипотенуза (Пифагор)',
|
title: 'Гипотенуза (Пифагор)', answerSym: 'c',
|
||||||
figure: { type: 'right-triangle', a: 'a', b: 'b', c: 'c', unknown: 'c' },
|
figure: { type: 'right-triangle', a: 'a', b: 'b', c: 'c', unknown: 'c' },
|
||||||
figurePrompt: 'Найдите гипотенузу прямоугольного треугольника (отмечена «?»).',
|
figurePrompt: 'Найдите гипотенузу прямоугольного треугольника (отмечена «?»).',
|
||||||
pick: { m: [2, 5], n: [1, 4] }, constraint: 'm > n',
|
pick: { m: [2, 5], n: [1, 4] }, constraint: 'm > n',
|
||||||
@@ -563,7 +563,7 @@
|
|||||||
/* катет по гипотенузе и катету */
|
/* катет по гипотенузе и катету */
|
||||||
{
|
{
|
||||||
id: 'pyth-leg', topic: 'g-pyth', order: 2, subject: 'geometry', grade: 8, kind: 'compute',
|
id: 'pyth-leg', topic: 'g-pyth', order: 2, subject: 'geometry', grade: 8, kind: 'compute',
|
||||||
title: 'Катет (Пифагор)',
|
title: 'Катет (Пифагор)', answerSym: 'b',
|
||||||
figure: { type: 'right-triangle', a: 'a', b: 'b', c: 'c', unknown: 'b' },
|
figure: { type: 'right-triangle', a: 'a', b: 'b', c: 'c', unknown: 'b' },
|
||||||
figurePrompt: 'Найдите неизвестный катет (отмечен «?»).',
|
figurePrompt: 'Найдите неизвестный катет (отмечен «?»).',
|
||||||
pick: { m: [2, 5], n: [1, 4] }, constraint: 'm > n',
|
pick: { m: [2, 5], n: [1, 4] }, constraint: 'm > n',
|
||||||
@@ -2088,7 +2088,7 @@
|
|||||||
/* сумма n членов арифметической прогрессии */
|
/* сумма n членов арифметической прогрессии */
|
||||||
{
|
{
|
||||||
id: 'prog-arith-sum', topic: 'progressions', order: 3, subject: 'algebra', grade: 9, kind: 'compute',
|
id: 'prog-arith-sum', topic: 'progressions', order: 3, subject: 'algebra', grade: 9, kind: 'compute',
|
||||||
title: 'Сумма арифм. прогрессии',
|
title: 'Сумма арифм. прогрессии', answerSym: 'S',
|
||||||
pick: { a: [-8, 12], d: [-6, 6], n: [4, 12] }, require: 'd != 0',
|
pick: { a: [-8, 12], d: [-6, 6], n: [4, 12] }, require: 'd != 0',
|
||||||
derive: { an: 'a + (n - 1)*d', sum: 'n*(a + an)/2' },
|
derive: { an: 'a + (n - 1)*d', sum: 'n*(a + an)/2' },
|
||||||
lhs: 'x', rhs: '{n}*({a} + {an})/2', display: 'Арифметическая прогрессия: a₁ = {a}, d = {d}. Найдите сумму первых {n} членов.',
|
lhs: 'x', rhs: '{n}*({a} + {an})/2', display: 'Арифметическая прогрессия: a₁ = {a}, d = {d}. Найдите сумму первых {n} членов.',
|
||||||
@@ -2173,7 +2173,7 @@
|
|||||||
/* сумма n членов геометрической прогрессии */
|
/* сумма n членов геометрической прогрессии */
|
||||||
{
|
{
|
||||||
id: 'prog-geom-sum', topic: 'progressions', order: 9, subject: 'algebra', grade: 9, kind: 'compute',
|
id: 'prog-geom-sum', topic: 'progressions', order: 9, subject: 'algebra', grade: 9, kind: 'compute',
|
||||||
title: 'Сумма геом. прогрессии',
|
title: 'Сумма геом. прогрессии', answerSym: 'S',
|
||||||
pick: { b: [1, 4], q: [2, 3], n: [2, 5] },
|
pick: { b: [1, 4], q: [2, 3], n: [2, 5] },
|
||||||
derive: { qn: 'q^n', sum: 'b*(q^n - 1)/(q - 1)' },
|
derive: { qn: 'q^n', sum: 'b*(q^n - 1)/(q - 1)' },
|
||||||
lhs: 'x', rhs: '{b}*({q}^{n} - 1)/({q} - 1)', display: 'Геометрическая прогрессия: b₁ = {b}, q = {q}. Найдите сумму первых {n} членов.',
|
lhs: 'x', rhs: '{b}*({q}^{n} - 1)/({q} - 1)', display: 'Геометрическая прогрессия: b₁ = {b}, q = {q}. Найдите сумму первых {n} членов.',
|
||||||
@@ -2187,7 +2187,7 @@
|
|||||||
/* текстовая задача (ряды кресел) */
|
/* текстовая задача (ряды кресел) */
|
||||||
{
|
{
|
||||||
id: 'prog-arith-word', topic: 'progressions', order: 10, subject: 'algebra', grade: 9, kind: 'compute',
|
id: 'prog-arith-word', topic: 'progressions', order: 10, subject: 'algebra', grade: 9, kind: 'compute',
|
||||||
title: 'Задача (ряды кресел)',
|
title: 'Задача (ряды кресел)', answerSym: 'S',
|
||||||
pick: { a: [10, 20], d: [2, 5], n: [5, 12] },
|
pick: { a: [10, 20], d: [2, 5], n: [5, 12] },
|
||||||
derive: { an: 'a + (n - 1)*d', sum: 'n*(a + an)/2' },
|
derive: { an: 'a + (n - 1)*d', sum: 'n*(a + an)/2' },
|
||||||
lhs: 'x', rhs: '{n}*({a} + {an})/2', display: 'В первом ряду зала {a} кресел, в каждом следующем на {d} больше. Сколько всего кресел в {n} рядах?',
|
lhs: 'x', rhs: '{n}*({a} + {an})/2', display: 'В первом ряду зала {a} кресел, в каждом следующем на {d} больше. Сколько всего кресел в {n} рядах?',
|
||||||
@@ -2659,7 +2659,7 @@
|
|||||||
/* найти сторону по площади (обратная) */
|
/* найти сторону по площади (обратная) */
|
||||||
{
|
{
|
||||||
id: 'area-rect-inverse', topic: 'g-area', order: 7, subject: 'geometry', grade: 8, kind: 'compute',
|
id: 'area-rect-inverse', topic: 'g-area', order: 7, subject: 'geometry', grade: 8, kind: 'compute',
|
||||||
title: 'Сторона по площади',
|
title: 'Сторона по площади', answerSym: 'b',
|
||||||
figure: { type: 'rectangle', w: 'a', h: 'b', unknown: 'h', area: 'S' },
|
figure: { type: 'rectangle', w: 'a', h: 'b', unknown: 'h', area: 'S' },
|
||||||
figurePrompt: 'Найдите неизвестную сторону прямоугольника.',
|
figurePrompt: 'Найдите неизвестную сторону прямоугольника.',
|
||||||
pick: { a: [2, 16], b: [2, 16] }, derive: { S: 'a*b', val: 'b' },
|
pick: { a: [2, 16], b: [2, 16] }, derive: { S: 'a*b', val: 'b' },
|
||||||
@@ -3036,6 +3036,153 @@
|
|||||||
{ note: 'Циферблат — 360°, каждый час — 30°. В {H}:00 между стрелками {ang0}°.', tex: '' },
|
{ note: 'Циферблат — 360°, каждый час — 30°. В {H}:00 между стрелками {ang0}°.', tex: '' },
|
||||||
{ note: 'Берём меньший угол (не больше 180°).', tex: 'x = {ans}' }
|
{ note: 'Берём меньший угол (не больше 180°).', tex: 'x = {ans}' }
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
/* ═══════════════════════════════════════════════════════════════════════
|
||||||
|
V4 C1 — новые форматы условий: choice (выбор), verify (верно/неверно),
|
||||||
|
estimate (прикидка в допуске). Разнообразие подачи во всех темах.
|
||||||
|
═══════════════════════════════════════════════════════════════════════ */
|
||||||
|
|
||||||
|
/* ── Выбор ответа ── */
|
||||||
|
|
||||||
|
/* площадь прямоугольника — выбор */
|
||||||
|
{
|
||||||
|
id: 'ch-area-rect', topic: 'g-area', order: 10, subject: 'geometry', grade: 8, kind: 'choice',
|
||||||
|
title: 'Площадь — выбор ответа',
|
||||||
|
figure: { type: 'rectangle', w: 'a', h: 'b' },
|
||||||
|
pick: { a: [3, 16], b: [3, 16] }, constraint: 'a != b',
|
||||||
|
derive: { val: 'a*b' }, answer: 'val', distractors: ['2*(a + b)', 'a + b', '2*a*b'], integerAnswer: true,
|
||||||
|
display: 'Чему равна площадь прямоугольника со сторонами {a} и {b}? Выберите ответ.',
|
||||||
|
solution: [
|
||||||
|
{ note: 'Площадь прямоугольника — произведение сторон (а не периметр!).', tex: 'S = {a} * {b}' },
|
||||||
|
{ note: 'Считаем.', tex: 'S = {val}' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
/* корень линейного уравнения — выбор */
|
||||||
|
{
|
||||||
|
id: 'ch-lin-basic', topic: 'linear-eq', order: 15, subject: 'algebra', grade: 7, kind: 'choice',
|
||||||
|
title: 'Корень уравнения — выбор',
|
||||||
|
pick: { a: [2, 9], b: [1, 20], root: [-9, 9] }, require: 'root != 0',
|
||||||
|
derive: { c: 'a*root + b' }, answer: 'root', distractors: ['-root', 'c - b', 'root + a'], integerAnswer: true,
|
||||||
|
display: 'Чему равен корень уравнения {a}x + {b} = {c}? Выберите ответ.',
|
||||||
|
solution: [
|
||||||
|
{ note: 'Переносим {b} вправо и делим на {a}.', tex: 'x = ({c} - {b}) / {a}' },
|
||||||
|
{ note: 'Получаем корень.', tex: 'x = {root}' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
/* процент от числа — выбор */
|
||||||
|
{
|
||||||
|
id: 'ch-pct-of', topic: 'percents', order: 10, subject: 'algebra', grade: 6, kind: 'choice',
|
||||||
|
title: 'Проценты — выбор ответа',
|
||||||
|
pick: { pidx: [2, 10], abase: [1, 15] },
|
||||||
|
derive: { p: 'pidx*5', a: 'abase*20', val: 'pidx*abase' }, answer: 'val',
|
||||||
|
distractors: ['p', 'val*2', 'a - val'], integerAnswer: true,
|
||||||
|
display: 'Сколько будет {p}% от числа {a}? Выберите ответ.',
|
||||||
|
solution: [
|
||||||
|
{ note: 'Процент — сотая доля: умножаем число на {p} и делим на 100.', tex: 'x = {a} * {p} / 100' },
|
||||||
|
{ note: 'Считаем.', tex: 'x = {val}' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
/* гипотенуза — выбор */
|
||||||
|
{
|
||||||
|
id: 'ch-pyth-hyp', topic: 'g-pyth', order: 7, subject: 'geometry', grade: 8, kind: 'choice',
|
||||||
|
title: 'Гипотенуза — выбор ответа',
|
||||||
|
figure: { type: 'right-triangle', a: 'a', b: 'b', c: 'c', unknown: 'c' },
|
||||||
|
pick: { m: [2, 5], n: [1, 4] }, constraint: 'm > n',
|
||||||
|
derive: { a: 'm*m - n*n', b: '2*m*n', c: 'm*m + n*n' }, answer: 'c',
|
||||||
|
distractors: ['a + b', 'c - 1', 'c + 2'], integerAnswer: true,
|
||||||
|
display: 'Чему равна гипотенуза прямоугольного треугольника с катетами {a} и {b}? Выберите ответ.',
|
||||||
|
solution: [
|
||||||
|
{ note: 'По теореме Пифагора c = √(a² + b²) (не сумма катетов!).', tex: 'c = sqrt({a}^2 + {b}^2)' },
|
||||||
|
{ note: 'Считаем.', tex: 'c = {c}' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
/* ── Верно / неверно ── */
|
||||||
|
|
||||||
|
/* сравнение дробей — верно/неверно */
|
||||||
|
{
|
||||||
|
id: 'vf-frac-compare', topic: 'fractions', order: 9, subject: 'algebra', grade: 6, kind: 'verify',
|
||||||
|
title: 'Сравнение дробей (верно?)',
|
||||||
|
pick: { a: [1, 7], b: [2, 9], c: [1, 7], d: [2, 9] }, constraint: 'a < b && c < d && a*d != c*b',
|
||||||
|
claim: 'a*d > c*b',
|
||||||
|
display: 'Верно ли, что {a}/{b} > {c}/{d}?',
|
||||||
|
solution: [
|
||||||
|
{ note: 'Сравниваем перекрёстные произведения: {a}·{d} и {c}·{b}.', tex: '' },
|
||||||
|
{ note: 'Первая дробь больше, если {a}·{d} больше {c}·{b}.', tex: '' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
/* делимость — верно/неверно */
|
||||||
|
{
|
||||||
|
id: 'vf-divisible', topic: 'gcd-lcm', order: 7, subject: 'algebra', grade: 5, kind: 'verify',
|
||||||
|
title: 'Делимость (верно?)',
|
||||||
|
pick: { N: [10, 99], k: [2, 9] }, derive: { rem: 'mod(N, k)' },
|
||||||
|
claim: 'mod(N, k) == 0',
|
||||||
|
display: 'Верно ли, что {N} делится нацело на {k}?',
|
||||||
|
solution: [
|
||||||
|
{ note: 'Остаток от деления {N} на {k} равен {rem}.', tex: '' },
|
||||||
|
{ note: 'Делится нацело только если остаток равен нулю.', tex: '' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
/* прямоугольный ли треугольник — верно/неверно (обратная т. Пифагора) */
|
||||||
|
{
|
||||||
|
id: 'vf-pyth', topic: 'g-pyth', order: 8, subject: 'geometry', grade: 8, kind: 'verify',
|
||||||
|
title: 'Прямоугольный ли? (верно?)',
|
||||||
|
pick: { m: [2, 5], n: [1, 4], yes: [0, 1] }, constraint: 'm > n',
|
||||||
|
derive: { a: 'm*m - n*n', b: '2*m*n', c: 'yes*(m*m + n*n) + (1 - yes)*(m*m + n*n + 1)', sumSq: '(m*m - n*n)^2 + (2*m*n)^2', cSq: '(yes*(m*m + n*n) + (1 - yes)*(m*m + n*n + 1))^2' },
|
||||||
|
claim: 'a*a + b*b == c*c',
|
||||||
|
display: 'Верно ли, что треугольник со сторонами {a}, {b} и {c} прямоугольный?',
|
||||||
|
solution: [
|
||||||
|
{ note: 'Проверяем обратную теорему Пифагора: {a}² + {b}² должно равняться {c}².', tex: '' },
|
||||||
|
{ note: '{a}² + {b}² = {sumSq}, а {c}² = {cSq}.', tex: '' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
/* проверка корня — верно/неверно */
|
||||||
|
{
|
||||||
|
id: 'vf-eq-root', topic: 'linear-eq', order: 16, subject: 'algebra', grade: 7, kind: 'verify',
|
||||||
|
title: 'Корень ли это? (верно?)',
|
||||||
|
pick: { a: [2, 6], b: [1, 12], r: [-6, 6], yes: [0, 1] }, require: 'r != 0',
|
||||||
|
derive: { c: 'yes*(a*r + b) + (1 - yes)*(a*r + b + 1)', lhsval: 'a*r + b' },
|
||||||
|
claim: 'a*r + b == c',
|
||||||
|
display: 'Верно ли, что x = {r} — корень уравнения {a}x + {b} = {c}?',
|
||||||
|
solution: [
|
||||||
|
{ note: 'Подставим x = {r}: {a}·({r}) + {b} = {lhsval}.', tex: '' },
|
||||||
|
{ note: 'Сравниваем с правой частью {c}.', tex: '' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
/* ── Прикидка (ответ в допуске) ── */
|
||||||
|
|
||||||
|
/* прикидка произведения */
|
||||||
|
{
|
||||||
|
id: 'est-product', topic: 'applied', order: 12, subject: 'algebra', grade: 6, kind: 'estimate',
|
||||||
|
title: 'Прикидка произведения',
|
||||||
|
pick: { a: [12, 89], b: [12, 89] }, derive: { val: 'a*b' }, tol: 'a*b*0.12',
|
||||||
|
lhs: 'x', rhs: '{a}*{b}', display: 'Оцените (приближённо) произведение {a} · {b}. Допускается близкий ответ.',
|
||||||
|
answerVar: 'x', answer: 'val',
|
||||||
|
solution: [
|
||||||
|
{ note: 'Округлим множители до десятков и перемножим. Точное значение:', tex: 'x = {a} * {b}' },
|
||||||
|
{ note: 'Получаем.', tex: 'x = {val}' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
/* прикидка процента */
|
||||||
|
{
|
||||||
|
id: 'est-percent', topic: 'percents', order: 11, subject: 'algebra', grade: 6, kind: 'estimate',
|
||||||
|
title: 'Прикидка процента',
|
||||||
|
pick: { p: [5, 95], a: [20, 400] }, derive: { val: 'p*a/100' }, tol: '(p*a/100)*0.12 + 1',
|
||||||
|
lhs: 'x', rhs: '{p}*{a}/100', display: 'Оцените (приближённо) {p}% от {a}. Допускается близкий ответ.',
|
||||||
|
answerVar: 'x', answer: 'val',
|
||||||
|
solution: [
|
||||||
|
{ note: 'Точное значение {p}% от {a}:', tex: 'x = {p} * {a} / 100' },
|
||||||
|
{ note: 'Подойдёт близкая прикидка.', tex: 'x = {val}' }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
];
|
];
|
||||||
@@ -3110,6 +3257,10 @@
|
|||||||
'ang-alt-exterior': 2, 'ang-coint-exterior': 2, 'ang-parallel-twostep': 3, 'ang-alt-solve': 3,
|
'ang-alt-exterior': 2, 'ang-coint-exterior': 2, 'ang-parallel-twostep': 3, 'ang-alt-solve': 3,
|
||||||
'ang-bisector': 1, 'ang-complementary': 1, 'ang-right-acute': 1, 'ang-parallelogram': 2,
|
'ang-bisector': 1, 'ang-complementary': 1, 'ang-right-acute': 1, 'ang-parallelogram': 2,
|
||||||
'ang-polygon-missing': 3, 'ang-triangle-ratio': 3, 'ang-clock': 2,
|
'ang-polygon-missing': 3, 'ang-triangle-ratio': 3, 'ang-clock': 2,
|
||||||
|
// V4 C1 — новые форматы (выбор/верно-неверно/прикидка)
|
||||||
|
'ch-area-rect': 1, 'ch-lin-basic': 1, 'ch-pct-of': 2, 'ch-pyth-hyp': 2,
|
||||||
|
'vf-frac-compare': 2, 'vf-divisible': 1, 'vf-pyth': 3, 'vf-eq-root': 2,
|
||||||
|
'est-product': 2, 'est-percent': 2,
|
||||||
'pyth-perimeter': 3, 'pyth-distance': 3, 'pyth-rect-diagonal': 2, 'pyth-space-diagonal': 3,
|
'pyth-perimeter': 3, 'pyth-distance': 3, 'pyth-rect-diagonal': 2, 'pyth-space-diagonal': 3,
|
||||||
'area-rect-inverse': 2, 'area-l-shape': 3, 'area-sector': 3,
|
'area-rect-inverse': 2, 'area-l-shape': 3, 'area-sector': 3,
|
||||||
'poly-diagonals': 2, 'poly-find-n': 3, 'poly-exterior-sum': 2,
|
'poly-diagonals': 2, 'poly-find-n': 3, 'poly-exterior-sum': 2,
|
||||||
|
|||||||
+61
-2
@@ -237,6 +237,15 @@
|
|||||||
.tr-fm-btn:hover { border-color: var(--g1); color: var(--accent-ink); }
|
.tr-fm-btn:hover { border-color: var(--g1); color: var(--accent-ink); }
|
||||||
.tr-fm-btn.on { color: #fff; border-color: transparent; background: linear-gradient(135deg, var(--g1), var(--g2)); box-shadow: 0 8px 16px -6px rgba(99,102,241,.5); }
|
.tr-fm-btn.on { color: #fff; border-color: transparent; background: linear-gradient(135deg, var(--g1), var(--g2)); box-shadow: 0 8px 16px -6px rgba(99,102,241,.5); }
|
||||||
|
|
||||||
|
/* ── варианты выбора (kind choice / verify) ── */
|
||||||
|
.tr-choices { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; max-width: 460px; margin: 0 auto 4px; }
|
||||||
|
.tr-choice-btn { font: inherit; font-size: 1.15rem; font-weight: 700; cursor: pointer; padding: 14px 16px; border-radius: 15px; border: 2px solid rgba(99,102,241,.22); background: #fbfbff; color: var(--ink); transition: .14s var(--ease); }
|
||||||
|
.tr-choice-btn:hover:not(:disabled) { border-color: var(--g1); background: #fff; transform: translateY(-1px); }
|
||||||
|
.tr-choice-btn:disabled { cursor: default; }
|
||||||
|
.tr-choice-btn.right { border-color: transparent; color: #fff; background: linear-gradient(135deg, #059669, #10b981); }
|
||||||
|
.tr-choice-btn.wrongpick { border-color: transparent; color: #fff; background: linear-gradient(135deg, #dc2626, #ef4444); }
|
||||||
|
#tr-choice-next { display: block; margin: 0 auto 4px; }
|
||||||
|
|
||||||
/* строка ответа */
|
/* строка ответа */
|
||||||
.tr-inrow { display: flex; gap: 10px; align-items: stretch; max-width: 460px; margin: 0 auto; }
|
.tr-inrow { display: flex; gap: 10px; align-items: stretch; max-width: 460px; margin: 0 auto; }
|
||||||
#tr-eqx { font-family: 'Cambria Math', serif; font-size: 1.55rem; font-weight: 600; color: var(--accent-ink); align-self: center; padding-left: 4px; }
|
#tr-eqx { font-family: 'Cambria Math', serif; font-size: 1.55rem; font-weight: 600; color: var(--accent-ink); align-self: center; padding-left: 4px; }
|
||||||
@@ -459,12 +468,14 @@
|
|||||||
<div class="tr-difficulty" id="tr-difficulty"></div>
|
<div class="tr-difficulty" id="tr-difficulty"></div>
|
||||||
|
|
||||||
<div id="tr-answerbox">
|
<div id="tr-answerbox">
|
||||||
<div class="tr-inrow">
|
<div class="tr-inrow" id="tr-inrow">
|
||||||
<span class="tr-eqx" id="tr-eqx">x =</span>
|
<span class="tr-eqx" id="tr-eqx">x =</span>
|
||||||
<input class="tr-input" id="tr-input" type="text" inputmode="text" autocomplete="off"
|
<input class="tr-input" id="tr-input" type="text" inputmode="text" autocomplete="off"
|
||||||
placeholder="ответ" aria-label="Ваш ответ"/>
|
placeholder="ответ" aria-label="Ваш ответ"/>
|
||||||
<button class="tr-btn tr-primary" id="tr-check" type="button">Проверить</button>
|
<button class="tr-btn tr-primary" id="tr-check" type="button">Проверить</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tr-choices" id="tr-choices" style="display:none"></div>
|
||||||
|
<button class="tr-btn tr-primary" id="tr-choice-next" type="button" style="display:none">Дальше</button>
|
||||||
<div class="tr-keypad" id="tr-keypad"></div>
|
<div class="tr-keypad" id="tr-keypad"></div>
|
||||||
<div class="tr-preview" id="tr-preview"></div>
|
<div class="tr-preview" id="tr-preview"></div>
|
||||||
<div class="tr-feedback" id="tr-feedback"></div>
|
<div class="tr-feedback" id="tr-feedback"></div>
|
||||||
@@ -924,20 +935,62 @@
|
|||||||
function applyInputMode() {
|
function applyInputMode() {
|
||||||
var k = cur && cur.kind;
|
var k = cur && cur.kind;
|
||||||
var multi = (k === 'roots' || k === 'simplify' || k === 'inequality' || k === 'system');
|
var multi = (k === 'roots' || k === 'simplify' || k === 'inequality' || k === 'system');
|
||||||
|
var isChoice = (k === 'choice' || k === 'verify'); // ответ кнопками-вариантами
|
||||||
var eqx = $('tr-eqx');
|
var eqx = $('tr-eqx');
|
||||||
if (eqx) {
|
if (eqx) {
|
||||||
eqx.style.display = multi ? 'none' : '';
|
eqx.style.display = (multi || isChoice) ? 'none' : '';
|
||||||
eqx.textContent = ((cur && (cur.answerSym || cur.answerVar)) || 'x') + ' ='; // P =/S =/x = по смыслу задачи
|
eqx.textContent = ((cur && (cur.answerSym || cur.answerVar)) || 'x') + ' ='; // P =/S =/x = по смыслу задачи
|
||||||
}
|
}
|
||||||
|
// переключение «ввод текста» ↔ «варианты выбора»
|
||||||
|
var inrow = $('tr-inrow'), kp = $('tr-keypad'), pv = $('tr-preview'), ch = $('tr-choices'), nx = $('tr-choice-next');
|
||||||
|
if (inrow) inrow.style.display = isChoice ? 'none' : '';
|
||||||
|
if (kp) kp.style.display = isChoice ? 'none' : '';
|
||||||
|
if (pv) pv.style.display = isChoice ? 'none' : '';
|
||||||
|
if (ch) ch.style.display = isChoice ? '' : 'none';
|
||||||
|
if (nx) nx.style.display = 'none';
|
||||||
$('tr-input').placeholder = (k === 'roots') ? 'корни через ;'
|
$('tr-input').placeholder = (k === 'roots') ? 'корни через ;'
|
||||||
: (k === 'simplify') ? 'упрощённое выражение'
|
: (k === 'simplify') ? 'упрощённое выражение'
|
||||||
: (k === 'inequality') ? ('напр. ' + (cur.answerVar || 'x') + ' < 3')
|
: (k === 'inequality') ? ('напр. ' + (cur.answerVar || 'x') + ' < 3')
|
||||||
: (k === 'system') ? 'напр. x = 2; y = 3'
|
: (k === 'system') ? 'напр. x = 2; y = 3'
|
||||||
|
: (k === 'estimate') ? 'примерно…'
|
||||||
: 'ответ';
|
: 'ответ';
|
||||||
var tog = $('tr-step-toggle'); if (tog) tog.style.display = canStep() ? '' : 'none';
|
var tog = $('tr-step-toggle'); if (tog) tog.style.display = canStep() ? '' : 'none';
|
||||||
var df = $('tr-difficulty'); if (df) df.style.display = (k === 'word') ? 'none' : '';
|
var df = $('tr-difficulty'); if (df) df.style.display = (k === 'word') ? 'none' : '';
|
||||||
renderDifficulty();
|
renderDifficulty();
|
||||||
}
|
}
|
||||||
|
// Варианты выбора (kind choice/verify): рисуем кнопки; клик = ответ.
|
||||||
|
function renderChoices() {
|
||||||
|
var box = $('tr-choices'); if (!box) return;
|
||||||
|
if (!cur || !cur.choices) { box.innerHTML = ''; return; }
|
||||||
|
box.innerHTML = cur.choices.map(function (c, i) {
|
||||||
|
return '<button class="tr-choice-btn" type="button" data-ci="' + i + '">' + esc(c.label) + '</button>';
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
function submitChoice(idx) {
|
||||||
|
if (answered || !cur || !cur.choices) return;
|
||||||
|
var c = cur.choices[idx]; if (!c) return;
|
||||||
|
var fb = $('tr-feedback');
|
||||||
|
setMode(true);
|
||||||
|
// показать раскраску вариантов
|
||||||
|
var btns = $('tr-choices').querySelectorAll('.tr-choice-btn');
|
||||||
|
for (var i = 0; i < btns.length; i++) {
|
||||||
|
btns[i].disabled = true;
|
||||||
|
if (cur.choices[i].correct) btns[i].classList.add('right');
|
||||||
|
else if (i === idx) btns[i].classList.add('wrongpick');
|
||||||
|
}
|
||||||
|
if (c.correct) {
|
||||||
|
fb.className = 'tr-feedback ok'; fb.innerHTML = ICON.ok + ' <span>Верно!</span>';
|
||||||
|
onSolved();
|
||||||
|
} else {
|
||||||
|
streak = 0; lastWrong = true;
|
||||||
|
fb.className = 'tr-feedback bad'; fb.innerHTML = ICON.bad + ' Неверно.';
|
||||||
|
$('tr-card').classList.add('tr-wrong');
|
||||||
|
recordAnswer(false); submitAttempt(false);
|
||||||
|
revealSolution();
|
||||||
|
}
|
||||||
|
var nx = $('tr-choice-next'); if (nx) nx.style.display = '';
|
||||||
|
updateStats();
|
||||||
|
}
|
||||||
// Текст ответа в фидбеке/раскрытии — по типу задачи.
|
// Текст ответа в фидбеке/раскрытии — по типу задачи.
|
||||||
var REL_SYM = { '<': '<', '>': '>', '<=': '≤', '>=': '≥' };
|
var REL_SYM = { '<': '<', '>': '>', '<=': '≤', '>=': '≥' };
|
||||||
function answerLabel() {
|
function answerLabel() {
|
||||||
@@ -976,6 +1029,7 @@
|
|||||||
renderFigure(cur); // чертёж для геометрии (иначе скрыт)
|
renderFigure(cur); // чертёж для геометрии (иначе скрыт)
|
||||||
renderFigureToggle(); // переключатель «Текст / На чертеже» (если уместен)
|
renderFigureToggle(); // переключатель «Текст / На чертеже» (если уместен)
|
||||||
applyInputMode();
|
applyInputMode();
|
||||||
|
renderChoices(); // варианты выбора (kind choice/verify)
|
||||||
var inp = $('tr-input');
|
var inp = $('tr-input');
|
||||||
inp.value = ''; inp.disabled = false;
|
inp.value = ''; inp.disabled = false;
|
||||||
var fb = $('tr-feedback'); fb.className = 'tr-feedback'; fb.textContent = '';
|
var fb = $('tr-feedback'); fb.className = 'tr-feedback'; fb.textContent = '';
|
||||||
@@ -1297,6 +1351,11 @@
|
|||||||
updateSession();
|
updateSession();
|
||||||
});
|
});
|
||||||
$('tr-check').addEventListener('click', check);
|
$('tr-check').addEventListener('click', check);
|
||||||
|
$('tr-choices').addEventListener('click', function (e) {
|
||||||
|
var b = e.target.closest('.tr-choice-btn'); if (!b) return;
|
||||||
|
submitChoice(+b.getAttribute('data-ci'));
|
||||||
|
});
|
||||||
|
$('tr-choice-next').addEventListener('click', advance);
|
||||||
$('tr-skip').addEventListener('click', newProblem);
|
$('tr-skip').addEventListener('click', newProblem);
|
||||||
// ИИ-репетитор: после неверного ответа — разбор ошибки; иначе — наводящая подсказка.
|
// ИИ-репетитор: после неверного ответа — разбор ошибки; иначе — наводящая подсказка.
|
||||||
// Опирается на известный ответ + шаги (движок), ИИ только ОБЪЯСНЯЕТ. Недоступен ИИ → решение.
|
// Опирается на известный ответ + шаги (движок), ИИ только ОБЪЯСНЯЕТ. Недоступен ИИ → решение.
|
||||||
|
|||||||
Reference in New Issue
Block a user