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

312 lines
12 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-worksheet3.html — 9-page static HTML worksheet
* combining task patterns from worksheet1 and worksheet2.
*
* Usage: node src/scripts/generate-worksheet3.mjs
* Output: output/html/space-worksheet3.html
*/
import fs from 'fs';
import path from 'path';
const ROOT = process.cwd();
// ── Icon pools ──────────────────────────────────────────────────────────────
const pack1Icons = fs.readdirSync(path.join(ROOT, 'assets/icons/pack1'))
.filter(f => f.endsWith('.png'))
.map(f => `assets/icons/pack1/${f}`);
const pack2Icons = fs.readdirSync(path.join(ROOT, 'assets/icons/pack2'))
.filter(f => f.endsWith('.png'))
.map(f => `assets/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 randSign() {
return Math.random() < 0.5 ? 1 : -1;
}
function pickAlignment() {
const opts = ['justify-start', 'justify-center', 'justify-end'];
return opts[Math.floor(Math.random() * opts.length)];
}
// ── Problem generators ──────────────────────────────────────────────────────
// Pattern 1: A + B ± C (A:8-16, B:4-8, C:1-3)
function genPattern1() {
const problems = [];
const seen = new Set();
while (problems.length < 20) {
const A = randInt(8, 16);
const B = randInt(4, 8);
const C = randInt(1, 3);
const sign = randSign();
const expr = sign > 0 ? `${A} + ${B} + ${C}` : `${A} + ${B} ${C}`;
const result = A + B + sign * C;
if (result >= 0 && !seen.has(expr)) {
seen.add(expr);
problems.push(expr);
}
}
return problems;
}
// Pattern 2: 5 × N ± C (N:1-5, C:1-6)
function genPattern2() {
const problems = [];
const seen = new Set();
while (problems.length < 20) {
const N = randInt(1, 5);
const C = randInt(1, 6);
const sign = randSign();
const expr = sign > 0 ? `5 × ${N} + ${C}` : `5 × ${N} ${C}`;
const result = 5 * N + sign * C;
if (result >= 0 && !seen.has(expr)) {
seen.add(expr);
problems.push(expr);
}
}
return problems;
}
// Pattern 3: A × B ± C (A,B:1-4, C:1-8)
function genPattern3() {
const problems = [];
const seen = new Set();
while (problems.length < 20) {
const A = randInt(1, 4);
const B = randInt(1, 4);
const C = randInt(1, 8);
const sign = randSign();
const expr = sign > 0 ? `${A} × ${B} + ${C}` : `${A} × ${B} ${C}`;
const result = A * B + sign * C;
if (result >= 0 && !seen.has(expr)) {
seen.add(expr);
problems.push(expr);
}
}
return problems;
}
// Pattern 4: A + B + C (A:12-24, B:±6-10, C:±2-5, result 0-40)
function genPattern4() {
const problems = [];
const seen = new Set();
while (problems.length < 20) {
const A = randInt(12, 24);
const signB = randSign();
const B = randInt(6, 10);
const signC = randSign();
const C = randInt(2, 5);
const result = A + signB * B + signC * C;
if (result >= 0 && result <= 40) {
const bPart = signB > 0 ? `+ ${B}` : ` ${B}`;
const cPart = signC > 0 ? `+ ${C}` : ` ${C}`;
const expr = `${A} ${bPart} ${cPart}`;
if (!seen.has(expr)) {
seen.add(expr);
problems.push(expr);
}
}
}
return problems;
}
// Pattern 5: mixed fives (5+5+5, 5×N, 5×N±5)
function genPattern5() {
const problems = [];
const seen = new Set();
// Type A: 5+5+5 (2,3,4 fives)
for (const count of [2, 3, 4]) {
const expr = Array(count).fill('5').join(' + ');
problems.push(expr);
seen.add(expr);
}
// Type B: 5×N (N:1-12)
const ns = shuffle([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
for (const n of ns) {
if (problems.length >= 20) break;
const expr = `5 × ${n}`;
if (!seen.has(expr)) {
seen.add(expr);
problems.push(expr);
}
}
// Type C: 5×N±5 (fill remaining)
while (problems.length < 20) {
const N = randInt(2, 12);
const sign = randSign();
const expr = sign > 0 ? `5 × ${N} + 5` : `5 × ${N} 5`;
if (!seen.has(expr)) {
seen.add(expr);
problems.push(expr);
}
}
return shuffle(problems);
}
// Pattern 6: A×B + C×D (A:2-4, B:1-3, C:2-3, D:1-2)
function genPattern6() {
const problems = [];
const seen = new Set();
while (problems.length < 20) {
const A = randInt(2, 4);
const B = randInt(1, 3);
const C = randInt(2, 3);
const D = randInt(1, 2);
const expr = `${A} × ${B} + ${C} × ${D}`;
if (!seen.has(expr)) {
seen.add(expr);
problems.push(expr);
}
}
return problems;
}
const generators = [genPattern1, genPattern2, genPattern3, genPattern4, genPattern5, genPattern6, genPattern1, genPattern2, genPattern3];
// ── Page config ─────────────────────────────────────────────────────────────
const pages = [
{ hero: 'assets/hero-images/spaceship1.jpeg', footer: 'assets/footers/planet1.jpeg', dir: 'flex-row-reverse', pack: 'pack1', gen: 0 },
{ hero: 'assets/hero-images/spaceship2.jpeg', footer: 'assets/footers/planet2.jpeg', dir: '', pack: 'pack1', gen: 1 },
{ hero: 'assets/hero-images/spaceship3.png', footer: 'assets/footers/planet3.jpeg', dir: 'flex-row-reverse', pack: 'pack1', gen: 2 },
{ hero: 'assets/hero-images/spaceship4.jpeg', footer: 'assets/footers/planet4.jpeg', dir: '', pack: 'pack2', gen: 3 },
{ hero: 'assets/hero-images/spaceship5.jpeg', footer: 'assets/footers/planet5.jpeg', dir: 'flex-row-reverse', pack: 'pack2', gen: 4, fivesHint: true },
{ hero: 'assets/hero-images/spaceship6.jpeg', footer: 'assets/footers/planet6.jpeg', dir: '', pack: 'pack2', gen: 5 },
{ hero: 'assets/hero-images/spaceship7.jpeg', footer: 'assets/footers/planet7.jpeg', dir: 'flex-row-reverse', pack: 'pack1', gen: 6 },
{ hero: 'assets/hero-images/spaceship8.jpeg', footer: 'assets/footers/planet8.jpeg', dir: '', pack: 'pack1', gen: 7 },
{ hero: 'assets/hero-images/spaceship9.jpeg', footer: 'assets/footers/planet9.jpeg', dir: 'flex-row-reverse', pack: 'pack1', gen: 8 },
];
// ── Assign icons per section (3 pages share a pool) ─────────────────────────
// Section 1 (pages 0-2): pack1 shuffled
// Section 2 (pages 3-5): pack2 shuffled
// Section 3 (pages 6-8): pack1 re-shuffled
const iconSections = [
shuffle(pack1Icons), // pages 0-2: 60 icons
shuffle(pack2Icons), // pages 3-5: 60 icons
shuffle(pack1Icons), // pages 6-8: 60 icons (reuse pack1, different shuffle)
];
// ── Build HTML ──────────────────────────────────────────────────────────────
function buildProblemCard(icon, expression, alignment, iconSize) {
return ` <div class="flex ${alignment}">
<div class="flex items-center gap-1">
<img src="../../${icon}" class="w-[${iconSize}px] h-[${iconSize}px] 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 iconSize = pg.pack === 'pack2' ? 58 : 58;
const problems = generators[pg.gen]();
const dirClass = pg.dir ? ` ${pg.dir}` : '';
const breakStyle = isLast ? '' : ' style="break-after: page;"';
const fivesHint = pg.fivesHint
? `\n <div class="font-semibold text-indigo-950 mb-3 text-center" style="font-size: 0.8rem; margin-top: -10px;">Посчитай пятёрками: <span style="font-size: 1rem; word-spacing: 0.25em;">5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60</span> для зарядки расщепителя</div>\n`
: '';
const cards = problems.map((expr, i) => {
return buildProblemCard(pageIcons[i], expr, pickAlignment(), iconSize);
}).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>
${fivesHint}
<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>
`;
// ── Write output ────────────────────────────────────────────────────────────
const outDir = path.join(ROOT, 'output/html');
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
const outPath = path.join(outDir, 'space-worksheet3.html');
fs.writeFileSync(outPath, html, 'utf-8');
console.log(`Generated: ${outPath} (${pages.length} pages, ${pages.length * 20} problems)`);