feat(math6): Глава 1, волна 4 — §12 прикладной + финал-боссы (глава завершена)

§12 «Математика вокруг нас»: задачи из жизни (покупки, сдача, измерения)
+ среднее значение. Финал главы: бой с 5 боссами (разряды, округление,
сложение/вычитание, умножение, деление на дробь) с HP-баром; победа 4/5+
даёт +40 XP и достижение «Глава 1 пройдена». Эталонная Глава 1 готова: все
12 параграфов наполнены. Тесты 12/12.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-02 15:06:54 +03:00
parent 826e7b04f2
commit 4b949f7ce2
2 changed files with 143 additions and 3 deletions
+13
View File
@@ -121,6 +121,19 @@ test('ch1 Волна 3: интерактивы §7–§10 монтируются
assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | ')); assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | '));
}); });
test('ch1 Волна 4: §12 прикладной и финал-боссы', async () => {
const { doc, errors } = await loadDom('math_6_ch1.html');
const win = doc.defaultView;
win.goTo('app'); await wait(80);
assert.ok(doc.querySelector('#app-q') && doc.querySelector('#app-aq'), 'задачи §12');
win.goTo('final'); await wait(80);
assert.ok(doc.querySelector('#fin-q') && doc.querySelector('#fin-go'), 'арена боссов');
// финал на 100% → достижение ch1_done (finalAch)
win.bumpProgress('final', 100); await wait(20);
assert.ok(win.M6STATE.achievements.has('ch1_done'), 'достижение «Глава 1 пройдена» при финале');
assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | '));
});
test('навигация и прогресс: переход на § и отметка прочтения', async () => { test('навигация и прогресс: переход на § и отметка прочтения', async () => {
const { doc, errors } = await loadDom('math_6_ch1.html'); const { doc, errors } = await loadDom('math_6_ch1.html');
const win = doc.defaultView; const win = doc.defaultView;
+130 -3
View File
@@ -716,6 +716,122 @@ function buildP10(){
})(); })();
} }
/* ===================== § 12. МАТЕМАТИКА ВОКРУГ НАС ===================== */
function buildApp(){
var box=document.getElementById('app-body'); var h='';
h+=makeCard('theory','Десятичные дроби в жизни','12.1',
'<p>Цены и сдача в магазине, рост и масса, показания счётчиков, спортивные результаты, средние значения — всё это <b>десятичные дроби</b>. Решим практические задачи.</p>'
+'<p>В рублях, метрах, килограммах, секундах — десятичная дробь показывает «часть» единицы: $0{,}5$ кг — это полкилограмма, $1{,}25$ ч — это час с четвертью.</p>');
h+='<div class="wg" id="app-iv1"><div class="wg-header"><span class="wg-badge">Интерактив 1</span><div class="wg-title">Задачи из жизни</div></div>'
+'<div class="wg-help">Реши практическую задачу. Ответ — число (запятая или точка), без единиц.</div>'
+'<div class="score-display"><span>Задача <b id="app-i">1</b> / 6</span><span>Очки: <b id="app-s">0</b> / 6</span></div>'
+'<div id="app-q" class="qbox" style="font-size:1rem;text-align:left"></div>'
+'<div style="display:flex;gap:10px;justify-content:center;align-items:center;flex-wrap:wrap"><input type="text" id="app-a" class="tinp" style="width:130px;text-align:center" placeholder="ответ"><button class="btn primary" id="app-go">Проверить</button></div>'
+'<div class="feedback" id="app-fb"></div></div>';
h+='<div class="wg" id="app-iv2"><div class="wg-header"><span class="wg-badge">Интерактив 2</span><div class="wg-title">Среднее значение</div></div>'
+'<div class="wg-help">Среднее = сумма всех значений, делённая на их количество.</div>'
+'<div class="score-display"><span>Задача <b id="app-ai">1</b> / 5</span><span>Очки: <b id="app-as">0</b> / 5</span></div>'
+'<div id="app-aq" class="qbox" style="font-size:1rem;text-align:left"></div>'
+'<div style="display:flex;gap:10px;justify-content:center;align-items:center;flex-wrap:wrap"><input type="text" id="app-aa" class="tinp" style="width:130px;text-align:center" placeholder="среднее"><button class="btn primary" id="app-ago">Проверить</button></div>'
+'<div class="feedback" id="app-afb"></div></div>';
h+=secNav('p10','final')+readBtn('app');
box.innerHTML=h; renderMath(box);
(function(){
var PROB=[
{q:'Булочка стоит 0,85 руб., сок — 1,4 руб. Сколько стоит вся покупка?',a:2.25},
{q:'Тетрадь стоит 0,6 руб. Сколько стоят 4 такие тетради?',a:2.4},
{q:'За товар ценой 3,7 руб. дали 5 руб. Сколько сдачи?',a:1.3},
{q:'1 кг яблок стоит 2,5 руб. Сколько заплатят за 1,2 кг?',a:3},
{q:'Ленту длиной 2,5 м разрезали на 5 равных частей. Какова длина одной части (в метрах)?',a:0.5},
{q:'Литр бензина стоит 1,45 руб. Сколько стоят 10 л?',a:14.5},
{q:'Шоколадка стоила 1,2 руб. и подешевела на 0,3 руб. Новая цена?',a:0.9},
{q:'Три друга поровну разделили 7,5 руб. Сколько досталось каждому?',a:2.5}
];
var order=[],i=0,score=0,cur=null;
function reorder(){ order=PROB.map(function(_,k){return k;}); for(var j=order.length-1;j>0;j--){ var k=_ri(0,j); var t=order[j];order[j]=order[k];order[k]=t; } }
reorder();
function show(){ if(i>=6){ document.getElementById('app-q').innerHTML='<b>Готово!</b> Результат: '+score+' / 6'; if(score>=5){addXp(15,'app-iv1');bumpProgress('app',30);}else if(score>=3){addXp(8,'app-iv1');bumpProgress('app',16);} return; }
cur=PROB[order[i]]; document.getElementById('app-i').textContent=i+1;
document.getElementById('app-q').innerHTML=cur.q;
document.getElementById('app-a').value=''; document.getElementById('app-fb').style.display='none'; }
function go(){ if(i>=6)return; var fb=document.getElementById('app-fb'), v=parseFloat(document.getElementById('app-a').value.replace(',','.').trim());
if(isNaN(v)){ feedback(fb,false,'Введи число.'); return; }
if(Math.abs(v-cur.a)<1e-9){ score++; feedback(fb,true,'✓ Верно: '+_kf(cur.a).replace('{,}',',')+'.'); } else feedback(fb,false,'✗ Нет. Правильно: '+_kf(cur.a).replace('{,}',',')+'.');
document.getElementById('app-s').textContent=score; i++; setTimeout(show,1300); }
document.getElementById('app-go').addEventListener('click',go);
document.getElementById('app-a').addEventListener('keydown',function(e){ if(e.key==='Enter')go(); }); show();
})();
(function(){
var AVG=[
{q:'Найди среднее чисел 4,2 и 5,8.',a:5},
{q:'Температуры за три дня: 1,5°; 2,5°; 3,5°. Средняя температура?',a:2.5},
{q:'Оценки: 8; 9; 7; 8. Средний балл?',a:8},
{q:'Рост двух мальчиков 1,4 м и 1,6 м. Средний рост?',a:1.5},
{q:'Найди среднее чисел 0,2; 0,4; 0,6.',a:0.4},
{q:'Время кругов: 2,4 с и 2,6 с. Среднее время?',a:2.5}
];
var order=[],i=0,score=0,cur=null;
order=AVG.map(function(_,k){return k;}); for(var j=order.length-1;j>0;j--){ var k=_ri(0,j); var t=order[j];order[j]=order[k];order[k]=t; }
function show(){ if(i>=5){ document.getElementById('app-aq').innerHTML='<b>Готово!</b> Результат: '+score+' / 5'; if(score>=4){addXp(15,'app-iv2');bumpProgress('app',30);}else if(score>=2){addXp(8,'app-iv2');bumpProgress('app',16);} return; }
cur=AVG[order[i]]; document.getElementById('app-ai').textContent=i+1;
document.getElementById('app-aq').innerHTML=cur.q;
document.getElementById('app-aa').value=''; document.getElementById('app-afb').style.display='none'; }
function go(){ if(i>=5)return; var fb=document.getElementById('app-afb'), v=parseFloat(document.getElementById('app-aa').value.replace(',','.').trim());
if(isNaN(v)){ feedback(fb,false,'Введи число.'); return; }
if(Math.abs(v-cur.a)<1e-9){ score++; feedback(fb,true,'✓ Верно: '+_kf(cur.a).replace('{,}',',')+'.'); } else feedback(fb,false,'✗ Нет. Правильно: '+_kf(cur.a).replace('{,}',',')+'.');
document.getElementById('app-as').textContent=score; i++; setTimeout(show,1300); }
document.getElementById('app-ago').addEventListener('click',go);
document.getElementById('app-aa').addEventListener('keydown',function(e){ if(e.key==='Enter')go(); }); show();
})();
}
/* ===================== ФИНАЛ ГЛАВЫ — БОССЫ ===================== */
function buildFinal(){
var box=document.getElementById('final-body'); var h='';
h+=makeCard('theory','Финал главы 1','★',
'<p>Пять боссов проверят всё, что ты освоил: разряды, округление, действия с десятичными дробями, деление на дробь и преобразования. Победи всех — и глава покорена!</p>');
h+='<div class="wg" id="fin"><div class="wg-header"><span class="wg-badge">Боссы</span><div class="wg-title">Сразись с главой 1</div></div>'
+'<div class="hp-boss"><div class="hp-boss-fill" id="fin-hp" style="width:100%"></div></div>'
+'<div class="score-display"><span>Босс <b id="fin-i">1</b> / 5</span><span>Побеждено: <b id="fin-s">0</b> / 5</span></div>'
+'<div id="fin-name" style="font-weight:800;color:#b91c1c;text-align:center;margin-bottom:6px"></div>'
+'<div id="fin-q" class="boss-q"></div>'
+'<div style="display:flex;gap:10px;justify-content:center;align-items:center;flex-wrap:wrap"><input type="text" id="fin-a" class="tinp" style="width:150px;text-align:center" placeholder="ответ"><button class="btn primary" id="fin-go">Удар!</button></div>'
+'<div class="feedback" id="fin-fb"></div></div>';
h+=secNav('app',null)+readBtn('final','Завершить главу 1 (+10 XP)');
box.innerHTML=h; renderMath(box);
(function(){
var bosses=[
function(){ var ws=String(_ri(10,99)), ds=String(_ri(100,999)); var num=ws+','+ds; return {name:'Страж Разрядов', q:'В числе $'+num.replace(',','{,}')+'$ назови цифру в разряде <b>сотых</b>.', ans:+ds[1]}; },
function(){ var n=_ri(115,985)/100; return {name:'Округлитель', q:'Округли $'+_kf(n)+'$ до <b>десятых</b>.', ans:_round(n,1)}; },
function(){ var a=_rnum(2),b=_rnum(2),op=_pick(['+','']); if(op===''&&b>a){var t=a;a=b;b=t;} var D=Math.max(_dec(a),_dec(b)), r=(op==='+'?_mant(a,D)+_mant(b,D):_mant(a,D)-_mant(b,D))/Math.pow(10,D); return {name:'Сумматор', q:'Вычисли $'+_kf(a)+' '+op+' '+_kf(b)+'$.', ans:r}; },
function(){ var a=_rnum(1),b=_rnum(1), da=_dec(a),db=_dec(b), r=_mant(a,da)*_mant(b,db)/Math.pow(10,da+db); return {name:'Множитель', q:'Вычисли $'+_kf(a)+' \\cdot '+_kf(b)+'$.', ans:r}; },
function(){ var R=_ri(2,20), b=_pick([0.5,0.2,0.25,0.4,0.05]), a=_round(R*b,_dec(b)); return {name:'Делитель', q:'Вычисли $'+_kf(a)+' \\div '+_kf(b)+'$.', ans:R}; }
];
var i=0,score=0,cur=null,done=false;
function show(){
if(i>=5){ done=true; document.getElementById('fin-name').textContent=''; document.getElementById('fin-q').innerHTML=(score>=4?'<b>Победа!</b> Глава 1 пройдена. ':'<b>Бой окончен.</b> ')+'Побеждено боссов: '+score+' / 5.';
document.getElementById('fin-hp').style.width=(score>=4?0:40)+'%';
if(score>=4){ addXp(40,'final'); bumpProgress('final',100); if(window.confetti)try{confetti();}catch(e){} } else { bumpProgress('final',60); }
return; }
cur=bosses[i](); document.getElementById('fin-i').textContent=i+1; document.getElementById('fin-s').textContent=score;
document.getElementById('fin-name').textContent='Босс '+(i+1)+': '+cur.name;
document.getElementById('fin-hp').style.width=(100-i*20)+'%';
document.getElementById('fin-q').innerHTML=cur.q; renderMath(document.getElementById('fin-q'));
document.getElementById('fin-a').value=''; document.getElementById('fin-fb').style.display='none';
}
function go(){ if(done||i>=5)return; var fb=document.getElementById('fin-fb'), v=parseFloat(document.getElementById('fin-a').value.replace(',','.').trim());
if(isNaN(v)){ feedback(fb,false,'Введи число.'); return; }
if(Math.abs(v-cur.ans)<1e-9){ score++; feedback(fb,true,'✓ Босс повержен! Ответ $'+_kf(cur.ans)+'$.'); } else feedback(fb,false,'✗ Босс устоял. Верный ответ: $'+_kf(cur.ans)+'$.');
document.getElementById('fin-s').textContent=score; i++; setTimeout(show,1400); }
document.getElementById('fin-go').addEventListener('click',go);
document.getElementById('fin-a').addEventListener('keydown',function(e){ if(e.key==='Enter')go(); });
show();
})();
}
/* ===================== ДАННЫЕ САЙДБАРА / ГЛОССАРИЯ ===================== */ /* ===================== ДАННЫЕ САЙДБАРА / ГЛОССАРИЯ ===================== */
var SIDEBARS = { var SIDEBARS = {
p1:{ title:'Шпаргалка § 1', rows:[ p1:{ title:'Шпаргалка § 1', rows:[
@@ -767,7 +883,16 @@ var SIDEBARS = {
['Десятичная → обыкн.','по разрядам и сократить'], ['Десятичная → обыкн.','по разрядам и сократить'],
['Обыкн. → десятичная','делением (если конечная)'], ['Обыкн. → десятичная','делением (если конечная)'],
['В выражении','привести к одному виду'], ['В выражении','привести к одному виду'],
['$0{,}4$','$=\\frac{2}{5}$'] ]} ['$0{,}4$','$=\\frac{2}{5}$'] ]},
app:{ title:'Шпаргалка § 12', rows:[
['Покупка','складываем цены'],
['Сдача','деньги − стоимость'],
['Среднее','сумма ÷ количество'],
['Единицы','$0{,}5$ кг — это полкилограмма'] ]},
final:{ title:'Финал главы 1', rows:[
['5 боссов','разряды, округление, действия, деление, преобразования'],
['Победа','4 из 5 и больше'],
['Награда','+40 XP и достижение «Глава 1 пройдена»'] ]}
}; };
var TIPS = [ var TIPS = [
{ sec:'p1', html:'Число цифр после запятой = числу нулей в знаменателе. У $0{,}305$ три цифры → знаменатель $1000$.' }, { sec:'p1', html:'Число цифр после запятой = числу нулей в знаменателе. У $0{,}305$ три цифры → знаменатель $1000$.' },
@@ -779,7 +904,9 @@ var TIPS = [
{ sec:'p7', html:'Запятую в частном ставят ровно тогда, когда переходят от целой части делимого к дробной. Не делится нацело — припиши нули.' }, { sec:'p7', html:'Запятую в частном ставят ровно тогда, когда переходят от целой части делимого к дробной. Не делится нацело — припиши нули.' },
{ sec:'p8', html:'Считай знаки <b>только у делителя</b>. Перенеси запятую на столько знаков вправо и у делимого, и у делителя — делитель станет целым.' }, { sec:'p8', html:'Считай знаки <b>только у делителя</b>. Перенеси запятую на столько знаков вправо и у делимого, и у делителя — делитель станет целым.' },
{ sec:'p9', html:'Чтобы понять, конечная ли дробь, разложи знаменатель на множители. Только 2 и 5 — конечная; есть 3, 7, … — бесконечная.' }, { sec:'p9', html:'Чтобы понять, конечная ли дробь, разложи знаменатель на множители. Только 2 и 5 — конечная; есть 3, 7, … — бесконечная.' },
{ sec:'p10', html:'Если все десятичные конечные — переведи дроби в десятичные и считай в десятичных. Иначе приводи к обыкновенным с общим знаменателем.' } { sec:'p10', html:'Если все десятичные конечные — переведи дроби в десятичные и считай в десятичных. Иначе приводи к обыкновенным с общим знаменателем.' },
{ sec:'app', html:'Читай задачу внимательно: «сколько всего» — складываем; «сколько сдачи» — вычитаем; «поровну» — делим; «среднее» — сумма делить на количество.' },
{ sec:'final', html:'Не спеши — у каждого босса один удар важен. Перечитай условие, прикинь ответ в уме, и только потом вводи число.' }
]; ];
var GLOSSARY = [ var GLOSSARY = [
{ term:'десятичная дробь', def:'Дробь со знаменателем $10,100,1000,\\ldots$, записанная через запятую.', sec:'p1', aliases:['десятичная дробь','десятичной дроби','десятичные дроби','десятичных дробей','десятичную дробь'] }, { term:'десятичная дробь', def:'Дробь со знаменателем $10,100,1000,\\ldots$, записанная через запятую.', sec:'p1', aliases:['десятичная дробь','десятичной дроби','десятичные дроби','десятичных дробей','десятичную дробь'] },
@@ -793,7 +920,7 @@ var GLOSSARY = [
{ term:'периодическая дробь', def:'Бесконечная десятичная дробь, в которой группа цифр (период) повторяется: $0{,}(3)$.', sec:'p9', aliases:['периодическая дробь','периодической дроби','период','периода','периодом'] }, { term:'периодическая дробь', def:'Бесконечная десятичная дробь, в которой группа цифр (период) повторяется: $0{,}(3)$.', sec:'p9', aliases:['периодическая дробь','периодической дроби','период','периода','периодом'] },
{ term:'конечная дробь', def:'Десятичная дробь с конечным числом знаков после запятой ($0{,}75$).', sec:'p9', aliases:['конечная дробь','конечной дроби','конечная десятичная'] } { term:'конечная дробь', def:'Десятичная дробь с конечным числом знаков после запятой ($0{,}75$).', sec:'p9', aliases:['конечная дробь','конечной дроби','конечная десятичная'] }
]; ];
var BUILDERS = { p1:buildP1, p2:buildP2, p3:buildP3, p4:buildP4, p5:buildP5, p6:buildP6, p7:buildP7, p8:buildP8, p9:buildP9, p10:buildP10 }; var BUILDERS = { p1:buildP1, p2:buildP2, p3:buildP3, p4:buildP4, p5:buildP5, p6:buildP6, p7:buildP7, p8:buildP8, p9:buildP9, p10:buildP10, app:buildApp, final:buildFinal };
Object.assign(window.M6, { sidebars:SIDEBARS, tips:TIPS, glossary:GLOSSARY, builders:BUILDERS }); Object.assign(window.M6, { sidebars:SIDEBARS, tips:TIPS, glossary:GLOSSARY, builders:BUILDERS });
</script> </script>