From d8f18c0d9dbec59e4053fb6e0bb1d91d52d61b2a Mon Sep 17 00:00:00 2001 From: Oleg Proskurin Date: Tue, 21 Apr 2026 12:37:11 +0700 Subject: [PATCH] doc: add asteroids3 --- .claude/settings.local.json | 4 +- src/scripts/generate-worksheet3.mjs | 311 ++++++++++++++++++++++++++ src/scripts/transform-collecting3.mjs | 203 +++++++++++++++++ src/scripts/verify-asteroids.mjs | 120 ++++++++++ tasks/collecting-asteroids-3.json | 141 ++++++++++++ tasks/space-worksheet3.json | 93 ++++++++ 6 files changed, 871 insertions(+), 1 deletion(-) create mode 100644 src/scripts/generate-worksheet3.mjs create mode 100644 src/scripts/transform-collecting3.mjs create mode 100644 src/scripts/verify-asteroids.mjs create mode 100644 tasks/collecting-asteroids-3.json create mode 100644 tasks/space-worksheet3.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 3f4f28f..f88392f 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -44,7 +44,9 @@ "Bash(pnpm pdf:*)", "Bash(mv freighter-trade1.png freighter1.png)", "Bash(mv freighter-mining1.png freighter11.png)", - "Bash(mv freighter-science1.png freighter12.png)" + "Bash(mv freighter-science1.png freighter12.png)", + "Bash(sort -t'\\(' -k2 -n)", + "Bash(grep -o 'scale\\(3.50\\).\\\\{0,200\\\\}')" ] } } diff --git a/src/scripts/generate-worksheet3.mjs b/src/scripts/generate-worksheet3.mjs new file mode 100644 index 0000000..48bf089 --- /dev/null +++ b/src/scripts/generate-worksheet3.mjs @@ -0,0 +1,311 @@ +/** + * 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 `
+
+ +
+ ${expression} =   +
+
+
`; +} + +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
Посчитай пятёрками: 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60 для зарядки расщепителя
\n` + : ''; + + const cards = problems.map((expr, i) => { + return buildProblemCard(pageIcons[i], expr, pickAlignment(), iconSize); + }).join('\n\n'); + + return ` +
+ +
+
+ +
+ +
+
+ Итого собрано на планете:   +
+
+ +
+ +
+ +
+

Исследуй Планету

+

Собери ресурсы, решая примеры!

+
+
+${fivesHint} +
+ +${cards} + +
+
+
`; +} + +const htmlPages = pages.map((_, i) => buildPage(i, i === pages.length - 1)).join('\n\n'); + +const html = ` + + + + + + + + + Исследуй Планету + + + + + +${htmlPages} + + + +`; + +// ── 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)`); diff --git a/src/scripts/transform-collecting3.mjs b/src/scripts/transform-collecting3.mjs new file mode 100644 index 0000000..d6f54c7 --- /dev/null +++ b/src/scripts/transform-collecting3.mjs @@ -0,0 +1,203 @@ +#!/usr/bin/env node + +/** + * Transform collecting-asteroids-2.html into collecting-asteroids-3.html + * Output goes to stdout for review before saving. + */ + +import { readFileSync } from 'fs'; +import { resolve } from 'path'; + +const root = resolve(import.meta.dirname, '../..'); +const v2Html = readFileSync(resolve(root, 'output/html/collecting-asteroids-2.html'), 'utf-8'); + +// Page configs for v3 +const pages = [ + { + comment: 'PAGE 1: ships 11(2), 9(2), 13(6) — R-L-R', + hero: 'splitter4', footer: 'cabin3', + cargoBays: [7, 8, 9], + ships: [11, 9, 13], + oldAsteroidTypes: ['asteroid7', 'asteroid14'], + newAsteroidTypes: ['asteroid3', 'asteroid10'], + asteroidValues: [3, 2, 1, 4, 5, 2, 3, 6, 5, 4, 1, 6], + }, + { + comment: 'PAGE 2: ships 12(3), 12(3), 18(4) — L-R-L', + hero: 'splitter7', footer: 'cabin6', + cargoBays: [1, 2, 3], + ships: [12, 12, 18], + oldAsteroidTypes: ['asteroid4', 'asteroid11'], + newAsteroidTypes: ['asteroid6', 'asteroid13'], + asteroidValues: [4, 5, 3, 6, 2, 7, 4, 3, 5, 2, 6, 4], + }, + { + comment: 'PAGE 3: ships 9(5), 11(3), 11(2) — R-L-R', + hero: 'splitter1', footer: 'cabin9', + cargoBays: [4, 5, 6], + ships: [9, 11, 11], + oldAsteroidTypes: ['asteroid10', 'asteroid3'], + newAsteroidTypes: ['asteroid2', 'asteroid8'], + asteroidValues: [2, 4, 1, 3, 5, 2, 1, 3, 4, 6, 5, 2], + }, + { + comment: 'PAGE 4: ships 15(4), 17(5), 11(2) — L-R-L', + hero: 'splitter6', footer: 'cabin2', + cargoBays: [7, 8, 9], + ships: [15, 17, 11], + oldAsteroidTypes: ['asteroid16', 'asteroid5'], + newAsteroidTypes: ['asteroid12', 'asteroid15'], + asteroidValues: [4, 3, 5, 2, 6, 1, 3, 4, 6, 2, 5, 4], + }, + { + comment: 'PAGE 5: ships 7(2), 9(4), 7(2) — R-L-R', + hero: 'splitter2', footer: 'cabin5', + cargoBays: [1, 2, 3], + ships: [7, 9, 7], + oldAsteroidTypes: ['asteroid2', 'asteroid13'], + newAsteroidTypes: ['asteroid1', 'asteroid9'], + asteroidValues: [3, 1, 4, 2, 5, 1, 6, 2, 3, 4, 5], + }, + { + comment: 'PAGE 6: ships 18(4), 18(5), 14(3) — L-R-L', + hero: 'splitter8', footer: 'cabin7', + cargoBays: [4, 5, 6], + ships: [18, 18, 14], + oldAsteroidTypes: ['asteroid8', 'asteroid12'], + newAsteroidTypes: ['asteroid5', 'asteroid16'], + asteroidValues: [3, 4, 5, 6, 2, 4, 7, 3, 2, 5, 6, 4, 3, 7], + }, + { + comment: 'PAGE 7: ships 10(2), 16(3), 22(6) — R-L-R', + hero: 'splitter9', footer: 'cabin4', + cargoBays: [7, 8, 9], + ships: [10, 16, 22], + oldAsteroidTypes: ['asteroid15', 'asteroid6'], + newAsteroidTypes: ['asteroid11', 'asteroid7'], + asteroidValues: [4, 3, 2, 6, 5, 3, 7, 4, 5, 2, 6, 4], + }, + { + comment: 'PAGE 8: ships 26(0), 18(0), 30(0) — UNSOLVABLE — L-R-L', + hero: 'splitter5', footer: 'cabin1', + cargoBays: [1, 2, 3], + ships: [26, 18, 30], + oldAsteroidTypes: ['asteroid9', 'asteroid1'], + newAsteroidTypes: ['asteroid14', 'asteroid4'], + asteroidValues: [6, 4, 5, 3, 7, 2, 4, 6, 5, 7, 3, 4, 6], + }, + { + comment: 'PAGE 9: ships 8(2), 18(3), 28(8) — R-L-R', + hero: 'splitter3', footer: 'cabin8', + cargoBays: [4, 5, 6], + ships: [8, 18, 28], + oldAsteroidTypes: ['asteroid11', 'asteroid4'], + newAsteroidTypes: ['asteroid10', 'asteroid6'], + asteroidValues: [4, 3, 2, 5, 6, 3, 4, 7, 2, 5, 4, 6, 3, 7, 5], + }, +]; + +// Split HTML into pages +// Pages are delimited by /, ``); + + // 2. Replace hero image + html = html.replace( + /splitters\/splitter\d+\.png/, + `splitters/${config.hero}.png` + ); + + // 3. Replace footer image + html = html.replace( + /footers\/cabin\d+\.jpeg/, + `footers/${config.footer}.jpeg` + ); + + // 4. Replace cargo bay images (3 per page, in order) + let cbIdx = 0; + html = html.replace( + /pack4-cargobay\/cargo-bay\d+\.png/g, + (match) => `pack4-cargobay/cargo-bay${config.cargoBays[cbIdx++]}.png` + ); + + // 5. Replace ship capacity numbers (text-xl spans inside blue circles) + let shipIdx = 0; + html = html.replace( + /()(\d+)(<\/span>)/g, + (match, pre, val, post) => `${pre}${config.ships[shipIdx++]}${post}` + ); + + // 6. Replace asteroid type images + for (let i = 0; i < config.oldAsteroidTypes.length; i++) { + const oldType = config.oldAsteroidTypes[i]; + const newType = config.newAsteroidTypes[i]; + html = html.replaceAll( + `pack3-asteroids/${oldType}.png`, + `pack3-asteroids/${newType}.png` + ); + } + + // 7. Replace asteroid values (text-2xl spans, sequential) + let avIdx = 0; + html = html.replace( + /(]*>)(\d+)(<\/span>)/g, + (match, pre, val, post) => { + if (avIdx < config.asteroidValues.length) { + return `${pre}${config.asteroidValues[avIdx++]}${post}`; + } + return match; + } + ); + + // Sanity check + if (shipIdx !== 3) { + console.error(`WARNING: Page ${pageIdx + 1} — replaced ${shipIdx} ship values (expected 3)`); + } + if (avIdx !== config.asteroidValues.length) { + console.error(`WARNING: Page ${pageIdx + 1} — replaced ${avIdx} asteroid values (expected ${config.asteroidValues.length})`); + } + if (cbIdx !== 3) { + console.error(`WARNING: Page ${pageIdx + 1} — replaced ${cbIdx} cargo bay refs (expected 3)`); + } + + return html; +} + +// Transform all pages +const newPages = pageBlocks.map((block, i) => transformPage(block, pages[i], i)); + +// Assemble +const result = header + newPages.join('') + footer; + +// Write to temp file +import { writeFileSync } from 'fs'; +const tmpPath = resolve(root, 'output/html/.collecting-asteroids-3-tmp.html'); +writeFileSync(tmpPath, result); +console.log(`Output written to: ${tmpPath}`); +console.log(`Total lines: ${result.split('\n').length}`); + +// Print summary of changes per page +for (let i = 0; i < pages.length; i++) { + const p = pages[i]; + console.log(`\nPage ${i + 1}: hero=${p.hero} footer=${p.footer} cargoBays=[${p.cargoBays}]`); + console.log(` Ships: [${p.ships}] Asteroids: [${p.asteroidValues}]`); + console.log(` Types: ${p.oldAsteroidTypes} → ${p.newAsteroidTypes}`); +} diff --git a/src/scripts/verify-asteroids.mjs b/src/scripts/verify-asteroids.mjs new file mode 100644 index 0000000..3c07b05 --- /dev/null +++ b/src/scripts/verify-asteroids.mjs @@ -0,0 +1,120 @@ +#!/usr/bin/env node + +/** + * Asteroid partition solver. + * Verifies that disjoint subsets of asteroid values can sum to each ship capacity. + * + * Usage: + * node verify-asteroids.mjs --ships 10,10,14 --asteroids 3,2,1,4,5,2,3,6,5,4,1,6 + * + * Output: prints whether a valid partition exists and one example solution. + */ + +const args = process.argv.slice(2); + +function parseArg(flag) { + const idx = args.indexOf(flag); + if (idx === -1 || idx + 1 >= args.length) return null; + return args[idx + 1].split(',').map(Number); +} + +const ships = parseArg('--ships'); +const asteroids = parseArg('--asteroids'); + +if (!ships || !asteroids) { + console.log('Usage: node verify-asteroids.mjs --ships 10,10,14 --asteroids 3,2,1,4,5,2,3,6,5,4,1,6'); + process.exit(1); +} + +console.log(`Ships: [${ships.join(', ')}] (sum=${ships.reduce((a, b) => a + b, 0)})`); +console.log(`Asteroids: [${asteroids.join(', ')}] (count=${asteroids.length}, sum=${asteroids.reduce((a, b) => a + b, 0)})`); + +/** + * Find disjoint subsets of `available` (by index) that sum to each capacity in `capacities`. + * Returns array of index arrays, or null if no solution. + */ +function findPartition(available, capacities, usedSet = new Set()) { + if (capacities.length === 0) return []; + + const target = capacities[0]; + const remaining = capacities.slice(1); + const remainingSum = remaining.reduce((a, b) => a + b, 0); + + // Available sum check + let availSum = 0; + for (let i = 0; i < available.length; i++) { + if (!usedSet.has(i)) availSum += available[i]; + } + if (availSum < target + remainingSum) return null; + + // Find all subsets summing to target using DFS + const subset = []; + + function dfs(startIdx, currentSum) { + if (currentSum === target) { + // Try solving remaining capacities + const newUsed = new Set(usedSet); + for (const idx of subset) newUsed.add(idx); + const result = findPartition(available, remaining, newUsed); + if (result !== null) { + return [subset.slice(), ...result]; + } + return null; + } + + if (currentSum > target) return null; + + for (let i = startIdx; i < available.length; i++) { + if (usedSet.has(i)) continue; + if (currentSum + available[i] > target) continue; + + // Pruning: check if remaining available sum can reach target + let futureSum = 0; + for (let j = i; j < available.length; j++) { + if (!usedSet.has(j) && !subset.includes(j)) futureSum += available[j]; + } + if (currentSum + futureSum < target) return null; + + subset.push(i); + const result = dfs(i + 1, currentSum + available[i]); + if (result) return result; + subset.pop(); + } + return null; + } + + return dfs(0, 0); +} + +// Sort ships descending for better pruning (hardest to fill first) +const sortedShips = [...ships].sort((a, b) => b - a); + +console.log(`\nSolving for ships (sorted desc): [${sortedShips.join(', ')}]`); + +const solution = findPartition(asteroids, sortedShips); + +if (solution) { + console.log('\n✓ SOLVABLE! Solution found:'); + // Map back to original ship order + const shipOrder = ships.map((cap, idx) => ({ cap, idx })) + .sort((a, b) => b.cap - a.cap); + + for (let i = 0; i < sortedShips.length; i++) { + const indices = solution[i]; + const values = indices.map(j => asteroids[j]); + const origShipIdx = shipOrder[i].idx; + console.log(` Ship ${origShipIdx + 1} (capacity ${sortedShips[i]}): asteroids [${values.join(' + ')}] = ${values.reduce((a, b) => a + b, 0)} (indices: ${indices.join(',')})`); + } + + const usedIndices = new Set(solution.flat()); + const unused = asteroids.map((v, i) => ({ v, i })).filter(x => !usedIndices.has(x.i)); + if (unused.length > 0) { + console.log(` Unused asteroids: [${unused.map(x => `${x.v}(idx ${x.i})`).join(', ')}]`); + } +} else { + console.log('\n✗ NO SOLUTION found.'); + console.log(` Ship sum: ${ships.reduce((a, b) => a + b, 0)}, Asteroid sum: ${asteroids.reduce((a, b) => a + b, 0)}`); + if (asteroids.reduce((a, b) => a + b, 0) < ships.reduce((a, b) => a + b, 0)) { + console.log(' Reason: asteroid sum < ship sum (mathematically impossible)'); + } +} diff --git a/tasks/collecting-asteroids-3.json b/tasks/collecting-asteroids-3.json new file mode 100644 index 0000000..c4c2652 --- /dev/null +++ b/tasks/collecting-asteroids-3.json @@ -0,0 +1,141 @@ +{ + "id": "collecting-asteroids-3", + "title": "Собери Астероиды", + "description": "9-page asteroid-matching worksheet v3: reshuffled assets, halved asteroid values", + "labels": { + "title": "Собери Астероиды", + "subtitle": "Загрузи трюмы кораблей!", + "footerBubble": "Итого загружено:" + }, + "theme": { + "style": "space-asteroids", + "template": "space-base", + "asteroids": "assets/icons/pack3-asteroids/" + }, + "layout": { + "type": "asteroid-matching", + "shipsPerPage": 3, + "asteroidTypesPerPage": 2, + "asteroidTypeRatio": "70:30" + }, + "pages": [ + { + "ships": [ + { "capacity": 11, "fillCount": 2 }, + { "capacity": 9, "fillCount": 2 }, + { "capacity": 13, "fillCount": 6 } + ], + "heroImage": "assets/hero-images/splitters/splitter4.png", + "footerImage": "assets/footers/cabin3.jpeg", + "heroDirection": "row", + "cargoBays": ["cargo-bay7", "cargo-bay8", "cargo-bay9"], + "asteroidTypes": ["asteroid3", "asteroid10"], + "asteroidValues": [3, 2, 1, 4, 5, 2, 3, 6, 5, 4, 1, 6] + }, + { + "ships": [ + { "capacity": 12, "fillCount": 3 }, + { "capacity": 12, "fillCount": 3 }, + { "capacity": 18, "fillCount": 4 } + ], + "heroImage": "assets/hero-images/splitters/splitter7.png", + "footerImage": "assets/footers/cabin6.jpeg", + "heroDirection": "row-reverse", + "cargoBays": ["cargo-bay1", "cargo-bay2", "cargo-bay3"], + "asteroidTypes": ["asteroid6", "asteroid13"], + "asteroidValues": [4, 5, 3, 6, 2, 7, 4, 3, 5, 2, 6, 4] + }, + { + "ships": [ + { "capacity": 9, "fillCount": 5 }, + { "capacity": 11, "fillCount": 3 }, + { "capacity": 11, "fillCount": 2 } + ], + "heroImage": "assets/hero-images/splitters/splitter1.png", + "footerImage": "assets/footers/cabin9.jpeg", + "heroDirection": "row", + "cargoBays": ["cargo-bay4", "cargo-bay5", "cargo-bay6"], + "asteroidTypes": ["asteroid2", "asteroid8"], + "asteroidValues": [2, 4, 1, 3, 5, 2, 1, 3, 4, 6, 5, 2] + }, + { + "ships": [ + { "capacity": 15, "fillCount": 4 }, + { "capacity": 17, "fillCount": 5 }, + { "capacity": 11, "fillCount": 2 } + ], + "heroImage": "assets/hero-images/splitters/splitter6.png", + "footerImage": "assets/footers/cabin2.jpeg", + "heroDirection": "row-reverse", + "cargoBays": ["cargo-bay7", "cargo-bay8", "cargo-bay9"], + "asteroidTypes": ["asteroid12", "asteroid15"], + "asteroidValues": [4, 3, 5, 2, 6, 1, 3, 4, 6, 2, 5, 4] + }, + { + "ships": [ + { "capacity": 7, "fillCount": 2 }, + { "capacity": 9, "fillCount": 4 }, + { "capacity": 7, "fillCount": 2 } + ], + "heroImage": "assets/hero-images/splitters/splitter2.png", + "footerImage": "assets/footers/cabin5.jpeg", + "heroDirection": "row", + "cargoBays": ["cargo-bay1", "cargo-bay2", "cargo-bay3"], + "asteroidTypes": ["asteroid1", "asteroid9"], + "asteroidValues": [3, 1, 4, 2, 5, 1, 6, 2, 3, 4, 5] + }, + { + "ships": [ + { "capacity": 18, "fillCount": 4 }, + { "capacity": 18, "fillCount": 5 }, + { "capacity": 14, "fillCount": 3 } + ], + "heroImage": "assets/hero-images/splitters/splitter8.png", + "footerImage": "assets/footers/cabin7.jpeg", + "heroDirection": "row-reverse", + "cargoBays": ["cargo-bay4", "cargo-bay5", "cargo-bay6"], + "asteroidTypes": ["asteroid5", "asteroid16"], + "asteroidValues": [3, 4, 5, 6, 2, 4, 7, 3, 2, 5, 6, 4, 3, 7] + }, + { + "ships": [ + { "capacity": 10, "fillCount": 2 }, + { "capacity": 16, "fillCount": 3 }, + { "capacity": 22, "fillCount": 6 } + ], + "heroImage": "assets/hero-images/splitters/splitter9.png", + "footerImage": "assets/footers/cabin4.jpeg", + "heroDirection": "row", + "cargoBays": ["cargo-bay7", "cargo-bay8", "cargo-bay9"], + "asteroidTypes": ["asteroid11", "asteroid7"], + "asteroidValues": [4, 3, 2, 6, 5, 3, 7, 4, 5, 2, 6, 4] + }, + { + "ships": [ + { "capacity": 26, "fillCount": 0 }, + { "capacity": 18, "fillCount": 0 }, + { "capacity": 30, "fillCount": 0 } + ], + "_unsolvable": true, + "heroImage": "assets/hero-images/splitters/splitter5.png", + "footerImage": "assets/footers/cabin1.jpeg", + "heroDirection": "row-reverse", + "cargoBays": ["cargo-bay1", "cargo-bay2", "cargo-bay3"], + "asteroidTypes": ["asteroid14", "asteroid4"], + "asteroidValues": [6, 4, 5, 3, 7, 2, 4, 6, 5, 7, 3, 4, 6] + }, + { + "ships": [ + { "capacity": 8, "fillCount": 2 }, + { "capacity": 18, "fillCount": 3 }, + { "capacity": 28, "fillCount": 8 } + ], + "heroImage": "assets/hero-images/splitters/splitter3.png", + "footerImage": "assets/footers/cabin8.jpeg", + "heroDirection": "row", + "cargoBays": ["cargo-bay4", "cargo-bay5", "cargo-bay6"], + "asteroidTypes": ["asteroid10", "asteroid6"], + "asteroidValues": [4, 3, 2, 5, 6, 3, 4, 7, 2, 5, 4, 6, 3, 7, 5] + } + ] +} diff --git a/tasks/space-worksheet3.json b/tasks/space-worksheet3.json new file mode 100644 index 0000000..a8e50b1 --- /dev/null +++ b/tasks/space-worksheet3.json @@ -0,0 +1,93 @@ +{ + "id": "space-worksheet3", + "title": "Исследуй Планету", + "description": "9-page space worksheet combining patterns from worksheet1 and worksheet2: addition, multiply by 5, multiplication table, constrained addition, mixed fives, compound multiplication", + "labels": { + "title": "Исследуй Планету", + "subtitle": "Собери ресурсы, решая примеры!", + "footerBubble": "Итого собрано на планете:" + }, + "theme": { + "style": "space", + "template": "space-base", + "icons": ["assets/icons/pack1/", "assets/icons/pack2/"] + }, + "layout": { + "columns": 2, + "problemsPerPage": 20 + }, + "pages": [ + { + "task": "A + B ± C, где A от 8 до 16, B от 4 до 8, C от 1 до 3. Знак ± выбирается случайно", + "problemCount": 20, + "heroImage": "assets/hero-images/spaceship1.jpeg", + "footerImage": "assets/footers/planet1.jpeg", + "heroDirection": "row-reverse", + "iconPack": "pack1" + }, + { + "task": "5 × N ± C, где N от 1 до 5, C от 1 до 6. Знак ± выбирается случайно", + "problemCount": 20, + "heroImage": "assets/hero-images/spaceship2.jpeg", + "footerImage": "assets/footers/planet2.jpeg", + "heroDirection": "row", + "iconPack": "pack1" + }, + { + "task": "A × B ± C, где A и B от 1 до 4, C от 1 до 8. Знак ± выбирается случайно", + "problemCount": 20, + "heroImage": "assets/hero-images/spaceship3.png", + "footerImage": "assets/footers/planet3.jpeg", + "heroDirection": "row-reverse", + "iconPack": "pack1" + }, + { + "task": "A + B + C, где A от 12 до 24, B ±(от 6 до 10), C ±(от 2 до 5). Результат от 0 до 40 включительно", + "problemCount": 20, + "heroImage": "assets/hero-images/spaceship4.jpeg", + "footerImage": "assets/footers/planet4.jpeg", + "heroDirection": "row", + "iconPack": "pack2" + }, + { + "task": "5 + 5 + 5 (сложение пятерок: 2, 3 или 4 штуки) ИЛИ 5 × N (N от 1 до 12) ИЛИ 5 × N ± 5", + "problemCount": 20, + "heroImage": "assets/hero-images/spaceship5.jpeg", + "footerImage": "assets/footers/planet5.jpeg", + "heroDirection": "row-reverse", + "iconPack": "pack2" + }, + { + "task": "A × B + C × D, где A от 2 до 4, B от 1 до 3, C от 2 до 3, D от 1 до 2", + "problemCount": 20, + "heroImage": "assets/hero-images/spaceship6.jpeg", + "footerImage": "assets/footers/planet6.jpeg", + "heroDirection": "row", + "iconPack": "pack2" + }, + { + "task": "A + B ± C, где A от 8 до 16, B от 4 до 8, C от 1 до 3. Знак ± выбирается случайно", + "problemCount": 20, + "heroImage": "assets/hero-images/spaceship7.jpeg", + "footerImage": "assets/footers/planet7.jpeg", + "heroDirection": "row-reverse", + "iconPack": "pack1" + }, + { + "task": "5 × N ± C, где N от 1 до 5, C от 1 до 6. Знак ± выбирается случайно", + "problemCount": 20, + "heroImage": "assets/hero-images/spaceship8.jpeg", + "footerImage": "assets/footers/planet8.jpeg", + "heroDirection": "row", + "iconPack": "pack1" + }, + { + "task": "A × B ± C, где A и B от 1 до 4, C от 1 до 8. Знак ± выбирается случайно", + "problemCount": 20, + "heroImage": "assets/hero-images/spaceship9.jpeg", + "footerImage": "assets/footers/planet9.jpeg", + "heroDirection": "row-reverse", + "iconPack": "pack1" + } + ] +}