From 4b949f7ce2904d82780af927c961e389aac20cde Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Tue, 2 Jun 2026 15:06:54 +0300 Subject: [PATCH] =?UTF-8?q?feat(math6):=20=D0=93=D0=BB=D0=B0=D0=B2=D0=B0?= =?UTF-8?q?=201,=20=D0=B2=D0=BE=D0=BB=D0=BD=D0=B0=204=20=E2=80=94=20=C2=A7?= =?UTF-8?q?12=20=D0=BF=D1=80=D0=B8=D0=BA=D0=BB=D0=B0=D0=B4=D0=BD=D0=BE?= =?UTF-8?q?=D0=B9=20+=20=D1=84=D0=B8=D0=BD=D0=B0=D0=BB-=D0=B1=D0=BE=D1=81?= =?UTF-8?q?=D1=81=D1=8B=20(=D0=B3=D0=BB=D0=B0=D0=B2=D0=B0=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D1=88=D0=B5=D0=BD=D0=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §12 «Математика вокруг нас»: задачи из жизни (покупки, сдача, измерения) + среднее значение. Финал главы: бой с 5 боссами (разряды, округление, сложение/вычитание, умножение, деление на дробь) с HP-баром; победа 4/5+ даёт +40 XP и достижение «Глава 1 пройдена». Эталонная Глава 1 готова: все 12 параграфов наполнены. Тесты 12/12. Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/tests/math6-page.test.js | 13 +++ frontend/textbooks/math_6_ch1.html | 133 ++++++++++++++++++++++++++++- 2 files changed, 143 insertions(+), 3 deletions(-) diff --git a/backend/tests/math6-page.test.js b/backend/tests/math6-page.test.js index 5a69251..b5a97d0 100644 --- a/backend/tests/math6-page.test.js +++ b/backend/tests/math6-page.test.js @@ -121,6 +121,19 @@ test('ch1 Волна 3: интерактивы §7–§10 монтируются 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 () => { const { doc, errors } = await loadDom('math_6_ch1.html'); const win = doc.defaultView; diff --git a/frontend/textbooks/math_6_ch1.html b/frontend/textbooks/math_6_ch1.html index 0cbcb50..c405004 100644 --- a/frontend/textbooks/math_6_ch1.html +++ b/frontend/textbooks/math_6_ch1.html @@ -716,6 +716,122 @@ function buildP10(){ })(); } +/* ===================== § 12. МАТЕМАТИКА ВОКРУГ НАС ===================== */ +function buildApp(){ + var box=document.getElementById('app-body'); var h=''; + h+=makeCard('theory','Десятичные дроби в жизни','12.1', + '

Цены и сдача в магазине, рост и масса, показания счётчиков, спортивные результаты, средние значения — всё это десятичные дроби. Решим практические задачи.

' + +'

В рублях, метрах, килограммах, секундах — десятичная дробь показывает «часть» единицы: $0{,}5$ кг — это полкилограмма, $1{,}25$ ч — это час с четвертью.

'); + h+='
Интерактив 1
Задачи из жизни
' + +'
Реши практическую задачу. Ответ — число (запятая или точка), без единиц.
' + +'
Задача 1 / 6Очки: 0 / 6
' + +'
' + +'
' + +'
'; + h+='
Интерактив 2
Среднее значение
' + +'
Среднее = сумма всех значений, делённая на их количество.
' + +'
Задача 1 / 5Очки: 0 / 5
' + +'
' + +'
' + +'
'; + 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='Готово! Результат: '+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='Готово! Результат: '+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','★', + '

Пять боссов проверят всё, что ты освоил: разряды, округление, действия с десятичными дробями, деление на дробь и преобразования. Победи всех — и глава покорена!

'); + h+='
Боссы
Сразись с главой 1
' + +'
' + +'
Босс 1 / 5Побеждено: 0 / 5
' + +'
' + +'
' + +'
' + +'
'; + 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(',','{,}')+'$ назови цифру в разряде сотых.', ans:+ds[1]}; }, + function(){ var n=_ri(115,985)/100; return {name:'Округлитель', q:'Округли $'+_kf(n)+'$ до десятых.', 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?'Победа! Глава 1 пройдена. ':'Бой окончен. ')+'Побеждено боссов: '+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 = { 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 = [ { sec:'p1', html:'Число цифр после запятой = числу нулей в знаменателе. У $0{,}305$ три цифры → знаменатель $1000$.' }, @@ -779,7 +904,9 @@ var TIPS = [ { sec:'p7', html:'Запятую в частном ставят ровно тогда, когда переходят от целой части делимого к дробной. Не делится нацело — припиши нули.' }, { sec:'p8', html:'Считай знаки только у делителя. Перенеси запятую на столько знаков вправо и у делимого, и у делителя — делитель станет целым.' }, { sec:'p9', html:'Чтобы понять, конечная ли дробь, разложи знаменатель на множители. Только 2 и 5 — конечная; есть 3, 7, … — бесконечная.' }, - { sec:'p10', html:'Если все десятичные конечные — переведи дроби в десятичные и считай в десятичных. Иначе приводи к обыкновенным с общим знаменателем.' } + { sec:'p10', html:'Если все десятичные конечные — переведи дроби в десятичные и считай в десятичных. Иначе приводи к обыкновенным с общим знаменателем.' }, + { sec:'app', html:'Читай задачу внимательно: «сколько всего» — складываем; «сколько сдачи» — вычитаем; «поровну» — делим; «среднее» — сумма делить на количество.' }, + { sec:'final', html:'Не спеши — у каждого босса один удар важен. Перечитай условие, прикинь ответ в уме, и только потом вводи число.' } ]; var GLOSSARY = [ { 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{,}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 });