From 1dcde8790a52b13e56baa3253df52673cbb41395 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Fri, 26 Jun 2026 12:49:15 +0300 Subject: [PATCH] =?UTF-8?q?feat(trainer):=20=D1=82=D0=B5=D0=BC=D0=B0=20?= =?UTF-8?q?=C2=AB=D0=9E=D0=BA=D1=80=D1=83=D0=B6=D0=BD=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D1=8C=C2=BB=20+=20=D1=80=D0=B5=D0=B6=D0=B8=D0=BC=20=C2=AB?= =?UTF-8?q?=D1=87=D0=B8=D1=82=D0=B0=D1=82=D1=8C=20=D1=83=D1=81=D0=BB=D0=BE?= =?UTF-8?q?=D0=B2=D0=B8=D0=B5=20=D1=81=20=D1=87=D0=B5=D1=80=D1=82=D0=B5?= =?UTF-8?q?=D0=B6=D0=B0=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Окружность (новая геом-тема g-circle, 9 кл, π ≈ 3,14 → ответ — конечная десятичная дробь, ученик вводит её): 4 генератора — длина окружности по радиусу (2πr), по диаметру (πd), площадь круга (πr²), длина дуги ((n/360)·2πr, n=45·k, require r·k чётно → дробь конечная). Новые типы фигур: circle (радиус/диаметр/ заливка) и circle-arc (два радиуса под центральным углом + выделенная дуга). Режим «читать значения с чертежа»: у всех 19 геом-генераторов добавлено figurePrompt (краткое условие); переключатель «Текст / На чертеже» (#tr-figmode) на странице, выбор сохраняется в localStorage. В режиме чертежа числа берутся с фигуры, текст минимальный. Движок прокидывает figurePrompt; showStatement выбирает полный текст или промпт; renderFigureToggle показан только для задач с чертежом; для текстовых/алгебраических задач режим скрыт, проверка ответа от режима не зависит. На чертеж n-угольника выведено число сторон (n = …). Верификация: headless-смоук 6968 проверок / 1140 рендеров; ответы окружности конечные и принимаются движком (1600 экземпляров, округление до 2 знаков ok); inline-скрипт парсится; адверсариал-ревью — circle clean, toggle без high/medium (2 low устранены: скрытие тумблера при неудаче генерации + подпись n сторон). Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/js/trainer/_trainer_engine.js | 1 + frontend/js/trainer/figures.js | 52 ++++++++++++++++ frontend/js/trainer/generators.js | 85 +++++++++++++++++++++++++- frontend/trainer.html | 42 +++++++++++-- 4 files changed, 175 insertions(+), 5 deletions(-) diff --git a/frontend/js/trainer/_trainer_engine.js b/frontend/js/trainer/_trainer_engine.js index 02515a7..0de3a82 100644 --- a/frontend/js/trainer/_trainer_engine.js +++ b/frontend/js/trainer/_trainer_engine.js @@ -370,6 +370,7 @@ title: gen.title, kind: kind, figure: gen.figure || null, // спека чертежа (данные) — рисует TrainerFigures по params + figurePrompt: gen.figurePrompt || null, // краткое условие для режима «читать с чертежа» lhsExpr: lhsExpr, rhsExpr: rhsExpr, display: (kind === 'system' && system) diff --git a/frontend/js/trainer/figures.js b/frontend/js/trainer/figures.js index 3c34183..7e99311 100644 --- a/frontend/js/trainer/figures.js +++ b/frontend/js/trainer/figures.js @@ -375,6 +375,7 @@ var sp = pts.map(function (pt) { return f.px(pt); }); var body = pgon(sp); for (var j = 0; j < sp.length; j++) body += dot(sp[j], 2.3); + body += txt(f.px(P(0, 0)), 'n = ' + n, { fill: 'rgba(255,255,255,.92)', size: 12, weight: 700 }); // число сторон — читается с чертежа if (spec.markAngle) { var v = sp[0], prev = sp[(n - 1) % n], next = sp[1]; var arc = angleArc(v, prev, next, 16); @@ -418,6 +419,57 @@ var between = P((c1.x + c2.x) / 2, Math.min(c1.y, c2.y) - 6); body += txt(between, 'k = ' + fmt(k), { fill: ARC, size: 12.5, weight: 800 }); return body; + }, + + /* Окружность/круг. r ИЛИ d задают размер. show: + 'radius' — отрезок-радиус, подпись r; 'diameter' — отрезок-диаметр, подпись d; + 'area' — лёгкая заливка круга + радиус. (Искомая величина — длина/площадь — на + чертеже не отмечается, как у площадей: фигура показывает данные.) */ + 'circle': function (spec, p) { + var r = num(p, spec.r), d = num(p, spec.d); + var radius = (r != null) ? r : (d != null ? d / 2 : null); + if (!(radius > 0)) return null; + var show = spec.show || 'radius'; + var f = fit([P(-1, 0), P(1, 0), P(0, -1), P(0, 1)]); + var Cs = f.px(P(0, 0)), rad = f.s; + var body = ''; + body += dot(Cs, 2.6); + if (show === 'diameter') { + var Ld = P(Cs.x - rad, Cs.y), Rd = P(Cs.x + rad, Cs.y); + body += ln(Ld, Rd, { dash: true, stroke: DASH, w: 1.8 }); + body += dot(Ld, 2.2) + dot(Rd, 2.2); + body += txt(P(Cs.x, Cs.y - 11), 'd = ' + fmt(d != null ? d : radius * 2), { fill: '#fff', size: 12.5 }); + } else { + var ang = -Math.PI / 4; // радиус в верхне-правый сектор + var E = P(Cs.x + Math.cos(ang) * rad, Cs.y + Math.sin(ang) * rad); + body += ln(Cs, E, { w: 2 }); + body += txt(P((Cs.x + E.x) / 2 + 5, (Cs.y + E.y) / 2 - 7), 'r = ' + fmt(radius), { fill: '#fff', size: 12.5, anchor: 'start' }); + } + return body; + }, + + /* Сектор/дуга: окружность (бледная) + два радиуса под центральным углом angle°, + дуга выделена; подписаны угол и радиус r. (Длина дуги — искомая, на чертеже нет.) */ + 'circle-arc': function (spec, p) { + var r = num(p, spec.r), nAng = num(p, spec.angle); + if (!(r > 0) || !(nAng > 0) || nAng >= 360) return null; + var f = fit([P(-1, 0), P(1, 0), P(0, -1), P(0, 1)]); + var Cs = f.px(P(0, 0)), rad = f.s; + function onC(deg) { var a = deg2rad(deg); return P(Cs.x + Math.cos(a) * rad, Cs.y - Math.sin(a) * rad); } + var P0 = onC(0), P1 = onC(nAng); + var body = ''; + var seg = 28, ap = []; + for (var i = 0; i <= seg; i++) ap.push(onC(nAng * i / seg)); + body += ''; + body += ln(Cs, P0, { w: 2 }) + ln(Cs, P1, { w: 2 }); + body += dot(Cs, 2.6); + var amid = deg2rad(nAng / 2); + body += txt(P(Cs.x + Math.cos(amid) * rad * 0.36, Cs.y - Math.sin(amid) * rad * 0.36), fmt(nAng) + '°', { fill: '#fff', size: 12 }); + body += txt(P((Cs.x + P0.x) / 2, (Cs.y + P0.y) / 2 - 9), 'r = ' + fmt(r), { fill: '#fff', size: 12 }); + return body; } }; diff --git a/frontend/js/trainer/generators.js b/frontend/js/trainer/generators.js index 7d43e7e..26f40ba 100644 --- a/frontend/js/trainer/generators.js +++ b/frontend/js/trainer/generators.js @@ -41,7 +41,8 @@ { key: 'g-pyth', label: 'Пифагор', subject: 'geometry', grade: 8, order: 13 }, { key: 'g-area', label: 'Площади', subject: 'geometry', grade: 8, order: 14 }, { key: 'g-poly', label: 'Многоугольники', subject: 'geometry', grade: 8, order: 15 }, - { key: 'g-sim', label: 'Подобие', subject: 'geometry', grade: 8, order: 16 } + { key: 'g-sim', label: 'Подобие', subject: 'geometry', grade: 8, order: 16 }, + { key: 'g-circle', label: 'Окружность', subject: 'geometry', grade: 9, order: 17 } ]; var GENERATORS = [ @@ -501,6 +502,7 @@ id: 'ang-triangle', topic: 'g-angles', order: 1, subject: 'geometry', grade: 7, kind: 'compute', title: 'Третий угол треугольника', figure: { type: 'triangle-angles', angA: 'a', angB: 'b' }, + figurePrompt: 'Найдите неизвестный угол треугольника (в градусах).', pick: { a: [20, 80], b: [20, 80] }, derive: { val: '180 - a - b' }, require: 'val >= 15 && val <= 150', lhs: 'x', rhs: '180 - {a} - {b}', display: 'В треугольнике два угла равны {a}° и {b}°. Найдите третий угол (в градусах).', answerVar: 'x', answer: 'val', integerAnswer: true, @@ -515,6 +517,7 @@ id: 'ang-adjacent', topic: 'g-angles', order: 2, subject: 'geometry', grade: 7, kind: 'compute', title: 'Смежный угол', figure: { type: 'adjacent-angles', ang: 'a' }, + figurePrompt: 'Найдите неизвестный угол (в градусах).', pick: { a: [25, 155] }, derive: { val: '180 - a' }, lhs: 'x', rhs: '180 - {a}', display: 'Один из смежных углов равен {a}°. Найдите другой смежный с ним угол.', answerVar: 'x', answer: 'val', integerAnswer: true, @@ -529,6 +532,7 @@ id: 'ang-exterior', topic: 'g-angles', order: 3, subject: 'geometry', grade: 7, kind: 'compute', title: 'Внешний угол треугольника', figure: { type: 'triangle-angles', angA: 'a', angB: 'b', ext: true }, + figurePrompt: 'Найдите внешний угол треугольника (в градусах).', pick: { a: [20, 80], b: [20, 80] }, derive: { val: 'a + b' }, require: 'val <= 160', lhs: 'x', rhs: '{a} + {b}', display: 'Внешний угол треугольника равен сумме двух не смежных с ним внутренних углов. Эти углы равны {a}° и {b}°. Найдите внешний угол.', answerVar: 'x', answer: 'val', integerAnswer: true, @@ -545,6 +549,7 @@ id: 'pyth-hyp', topic: 'g-pyth', order: 1, subject: 'geometry', grade: 8, kind: 'compute', title: 'Гипотенуза (Пифагор)', figure: { type: 'right-triangle', a: 'a', b: 'b', c: 'c', unknown: 'c' }, + figurePrompt: 'Найдите гипотенузу прямоугольного треугольника (отмечена «?»).', 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' }, lhs: 'x', rhs: 'sqrt({a}^2 + {b}^2)', display: 'Катеты прямоугольного треугольника равны {a} и {b}. Найдите гипотенузу.', @@ -560,6 +565,7 @@ id: 'pyth-leg', topic: 'g-pyth', order: 2, subject: 'geometry', grade: 8, kind: 'compute', title: 'Катет (Пифагор)', figure: { type: 'right-triangle', a: 'a', b: 'b', c: 'c', unknown: 'b' }, + figurePrompt: 'Найдите неизвестный катет (отмечен «?»).', 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' }, lhs: 'x', rhs: 'sqrt({c}^2 - {a}^2)', display: 'Гипотенуза прямоугольного треугольника {c}, один катет {a}. Найдите второй катет.', @@ -577,6 +583,7 @@ id: 'area-rect', topic: 'g-area', order: 1, subject: 'geometry', grade: 8, kind: 'compute', title: 'Площадь прямоугольника', figure: { type: 'rectangle', w: 'a', h: 'b' }, + figurePrompt: 'Найдите площадь прямоугольника.', pick: { a: [2, 16], b: [2, 16] }, derive: { val: 'a*b' }, lhs: 'x', rhs: '{a}*{b}', display: 'Стороны прямоугольника {a} и {b}. Найдите его площадь.', answerVar: 'x', answer: 'val', integerAnswer: true, @@ -591,6 +598,7 @@ id: 'area-triangle', topic: 'g-area', order: 2, subject: 'geometry', grade: 8, kind: 'compute', title: 'Площадь треугольника', figure: { type: 'triangle-base-height', base: 'a', height: 'h' }, + figurePrompt: 'Найдите площадь треугольника.', pick: { a: [2, 16], h: [2, 16] }, require: 'mod(a*h, 2) == 0', derive: { val: 'a*h/2' }, lhs: 'x', rhs: '{a}*{h}/2', display: 'Основание треугольника {a}, высота к нему {h}. Найдите площадь.', @@ -606,6 +614,7 @@ id: 'area-square', topic: 'g-area', order: 3, subject: 'geometry', grade: 8, kind: 'compute', title: 'Площадь квадрата', figure: { type: 'square', a: 'a' }, + figurePrompt: 'Найдите площадь квадрата.', pick: { a: [2, 20] }, derive: { val: 'a*a' }, lhs: 'x', rhs: '{a}^2', display: 'Сторона квадрата {a}. Найдите его площадь.', answerVar: 'x', answer: 'val', integerAnswer: true, @@ -733,6 +742,7 @@ id: 'area-trapezoid', topic: 'g-area', order: 4, subject: 'geometry', grade: 8, kind: 'compute', title: 'Площадь трапеции', figure: { type: 'trapezoid', bottom: 'a', top: 'b', height: 'h' }, + figurePrompt: 'Найдите площадь трапеции.', pick: { a: [2, 14], b: [2, 14], h: [2, 12] }, require: 'mod((a + b)*h, 2) == 0', derive: { val: '(a + b)*h/2' }, lhs: 'x', rhs: '({a} + {b})*{h}/2', display: 'Основания трапеции {a} и {b}, высота {h}. Найдите площадь.', @@ -748,6 +758,7 @@ id: 'area-parallelogram', topic: 'g-area', order: 5, subject: 'geometry', grade: 8, kind: 'compute', title: 'Площадь параллелограмма', figure: { type: 'parallelogram', base: 'a', height: 'h' }, + figurePrompt: 'Найдите площадь параллелограмма.', pick: { a: [2, 16], h: [2, 14] }, derive: { val: 'a*h' }, lhs: 'x', rhs: '{a}*{h}', display: 'Сторона параллелограмма {a}, высота к ней {h}. Найдите площадь.', answerVar: 'x', answer: 'val', integerAnswer: true, @@ -762,6 +773,7 @@ id: 'area-rhombus', topic: 'g-area', order: 6, subject: 'geometry', grade: 8, kind: 'compute', title: 'Площадь ромба', figure: { type: 'rhombus', d1: 'd1', d2: 'd2' }, + figurePrompt: 'Найдите площадь ромба.', pick: { d1: [2, 16], d2: [2, 16] }, require: 'mod(d1*d2, 2) == 0', derive: { val: 'd1*d2/2' }, lhs: 'x', rhs: '{d1}*{d2}/2', display: 'Диагонали ромба {d1} и {d2}. Найдите площадь.', @@ -779,6 +791,7 @@ id: 'poly-angles-sum', topic: 'g-poly', order: 1, subject: 'geometry', grade: 8, kind: 'compute', title: 'Сумма углов многоугольника', figure: { type: 'regular-polygon', n: 'n' }, + figurePrompt: 'Найдите сумму углов многоугольника на чертеже (в градусах).', pick: { n: [3, 12] }, derive: { val: '180*(n - 2)' }, lhs: 'x', rhs: '180*({n} - 2)', display: 'Найдите сумму углов выпуклого {n}-угольника (в градусах).', answerVar: 'x', answer: 'val', integerAnswer: true, @@ -793,6 +806,7 @@ id: 'poly-regular-angle', topic: 'g-poly', order: 2, subject: 'geometry', grade: 8, kind: 'compute', title: 'Угол правильного многоугольника', figure: { type: 'regular-polygon', n: 'n', markAngle: true }, + figurePrompt: 'Найдите угол правильного многоугольника на чертеже (в градусах).', pick: { n: [3, 20] }, require: 'mod(180*(n - 2), n) == 0', derive: { val: '180*(n - 2)/n' }, lhs: 'x', rhs: '180*({n} - 2)/{n}', display: 'Найдите величину угла правильного {n}-угольника (в градусах).', @@ -810,6 +824,7 @@ id: 'sim-side', topic: 'g-sim', order: 1, subject: 'geometry', grade: 8, kind: 'compute', title: 'Сторона по коэффициенту', figure: { type: 'two-similar', side: 'a', k: 'k', mode: 'side' }, + figurePrompt: 'Найдите сходственную сторону подобного треугольника (отмечена «?»).', pick: { a: [2, 15], k: [2, 5] }, derive: { val: 'a*k' }, lhs: 'x', rhs: '{a}*{k}', display: 'Треугольники подобны с коэффициентом {k}. Сторона первого равна {a}. Найдите сходственную сторону второго.', answerVar: 'x', answer: 'val', integerAnswer: true, @@ -824,6 +839,7 @@ id: 'sim-perimeter', topic: 'g-sim', order: 2, subject: 'geometry', grade: 8, kind: 'compute', title: 'Периметр подобной фигуры', figure: { type: 'two-similar', perim: 'p', k: 'k', mode: 'perimeter' }, + figurePrompt: 'Найдите периметр подобной фигуры (отмечена «?»).', pick: { p: [5, 30], k: [2, 5] }, derive: { val: 'p*k' }, lhs: 'x', rhs: '{p}*{k}', display: 'Фигуры подобны с коэффициентом {k}. Периметр первой равен {p}. Найдите периметр второй.', answerVar: 'x', answer: 'val', integerAnswer: true, @@ -991,6 +1007,71 @@ { note: 'Перемножаем модули; знак минус — если множители разных знаков, плюс — если одинаковых:', tex: 'x = {a}*{b}' }, { note: 'Получаем:', tex: 'x = {ans}' } ] + }, + + /* ═══ Тема: Окружность (геометрия, 9 класс) ═══ + Считаем с π ≈ 3,14 (как в учебниках) → ответ — КОНЕЧНАЯ десятичная дробь, + ученик вводит её (SimExpr принимает «31.4»). Чертёж показывает радиус/диаметр/угол. */ + + /* длина окружности по радиусу: C = 2πr */ + { + id: 'circ-length', topic: 'g-circle', order: 1, subject: 'geometry', grade: 9, kind: 'compute', + title: 'Длина окружности', + figure: { type: 'circle', r: 'r', show: 'radius' }, + figurePrompt: 'Найдите длину окружности (π ≈ 3,14).', + pick: { r: [1, 20] }, derive: { val: '2*3.14*r' }, + lhs: 'x', rhs: '2*3.14*{r}', display: 'Радиус окружности равен {r}. Найдите длину окружности (π ≈ 3,14).', + answerVar: 'x', answer: 'val', + solution: [ + { note: 'Длина окружности вычисляется по формуле C = 2πr. Подставляем r = {r} и π ≈ 3,14:', tex: 'x = 2*3.14*{r}' }, + { note: 'Считаем:', tex: 'x = {ans}' } + ] + }, + + /* длина окружности по диаметру: C = πd */ + { + id: 'circ-diam', topic: 'g-circle', order: 2, subject: 'geometry', grade: 9, kind: 'compute', + title: 'Длина по диаметру', + figure: { type: 'circle', d: 'd', show: 'diameter' }, + figurePrompt: 'Найдите длину окружности (π ≈ 3,14).', + pick: { d: [2, 30] }, derive: { val: '3.14*d' }, + lhs: 'x', rhs: '3.14*{d}', display: 'Диаметр окружности равен {d}. Найдите длину окружности (π ≈ 3,14).', + answerVar: 'x', answer: 'val', + solution: [ + { note: 'Длину окружности можно найти через диаметр: C = πd. Подставляем d = {d} и π ≈ 3,14:', tex: 'x = 3.14*{d}' }, + { note: 'Считаем:', tex: 'x = {ans}' } + ] + }, + + /* площадь круга: S = πr² */ + { + id: 'circ-area', topic: 'g-circle', order: 3, subject: 'geometry', grade: 9, kind: 'compute', + title: 'Площадь круга', + figure: { type: 'circle', r: 'r', show: 'area' }, + figurePrompt: 'Найдите площадь круга (π ≈ 3,14).', + pick: { r: [1, 15] }, derive: { val: '3.14*r^2' }, + lhs: 'x', rhs: '3.14*{r}^2', display: 'Радиус круга равен {r}. Найдите его площадь (π ≈ 3,14).', + answerVar: 'x', answer: 'val', + solution: [ + { note: 'Площадь круга вычисляется по формуле S = πr². Подставляем r = {r} и π ≈ 3,14:', tex: 'x = 3.14*{r}^2' }, + { note: 'Считаем:', tex: 'x = {ans}' } + ] + }, + + /* длина дуги: L = (n/360)·2πr; n = 45·k, требуем конечную дробь (r·k чётно) */ + { + id: 'circ-arc', topic: 'g-circle', order: 4, subject: 'geometry', grade: 9, kind: 'compute', + title: 'Длина дуги', + figure: { type: 'circle-arc', r: 'r', angle: 'n' }, + figurePrompt: 'Найдите длину дуги (π ≈ 3,14).', + pick: { r: [3, 12], k: [1, 7] }, require: 'mod(r*k, 2) == 0', + derive: { n: '45*k', val: '45*k/360*2*3.14*r' }, + lhs: 'x', rhs: '{n}/360*2*3.14*{r}', display: 'Дуга окружности радиуса {r} опирается на центральный угол {n}°. Найдите длину дуги (π ≈ 3,14).', + answerVar: 'x', answer: 'val', + solution: [ + { note: 'Длина дуги — это часть длины окружности: L = (n/360)·2πr. Здесь n = {n}°, r = {r}, π ≈ 3,14:', tex: 'x = {n}/360*2*3.14*{r}' }, + { note: 'Считаем:', tex: 'x = {ans}' } + ] } ]; @@ -1031,6 +1112,8 @@ 'area-parallelogram': 2, 'area-trapezoid': 3, 'area-rhombus': 2, // Геометрия — Многоугольники / Подобие 'poly-angles-sum': 1, 'poly-regular-angle': 2, 'sim-side': 1, 'sim-perimeter': 2, + // Геометрия — Окружность + 'circ-length': 1, 'circ-diam': 1, 'circ-area': 2, 'circ-arc': 3, // НОД/НОК / Дроби / Десятичные / Отрицательные 'gcd-pair': 1, 'lcm-pair': 2, 'frac-of-number': 1, 'frac-add-same': 2, diff --git a/frontend/trainer.html b/frontend/trainer.html index 9bf51c0..e28c8b2 100644 --- a/frontend/trainer.html +++ b/frontend/trainer.html @@ -230,6 +230,13 @@ .tr-diff-btn:hover { border-color: var(--g1); color: var(--accent-ink); } .tr-diff-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-figmode { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; justify-content: center; margin-bottom: 16px; } + .tr-fm-label { font-size: .68rem; font-weight: 800; color: var(--ink-faint); text-transform: uppercase; letter-spacing: .07em; margin-right: 4px; } + .tr-fm-btn { font: inherit; font-size: .8rem; font-weight: 800; cursor: pointer; padding: 6px 14px; border-radius: 99px; border: 1px solid var(--line); background: #fff; color: var(--ink-soft); transition: .14s var(--ease); } + .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-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; } @@ -448,6 +455,7 @@
+
@@ -600,6 +608,22 @@ if (svg) { box.innerHTML = svg; box.style.display = ''; } else { box.innerHTML = ''; box.style.display = 'none'; } } + // Условие: полный текст ИЛИ краткий промпт (числа читаются с чертежа). + function showStatement(problem) { + var eq = $('tr-eq'); if (!eq || !problem) return; + var useFig = figureMode && problem.figure && problem.figurePrompt; + if (useFig) { eq.classList.add('tr-eq-text'); setMath(eq, null, problem.figurePrompt, true); } + else { eq.classList.toggle('tr-eq-text', !problem.latex); setMath(eq, problem.latex, problem.display, true); } + } + // Переключатель «Текст / На чертеже» — только для задач с чертежом и кратким условием. + function renderFigureToggle() { + var box = $('tr-figmode'); if (!box) return; + if (!(cur && cur.figure && cur.figurePrompt)) { box.style.display = 'none'; box.innerHTML = ''; return; } + box.style.display = ''; + box.innerHTML = 'Условие' + + '' + + ''; + } var topics = (TG.topics ? TG.topics() : [{ key: null, label: 'Задачи' }]).concat([{ key: 'word', label: 'Текстовые задачи', word: true }]); var isTeacher = !!(ip && ip.isTeacher); @@ -608,6 +632,8 @@ var diffMode = 'auto'; // уровень сложности: 'auto' | 1 | 2 | 3 (= структурный вариант) var pinned = null; // закреплённый навык (id) при явном клике по чипу var customGens = []; // пользовательские генераторы (P13), тема «Авторские» + var figureMode = false; // «читать условие с чертежа» (числа на фигуре, текст краткий) + try { figureMode = localStorage.getItem('tr-figure-mode') === '1'; } catch (e) {} function skillKey(g) { return g.skill || g.id; } function skillsOf(topicKey) { if (topicKey === 'custom') return customGens; @@ -641,6 +667,7 @@ function serveWordProblem() { var eq = $('tr-eq'); eq.classList.add('tr-eq-text'); renderFigure(null); // текстовые задачи — без чертежа + var fmb = $('tr-figmode'); if (fmb) { fmb.style.display = 'none'; fmb.innerHTML = ''; } $('tr-solution').style.display = 'none'; $('tr-solution').innerHTML = ''; var fb = $('tr-feedback'); fb.className = 'tr-feedback'; fb.textContent = ''; if (!wordPool.length) { @@ -937,14 +964,13 @@ // strict:false + несколько попыток на случай редкой неудачи с ограничениями cur = null; for (var i = 0; i < 6 && !cur; i++) cur = TE.instantiate(curGen, { seed: randSeed(), strict: false }); - if (!cur) { $('tr-eq').textContent = 'Не удалось сгенерировать задачу'; return; } + if (!cur) { $('tr-eq').textContent = 'Не удалось сгенерировать задачу'; renderFigure(null); renderFigureToggle(); return; } renderSkills(); // подсветить активный навык (мог смениться вместе с уровнем) $('tr-skill').textContent = curGen.title; - var eq = $('tr-eq'); - eq.classList.toggle('tr-eq-text', !cur.latex); // текстовый prompt (проценты/упрощение) — другим шрифтом - setMath(eq, cur.latex, cur.display, true); + showStatement(cur); // условие: полный текст ИЛИ краткий промпт (числа на чертеже) renderFigure(cur); // чертёж для геометрии (иначе скрыт) + renderFigureToggle(); // переключатель «Текст / На чертеже» (если уместен) applyInputMode(); var inp = $('tr-input'); inp.value = ''; inp.disabled = false; @@ -1202,6 +1228,14 @@ $('tr-teacher').addEventListener('click', function (e) { if (e.target === $('tr-teacher')) $('tr-teacher').style.display = 'none'; }); $('tr-analytics-btn').addEventListener('click', openAnalytics); $('tr-builder-btn').addEventListener('click', function () { location.href = '/trainer-builder'; }); + $('tr-figmode').addEventListener('click', function (e) { + var b = e.target.closest('.tr-fm-btn'); if (!b) return; + var v = b.getAttribute('data-fm') === '1'; + if (v === figureMode) return; + figureMode = v; + try { localStorage.setItem('tr-figure-mode', v ? '1' : '0'); } catch (_) {} + showStatement(cur); renderFigureToggle(); + }); $('tr-difficulty').addEventListener('click', function (e) { var b = e.target.closest('.tr-diff-btn'); if (!b) return; var d = b.getAttribute('data-d');