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:
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user