75165d900b
В Физике 8 ch1 §6, §7, §9, §11 уже имели IV-4 'Тренажёр N расчётных задач'. У §1-5, §8, §10 IV-4 был только MCQ — числовых задач не было. inject_p8_ch1_tasks.cjs добавляет IV-5 виджет после IV-4 в build_pN: - §1 Внутр. энергия: 5 задач (T-конверсия, U vs масса/высота) - §2 Способы изменения U: 5 (Q=ΔU+A, кин. энергия молота → тепло) - §3 Теплопроводность: 5 (тепловой поток P=Q/t, зависимости от d, S, λ) - §4 Конвекция: 5 (плотности тёплого/холодного, нагрев радиатором) - §5 Излучение: 5 (солнечный поток, Стефан-Больцман упрощённо) - §8 Плавление: 5 (Q=λm) - §10 Испарение: 5 (Q=rm, испарение пота, лужи) Всего 35 новых задач с автопроверкой числового ответа (±tol), подсказкой-решением (KaTeX) и +20 XP при прохождении всей серии. Используется существующий design system (.wg, .tinp, .feedback, .score-display) — уже подключён через phys-textbook-widgets.css.
178 lines
16 KiB
JavaScript
178 lines
16 KiB
JavaScript
// Inject IV-5 «Расчётные задачи» widget into build_pN of physics_8_ch1.html
|
||
// for paragraphs where IV-4 is только MCQ (§1, §2, §3, §4, §5, §8, §10).
|
||
// §6, §7, §9, §11 already have numeric task trainers.
|
||
'use strict';
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const DST = path.join(__dirname, '..', '..', 'frontend', 'textbooks', 'physics_8_ch1.html');
|
||
let h = fs.readFileSync(DST, 'utf8');
|
||
|
||
// === Numeric tasks per paragraph (§1..§11 thermal) ===
|
||
// Каждая задача: q (вопрос с KaTeX в $..$), ans (число), tol (допуск), why (пошаговое решение)
|
||
const TASKS = {
|
||
|
||
p1: [ // Внутренняя энергия
|
||
{ q: 'Переведите температуру $t = 27\\,^\\circ$C в кельвины. ($T = t + 273$)', ans: 300, tol: 1, why: '$T = 27 + 273 = 300$ К.' },
|
||
{ q: 'Температура воды $T = 373$ К. Чему равно $t$ в градусах Цельсия?', ans: 100, tol: 1, why: '$t = T - 273 = 373 - 273 = 100\\,^\\circ$C — кипение воды.' },
|
||
{ q: 'У стакана воды массой $m_1 = 0{,}5$ кг и у бочки воды массой $m_2 = 50$ кг одинаковая температура. У кого внутренняя энергия больше во сколько раз?', ans: 100, tol: 1, why: '$U \\propto m$ при одинаковой $T$. $U_2/U_1 = m_2/m_1 = 50/0{,}5 = 100$.' },
|
||
{ q: 'Тело нагрели на $\\Delta T = 30$ К. На сколько градусов Цельсия изменилась его температура?', ans: 30, tol: 0.5, why: 'Шкалы Кельвина и Цельсия отличаются только сдвигом — разность температур одинакова.' },
|
||
{ q: 'При какой температуре по шкале Цельсия средняя кинетическая энергия молекул равна нулю (абсолютный ноль)?', ans: -273, tol: 1, why: 'Абсолютный ноль $T = 0$ К соответствует $t = 0 - 273 = -273\\,^\\circ$C.' },
|
||
],
|
||
|
||
p2: [ // Способы изменения U
|
||
{ q: 'Газу передали $Q = 200$ Дж теплоты, и он совершил работу $A = 60$ Дж. На сколько увеличилась его внутренняя энергия? ($\\Delta U = Q - A$)', ans: 140, tol: 2, why: '$\\Delta U = Q - A = 200 - 60 = 140$ Дж (первое начало термодинамики).' },
|
||
{ q: 'Над газом совершили работу $A_{внеш} = 150$ Дж, газ отдал $Q = 50$ Дж тепла. На сколько изменилась $U$?', ans: 100, tol: 2, why: '$\\Delta U = A_{внеш} - Q_{отд} = 150 - 50 = 100$ Дж.' },
|
||
{ q: 'Газ адиабатно (без теплообмена, $Q = 0$) расширился, совершив $A = 80$ Дж. Найдите $|\\Delta U|$.', ans: 80, tol: 2, why: 'При $Q = 0$: $\\Delta U = -A = -80$ Дж. Модуль изменения $|\\Delta U| = 80$ Дж.' },
|
||
{ q: 'Молотом массой $0{,}5$ кг, движущимся со скоростью $v = 4$ м/с, ударили по гвоздю. Вся кинетическая энергия перешла в тепло. На сколько Джоулей увеличилась $U$ гвоздя?', ans: 4, tol: 0.1, why: '$E_к = \\dfrac{mv^2}{2} = \\dfrac{0{,}5 \\cdot 16}{2} = 4$ Дж $= \\Delta U$.' },
|
||
{ q: 'Газу сообщили $Q = 500$ Дж, при этом $\\Delta U = 350$ Дж. Какую работу совершил газ?', ans: 150, tol: 3, why: '$A = Q - \\Delta U = 500 - 350 = 150$ Дж.' },
|
||
],
|
||
|
||
p3: [ // Теплопроводность
|
||
{ q: 'Через стенку площадью $S = 2$ м² с разностью температур $\\Delta T = 20$ К за $t = 100$ с прошло $Q = 200$ Дж. Найдите тепловой поток (Вт): $P = Q/t$.', ans: 2, tol: 0.05, why: '$P = Q/t = 200 / 100 = 2$ Вт.' },
|
||
{ q: 'Тепловой поток через стенку $P = 50$ Вт. Сколько джоулей теплоты пройдёт через неё за $t = 1$ час?', ans: 180000, tol: 1000, why: '$Q = P \\cdot t = 50 \\cdot 3600 = 180\\,000$ Дж.' },
|
||
{ q: 'У какого материала теплопроводность больше при прочих равных: у $\\lambda_1 = 400$ Вт/(м·К) (медь) или $\\lambda_2 = 0{,}5$ Вт/(м·К) (вода)? Введите $\\lambda_1/\\lambda_2$.', ans: 800, tol: 5, why: '$\\lambda_1 / \\lambda_2 = 400 / 0{,}5 = 800$ — металлы намного лучше проводят тепло.' },
|
||
{ q: 'Стенка толщиной $d_1 = 0{,}1$ м заменена на стенку толщиной $d_2 = 0{,}05$ м из того же материала. Во сколько раз вырастет тепловой поток?', ans: 2, tol: 0.05, why: 'Поток $P \\propto 1/d$, поэтому $P_2/P_1 = d_1/d_2 = 0{,}1/0{,}05 = 2$.' },
|
||
{ q: 'Площадь стенки увеличили в 3 раза. Во сколько раз вырастет тепловой поток (при той же толщине и $\\Delta T$)?', ans: 3, tol: 0.05, why: 'Поток $P \\propto S$, поэтому увеличивается в 3 раза.' },
|
||
],
|
||
|
||
p4: [ // Конвекция
|
||
{ q: 'Плотность тёплого воздуха в $\\rho_1 = 1{,}1$ кг/м³, холодного $\\rho_2 = 1{,}3$ кг/м³. На сколько % холодный плотнее? $((\\rho_2 - \\rho_1)/\\rho_1) \\cdot 100$.', ans: 18, tol: 1, why: '$(1{,}3 - 1{,}1)/1{,}1 \\cdot 100 \\approx 18\\,\\%$.' },
|
||
{ q: 'Радиатор отдаёт мощность $P = 1500$ Вт, нагревая воздух массой $m = 50$ кг за $t = 60$ с. На сколько $\\Delta T$ нагрелся воздух? ($c_{возд} = 1000$ Дж/(кг·К))', ans: 1.8, tol: 0.1, why: '$\\Delta T = Q/(cm) = (P\\cdot t)/(cm) = (1500 \\cdot 60)/(1000 \\cdot 50) = 1{,}8$ К.' },
|
||
{ q: 'Вода нагревается снизу. Где будет тёплая вода: $a)$ снизу, $b)$ сверху? Введите 2, если сверху, 1, если снизу.', ans: 2, tol: 0.1, why: 'Тёплая вода легче — поднимается вверх. Это и есть конвекция.' },
|
||
{ q: 'Холодильник остужает $m = 2$ кг воздуха с $T_1 = 25$ до $T_2 = 5\\,^\\circ$C. Какое тепло (в кДж) он унёс? ($c = 1000$)', ans: 40, tol: 1, why: '$Q = cm\\Delta T = 1000 \\cdot 2 \\cdot 20 = 40\\,000$ Дж $= 40$ кДж.' },
|
||
{ q: 'Ветер охлаждает кожу. Если без ветра тело отдаёт $P_0 = 50$ Вт, а с ветром $P = 200$ Вт, во сколько раз быстрее идёт теплоотдача?', ans: 4, tol: 0.1, why: '$P/P_0 = 200/50 = 4$ раза.' },
|
||
],
|
||
|
||
p5: [ // Излучение
|
||
{ q: 'Солнце нагревает квадратный метр земной поверхности с мощностью $P = 1000$ Вт. Сколько теплоты получит $S = 5$ м² за $t = 60$ с?', ans: 300000, tol: 5000, why: '$Q = P \\cdot S \\cdot t = 1000 \\cdot 5 \\cdot 60 = 300\\,000$ Дж = $300$ кДж.' },
|
||
{ q: 'Черное тело излучает в 2 раза эффективнее белого. Если белое тело отдаёт $P_1 = 100$ Вт, сколько отдаст чёрное при той же $T$?', ans: 200, tol: 5, why: '$P_{черн} = 2 \\cdot P_{белого} = 2 \\cdot 100 = 200$ Вт.' },
|
||
{ q: 'Какая температура (в К) горячей плиты, если её излучение в 16 раз сильнее излучения тела при $T_0 = 300$ К? ($P \\propto T^4$)', ans: 600, tol: 10, why: '$P/P_0 = (T/T_0)^4 = 16$, откуда $T/T_0 = 2$, $T = 600$ К.' },
|
||
{ q: 'Какой цвет одежды летом холоднее: белый или чёрный? Введите 1, если чёрный, 2, если белый.', ans: 2, tol: 0.1, why: 'Белая отражает солнечное излучение лучше — в ней прохладнее.' },
|
||
{ q: 'Тело площадью $S = 0{,}5$ м² излучает $P = 200$ Вт. Найдите интенсивность излучения $I = P/S$ (Вт/м²).', ans: 400, tol: 10, why: '$I = P/S = 200/0{,}5 = 400$ Вт/м².' },
|
||
],
|
||
|
||
p8: [ // Плавление (Q = λm)
|
||
{ q: 'Сколько теплоты (в кДж) нужно для плавления $m = 2$ кг льда при $0\\,^\\circ$C? ($\\lambda_{льда} = 330$ кДж/кг)', ans: 660, tol: 5, why: '$Q = \\lambda m = 330 \\cdot 2 = 660$ кДж.' },
|
||
{ q: 'Какая масса (в кг) свинца расплавится, получив $Q = 50$ кДж? ($\\lambda_{св} = 25$ кДж/кг)', ans: 2, tol: 0.1, why: '$m = Q/\\lambda = 50/25 = 2$ кг.' },
|
||
{ q: 'Найдите удельную теплоту плавления вещества (кДж/кг), если на плавление $m = 0{,}5$ кг затрачено $Q = 100$ кДж.', ans: 200, tol: 5, why: '$\\lambda = Q/m = 100/0{,}5 = 200$ кДж/кг.' },
|
||
{ q: 'Сколько теплоты (кДж) нужно, чтобы расплавить $m = 5$ кг алюминия при $T_{пл}$? ($\\lambda_{Al} = 380$ кДж/кг)', ans: 1900, tol: 20, why: '$Q = \\lambda m = 380 \\cdot 5 = 1900$ кДж.' },
|
||
{ q: 'Лёд массой $m = 1$ кг при $0\\,^\\circ$C сначала нагрели до $t = 0\\,^\\circ$C (не нужно тепла), затем расплавили. Сколько кДж потратили? ($\\lambda = 330$)', ans: 330, tol: 3, why: '$Q = \\lambda m = 330 \\cdot 1 = 330$ кДж — только на плавление.' },
|
||
],
|
||
|
||
p10: [ // Испарение (Q = rm)
|
||
{ q: 'Сколько теплоты (в кДж) нужно, чтобы испарить $m = 0{,}2$ кг воды при $100\\,^\\circ$C? ($r_{воды} = 2300$ кДж/кг)', ans: 460, tol: 5, why: '$Q = rm = 2300 \\cdot 0{,}2 = 460$ кДж.' },
|
||
{ q: 'При испарении $m = 5$ кг этилового спирта поглощено $Q = 4500$ кДж. Найдите $r$ (кДж/кг).', ans: 900, tol: 10, why: '$r = Q/m = 4500/5 = 900$ кДж/кг.' },
|
||
{ q: 'Какая масса (в кг) воды испарится, если ей сообщили $Q = 1150$ кДж при $100\\,^\\circ$C? ($r = 2300$)', ans: 0.5, tol: 0.02, why: '$m = Q/r = 1150/2300 = 0{,}5$ кг.' },
|
||
{ q: 'Лужа площадью $S = 0{,}5$ м² и толщиной $d = 1$ мм испаряется. Сколько кДж нужно? ($\\rho_{воды} = 1000$ кг/м³, $r = 2300$ кДж/кг)', ans: 1.15, tol: 0.05, why: '$V = Sd = 0{,}5 \\cdot 0{,}001 = 5 \\cdot 10^{-4}$ м³, $m = \\rho V = 0{,}5$ кг $\\cdot 10^{-3} = 0{,}0005$ кг, $Q = rm = 2300 \\cdot 0{,}0005 = 1{,}15$ кДж.' },
|
||
{ q: 'Почему пот холодит кожу? При испарении пота поглощается теплота. Если испарилось $m = 100$ г пота ($r \\approx 2400$ кДж/кг), сколько кДж унесено с кожи?', ans: 240, tol: 5, why: '$Q = rm = 2400 \\cdot 0{,}1 = 240$ кДж.' },
|
||
],
|
||
|
||
};
|
||
|
||
// === Generate iv5 widget HTML + initializer function per pid ===
|
||
function makeIv5Widget(pid) {
|
||
const n = pid.slice(1);
|
||
return `
|
||
/* IV5 — Расчётные задачи (auto-injected) */
|
||
h += '<div class="wg">'
|
||
+'<div class="wg-header"><span class="wg-badge">IV-5</span><div class="wg-title">Тренажёр: ${TASKS[pid].length} расчётных задач</div></div>'
|
||
+'<div class="wg-help">Введи числовой ответ (можно с точкой как разделителем). Решено все верно — +20 XP.</div>'
|
||
+'<div id="${pid}-tasks5"></div>'
|
||
+'<div class="score-display" style="margin-top:10px"><span>Задача: <b id="${pid}-tasks5-i">1</b> / ${TASKS[pid].length}</span><span>Правильно: <b id="${pid}-tasks5-ok">0</b></span></div>'
|
||
+'</div>';
|
||
`;
|
||
}
|
||
|
||
function makeIv5Init(pid) {
|
||
const n = pid.slice(1);
|
||
const tasksLit = JSON.stringify(TASKS[pid]);
|
||
return `
|
||
function _init${pid}_iv5(){
|
||
const TASKS = ${tasksLit};
|
||
let i = 0, ok = 0, awarded = false;
|
||
function render(){
|
||
const t = TASKS[i]; const wrap = document.getElementById('${pid}-tasks5'); if(!wrap) return;
|
||
wrap.innerHTML =
|
||
'<div style="padding:10px 14px;background:rgba(15,23,42,.04);border-radius:9px;margin-bottom:10px;font-size:.95rem;line-height:1.55"><b>Задача '+(i+1)+'.</b> '+t.q+'</div>'
|
||
+'<div class="actions"><input type="number" step="0.001" class="tinp" id="${pid}-iv5-inp" placeholder="число" style="width:140px">'
|
||
+'<button class="btn primary" id="${pid}-iv5-go">Ответ</button>'
|
||
+'<button class="btn" id="${pid}-iv5-hint">Подсказка</button>'
|
||
+'<button class="btn" id="${pid}-iv5-next">Следующая</button></div>'
|
||
+'<details class="spoiler" id="${pid}-iv5-why-wrap" style="margin-top:8px;display:none"><summary>Решение</summary><div class="spoiler-body">'+t.why+'</div></details>'
|
||
+'<div class="feedback" id="${pid}-iv5-fb"></div>';
|
||
if (window.renderMathInElement) try { renderMathInElement(wrap, {delimiters:[{left:'$',right:'$',display:false}],throwOnError:false}); } catch(e){}
|
||
document.getElementById('${pid}-iv5-go').onclick = () => {
|
||
const v = parseFloat(document.getElementById('${pid}-iv5-inp').value.replace(',','.'));
|
||
const fb = document.getElementById('${pid}-iv5-fb');
|
||
const wh = document.getElementById('${pid}-iv5-why-wrap');
|
||
if (Math.abs(v - t.ans) <= t.tol) {
|
||
fb.className = 'feedback ok'; fb.innerHTML = 'Верно!'; ok++;
|
||
document.getElementById('${pid}-tasks5-ok').textContent = ok;
|
||
wh.style.display = 'block';
|
||
} else {
|
||
fb.className = 'feedback fail'; fb.innerHTML = 'Не совсем. Ожидался $' + t.ans + '$. Загляни в подсказку.';
|
||
if (window.renderMathInElement) try { renderMathInElement(fb, {delimiters:[{left:'$',right:'$',display:false}],throwOnError:false}); } catch(e){}
|
||
}
|
||
};
|
||
document.getElementById('${pid}-iv5-hint').onclick = () => {
|
||
const wh = document.getElementById('${pid}-iv5-why-wrap');
|
||
wh.style.display = wh.style.display === 'block' ? 'none' : 'block';
|
||
};
|
||
document.getElementById('${pid}-iv5-next').onclick = () => {
|
||
i = (i + 1) % TASKS.length;
|
||
document.getElementById('${pid}-tasks5-i').textContent = i + 1;
|
||
render();
|
||
if (ok === TASKS.length && !awarded) { awarded = true; if (typeof addXp === 'function') addXp(20, '${pid}-iv5'); }
|
||
};
|
||
}
|
||
render();
|
||
}
|
||
`;
|
||
}
|
||
|
||
// === Patch ch1 file ===
|
||
let patchedCount = 0;
|
||
for (const pid of Object.keys(TASKS)) {
|
||
// 1. Append IV-5 widget HTML inside build_pN before `box.innerHTML = h + secNavFor`
|
||
// 2. Append init call after wireReadBtn line
|
||
// 3. Append _initPN_iv5 function after the build_pN function block
|
||
const widget = makeIv5Widget(pid);
|
||
const init = makeIv5Init(pid);
|
||
|
||
// Marker to insert widget: find `box.innerHTML = h + secNavFor('pN') + readButton('pN');`
|
||
const insertWidgetBefore = `box.innerHTML = h + secNavFor('${pid}') + readButton('${pid}');`;
|
||
if (!h.includes(insertWidgetBefore)) {
|
||
console.warn(`${pid}: insert marker not found`);
|
||
continue;
|
||
}
|
||
h = h.replace(insertWidgetBefore, widget.trim() + '\n\n ' + insertWidgetBefore);
|
||
|
||
// Marker to insert init call: after `wireReadBtn('pN');` add `_initPN_iv5();`
|
||
const wireMarker = `wireReadBtn('${pid}');`;
|
||
h = h.replace(wireMarker, wireMarker + `\n _init${pid}_iv5();`);
|
||
|
||
// Append the _initPN_iv5 function — insert before the closing }\n of build_pN
|
||
// Search end of build_pN
|
||
const fnStart = h.indexOf(`function build_${pid}()`);
|
||
// Find closing brace of the function (look for `\n}\n` after fnStart, going past the new init call)
|
||
const fnEnd = h.indexOf('\n}\n', fnStart);
|
||
// Insert init function AFTER build_pN closing brace
|
||
const insertPos = fnEnd + 3; // skip "\n}\n"
|
||
h = h.slice(0, insertPos) + '\n' + init.trim() + '\n' + h.slice(insertPos);
|
||
patchedCount++;
|
||
console.log(` ${pid}: patched (${TASKS[pid].length} tasks)`);
|
||
}
|
||
|
||
fs.writeFileSync(DST, h);
|
||
console.log('Patched', patchedCount, '/', Object.keys(TASKS).length, 'paragraphs');
|
||
console.log('File size:', h.length);
|
||
|
||
// Sanity parse
|
||
const scripts = [...h.matchAll(/<script>([\s\S]*?)<\/script>/g)];
|
||
for (const m of scripts) {
|
||
try { new Function(m[1]); }
|
||
catch(e) { console.error('JS PARSE FAIL:', e.message); process.exit(1); }
|
||
}
|
||
console.log('inline JS parses OK');
|