feat(trainer): уровни сложности заданий (Лёгкий/Средний/Сложный + Авто)
- движок: instantiate(gen,{level}) масштабирует диапазоны pick (_scaleRange): L2=база, L1 меньше магнитуды/меньше отрицательных, L3 шире → сложнее; универсально для всех генераторов (корень-вперёд + самопроверка держат корректность), opt-out gen.noScale; generateBatch прокидывает level
- страница: контрол «Сложность: Авто / Лёгкий / Средний / Сложный» в рабочей зоне; «Авто» поднимает уровень с серией верных (streak≥2→2, ≥4→3, ошибка→1); скрыт для текстовых задач из банка
- смоук движка 682/682 (T18: 36 ген × L1/L2/L3, L3 шире L1, L2==база), страница 34/34; эмодзи/eval 0
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -68,6 +68,24 @@
|
||||
}
|
||||
function randInt(rng, lo, hi) { return lo + Math.floor(rng() * (hi - lo + 1)); }
|
||||
|
||||
/* ── Уровни сложности: масштабирование диапазона pick ──
|
||||
level 2 — базовый (как задано); 1 — легче (меньше магнитуды, меньше
|
||||
отрицательных); 3 — сложнее (шире магнитуды). Универсально для всех
|
||||
генераторов; корректность держит «корень-вперёд» + самопроверка. */
|
||||
function _scaleRange(r, level) {
|
||||
var lo = r[0], hi = r[1];
|
||||
if (!level || level === 2) return [lo, hi];
|
||||
if (level === 1) {
|
||||
var nlo = lo < 0 ? Math.ceil(lo / 2) : lo;
|
||||
var nhi = hi > 0 ? Math.max(nlo + 1, Math.round(hi / 2)) : hi;
|
||||
return [nlo, nhi];
|
||||
}
|
||||
var elo = lo < 0 ? Math.floor(lo * 1.8) : lo;
|
||||
var ehi = Math.round(hi * 1.8);
|
||||
if (ehi <= elo) ehi = elo + 1;
|
||||
return [elo, ehi];
|
||||
}
|
||||
|
||||
/* ── Кэш компиляции выражений (рендеренные строки часто повторяются) ── */
|
||||
var _cache = Object.create(null);
|
||||
function compileExpr(src) {
|
||||
@@ -245,8 +263,10 @@
|
||||
for (var attempt = 0; attempt < maxTries; attempt++) {
|
||||
var env = {};
|
||||
var pk = gen.pick || {}, k;
|
||||
var lvl = opts.level;
|
||||
for (k in pk) if (Object.prototype.hasOwnProperty.call(pk, k)) {
|
||||
env[k] = randInt(rng, pk[k][0], pk[k][1]);
|
||||
var rk = (lvl && !gen.noScale) ? _scaleRange(pk[k], lvl) : pk[k];
|
||||
env[k] = randInt(rng, rk[0], rk[1]);
|
||||
}
|
||||
|
||||
if (gen.constraint && !truthy(evalExpr(gen.constraint, env))) continue;
|
||||
@@ -354,7 +374,7 @@
|
||||
var out = [], seen = Object.create(null);
|
||||
var guard = n * 20 + 50;
|
||||
while (out.length < n && guard-- > 0) {
|
||||
var p = instantiate(gen, { rng: rng, strict: opts.strict, maxTries: opts.maxTries });
|
||||
var p = instantiate(gen, { rng: rng, strict: opts.strict, maxTries: opts.maxTries, level: opts.level });
|
||||
if (!p) break;
|
||||
if (seen[p.display]) continue;
|
||||
seen[p.display] = 1;
|
||||
|
||||
+35
-1
@@ -110,6 +110,13 @@
|
||||
.tr-card.tr-wrong .tr-stage { background: linear-gradient(135deg, #dc2626, #ef4444 58%, #fb7185); }
|
||||
.tr-work { padding: 24px 26px 28px; }
|
||||
|
||||
/* ── уровни сложности ── */
|
||||
.tr-difficulty { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; justify-content: center; margin-bottom: 18px; }
|
||||
.tr-diff-label { font-size: .72rem; font-weight: 800; color: var(--ink-faint); text-transform: uppercase; letter-spacing: .06em; margin-right: 2px; }
|
||||
.tr-diff-btn { font: inherit; font-size: .8rem; font-weight: 700; cursor: pointer; padding: 5px 13px; border-radius: 99px; border: 1px solid rgba(99,102,241,.2); background: #fff; color: var(--ink-soft); transition: .14s var(--ease); }
|
||||
.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 6px 14px rgba(99,102,241,.26); }
|
||||
|
||||
#tr-skill {
|
||||
color: rgba(255,255,255,.75); font-family: 'Manrope', sans-serif; font-size: .72rem; font-weight: 800;
|
||||
text-transform: uppercase; letter-spacing: .1em; margin-bottom: 14px;
|
||||
@@ -359,6 +366,8 @@
|
||||
</div>
|
||||
<div class="tr-work">
|
||||
|
||||
<div class="tr-difficulty" id="tr-difficulty"></div>
|
||||
|
||||
<div id="tr-answerbox">
|
||||
<div class="tr-inrow">
|
||||
<span class="tr-eqx" id="tr-eqx">x =</span>
|
||||
@@ -479,6 +488,7 @@
|
||||
var isTeacher = !!(ip && ip.isTeacher);
|
||||
var isAdmin = !!(ip && ip.isAdmin);
|
||||
var curSubject = 'algebra'; // фильтр предмета (Алгебра/Геометрия)
|
||||
var diffMode = 'auto'; // уровень сложности: 'auto' | 1 | 2 | 3
|
||||
var customGens = []; // пользовательские генераторы (P13), тема «Авторские»
|
||||
function skillKey(g) { return g.skill || g.id; }
|
||||
function skillsOf(topicKey) {
|
||||
@@ -625,6 +635,20 @@
|
||||
$('tr-check').textContent = done ? 'Дальше' : 'Проверить';
|
||||
var sc = $('tr-stepcheck'); if (sc) sc.textContent = done ? 'Дальше' : 'Шаг';
|
||||
}
|
||||
// уровень сложности: ручной (1/2/3) или «Авто» — растёт с серией верных в сессии
|
||||
function currentLevel() {
|
||||
if (diffMode === 1 || diffMode === 2 || diffMode === 3) return diffMode;
|
||||
return streak >= 4 ? 3 : streak >= 2 ? 2 : 1;
|
||||
}
|
||||
function renderDifficulty() {
|
||||
var el = $('tr-difficulty'); if (!el) return;
|
||||
var opts = [['auto', 'Авто'], [1, 'Лёгкий'], [2, 'Средний'], [3, 'Сложный']];
|
||||
var autoLvl = (diffMode === 'auto') ? (' · ур.' + currentLevel()) : '';
|
||||
el.innerHTML = '<span class="tr-diff-label">Сложность</span>' + opts.map(function (o) {
|
||||
var lbl = (o[0] === 'auto') ? ('Авто' + autoLvl) : o[1];
|
||||
return '<button class="tr-diff-btn' + (String(diffMode) === String(o[0]) ? ' on' : '') + '" type="button" data-d="' + o[0] + '">' + lbl + '</button>';
|
||||
}).join('');
|
||||
}
|
||||
// общие эффекты «задача решена» (из обычного ответа и из пошагового режима)
|
||||
function onSolved() {
|
||||
solved++; streak++;
|
||||
@@ -730,6 +754,8 @@
|
||||
: (k === 'inequality') ? ('напр. ' + (cur.answerVar || 'x') + ' < 3')
|
||||
: 'ответ';
|
||||
var tog = $('tr-step-toggle'); if (tog) tog.style.display = canStep() ? '' : 'none';
|
||||
var df = $('tr-difficulty'); if (df) df.style.display = (k === 'word') ? 'none' : '';
|
||||
renderDifficulty();
|
||||
}
|
||||
// Текст ответа в фидбеке/раскрытии — по типу задачи.
|
||||
var REL_SYM = { '<': '<', '>': '>', '<=': '≤', '>=': '≥' };
|
||||
@@ -757,7 +783,8 @@
|
||||
if (isWord()) { serveWordProblem(); return; }
|
||||
// strict:false + несколько попыток на случай редкой неудачи с ограничениями
|
||||
cur = null;
|
||||
for (var i = 0; i < 6 && !cur; i++) cur = TE.instantiate(curGen, { seed: randSeed(), strict: false });
|
||||
var lvl = currentLevel();
|
||||
for (var i = 0; i < 6 && !cur; i++) cur = TE.instantiate(curGen, { seed: randSeed(), strict: false, level: lvl });
|
||||
if (!cur) { $('tr-eq').textContent = 'Не удалось сгенерировать задачу'; return; }
|
||||
|
||||
$('tr-skill').textContent = curGen.title;
|
||||
@@ -1008,6 +1035,13 @@
|
||||
$('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-difficulty').addEventListener('click', function (e) {
|
||||
var b = e.target.closest('.tr-diff-btn'); if (!b) return;
|
||||
var d = b.getAttribute('data-d');
|
||||
diffMode = (d === 'auto') ? 'auto' : (+d);
|
||||
renderDifficulty();
|
||||
if (cur && cur.kind !== 'word') newProblem(); // свежая задача на выбранном уровне
|
||||
});
|
||||
$('tr-subjects').addEventListener('click', function (e) {
|
||||
var b = e.target.closest('.tr-subbtn'); if (!b) return;
|
||||
curSubject = b.getAttribute('data-sub');
|
||||
|
||||
@@ -111,6 +111,14 @@ LLM-задач (P3) из UI, генерация по теме урока.
|
||||
|
||||
---
|
||||
|
||||
## Уровни сложности — DONE
|
||||
Движок: `instantiate(gen,{level})` масштабирует диапазоны `pick` (`_scaleRange`):
|
||||
L2 = база, L1 — меньше магнитуды/меньше отрицательных (легче), L3 — шире (сложнее).
|
||||
Универсально для всех генераторов (корень-вперёд + самопроверка держат корректность),
|
||||
opt-out `gen.noScale`. Страница: контрол **Сложность: Авто / Лёгкий / Средний /
|
||||
Сложный**; «Авто» поднимает уровень с серией верных в сессии (streak≥2→2, ≥4→3, ошибка→1).
|
||||
Смоук движка T18 (36 ген × L1/L2/L3 материализуются; L3 шире L1; L2==база).
|
||||
|
||||
## Сквозное
|
||||
Тесты/смоуки на каждую фазу; доступность (ARIA, клавиатура, озвучка формул);
|
||||
офлайн-режим (PWA) для параметрики; производительность.
|
||||
|
||||
Reference in New Issue
Block a user