math-tasks/tasks/space-exploration/scripts/generate-crossing.mjs

212 lines
8.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Generate space-exploration-4.template.html — 9-page worksheet with
* addition and subtraction crossing 10 and 20.
*
* Usage: node tasks/space-exploration/scripts/generate-crossing.mjs
* Output: tasks/space-exploration/docs/space-exploration-4.template.html
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(__dirname, '../../..');
const pack1Icons = fs.readdirSync(path.join(ROOT, 'assets/themes/nms/icons/pack1'))
.filter(f => f.endsWith('.png'))
.map(f => `assets/themes/nms/icons/pack1/${f}`);
const pack2Icons = fs.readdirSync(path.join(ROOT, 'assets/themes/nms/icons/pack2'))
.filter(f => f.endsWith('.png'))
.map(f => `assets/themes/nms/icons/pack2/${f}`);
function shuffle(arr) {
const a = [...arr];
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
function randInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function pickAlignment() {
const opts = ['justify-start', 'justify-center', 'justify-end'];
return opts[Math.floor(Math.random() * opts.length)];
}
// Generate 20 unique crossing problems (A ± B crosses 10 or 20)
function genCrossingProblems() {
const problems = [];
const seen = new Set();
while (problems.length < 20) {
const isAdd = Math.random() < 0.5;
const crossAt = Math.random() < 0.5 ? 10 : 20;
let a, b;
if (isAdd) {
if (crossAt === 10) {
// A ∈ [1,9], A+B ∈ [11,min(29,A+10)]
a = randInt(1, 9);
const minB = 11 - a;
const maxB = Math.min(10, 29 - a);
if (minB > maxB) continue;
b = randInt(minB, maxB);
} else {
// A ∈ [11,19], A+B ∈ [21,min(29,A+10)]
a = randInt(11, 19);
const minB = 21 - a;
const maxB = Math.min(10, 29 - a);
if (minB > maxB) continue;
b = randInt(minB, maxB);
}
const result = a + b;
if (result < 1 || result > 29) continue;
const expr = `${a} + ${b}`;
if (!seen.has(expr)) { seen.add(expr); problems.push(expr); }
} else {
if (crossAt === 10) {
// A ∈ [11,19], AB ∈ [1,9]
a = randInt(11, 19);
const minB = a - 9;
const maxB = Math.min(10, a - 1);
if (minB > maxB || minB < 1) continue;
b = randInt(Math.max(1, minB), maxB);
} else {
// A ∈ [21,29], AB ∈ [11,19]
a = randInt(21, 29);
const minB = a - 19;
const maxB = Math.min(10, a - 11);
if (minB > maxB || minB < 1) continue;
b = randInt(Math.max(1, minB), maxB);
}
const result = a - b;
if (result < 1 || result > 29) continue;
const expr = `${a} ${b}`;
if (!seen.has(expr)) { seen.add(expr); problems.push(expr); }
}
}
return problems;
}
const pages = [
{ hero: 'assets/themes/nms/hero-images/spaceship1.jpeg', footer: 'assets/themes/nms/footers/planet1.jpeg', dir: 'flex-row-reverse', pack: 'pack1' },
{ hero: 'assets/themes/nms/hero-images/spaceship2.jpeg', footer: 'assets/themes/nms/footers/planet2.jpeg', dir: '', pack: 'pack1' },
{ hero: 'assets/themes/nms/hero-images/spaceship3.png', footer: 'assets/themes/nms/footers/planet3.jpeg', dir: 'flex-row-reverse', pack: 'pack1' },
{ hero: 'assets/themes/nms/hero-images/spaceship4.jpeg', footer: 'assets/themes/nms/footers/planet4.jpeg', dir: '', pack: 'pack1' },
{ hero: 'assets/themes/nms/hero-images/spaceship5.jpeg', footer: 'assets/themes/nms/footers/planet5.jpeg', dir: 'flex-row-reverse', pack: 'pack1' },
{ hero: 'assets/themes/nms/hero-images/spaceship6.jpeg', footer: 'assets/themes/nms/footers/planet6.jpeg', dir: '', pack: 'pack1' },
{ hero: 'assets/themes/nms/hero-images/spaceship7.jpeg', footer: 'assets/themes/nms/footers/planet7.jpeg', dir: 'flex-row-reverse', pack: 'pack2' },
{ hero: 'assets/themes/nms/hero-images/spaceship8.jpeg', footer: 'assets/themes/nms/footers/planet8.jpeg', dir: '', pack: 'pack2' },
{ hero: 'assets/themes/nms/hero-images/spaceship9.jpeg', footer: 'assets/themes/nms/footers/planet9.jpeg', dir: 'flex-row-reverse', pack: 'pack2' },
];
// Icons: pages 0-5 → pack1 (shuffled twice across 3-page sections), pages 6-8 → pack2
const iconSections = [
shuffle(pack1Icons), // pages 0-2
shuffle(pack1Icons), // pages 3-5
shuffle(pack2Icons), // pages 6-8
];
function buildProblemCard(icon, expression, alignment) {
return ` <div class="flex ${alignment}">
<div class="flex items-center gap-1">
<img src="../../../${icon}" class="w-[58px] h-[58px] shrink-0 object-contain" alt="">
<div class="pl-3 pr-4 py-1 border-[1.5px] border-indigo-100 rounded-full bg-gradient-to-br from-white to-indigo-50/40 shadow-sm shadow-indigo-100/30">
<span class="text-[1.2rem] font-semibold text-indigo-950 whitespace-nowrap">${expression} = <span class="inline-block w-16 border-b-[2px] border-indigo-300">&nbsp;</span></span>
</div>
</div>
</div>`;
}
function buildPage(pageIdx, isLast) {
const pg = pages[pageIdx];
const sectionIdx = Math.floor(pageIdx / 3);
const posInSection = pageIdx % 3;
const sectionIcons = iconSections[sectionIdx];
const pageIcons = sectionIcons.slice(posInSection * 20, posInSection * 20 + 20);
const problems = genCrossingProblems();
const dirClass = pg.dir ? ` ${pg.dir}` : '';
const breakStyle = isLast ? '' : ' style="break-after: page;"';
const cards = problems.map((expr, i) =>
buildProblemCard(pageIcons[i], expr, pickAlignment())
).join('\n\n');
return ` <!-- PAGE ${pageIdx + 1} -->
<div class="w-[210mm] h-[297mm] relative overflow-hidden mx-auto bg-white"${breakStyle}>
<div class="absolute bottom-0 left-0 right-0 h-[80mm] z-0">
<div class="absolute top-0 left-0 right-0 h-full z-10" style="background: linear-gradient(to bottom, white 0%, rgba(255,255,255,0.6) 25%, transparent 50%);"></div>
<img src="../../../${pg.footer}" class="w-full h-full object-cover object-top" alt="">
</div>
<div class="absolute bottom-[12mm] left-0 right-0 z-20 flex justify-center">
<div style="background: rgba(255,255,255,0.85);" class="rounded-full px-6 py-1.5 border-[1.5px] border-indigo-200 shadow-md">
<span class="text-[0.95rem] font-bold text-indigo-950">Итого собрано ресурсов: <span class="inline-block w-14 border-b-2 border-indigo-400 text-center ml-1">&nbsp;</span></span>
</div>
</div>
<div class="relative z-10 w-full h-full px-[12mm] pt-[4mm] pb-[65mm] flex flex-col">
<div class="flex items-center gap-4 mb-4${dirClass}">
<img src="../../../${pg.hero}" class="w-[48%] shrink-0 object-contain" alt="">
<div class="flex-1 flex flex-col items-center justify-center text-center">
<h1 class="text-2xl font-extrabold leading-tight tracking-tight text-indigo-950">Исследуй Планету</h1>
<p class="text-sm font-medium text-indigo-400 mt-1">Собери ресурсы, решая примеры!</p>
</div>
</div>
<div class="grid grid-cols-2 gap-x-3 gap-y-[3px] flex-1 content-start">
${cards}
</div>
</div>
</div>`;
}
const htmlPages = pages.map((_, i) => buildPage(i, i === pages.length - 1)).join('\n\n');
const html = `<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.tailwindcss.com"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700;800;900&display=swap" rel="stylesheet">
<title>Исследуй Планету</title>
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: { nunito: ['Nunito', 'sans-serif'] },
}
}
}
</script>
<style>
@page { size: A4; margin: 0; }
</style>
</head>
<body class="bg-gray-200 font-nunito">
${htmlPages}
</body>
</html>
`;
const outPath = path.join(__dirname, '..', 'docs', 'space-exploration-4.template.html');
fs.writeFileSync(outPath, html, 'utf-8');
console.log(`Generated: ${outPath} (${pages.length} pages, ${pages.length * 20} problems)`);