feat(math6): умножение-прыжки (Гл.4 §7) + координатный тир (Гл.5 §1)
Math6Anim.numberLineJumps — a·b как a прыжков-дуг по b на числовой прямой (зелёные вправо, красные влево, приземление на произведение); ползунки a,b. Math6Anim.coordGame — «поставь точку (x;y)»: клик по узлу сетки, проверка, счёт, при промахе показывает верную точку. План: 3D-тела исключены. Headless-safe. Тесты math6: 20/20. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -305,6 +305,79 @@ M.thermometer = function (host, opts) {
|
||||
return { stop: L.stop, set: function (v) { st.target = v; setCap(v); } };
|
||||
};
|
||||
|
||||
function _riA(a, b) { return a + Math.floor(Math.random() * (b - a + 1)); }
|
||||
|
||||
/* ============================ ДЕМО 8: УМНОЖЕНИЕ КАК ПРЫЖКИ (a · b) ============================ */
|
||||
M.numberLineJumps = function (host, opts) {
|
||||
opts = opts || {}; var a = opts.a != null ? opts.a : 3, b = opts.b != null ? opts.b : -2; // a прыжков по b
|
||||
var prod = a * b, W0 = 540, H0 = 150; var sc = sceneCanvas(host, W0, H0); var cap = caption(host, '');
|
||||
var min = Math.min(-2, 0, prod) - 1, max = Math.max(2, 0, prod) + 1, period = Math.max(3.5, a * 0.8 + 1.6);
|
||||
function X(v) { var pad = 30; return pad + (v - min) / (max - min) * (W0 - 2 * pad); }
|
||||
function hop(ctx, x1, x2, baseY, col) {
|
||||
var mx = (x1 + x2) / 2; ctx.strokeStyle = col; ctx.lineWidth = 2.5; ctx.beginPath(); ctx.moveTo(x1, baseY); ctx.quadraticCurveTo(mx, baseY - 24, x2, baseY); ctx.stroke();
|
||||
var d = x2 >= x1 ? 1 : -1; ctx.fillStyle = col; ctx.beginPath(); ctx.moveTo(x2, baseY); ctx.lineTo(x2 - d * 7, baseY - 6); ctx.lineTo(x2 - d * 7, baseY + 2); ctx.closePath(); ctx.fill();
|
||||
}
|
||||
function draw(t) {
|
||||
var ctx = sc.ctx; if (!ctx) return; var mut = cssVar('--muted', '#64748b'), acc = cssVar('--pri2', '#3730a3');
|
||||
var axisY = H0 - 46; ctx.clearRect(0, 0, W0, H0);
|
||||
ctx.strokeStyle = mut; ctx.lineWidth = 1.5; ctx.beginPath(); ctx.moveTo(15, axisY); ctx.lineTo(W0 - 15, axisY); ctx.stroke();
|
||||
ctx.font = '11px JetBrains Mono, monospace'; ctx.textAlign = 'center';
|
||||
for (var v = Math.ceil(min); v <= Math.floor(max); v++) { var x = X(v); ctx.strokeStyle = mut; ctx.beginPath(); ctx.moveTo(x, axisY - 5); ctx.lineTo(x, axisY + 5); ctx.stroke(); ctx.fillStyle = (v === 0 ? acc : mut); ctx.fillText(v, x, axisY + 20); }
|
||||
var p = (t % period) / period, prog = p * (a + 0.5), doneJumps = Math.min(a, Math.floor(prog)), partial = Math.min(1, prog - doneJumps);
|
||||
var col = b >= 0 ? '#059669' : '#e11d48', prev = 0;
|
||||
for (var j = 0; j < doneJumps; j++) { hop(ctx, X(prev), X(prev + b), axisY, col); prev += b; }
|
||||
if (doneJumps < a) { hop(ctx, X(prev), X(prev + b * partial), axisY, col); }
|
||||
var pos = doneJumps >= a ? prod : prev;
|
||||
ctx.fillStyle = '#e11d48'; ctx.beginPath(); ctx.arc(X(pos), axisY, 5, 0, 2 * Math.PI); ctx.fill();
|
||||
if (doneJumps >= a) { ctx.fillStyle = acc; ctx.font = '14px Unbounded, sans-serif'; ctx.fillText('' + prod, X(prod), axisY - 30); }
|
||||
}
|
||||
var L = loop(host, draw);
|
||||
var bp = b < 0 ? '(' + b + ')' : '' + b;
|
||||
cap.innerHTML = '$' + a + ' \\cdot ' + bp + ' = ' + prod + '$ — это ' + a + ' ' + (a === 1 ? 'прыжок' : (a < 5 ? 'прыжка' : 'прыжков')) + ' по ' + b + ' от нуля.';
|
||||
if (W.renderMathInElement) try { W.renderMathInElement(cap, { delimiters: [{ left: '$', right: '$', display: false }], throwOnError: false }); } catch (e) {}
|
||||
return { stop: L.stop };
|
||||
};
|
||||
|
||||
/* ============================ ДЕМО 9: КООРДИНАТНЫЙ ТИР («поставь точку») ============================ */
|
||||
M.coordGame = function (host, opts) {
|
||||
var W0 = 320, H0 = 320; var sc = sceneCanvas(host, W0, H0);
|
||||
var ui = D.createElement('div'); ui.style.cssText = 'text-align:center;margin-top:8px';
|
||||
ui.innerHTML = '<div class="qbox" id="cg-q" style="margin-bottom:6px"></div><div style="color:var(--muted);font-size:.9rem">Очки: <b id="cg-s">0</b> · кликни по узлу сетки</div>';
|
||||
host.appendChild(ui);
|
||||
var XMIN = -5, XMAX = 5, YMIN = -5, YMAX = 5, pad = 24;
|
||||
function X(x) { return pad + (x - XMIN) / (XMAX - XMIN) * (W0 - 2 * pad); }
|
||||
function Y(y) { return H0 - pad - (y - YMIN) / (YMAX - YMIN) * (H0 - 2 * pad); }
|
||||
var st = { tx: 2, ty: 3, score: 0, placed: null, ok: false, reveal: 0 };
|
||||
function setQ() { var q = ui.querySelector('#cg-q'); if (q) { q.innerHTML = 'Поставь точку $(' + st.tx + ';\\,' + st.ty + ')$'; if (W.renderMathInElement) try { W.renderMathInElement(q, { delimiters: [{ left: '$', right: '$', display: false }], throwOnError: false }); } catch (e) {} } }
|
||||
function newTarget() { st.tx = _riA(-5, 5); st.ty = _riA(-5, 5); st.placed = null; st.reveal = 0; setQ(); }
|
||||
function draw() {
|
||||
var ctx = sc.ctx; if (!ctx) return; var bd = cssVar('--border', '#e2e8f0'), axc = cssVar('--text', '#0f172a'), mut = cssVar('--muted', '#64748b');
|
||||
ctx.clearRect(0, 0, W0, H0);
|
||||
ctx.strokeStyle = bd; ctx.lineWidth = 0.8;
|
||||
for (var gx = XMIN; gx <= XMAX; gx++) { ctx.beginPath(); ctx.moveTo(X(gx), Y(YMIN)); ctx.lineTo(X(gx), Y(YMAX)); ctx.stroke(); }
|
||||
for (var gy = YMIN; gy <= YMAX; gy++) { ctx.beginPath(); ctx.moveTo(X(XMIN), Y(gy)); ctx.lineTo(X(XMAX), Y(gy)); ctx.stroke(); }
|
||||
ctx.strokeStyle = axc; ctx.lineWidth = 1.6; ctx.beginPath(); ctx.moveTo(X(XMIN), Y(0)); ctx.lineTo(X(XMAX), Y(0)); ctx.moveTo(X(0), Y(YMIN)); ctx.lineTo(X(0), Y(YMAX)); ctx.stroke();
|
||||
ctx.fillStyle = axc; ctx.font = 'italic 12px serif'; ctx.fillText('x', X(XMAX) - 2, Y(0) + 14); ctx.fillText('y', X(0) + 7, Y(YMAX) + 11);
|
||||
if (st.reveal > 0) { ctx.fillStyle = '#059669'; ctx.beginPath(); ctx.arc(X(st.tx), Y(st.ty), 7, 0, 2 * Math.PI); ctx.fill(); }
|
||||
if (st.placed) { ctx.fillStyle = st.ok ? '#059669' : '#e11d48'; ctx.beginPath(); ctx.arc(X(st.placed.x), Y(st.placed.y), 6, 0, 2 * Math.PI); ctx.fill(); }
|
||||
}
|
||||
var L = loop(host, draw);
|
||||
if (!HEADLESS && sc.ctx) {
|
||||
sc.cv.style.cursor = 'crosshair';
|
||||
sc.cv.addEventListener('pointerdown', function (e) {
|
||||
var r = sc.cv.getBoundingClientRect();
|
||||
var dx = Math.round(XMIN + (e.clientX - r.left) / r.width * (XMAX - XMIN));
|
||||
var dy = Math.round(YMIN + (r.height - (e.clientY - r.top)) / r.height * (YMAX - YMIN));
|
||||
dx = Math.max(XMIN, Math.min(XMAX, dx)); dy = Math.max(YMIN, Math.min(YMAX, dy));
|
||||
st.placed = { x: dx, y: dy }; st.ok = (dx === st.tx && dy === st.ty);
|
||||
if (st.ok) { st.score++; var s = ui.querySelector('#cg-s'); if (s) s.textContent = st.score; setTimeout(newTarget, 700); }
|
||||
else { st.reveal = 1; setTimeout(function () { st.reveal = 0; st.placed = null; }, 1100); }
|
||||
});
|
||||
}
|
||||
setQ();
|
||||
return { stop: L.stop };
|
||||
};
|
||||
|
||||
/* ============================ КОМПОНЕНТ: ПОШАГОВЫЙ ПЛЕЕР (DOM, не canvas) ============================ */
|
||||
M.stepPlayer = function (host, opts) {
|
||||
opts = opts || {}; var steps = opts.steps || []; if (!steps.length) return { stop: function () {} };
|
||||
|
||||
Reference in New Issue
Block a user