diff --git a/frontend/js/trainer/figures.js b/frontend/js/trainer/figures.js
index 7e99311..91b40b4 100644
--- a/frontend/js/trainer/figures.js
+++ b/frontend/js/trainer/figures.js
@@ -177,6 +177,21 @@
};
/* ════════════════ ТИПЫ ФИГУР ════════════════ */
+ // Штрих равенства на середине отрезка a-b (метка равных сторон).
+ function tick(a, b) {
+ var m = mid(a, b), u = norm(sub(b, a)), nrm = P(-u.y, u.x);
+ return '';
+ }
+ // Окружность как строка SVG (центр-экран Cs, радиус rad px).
+ function circleSvg(Cs, rad, opt) {
+ opt = opt || {};
+ return '';
+ }
+
var TYPES = {
/* Прямоугольный треугольник (Пифагор).
@@ -270,8 +285,14 @@
var f = fit([A, B, C, D]);
var As = f.px(A), Bs = f.px(B), Cs = f.px(C), Ds = f.px(D), cen = f.px(P(w / 2, h / 2));
var body = pgon([As, Bs, Cs, Ds]);
- body += edgeLabel(As, Bs, cen, fmt(w), {});
- body += edgeLabel(Bs, Cs, cen, fmt(h), {});
+ body += edgeLabel(As, Bs, cen, lbl(w, 'w', spec.unknown), unkOpt('w', spec.unknown));
+ body += edgeLabel(Bs, Cs, cen, lbl(h, 'h', spec.unknown), unkOpt('h', spec.unknown));
+ if (spec.diagonal) { // пунктирная диагональ с «?»
+ body += ln(As, Cs, { dash: true, stroke: DASH, w: 1.8 });
+ body += txt(P((As.x + Cs.x) / 2 + 8, (As.y + Cs.y) / 2 - 8), '?', { fill: UNK, size: 15, weight: 800, anchor: 'start' });
+ }
+ var area = num(p, spec.area); // подпись площади в центре (для обратной задачи)
+ if (area != null) body += txt(cen, 'S = ' + fmt(area), { fill: '#fff', size: 12.5 });
return body;
},
@@ -391,7 +412,8 @@
var k = num(p, spec.k);
if (!(k > 0)) return null;
var mode = spec.mode || 'side';
- var known = (mode === 'perimeter') ? num(p, spec.perim) : num(p, spec.side);
+ var known = (mode === 'perimeter') ? num(p, spec.perim) : (mode === 'area' ? num(p, spec.area) : num(p, spec.side));
+ var side2 = num(p, spec.side2); // если задана — правая сторона подписана числом, а не «?»
var vk = Math.min(1.85, Math.max(1.15, k)); // визуальный масштаб (не буквальный k)
// базовый треугольник (форма), две копии бок о бок
var shape = [P(0, 0), P(1.0, 0), P(0.35, 0.85)];
@@ -407,17 +429,21 @@
var body = pgon(s1) + pgon(s2);
var c1 = f.px(P((t1[0].x + t1[1].x + t1[2].x) / 3, (t1[0].y + t1[1].y + t1[2].y) / 3));
var c2 = f.px(P((t2[0].x + t2[1].x + t2[2].x) / 3, (t2[0].y + t2[1].y + t2[2].y) / 3));
- if (mode === 'perimeter') {
- body += txt(P(c1.x, c1.y), (known != null ? 'P=' + fmt(known) : 'P'), { fill: '#fff', size: 12.5 });
+ if (mode === 'perimeter' || mode === 'area') {
+ var pref = (mode === 'area') ? 'S=' : 'P=';
+ body += txt(P(c1.x, c1.y), (known != null ? pref + fmt(known) : pref.charAt(0)), { fill: '#fff', size: 12.5 });
body += txt(P(c2.x, c2.y), '?', { fill: UNK, size: 16, weight: 800 });
} else {
// подпись нижней (сходственной) стороны каждого треугольника
body += edgeLabel(s1[0], s1[1], c1, (known != null ? fmt(known) : ''), {});
- body += edgeLabel(s2[0], s2[1], c2, '?', { fill: UNK, size: 15, weight: 800 });
+ body += edgeLabel(s2[0], s2[1], c2, (side2 != null ? fmt(side2) : '?'),
+ side2 != null ? { fill: '#fff', size: 13 } : { fill: UNK, size: 15, weight: 800 });
+ }
+ // коэффициент подобия между фигурами (скрывается, если k — искомая величина)
+ if (!spec.hideK) {
+ 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 });
}
- // коэффициент подобия между фигурами
- 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;
},
@@ -470,6 +496,190 @@
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;
+ },
+
+ /* ── V4.1 группа 6: новые геометрические фигуры ── */
+
+ /* Параллельные прямые и секущая; данный угол a° сверху, искомый «?» снизу. */
+ 'parallel-lines-transversal': function (spec, p) {
+ var a = num(p, spec.given);
+ if (!(a > 0)) return null;
+ var rel = spec.rel || 'corresponding';
+ var L1a = P(-1.3, 1), L1b = P(1.3, 1), L2a = P(-1.3, -1), L2b = P(1.3, -1);
+ var Sx = P(-0.8, -1.7), Ex = P(0.8, 1.7);
+ var T1 = P(0.5, 1), T2 = P(-0.5, -1);
+ var f = fit([L1a, L1b, L2a, L2b, Sx, Ex]);
+ var body = ln(f.px(L1a), f.px(L1b), { w: 2.4 }) + ln(f.px(L2a), f.px(L2b), { w: 2.4 }) + ln(f.px(Sx), f.px(Ex), { w: 2.2 });
+ var t1 = f.px(T1), t2 = f.px(T2);
+ body += dot(t1) + dot(t2);
+ var a1 = angleArc(t1, f.px(P(1.3, 1)), f.px(Ex), 20);
+ body += a1.path + txt(a1.labelPos, fmt(a) + '°', { fill: '#fff', size: 12 });
+ var arm = (rel === 'alternate') ? f.px(P(-1.3, -1)) : f.px(P(1.3, -1));
+ var arm2 = (rel === 'cointerior') ? f.px(Sx) : f.px(Ex);
+ var a2 = angleArc(t2, arm, arm2, 20);
+ body += a2.path + txt(a2.labelPos, '?', { fill: UNK, size: 15, weight: 800 });
+ return body;
+ },
+
+ /* Равнобедренный треугольник: угол при вершине apex°, базовый угол «?», штрихи на равных сторонах. */
+ 'isosceles': function (spec, p) {
+ var apex = num(p, spec.apex);
+ if (!(apex > 0)) return null;
+ var T = P(0, 1), Lp = P(-0.75, -0.7), Rp = P(0.75, -0.7);
+ var f = fit([T, Lp, Rp]);
+ var Ts = f.px(T), Ls = f.px(Lp), Rs = f.px(Rp);
+ var body = pgon([Ts, Ls, Rs]) + dot(Ts) + dot(Ls) + dot(Rs);
+ var aA = angleArc(Ts, Ls, Rs, 20);
+ body += aA.path + txt(aA.labelPos, fmt(apex) + '°', { fill: '#fff', size: 12 });
+ var aL = angleArc(Ls, Rs, Ts, 18);
+ body += aL.path + txt(aL.labelPos, '?', { fill: UNK, size: 15, weight: 800 });
+ body += tick(Ts, Ls) + tick(Ts, Rs);
+ return body;
+ },
+
+ /* Вертикальные углы: две пересекающиеся прямые; данный угол a°, противоположный «?». */
+ 'vertical-angles': function (spec, p) {
+ var a = num(p, spec.given);
+ if (!(a > 0)) return null;
+ var A1 = P(-1, -0.55), A2 = P(1, 0.55), B1 = P(-1, 0.55), B2 = P(1, -0.55);
+ var f = fit([A1, A2, B1, B2]);
+ var a1 = f.px(A1), a2 = f.px(A2), b1 = f.px(B1), b2 = f.px(B2), O = f.px(P(0, 0));
+ var body = ln(a1, a2, { w: 2.4 }) + ln(b1, b2, { w: 2.4 }) + dot(O);
+ var arcR = angleArc(O, a2, b2, 20);
+ body += arcR.path + txt(arcR.labelPos, fmt(a) + '°', { fill: '#fff', size: 12 });
+ var arcL = angleArc(O, a1, b1, 20);
+ body += arcL.path + txt(arcL.labelPos, '?', { fill: UNK, size: 15, weight: 800 });
+ return body;
+ },
+
+ /* Расстояние между точками A,B на координатной сетке: катеты пунктиром, гипотенуза «?». */
+ 'points-distance': function (spec, p) {
+ var x1 = num(p, spec.x1), y1 = num(p, spec.y1), x2 = num(p, spec.x2), y2 = num(p, spec.y2);
+ if (x1 == null || y1 == null || x2 == null || y2 == null) return null;
+ var pad = 1;
+ var minx = Math.min(x1, x2, 0) - pad, maxx = Math.max(x1, x2, 0) + pad;
+ var miny = Math.min(y1, y2, 0) - pad, maxy = Math.max(y1, y2, 0) + pad;
+ var f = fit([P(minx, miny), P(maxx, maxy)]);
+ var A = f.px(P(x1, y1)), B = f.px(P(x2, y2)), K = f.px(P(x2, y1));
+ var body = ln(f.px(P(minx, 0)), f.px(P(maxx, 0)), { stroke: 'rgba(255,255,255,.45)', w: 1.5 });
+ body += ln(f.px(P(0, miny)), f.px(P(0, maxy)), { stroke: 'rgba(255,255,255,.45)', w: 1.5 });
+ body += ln(A, K, { dash: true, stroke: DASH, w: 1.8 }) + ln(K, B, { dash: true, stroke: DASH, w: 1.8 });
+ body += ln(A, B, { w: 2.6 }) + rightAngle(K, A, B, 10) + dot(A) + dot(B);
+ body += txt(P(A.x - 6, A.y + 12), 'A', { fill: '#fff', size: 12, anchor: 'end' });
+ body += txt(P(B.x + 6, B.y - 10), 'B', { fill: '#fff', size: 12, anchor: 'start' });
+ body += txt(P((A.x + B.x) / 2 + 8, (A.y + B.y) / 2 - 8), '?', { fill: UNK, size: 14, weight: 800, anchor: 'start' });
+ return body;
+ },
+
+ /* Прямоугольный параллелепипед (кабинетная проекция): рёбра a,b,c; диагональ «?». */
+ 'space-diagonal-box': function (spec, p) {
+ var a = num(p, spec.a), b = num(p, spec.b), c = num(p, spec.c);
+ if (!(a > 0) || !(b > 0) || !(c > 0)) return null;
+ var ox = b * 0.5, oy = b * 0.42;
+ var A = P(0, 0), B = P(a, 0), C = P(a, c), D = P(0, c);
+ var A2 = P(ox, oy), B2 = P(a + ox, oy), C2 = P(a + ox, c + oy), D2 = P(ox, c + oy);
+ var f = fit([A, B, C, D, A2, B2, C2, D2]);
+ function g(q) { return f.px(q); }
+ var as = g(A), bs = g(B), cs = g(C), ds = g(D), a2 = g(A2), b2 = g(B2), c2 = g(C2), d2 = g(D2);
+ var body = pgon([as, bs, cs, ds]);
+ body += ln(bs, b2, { w: 2 }) + ln(cs, c2, { w: 2 }) + ln(ds, d2, { w: 2 });
+ body += ln(b2, c2, { w: 2 }) + ln(c2, d2, { w: 2 });
+ body += ln(a2, b2, { w: 1.8, stroke: DASH }) + ln(a2, d2, { w: 1.8, stroke: DASH }) + ln(as, a2, { w: 1.8, stroke: DASH });
+ body += ln(as, c2, { dash: true, stroke: ARC, w: 2.4 });
+ body += dot(as) + dot(c2);
+ body += edgeLabel(as, bs, cs, fmt(a), {});
+ body += edgeLabel(as, ds, bs, fmt(c), {});
+ body += txt(P((as.x + a2.x) / 2 - 6, (as.y + a2.y) / 2 + 8), fmt(b), { fill: '#fff', size: 12, anchor: 'end' });
+ body += txt(P((as.x + c2.x) / 2 + 8, (as.y + c2.y) / 2), '?', { fill: UNK, size: 14, weight: 800, anchor: 'start' });
+ return body;
+ },
+
+ /* L-образная фигура: размеры W,H и вырез cw×ch (правый верхний угол). */
+ 'l-shape': function (spec, p) {
+ var W = num(p, spec.W), H = num(p, spec.H), cw = num(p, spec.cw), ch = num(p, spec.ch);
+ if (!(W > 0) || !(H > 0) || !(cw > 0) || !(ch > 0) || cw >= W || ch >= H) return null;
+ var pts = [P(0, 0), P(W, 0), P(W, H - ch), P(W - cw, H - ch), P(W - cw, H), P(0, H)];
+ var f = fit(pts);
+ var sp = pts.map(function (q) { return f.px(q); });
+ var cen = f.px(P(W * 0.32, H * 0.4));
+ var body = pgon(sp);
+ body += edgeLabel(sp[0], sp[1], cen, fmt(W), {}); // низ
+ body += edgeLabel(sp[0], sp[5], cen, fmt(H), {}); // лево
+ body += edgeLabel(sp[2], sp[3], cen, fmt(cw), {}); // вырез — горизонталь
+ body += edgeLabel(sp[3], sp[4], cen, fmt(ch), {}); // вырез — вертикаль
+ return body;
+ },
+
+ /* Теорема Фалеса: треугольник + параллельная база; отрезки a,b слева, c и «?» справа. */
+ 'thales-parallel': function (spec, p) {
+ var a = num(p, spec.a), b = num(p, spec.b), c = num(p, spec.c);
+ if (!(a > 0) || !(b > 0) || !(c > 0)) return null;
+ var T = P(0, 1.1), Lp = P(-0.95, -0.7), Rp = P(0.95, -0.7), fr = 0.45;
+ var PL = P(T.x + (Lp.x - T.x) * fr, T.y + (Lp.y - T.y) * fr);
+ var PR = P(T.x + (Rp.x - T.x) * fr, T.y + (Rp.y - T.y) * fr);
+ var f = fit([T, Lp, Rp]);
+ var ts = f.px(T), ls = f.px(Lp), rs = f.px(Rp), pl = f.px(PL), pr = f.px(PR);
+ var body = pgon([ts, ls, rs]) + ln(pl, pr, { w: 2.4, stroke: ARC }) + dot(pl) + dot(pr);
+ body += txt(P((ts.x + pl.x) / 2 - 8, (ts.y + pl.y) / 2), fmt(a), { fill: '#fff', size: 12, anchor: 'end' });
+ body += txt(P((pl.x + ls.x) / 2 - 8, (pl.y + ls.y) / 2), fmt(b), { fill: '#fff', size: 12, anchor: 'end' });
+ body += txt(P((ts.x + pr.x) / 2 + 8, (ts.y + pr.y) / 2), fmt(c), { fill: '#fff', size: 12, anchor: 'start' });
+ body += txt(P((pr.x + rs.x) / 2 + 8, (pr.y + rs.y) / 2), '?', { fill: UNK, size: 14, weight: 800, anchor: 'start' });
+ return body;
+ },
+
+ /* Вписанный и центральный угол: центральный a° при O, вписанный «?» при M. */
+ 'inscribed-central-angle': function (spec, p) {
+ var a = num(p, spec.central);
+ if (!(a > 0) || a >= 320) return null;
+ var f = fit([P(-1, 0), P(1, 0), P(0, -1), P(0, 1)]);
+ var O = f.px(P(0, 0)), rad = f.s;
+ function onC(deg) { var t = deg2rad(deg); return P(O.x + Math.cos(t) * rad, O.y - Math.sin(t) * rad); }
+ var Pp = onC(90 - a / 2), Qp = onC(90 + a / 2), M = onC(-90);
+ var body = circleSvg(O, rad);
+ body += ln(O, Pp, { w: 2 }) + ln(O, Qp, { w: 2 }) + ln(M, Pp, { w: 2 }) + ln(M, Qp, { w: 2 });
+ body += dot(O, 2.6) + dot(Pp) + dot(Qp) + dot(M);
+ var arcO = angleArc(O, Pp, Qp, 18);
+ body += arcO.path + txt(arcO.labelPos, fmt(a) + '°', { fill: '#fff', size: 12 });
+ var arcM = angleArc(M, Pp, Qp, 22);
+ body += arcM.path + txt(arcM.labelPos, '?', { fill: UNK, size: 14, weight: 800 });
+ return body;
+ },
+
+ /* Хорда и расстояние до центра: перпендикуляр (пунктир) + прямой угол; хорда «?». */
+ 'chord-circle': function (spec, p) {
+ var r = num(p, spec.r), dd = num(p, spec.dd);
+ if (!(r > 0) || !(dd > 0) || dd >= r) return null;
+ var f = fit([P(-1, 0), P(1, 0), P(0, -1), P(0, 1)]);
+ var O = f.px(P(0, 0)), rad = f.s;
+ var dpx = rad * dd / r, half = Math.sqrt(Math.max(0, rad * rad - dpx * dpx));
+ var Fc = P(O.x, O.y + dpx), Ch1 = P(Fc.x - half, Fc.y), Ch2 = P(Fc.x + half, Fc.y);
+ var body = circleSvg(O, rad);
+ body += ln(Ch1, Ch2, { w: 2.6 }) + ln(O, Fc, { dash: true, stroke: DASH, w: 1.8 });
+ body += ln(O, Ch2, { w: 1.8, stroke: 'rgba(255,255,255,.7)' }) + rightAngle(Fc, Ch2, O, 9);
+ body += dot(O, 2.6) + dot(Ch1) + dot(Ch2);
+ body += txt(P((O.x + Fc.x) / 2 + 7, (O.y + Fc.y) / 2), fmt(dd), { fill: '#fff', size: 11, anchor: 'start' });
+ body += txt(P((O.x + Ch2.x) / 2 + 6, (O.y + Ch2.y) / 2 - 6), 'r=' + fmt(r), { fill: '#fff', size: 11, anchor: 'start' });
+ body += txt(P(Fc.x, Ch2.y + 13), '?', { fill: UNK, size: 14, weight: 800 });
+ return body;
+ },
+
+ /* Касательная из внешней точки: радиус ⟂ касательной; касательная «?». */
+ 'tangent-circle': function (spec, p) {
+ var r = num(p, spec.r), op = num(p, spec.op);
+ if (!(r > 0) || !(op > 0) || op <= r) return null;
+ var f = fit([P(-1, 0), P(1.6, 0), P(0, -1), P(0, 1)]);
+ var O = f.px(P(0, 0)), rad = f.s;
+ var Tang = P(O.x + Math.cos(deg2rad(60)) * rad, O.y - Math.sin(deg2rad(60)) * rad);
+ var ur = norm(sub(Tang, O)), tdir = P(-ur.y, ur.x);
+ var Pt = P(Tang.x + tdir.x * rad * 1.3, Tang.y + tdir.y * rad * 1.3);
+ var body = circleSvg(O, rad);
+ body += ln(O, Tang, { w: 1.8, stroke: 'rgba(255,255,255,.7)' }) + ln(Tang, Pt, { w: 2.6 });
+ body += ln(O, Pt, { dash: true, stroke: DASH, w: 1.8 }) + rightAngle(Tang, O, Pt, 9);
+ body += dot(O, 2.6) + dot(Tang) + dot(Pt);
+ body += txt(P((O.x + Tang.x) / 2 - 6, (O.y + Tang.y) / 2), 'r=' + fmt(r), { fill: '#fff', size: 11, anchor: 'end' });
+ body += txt(P((O.x + Pt.x) / 2, (O.y + Pt.y) / 2 + 13), fmt(op), { fill: '#fff', size: 11 });
+ body += txt(P((Tang.x + Pt.x) / 2 + 4, (Tang.y + Pt.y) / 2 - 8), '?', { fill: UNK, size: 14, weight: 800, anchor: 'start' });
+ return body;
}
};
diff --git a/frontend/js/trainer/generators.js b/frontend/js/trainer/generators.js
index 6f4e762..25774a0 100644
--- a/frontend/js/trainer/generators.js
+++ b/frontend/js/trainer/generators.js
@@ -2487,6 +2487,333 @@
{ note: 'Квадрат — это умножение числа на себя. Минус на минус даёт плюс.', tex: 'x = ({a})*({a})' },
{ note: 'Считаем.', tex: 'x = {ans}' }
]
+ },
+
+ /* ═══════════════════════════════════════════════════════════════════════
+ V4.1 — Группа 6: геометрия (углы, Пифагор, площади, многоугольники,
+ подобие, окружность). Все compute «корень-вперёд» + чертежи (figures.js).
+ ═══════════════════════════════════════════════════════════════════════ */
+
+ /* ── Углы ── */
+
+ /* параллельные и секущая — соответственный угол */
+ {
+ id: 'ang-parallel-transversal', topic: 'g-angles', order: 4, subject: 'geometry', grade: 7, kind: 'compute',
+ title: 'Параллельные и секущая',
+ figure: { type: 'parallel-lines-transversal', given: 'a', rel: 'corresponding' },
+ figurePrompt: 'Найдите соответственный угол по чертежу (в градусах).',
+ pick: { a: [30, 150] }, derive: { val: 'a' },
+ lhs: 'x', rhs: '{a}', display: 'Прямые параллельны, секущая образует угол {a}°. Найдите соответственный ему угол (в градусах).',
+ answerVar: 'x', answer: 'val', integerAnswer: true,
+ solution: [
+ { note: 'Соответственные углы при параллельных прямых и секущей равны.', tex: 'x = {a}' },
+ { note: 'Ответ.', tex: 'x = {ans}' }
+ ]
+ },
+
+ /* угол при основании равнобедренного */
+ {
+ id: 'ang-isosceles-base', topic: 'g-angles', order: 5, subject: 'geometry', grade: 7, kind: 'compute',
+ title: 'Углы равнобедренного',
+ figure: { type: 'isosceles', apex: 'a' },
+ figurePrompt: 'Найдите угол при основании (в градусах).',
+ pick: { bAng: [20, 80] }, derive: { a: '180 - 2*bAng', val: 'bAng' }, require: 'a > 0 && a < 140',
+ lhs: 'x', rhs: '(180 - {a})/2', display: 'В равнобедренном треугольнике угол при вершине равен {a}°. Найдите угол при основании.',
+ answerVar: 'x', answer: 'val', integerAnswer: true,
+ solution: [
+ { note: 'Сумма углов треугольника 180°, два угла при основании равны: (180 − {a}) ÷ 2.', tex: 'x = (180 - {a}) / 2' },
+ { note: 'Считаем.', tex: 'x = {ans}' }
+ ]
+ },
+
+ /* вертикальные углы */
+ {
+ id: 'ang-vertical', topic: 'g-angles', order: 6, subject: 'geometry', grade: 7, kind: 'compute',
+ title: 'Вертикальные углы',
+ figure: { type: 'vertical-angles', given: 'a' },
+ figurePrompt: 'Найдите вертикальный угол (в градусах).',
+ pick: { a: [20, 160] }, derive: { val: 'a' },
+ lhs: 'x', rhs: '{a}', display: 'Два угла — вертикальные. Один из них равен {a}°. Найдите другой.',
+ answerVar: 'x', answer: 'val', integerAnswer: true,
+ solution: [
+ { note: 'Вертикальные углы равны.', tex: 'x = {a}' },
+ { note: 'Ответ.', tex: 'x = {ans}' }
+ ]
+ },
+
+ /* ── Пифагор ── */
+
+ /* периметр прямоугольного треугольника */
+ {
+ id: 'pyth-perimeter', topic: 'g-pyth', order: 3, subject: 'geometry', grade: 8, kind: 'compute',
+ title: 'Периметр прям. треугольника',
+ figure: { type: 'right-triangle', a: 'a', b: 'b', c: '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', val: '(m*m - n*n) + 2*m*n + (m*m + n*n)' },
+ lhs: 'x', rhs: '{a} + {b} + {c}', display: 'Катеты прямоугольного треугольника равны {a} и {b}. Найдите его периметр.',
+ answerVar: 'x', answer: 'val', integerAnswer: true,
+ solution: [
+ { note: 'По теореме Пифагора гипотенуза c = √(a² + b²) = {c}.', tex: 'sqrt({a}^2 + {b}^2) = {c}' },
+ { note: 'Периметр — сумма всех сторон.', tex: 'x = {a} + {b} + {c}' },
+ { note: 'Считаем.', tex: 'x = {ans}' }
+ ]
+ },
+
+ /* расстояние между точками */
+ {
+ id: 'pyth-distance', topic: 'g-pyth', order: 4, subject: 'geometry', grade: 8, kind: 'compute',
+ title: 'Расстояние между точками',
+ figure: { type: 'points-distance', x1: 'x1', y1: 'y1', x2: 'x2', y2: 'y2' },
+ figurePrompt: 'Найдите расстояние между точками A и B.',
+ pick: { m: [2, 4], n: [1, 3], x1: [-4, 4], y1: [-4, 4] }, constraint: 'm > n',
+ derive: { dx: 'm*m - n*n', dy: '2*m*n', x2: 'x1 + (m*m - n*n)', y2: 'y1 + 2*m*n', val: 'm*m + n*n' },
+ lhs: 'x', rhs: 'sqrt(({x2} - {x1})^2 + ({y2} - {y1})^2)', display: 'Найдите расстояние между точками A({x1}; {y1}) и B({x2}; {y2}).',
+ answerVar: 'x', answer: 'val', integerAnswer: true,
+ solution: [
+ { note: 'Расстояние = √((x₂ − x₁)² + (y₂ − y₁)²).', tex: 'x = sqrt(({x2} - {x1})^2 + ({y2} - {y1})^2)' },
+ { note: 'Считаем (получается целое — пифагорова тройка).', tex: 'x = {ans}' }
+ ]
+ },
+
+ /* диагональ прямоугольника */
+ {
+ id: 'pyth-rect-diagonal', topic: 'g-pyth', order: 5, subject: 'geometry', grade: 8, kind: 'compute',
+ title: 'Диагональ прямоугольника',
+ figure: { type: 'rectangle', w: 'a', h: 'b', diagonal: true },
+ figurePrompt: 'Найдите диагональ прямоугольника.',
+ pick: { m: [2, 5], n: [1, 4] }, constraint: 'm > n',
+ derive: { a: 'm*m - n*n', b: '2*m*n', val: 'm*m + n*n' },
+ lhs: 'x', rhs: 'sqrt({a}^2 + {b}^2)', display: 'Стороны прямоугольника {a} и {b}. Найдите его диагональ.',
+ answerVar: 'x', answer: 'val', integerAnswer: true,
+ solution: [
+ { note: 'Диагональ — гипотенуза прямоугольного треугольника: d = √(a² + b²).', tex: 'x = sqrt({a}^2 + {b}^2)' },
+ { note: 'Считаем.', tex: 'x = {ans}' }
+ ]
+ },
+
+ /* диагональ параллелепипеда (3D) */
+ {
+ id: 'pyth-space-diagonal', topic: 'g-pyth', order: 6, subject: 'geometry', grade: 9, kind: 'compute',
+ title: 'Диагональ параллелепипеда',
+ figure: { type: 'space-diagonal-box', a: 'a', b: 'b', c: 'c' },
+ figurePrompt: 'Найдите диагональ параллелепипеда.',
+ pick: { s: [1, 5] }, derive: { a: '1*s', b: '2*s', c: '2*s', val: '3*s' },
+ lhs: 'x', rhs: 'sqrt({a}^2 + {b}^2 + {c}^2)', display: 'Измерения прямоугольного параллелепипеда {a}, {b} и {c}. Найдите его диагональ.',
+ answerVar: 'x', answer: 'val', integerAnswer: true,
+ solution: [
+ { note: 'Диагональ параллелепипеда: d = √(a² + b² + c²).', tex: 'x = sqrt({a}^2 + {b}^2 + {c}^2)' },
+ { note: 'Считаем.', tex: 'x = {ans}' }
+ ]
+ },
+
+ /* ── Площади ── */
+
+ /* найти сторону по площади (обратная) */
+ {
+ id: 'area-rect-inverse', topic: 'g-area', order: 7, subject: 'geometry', grade: 8, kind: 'compute',
+ title: 'Сторона по площади',
+ figure: { type: 'rectangle', w: 'a', h: 'b', unknown: 'h', area: 'S' },
+ figurePrompt: 'Найдите неизвестную сторону прямоугольника.',
+ pick: { a: [2, 16], b: [2, 16] }, derive: { S: 'a*b', val: 'b' },
+ lhs: 'x', rhs: '{S}/{a}', display: 'Площадь прямоугольника {S}, одна сторона {a}. Найдите вторую сторону.',
+ answerVar: 'x', answer: 'val', integerAnswer: true,
+ solution: [
+ { note: 'Площадь = произведение сторон, значит вторая сторона = площадь ÷ известная сторона.', tex: 'x = {S} / {a}' },
+ { note: 'Считаем.', tex: 'x = {ans}' }
+ ]
+ },
+
+ /* площадь L-образной фигуры */
+ {
+ id: 'area-l-shape', topic: 'g-area', order: 8, subject: 'geometry', grade: 8, kind: 'compute',
+ title: 'Площадь L-фигуры',
+ figure: { type: 'l-shape', W: 'W', H: 'H', cw: 'cw', ch: 'ch' },
+ figurePrompt: 'Найдите площадь фигуры (все углы прямые).',
+ pick: { W: [6, 14], H: [5, 12], cw: [1, 10], ch: [1, 10] }, constraint: 'cw < W - 1 && ch < H - 1',
+ derive: { val: 'W*H - cw*ch' },
+ lhs: 'x', rhs: '{W}*{H} - {cw}*{ch}', display: 'Найдите площадь L-образной фигуры на чертеже (все углы прямые, размеры указаны).',
+ answerVar: 'x', answer: 'val', integerAnswer: true,
+ solution: [
+ { note: 'Достроим до большого прямоугольника {W}×{H} и вычтем вырез {cw}×{ch}.', tex: 'x = {W}*{H} - {cw}*{ch}' },
+ { note: 'Считаем.', tex: 'x = {ans}' }
+ ]
+ },
+
+ /* площадь сектора */
+ {
+ id: 'area-sector', topic: 'g-area', order: 9, subject: 'geometry', grade: 9, kind: 'compute',
+ title: 'Площадь сектора',
+ figure: { type: 'circle-arc', r: 'r', angle: 'n' },
+ figurePrompt: 'Найдите площадь сектора (π ≈ 3,14).',
+ pick: { r: [2, 12], k2: [1, 3] }, require: 'mod(k2*r*r, 4) == 0',
+ derive: { n: '90*k2', val: '3.14*(90*k2)/360*r^2' },
+ lhs: 'x', rhs: '3.14*{n}/360*{r}^2', display: 'Найдите площадь сектора радиуса {r} с центральным углом {n}° (π ≈ 3,14).',
+ answerVar: 'x', answer: 'val',
+ solution: [
+ { note: 'Площадь сектора — часть площади круга: S = (n/360)·π·r².', tex: 'x = {n}/360 * 3.14 * {r}^2' },
+ { note: 'Считаем.', tex: 'x = {ans}' }
+ ]
+ },
+
+ /* ── Многоугольники ── */
+
+ /* число диагоналей */
+ {
+ id: 'poly-diagonals', topic: 'g-poly', order: 3, subject: 'geometry', grade: 8, kind: 'compute',
+ title: 'Число диагоналей',
+ figure: { type: 'regular-polygon', n: 'n' },
+ figurePrompt: 'Сколько диагоналей у многоугольника на чертеже?',
+ pick: { n: [4, 15] }, derive: { val: 'n*(n - 3)/2' },
+ lhs: 'x', rhs: '{n}*({n} - 3)/2', display: 'Сколько диагоналей у выпуклого {n}-угольника?',
+ answerVar: 'x', answer: 'val', integerAnswer: true,
+ solution: [
+ { note: 'Число диагоналей n-угольника = n(n − 3)/2.', tex: 'x = {n}*({n} - 3) / 2' },
+ { note: 'Считаем.', tex: 'x = {ans}' }
+ ]
+ },
+
+ /* число сторон по углу (обратная) */
+ {
+ id: 'poly-find-n', topic: 'g-poly', order: 4, subject: 'geometry', grade: 8, kind: 'compute',
+ title: 'Число сторон по углу',
+ figure: { type: 'regular-polygon', n: 'n', markAngle: true },
+ figurePrompt: 'Сколько сторон у многоугольника?',
+ pick: { n: [3, 12] }, require: 'mod(180*(n - 2), n) == 0',
+ derive: { ang: '180*(n - 2)/n', val: 'n' },
+ lhs: 'x', rhs: '360/(180 - {ang})', display: 'Каждый угол правильного многоугольника равен {ang}°. Сколько у него сторон?',
+ answerVar: 'x', answer: 'val', integerAnswer: true,
+ solution: [
+ { note: 'Внешний угол = 180 − {ang}, а число сторон = 360 ÷ внешний угол.', tex: 'x = 360 / (180 - {ang})' },
+ { note: 'Считаем.', tex: 'x = {ans}' }
+ ]
+ },
+
+ /* внешний угол правильного многоугольника */
+ {
+ id: 'poly-exterior-sum', topic: 'g-poly', order: 5, subject: 'geometry', grade: 8, kind: 'compute',
+ title: 'Внешний угол правильного',
+ figure: { type: 'regular-polygon', n: 'n' },
+ figurePrompt: 'Найдите внешний угол правильного многоугольника (в градусах).',
+ pick: { n: [3, 18] }, require: 'mod(360, n) == 0',
+ derive: { val: '360/n' },
+ lhs: 'x', rhs: '360/{n}', display: 'Найдите внешний угол правильного {n}-угольника (в градусах).',
+ answerVar: 'x', answer: 'val', integerAnswer: true,
+ solution: [
+ { note: 'Сумма внешних углов любого выпуклого многоугольника равна 360°, значит каждый = 360 ÷ n.', tex: 'x = 360 / {n}' },
+ { note: 'Считаем.', tex: 'x = {ans}' }
+ ]
+ },
+
+ /* ── Подобие ── */
+
+ /* коэффициент подобия по сторонам */
+ {
+ id: 'sim-scale-factor', topic: 'g-sim', order: 3, subject: 'geometry', grade: 8, kind: 'compute',
+ title: 'Коэффициент по сторонам',
+ figure: { type: 'two-similar', side: 'a', side2: 'b', k: 'k', mode: 'side', hideK: true },
+ figurePrompt: 'Найдите коэффициент подобия (большего к меньшему).',
+ pick: { a: [2, 12], k: [2, 5] }, derive: { b: 'a*k', val: 'k' },
+ lhs: 'x', rhs: '{b}/{a}', display: 'Треугольники подобны. Сходственные стороны равны {a} и {b}. Найдите коэффициент подобия (большего к меньшему).',
+ answerVar: 'x', answer: 'val', integerAnswer: true,
+ solution: [
+ { note: 'Коэффициент подобия — отношение сходственных сторон: {b} ÷ {a}.', tex: 'x = {b} / {a}' },
+ { note: 'Считаем.', tex: 'x = {ans}' }
+ ]
+ },
+
+ /* отношение площадей подобных = k² */
+ {
+ id: 'sim-area-ratio', topic: 'g-sim', order: 4, subject: 'geometry', grade: 9, kind: 'compute',
+ title: 'Отношение площадей (k²)',
+ figure: { type: 'two-similar', area: 'S', k: 'k', mode: 'area' },
+ figurePrompt: 'Найдите площадь второй фигуры.',
+ pick: { k: [2, 4], S: [2, 20] }, derive: { val: 'S*k*k' },
+ lhs: 'x', rhs: '{S}*{k}*{k}', display: 'Фигуры подобны с коэффициентом {k}. Площадь первой {S}. Найдите площадь второй.',
+ answerVar: 'x', answer: 'val', integerAnswer: true,
+ solution: [
+ { note: 'Площади подобных фигур относятся как квадрат коэффициента: S₂ = S₁·k².', tex: 'x = {S} * {k}^2' },
+ { note: 'Считаем.', tex: 'x = {ans}' }
+ ]
+ },
+
+ /* отрезок по теореме Фалеса */
+ {
+ id: 'sim-thales', topic: 'g-sim', order: 5, subject: 'geometry', grade: 8, kind: 'compute',
+ title: 'Теорема Фалеса',
+ figure: { type: 'thales-parallel', a: 'a', b: 'b', c: 'c' },
+ figurePrompt: 'Найдите неизвестный отрезок x.',
+ pick: { a: [2, 9], b: [2, 9], t: [2, 9] }, derive: { c: 'a*t', val: 'b*t' },
+ lhs: 'x', rhs: '{b}*{c}/{a}', display: 'Прямая, параллельная стороне треугольника, отсекает пропорциональные отрезки: {a} относится к {b}, как {c} относится к x. Найдите x.',
+ answerVar: 'x', answer: 'val', integerAnswer: true,
+ solution: [
+ { note: 'По теореме Фалеса {a} : {b} = {c} : x, отсюда x = {b}·{c} ÷ {a}.', tex: 'x = {b} * {c} / {a}' },
+ { note: 'Считаем.', tex: 'x = {ans}' }
+ ]
+ },
+
+ /* масштаб карты (подобие) */
+ {
+ id: 'sim-map-scale', topic: 'g-sim', order: 6, subject: 'geometry', grade: 8, kind: 'compute',
+ title: 'Масштаб карты',
+ pick: { scaleK: [1, 9], mcm: [2, 12] }, derive: { scale: 'scaleK*10000', val: 'mcm*scaleK*100' },
+ lhs: 'x', rhs: '{mcm}*{scale}/100', display: 'Масштаб карты 1 : {scale}. На карте расстояние {mcm} см. Найдите расстояние на местности (в метрах).',
+ answerVar: 'x', answer: 'val', integerAnswer: true,
+ solution: [
+ { note: '1 см карты = {scale} см на местности. Расстояние {mcm}·{scale} см переводим в метры (÷100).', tex: 'x = {mcm} * {scale} / 100' },
+ { note: 'Считаем.', tex: 'x = {ans}' }
+ ]
+ },
+
+ /* ── Окружность ── */
+
+ /* вписанный угол */
+ {
+ id: 'circ-inscribed-angle', topic: 'g-circle', order: 5, subject: 'geometry', grade: 9, kind: 'compute',
+ title: 'Вписанный угол',
+ figure: { type: 'inscribed-central-angle', central: 'a' },
+ figurePrompt: 'Найдите вписанный угол (в градусах).',
+ pick: { ins: [10, 80] }, derive: { a: '2*ins', val: 'ins' },
+ lhs: 'x', rhs: '{a}/2', display: 'Центральный угол окружности равен {a}°. Найдите вписанный угол, опирающийся на ту же дугу.',
+ answerVar: 'x', answer: 'val', integerAnswer: true,
+ solution: [
+ { note: 'Вписанный угол вдвое меньше центрального, опирающегося на ту же дугу.', tex: 'x = {a} / 2' },
+ { note: 'Считаем.', tex: 'x = {ans}' }
+ ]
+ },
+
+ /* длина хорды через радиус */
+ {
+ id: 'circ-chord-pyth', topic: 'g-circle', order: 6, subject: 'geometry', grade: 9, kind: 'compute',
+ title: 'Длина хорды',
+ figure: { type: 'chord-circle', r: 'r', dd: 'dd' },
+ figurePrompt: 'Найдите длину хорды.',
+ pick: { m: [2, 5], n: [1, 4] }, constraint: 'm > n',
+ derive: { half: 'm*m - n*n', dd: '2*m*n', r: 'm*m + n*n', val: '2*(m*m - n*n)' },
+ lhs: 'x', rhs: '2*sqrt({r}^2 - {dd}^2)', display: 'Хорда удалена от центра окружности на {dd} при радиусе {r}. Найдите длину хорды.',
+ answerVar: 'x', answer: 'val', integerAnswer: true,
+ solution: [
+ { note: 'Половина хорды, расстояние до центра {dd} и радиус {r} образуют прямоугольный треугольник.', tex: '' },
+ { note: 'Длина хорды = 2·√(r² − d²).', tex: 'x = 2 * sqrt({r}^2 - {dd}^2)' },
+ { note: 'Считаем.', tex: 'x = {ans}' }
+ ]
+ },
+
+ /* длина касательной */
+ {
+ id: 'circ-tangent-len', topic: 'g-circle', order: 7, subject: 'geometry', grade: 9, kind: 'compute',
+ title: 'Длина касательной',
+ figure: { type: 'tangent-circle', r: 'r', op: 'op' },
+ figurePrompt: 'Найдите длину касательной.',
+ pick: { m: [2, 5], n: [1, 4] }, constraint: 'm > n',
+ derive: { r: 'm*m - n*n', op: 'm*m + n*n', val: '2*m*n' },
+ lhs: 'x', rhs: 'sqrt({op}^2 - {r}^2)', display: 'Из точки на расстоянии {op} от центра окружности радиуса {r} проведена касательная. Найдите её длину.',
+ answerVar: 'x', answer: 'val', integerAnswer: true,
+ solution: [
+ { note: 'Касательная перпендикулярна радиусу: r, касательная и расстояние до центра образуют прямоугольный треугольник.', tex: '' },
+ { note: 'Длина касательной = √(OP² − r²).', tex: 'x = sqrt({op}^2 - {r}^2)' },
+ { note: 'Считаем.', tex: 'x = {ans}' }
+ ]
}
];
@@ -2555,6 +2882,13 @@
'frac-reduce': 2, 'frac-add-unlike': 3, 'frac-mult': 2, 'frac-compare': 2, 'frac-of-whole-inverse': 3, 'frac-to-decimal': 2,
'dec-div': 3, 'dec-round': 2, 'dec-times-pow10': 1, 'dec-compare': 1,
'neg-div': 2, 'neg-order-ops': 3, 'neg-abs': 2, 'neg-compare-line': 1, 'neg-square': 2,
+ // V4.1 — Геометрия (углы/Пифагор/площади/многоугольники/подобие/окружность)
+ 'ang-parallel-transversal': 2, 'ang-isosceles-base': 2, 'ang-vertical': 1,
+ '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,
+ 'poly-diagonals': 2, 'poly-find-n': 3, 'poly-exterior-sum': 2,
+ 'sim-scale-factor': 2, 'sim-area-ratio': 3, 'sim-thales': 3, 'sim-map-scale': 2,
+ 'circ-inscribed-angle': 3, 'circ-chord-pyth': 3, 'circ-tangent-len': 3,
// НОД/НОК / Дроби / Десятичные / Отрицательные
'gcd-pair': 1, 'lcm-pair': 2,
'frac-of-number': 1, 'frac-add-same': 2,