Skill: factoring-trinomials-a1
Factoring Trinomials (a = 1) Worksheet
Factor each trinomial completely: GCF first, then the two-numbers method. If no integer pair works, write PRIME. For equations, solve by zero product and check each root in the original equation.
Each click of Generate builds a brand-new set of 20 problems (7 easy, 8 medium, 5 hard). Toggle the answer key any time; printing includes the key only when it is visible.
Factoring Trinomials (a = 1) Worksheet
Directions: Factor each trinomial completely: GCF first, then the two-numbers method. If no integer pair works, write PRIME. For equations, solve by zero product and check each root in the original equation.
Answer Key
For the teacher or for self-checking after you finish.
/* ============================================================================ FiboPoly — polynomial / rational-expression math core for FiboTutors skill ecosystems. Pure JavaScript, no DOM access. Free of the double-ampersand operator by construction (nested ifs / De Morgan). Exposed as FiboPoly (browser) or module.exports (node). ============================================================================ */ (function (root, factory) { 'use strict'; var api = factory(); if (typeof module === 'undefined') { root.FiboPoly = api; } else if (module.exports) { module.exports = api; } else { root.FiboPoly = api; } })(typeof self !== 'undefined' ? self : this, function () { 'use strict';
/* ================= integers ================= */ function igcd(a, b) { a = Math.abs(a); b = Math.abs(b); while (b) { var t = a % b; a = b; b = t; } return a || 1; }
/* ================= fractions {n,d}, d > 0, reduced ================= */ function fred(n, d) { if (d === 0) throw new Error('fred: zero denominator'); if (d < 0) { n = -n; d = -d; } if (n === 0) return { n: 0, d: 1 }; var g = igcd(n, d); return { n: n / g, d: d / g }; } function fi(k) { return { n: k, d: 1 }; } function fadd(a, b) { return fred(a.n * b.d + b.n * a.d, a.d * b.d); } function fsub(a, b) { return fred(a.n * b.d - b.n * a.d, a.d * b.d); } function fmul(a, b) { return fred(a.n * b.n, a.d * b.d); } function fdiv(a, b) { if (b.n === 0) throw new Error('fdiv: division by zero'); return fred(a.n * b.d, a.d * b.n); } function fneg(a) { return { n: -a.n, d: a.d }; } function feq(a, b) { return !(a.n !== b.n || a.d !== b.d); } function fis0(a) { return a.n === 0; } function ffmt(f) { return f.d === 1 ? String(f.n) : f.n + '/' + f.d; } /* parse "5", "-3", "3/4", "0.75" (also unicode minus) -> fraction or null */ function fparse(str) { var s = String(str).replace(/−/g, '-').replace(/\s+/g, ''); if (!s) return null; var m = /^([+-]?\d+)\/([+-]?\d+)$/.exec(s); if (m) { var d = parseInt(m[2], 10); if (d === 0) return null; return fred(parseInt(m[1], 10), d); } m = /^([+-]?(?:\d+\.?\d*|\.\d+))$/.exec(s); if (m) { var body = m[1].replace(/^[+-]/, ''); var dot = body.indexOf('.'); var places = dot < 0 ? 0 : body.length - dot - 1; if (places > 9) places = 9; var k = Math.pow(10, places); var num = Math.round(parseFloat(m[1]) * k); if (!isFinite(num)) return null; return fred(num, k); } return null; }
/* ================= polys: arrays of fractions, index = degree ================= */ function pnorm(p) { var q = p.slice(); while (q.length > 1) { if (!fis0(q[q.length - 1])) break; q.pop(); } return q; } function pzero() { return [{ n: 0, d: 1 }]; } function pconst(k) { return pnorm([fi(k)]); } function pconstF(f) { return pnorm([{ n: f.n, d: f.d }]); } function pvar() { return [{ n: 0, d: 1 }, { n: 1, d: 1 }]; } function pdeg(p) { return pnorm(p).length - 1; } function pis0(p) { var q = pnorm(p); if (q.length !== 1) return false; return fis0(q[0]); } function padd(a, b) { var n = Math.max(a.length, b.length), r = [], i; for (i = 0; i < n; i++) { var x = i < a.length ? a[i] : fi(0); var y = i < b.length ? b[i] : fi(0); r.push(fadd(x, y)); } return pnorm(r); } function psub(a, b) { var n = Math.max(a.length, b.length), r = [], i; for (i = 0; i < n; i++) { var x = i < a.length ? a[i] : fi(0); var y = i < b.length ? b[i] : fi(0); r.push(fsub(x, y)); } return pnorm(r); } function pmul(a, b) { var r = [], i, j; for (i = 0; i < a.length + b.length - 1; i++) r.push(fi(0)); for (i = 0; i < a.length; i++) { for (j = 0; j < b.length; j++) { r[i + j] = fadd(r[i + j], fmul(a[i], b[j])); } } return pnorm(r); } function pmulC(a, k) { return pnorm(a.map(function (c) { return fmul(c, k); })); } function ppow(p, e) { var r = pconst(1); for (var i = 0; i < e; i++) r = pmul(r, p); return r; } function peq(a, b) { a = pnorm(a); b = pnorm(b); if (a.length !== b.length) return false; for (var i = 0; i < a.length; i++) if (!feq(a[i], b[i])) return false; return true; } function peval(p, x) { var acc = fi(0); for (var i = p.length - 1; i >= 0; i--) acc = fadd(fmul(acc, x), p[i]); return acc; } /* synthetic division by (x - r), r a fraction. returns {q, rem} */ function pdivLin(p, r) { var a = pnorm(p).slice(), n = a.length - 1, i; if (n < 1) return { q: pzero(), rem: a[0] || fi(0) }; var q = new Array(n), carry = fi(0); for (i = n; i >= 1; i--) { var c = fadd(a[i], carry); q[i - 1] = c; carry = fmul(c, r); } return { q: pnorm(q), rem: fadd(a[0], carry) }; } /* scale to integer coefficients: returns {p:[ints], k} where p = k * orig */ function pscaleInt(p) { p = pnorm(p); var L = 1, i; for (i = 0; i < p.length; i++) { var d = p[i].d; L = (L / igcd(L, d)) * d; } var ints = p.map(function (c) { return (c.n * L) / c.d; }); return { p: ints, k: L }; } /* gcd of every integer coefficient of num and den (after scaling). Uses a zero-safe gcd so leading zeros do not corrupt the accumulation. */ function pgcdAll(numP, denP) { function g0(x, y) { x = Math.abs(x); y = Math.abs(y); while (y) { var t = x % y; x = y; y = t; } return x; } var a = pscaleInt(numP).p, b = pscaleInt(denP).p, g = 0, i; for (i = 0; i < a.length; i++) g = g0(g, a[i]); for (i = 0; i < b.length; i++) g = g0(g, b[i]); return g === 0 ? 1 : g; } /* ================= parsing ================= */ function tokenize(s) { var toks = [], i = 0, n = s.length, c, j; while (i < n) { c = s[i]; if (c === ' ' || c === '\t' || c === '\n' || c === '\r') { i++; continue; } if (c === '+' || c === '-' || c === '*' || c === '^' || c === '(' || c === ')') { toks.push({ t: c }); i++; continue; } if (c === 'x' || c === 'X') { toks.push({ t: 'x' }); i++; continue; } var isDig = (c >= '0'); if (isDig) isDig = (c <= '9'); if (isDig || c === '.') { j = i; while (j < n) { var cj = s[j]; var jd = (cj >= '0'); if (jd) jd = (cj <= '9'); if (jd || cj === '.') { j++; continue; } break; } toks.push({ t: 'num', v: s.slice(i, j) }); i = j; continue; } return null; } return toks; } function decFrac(v) { var m = /^(\d*)\.?(\d*)$/.exec(v); if (!m) return null; var ip = m[1] || '0', fp = m[2] || ''; if (ip === '0') { if (fp === '') return { n: 0, d: 1 }; } if (!/^\d+$/.test(ip)) return null; if (fp !== '') { if (!/^\d+$/.test(fp)) return null; } var places = fp.length; var k = Math.pow(10, Math.min(places, 9)); var num; if (places > 9) { num = Math.round(parseFloat(v) * k); } else { num = parseInt(ip, 10) * k + (fp === '' ? 0 : parseInt(fp, 10)); } return fred(num, k); } /* parsePoly(str) -> Poly or null. Supports + - * ^ ( ) x, decimals, implicit multiplication (2x, 2(x+1), (x+1)(x+2), x(x+3)), x^2, unicode. */ function parsePoly(str) { var s = String(str).replace(/−/g, '-').replace(/²/g, '^2').replace(/³/g, '^3').replace(/×/g, '*'); var toks = tokenize(s); if (!toks || toks.length === 0) return null; var t2 = [], i; for (i = 0; i < toks.length; i++) { t2.push(toks[i]); if (i + 1 < toks.length) { var a = toks[i].t, b = toks[i + 1].t; var left = (a === 'num' || a === 'x' || a === ')'); var right = (b === 'num' || b === 'x' || b === '('); if (left) { if (right) { if (a !== 'num' || b !== 'num') t2.push({ t: '*' }); } } } } var pos = 0; function peek() { return pos < t2.length ? t2[pos] : null; } function parseIntExp() { var k = peek(); if (!k || k.t !== 'num') return null; if (!/^\d+$/.test(k.v)) return null; pos++; return parseInt(k.v, 10); } function parseExpr() { var p = parseTerm(); if (!p) return null; for (;;) { var k = peek(); if (!k) break; if (k.t === '+') { pos++; var q = parseTerm(); if (!q) return null; p = padd(p, q); } else if (k.t === '-') { pos++; var q2 = parseTerm(); if (!q2) return null; p = psub(p, q2); } else break; } return p; } function parseTerm() { var p = parseFactor(); if (!p) return null; for (;;) { var k = peek(); if (!k || k.t !== '*') break; pos++; var q = parseFactor(); if (!q) return null; p = pmul(p, q); } return p; } function parseFactor() { var neg = false, k = peek(); if (k) { if (k.t === '+' || k.t === '-') { neg = (k.t === '-'); pos++; } } k = peek(); if (!k) return null; var p = null; if (k.t === 'num') { pos++; var f = decFrac(k.v); if (!f) return null; p = [f]; var kk = peek(); if (kk) { if (kk.t === '^') { pos++; var e = parseIntExp(); if (e === null || e > 9) return null; p = ppow(p, e); } } } else if (k.t === 'x') { pos++; p = pvar(); var kk2 = peek(); if (kk2) { if (kk2.t === '^') { pos++; var e2 = parseIntExp(); if (e2 === null || e2 > 9) return null; p = ppow(pvar(), e2); } } } else if (k.t === '(') { pos++; p = parseExpr(); if (!p) return null; k = peek(); if (!k || k.t !== ')') return null; pos++; var kk3 = peek(); if (kk3) { if (kk3.t === '^') { pos++; var e3 = parseIntExp(); if (e3 === null || e3 > 4) return null; p = ppow(p, e3); } } } else { return null; } if (neg) p = pmulC(p, fi(-1)); return p; } var res = parseExpr(); if (!res || pos !== t2.length) return null; return pnorm(res); } /* parseRational(str) -> {num, den} or null. Splits on one top-level '/'. */ function parseRational(str) { var s = String(str).replace(/−/g, '-').replace(/²/g, '^2').replace(/³/g, '^3').replace(/×/g, '*'); var depth = 0, idx = -1, i; for (i = 0; i < s.length; i++) { var c = s[i]; if (c === '(') depth++; else if (c === ')') { depth--; if (depth < 0) return null; } else if (c === '/') { if (depth === 0) { if (idx >= 0) return null; idx = i; } } } if (depth !== 0) return null; var num, den; if (idx < 0) { num = parsePoly(s); den = pconst(1); } else { num = parsePoly(s.slice(0, idx)); den = parsePoly(s.slice(idx + 1)); } if (!num || !den) return null; if (pis0(den)) return null; return { num: num, den: den }; } /* parse a list of excluded values: "0, -1", "x≠0,-1", "x != 0" -> [frac] or null */ function parseDomainList(str) { var s = String(str).replace(/−/g, '-').replace(/≠/g, '').replace(/!/g, '') .replace(/=/g, '').replace(/x/gi, '').replace(/\s+/g, ''); if (!s) return null; var parts = s.split(/[,;]+/), out = [], i; for (i = 0; i < parts.length; i++) { if (parts[i] === '') continue; var f = fparse(parts[i]); if (!f) return null; out.push(f); } if (out.length === 0) return null; return out; } function domainSetEq(a, b) { if (a.length !== b.length) return false; var used = [], i, j; for (i = 0; i < a.length; i++) used.push(false); for (i = 0; i < a.length; i++) { var found = false; for (j = 0; j < b.length; j++) { if (!used[j]) { if (feq(a[i], b[j])) { used[j] = true; found = true; break; } } } if (!found) return false; } return true; } /* ================= formatting ================= */ function coefMagText(c) { var a = Math.abs(c.n); return c.d === 1 ? String(a) : a + '/' + c.d; } /* canonical plain text: "4x^2-5x-1" */ function pfmtText(p) { p = pnorm(p); if (pis0(p)) return '0'; var s = '', d; for (d = p.length - 1; d >= 0; d--) { var c = p[d]; if (fis0(c)) continue; var neg = c.n < 0, mag; var one = !(Math.abs(c.n) !== 1 || c.d !== 1); if (d === 0) mag = coefMagText(c); else if (d === 1) mag = one ? 'x' : coefMagText(c) + 'x'; else mag = one ? 'x^' + d : coefMagText(c) + 'x^' + d; if (s === '') s = (neg ? '-' : '') + mag; else s += (neg ? '-' : '+') + mag; } return s; } /* pretty HTML with exponents and proper minus signs */ function pfmtHTML(p) { p = pnorm(p); if (pis0(p)) return '0'; var s = '', d; for (d = p.length - 1; d >= 0; d--) { var c = p[d]; if (fis0(c)) continue; var neg = c.n < 0, mag; var one = !(Math.abs(c.n) !== 1 || c.d !== 1); if (d === 0) mag = coefMagText(c); else if (d === 1) mag = one ? 'x' : coefMagText(c) + 'x'; else mag = one ? 'x' + d + '' : coefMagText(c) + 'x' + d + ''; if (s === '') s = (neg ? '−' : '') + mag; else s += (neg ? ' − ' : ' + ') + mag; } return s; } /* factored-form HTML from factorQuad-style linear factors */ function linFactorHTML(d, e) { /* (dx + e) with integers d, e */ var s = '('; if (d === 1) s += 'x'; else if (d === -1) s += '−x'; else s += String(d).replace('-', '−') + 'x'; if (e > 0) s += ' + ' + e; else if (e < 0) s += ' − ' + Math.abs(e); return s + ')'; } function escHTML(s) { return String(s).replace(/\x26/g, '&').replace(//g, '>'); }
/* ================= factoring ax^2 + bx + c over integers ================= */ function divisors(n) { n = Math.abs(n); var d = [], i; for (i = 1; i * i <= n; i++) { if (n % i === 0) { d.push(i); if (i * i !== n) d.push(n / i); } } return d.sort(function (x, y) { return x - y; }); } /* factorQuad(a,b,c): integers. Returns a result object (see below). */ function factorQuad(a, b, c) { if (a === 0) return { ok: false, error: 'not quadratic (a = 0)' }; var neg = false; if (a < 0) { neg = true; a = -a; b = -b; c = -c; } var g = igcd(igcd(a, b), c); var A = a / g, B = b / g, C = c / g; var factors = []; var res = { ok: true, neg: neg, gcf: g, A: A, B: B, C: C, cZero: false, factors: factors }; if (C === 0) { res.cZero = true; if (B === 0) { if (A !== 1) factors.push(pconst(A)); factors.push(pvar()); factors.push(pvar()); res.bZero = true; } else { var g2 = igcd(A, B); var f1 = A / g2, g1 = B / g2; if (g2 > 1) factors.push(pconst(g2)); factors.push(pvar()); factors.push(pnorm([fi(g1), fi(f1)])); res.lin = { f: f1, g: g1 }; } } else { var da = divisors(A), dc = divisors(C), sol = null, i, j, k; var done = false; for (i = 0; i < da.length; i++) { if (done) break; var d = da[i], f = A / d; for (j = 0; j < dc.length; j++) { if (done) break; var e0 = dc[j], g0 = C / e0; var pairs = [[e0, g0], [-e0, -g0]]; for (k = 0; k < pairs.length; k++) { var e = pairs[k][0], gg = pairs[k][1]; if (d * gg + e * f === B) { sol = { d: d, e: e, f: f, g: gg }; done = true; break; } } } } if (!sol) { res.ok = false; res.error = 'does not factor over the integers'; return res; } res.sol = sol; res.hunt = { p: sol.d * sol.g, q: sol.e * sol.f }; factors.push(pnorm([fi(sol.e), fi(sol.d)])); factors.push(pnorm([fi(sol.g), fi(sol.f)])); } if (g > 1) factors.unshift(pconst(g)); if (neg) factors.unshift(pconst(-1)); return res; } /* verify a factorQuad result by expansion; origA,origB,origC are the ORIGINAL coeffs */ function factorQuadVerify(r, oa, ob, oc) { if (!r.ok) return false; var acc = pconst(1), i; for (i = 0; i < r.factors.length; i++) acc = pmul(acc, r.factors[i]); return peq(acc, pnorm([fi(oc), fi(ob), fi(oa)])); } /* ================= rational expressions ================= */ /* integer/rational roots of a poly (deg <= 2); {ok, roots:[frac]} or {ok:false, approx:[...]} */ /* exact rational roots of an integer-coefficient quadratic; {ok, roots:[frac]} or {ok:false, approx:[numbers]} */ function intRootsDeg2(c) { var roots = [], i; var r = factorQuad(c[2], c[1], c[0]); if (!r.ok) { var D = c[1] * c[1] - 4 * c[2] * c[0], approx = []; if (D >= 0) { var sq = Math.sqrt(D); approx.push((-c[1] + sq) / (2 * c[2])); approx.push((-c[1] - sq) / (2 * c[2])); } return { ok: false, approx: approx }; } for (i = 0; i < r.factors.length; i++) { var f = pnorm(r.factors[i]); if (f.length === 2) { if (!fis0(f[1])) roots.push(fdiv(fneg(f[0]), f[1])); } } return { ok: true, roots: roots }; } /* candidate rational roots p/q for an integer-coefficient array c (const term nonzero) */ function ratRootCandidates(c) { var lead = Math.abs(c[c.length - 1]), con = Math.abs(c[0]); var ps = divisors(con), qs = divisors(lead), out = [], seen = {}, i, j; for (i = 0; i < ps.length; i++) { for (j = 0; j < qs.length; j++) { var k1 = ps[i] + '/' + qs[j]; if (!seen[k1]) { seen[k1] = 1; out.push(fred(ps[i], qs[j])); } var k2 = (-ps[i]) + '/' + qs[j]; if (!seen[k2]) { seen[k2] = 1; out.push(fred(-ps[i], qs[j])); } } } return out; } function intRootsOfPoly(p) { var s = pscaleInt(p), c = s.p, roots = [], i; var deg = c.length - 1; if (deg <= 0) return { ok: true, roots: [] }; if (deg === 1) { if (c[1] === 0) return { ok: false }; return { ok: true, roots: [fred(-c[0], c[1])] }; } if (deg === 2) return intRootsDeg2(c); /* deg >= 3: peel off rational roots one by one, finish with the quadratic case */ var cc = c.map(fi), guard = 0; for (;;) { guard++; if (guard > 64) break; var dd = cc.length - 1; if (dd < 2) break; while (!(!(cc.length > 1) || !fis0(cc[0]))) { roots.push(fi(0)); cc = cc.slice(1); } dd = cc.length - 1; if (dd < 2) break; if (fis0(cc[dd])) break; var ci = pscaleInt(cc).p, found = null, cands = ratRootCandidates(ci), k; for (k = 0; k < cands.length; k++) { if (fis0(peval(cc, cands[k]))) { found = cands[k]; break; } } if (found === null) break; var dv = pdivLin(cc, found); if (!fis0(dv.rem)) break; roots.push(found); cc = dv.q; } var rest = cc.length - 1; if (rest === 2) { var q2 = intRootsDeg2(pscaleInt(cc).p); if (!q2.ok) return { ok: false, approx: q2.approx }; return { ok: true, roots: roots.concat(q2.roots) }; } if (rest === 1) { if (!fis0(cc[1])) roots.push(fdiv(fneg(cc[0]), cc[1])); return { ok: true, roots: roots }; } if (rest <= 0) return { ok: true, roots: roots }; return { ok: false }; } /* rationalSimplify(numP, denP) -> simplified form + domain. Domain roots are exact fractions when the denominator factors over rationals. */ function rationalSimplify(numP, denP) { if (pis0(denP)) return { ok: false, error: 'The denominator cannot be zero.' }; var dn = intRootsOfPoly(denP); if (!dn.ok) { return { ok: false, error: 'The denominator does not factor over the rationals.', approx: dn.approx || [] }; } var domain = dn.roots.slice(); var nq = pnorm(numP), dq = pnorm(denP), canceled = [], i; var seen = {}; for (i = 0; i < domain.length; i++) { var r = domain[i]; var key = r.n + '/' + r.d; if (seen[key]) continue; seen[key] = 1; for (;;) { var dvn = pdivLin(nq, r); if (!fis0(dvn.rem)) break; var dvd = pdivLin(dq, r); if (!fis0(dvd.rem)) break; nq = dvn.q; dq = dvd.q; canceled.push(r); } } var G = pgcdAll(nq, dq); if (G > 1) { nq = pnorm(nq.map(function (cc) { return fdiv(cc, fi(G)); })); dq = pnorm(dq.map(function (cc) { return fdiv(cc, fi(G)); })); } var lc = dq[dq.length - 1]; if (lc.n < 0) { nq = pmulC(nq, fi(-1)); dq = pmulC(dq, fi(-1)); } return { ok: true, num: nq, den: dq, canceled: canceled, domain: domain, constGcd: G, simplest: !(canceled.length !== 0 || G > 1) }; } /* (n1/d1) op (n2/d2); op in 'add','sub','mul','div'. Domain from ORIGINAL dens. */ function rationalOp(n1, d1, op, n2, d2) { var r1 = intRootsOfPoly(d1), r2 = intRootsOfPoly(d2); if (!r1.ok || !r2.ok) return { ok: false, error: 'A denominator does not factor over the rationals.' }; var domain = r1.roots.concat(r2.roots); var num, den; if (op === 'mul') { num = pmul(n1, n2); den = pmul(d1, d2); } else if (op === 'div') { if (pis0(n2)) return { ok: false, error: 'Cannot divide by zero.' }; var rn2 = intRootsOfPoly(n2); if (!rn2.ok) return { ok: false, error: 'The divisor does not factor over the rationals.' }; /* dividing by n2/d2 also excludes the zeros of n2 */ domain = domain.concat(rn2.roots); num = pmul(n1, d2); den = pmul(d1, n2); } else if (op === 'add') { num = padd(pmul(n1, d2), pmul(n2, d1)); den = pmul(d1, d2); } else if (op === 'sub') { num = psub(pmul(n1, d2), pmul(n2, d1)); den = pmul(d1, d2); } else return { ok: false, error: 'Unknown operation.' }; var s = rationalSimplify(num, den); if (!s.ok) return s; s.domain = domain; return s; } /* pretty domain text: "x ≠ −3" / "x ≠ 0, −1" */ function fmtDomain(domain) { if (!domain.length) return 'no excluded values'; var parts = domain.map(function (f) { return ffmt(f).replace('-', '−'); }); return 'x ≠ ' + parts.join(', '); } /* simplified rational as HTML: stacked fraction when needed */ function rationalHTML(num, den) { var denOne = (den.length === 1); if (denOne) denOne = feq(den[0], fi(1)); if (denOne) return pfmtHTML(num); return '' + pfmtHTML(num) + '' + pfmtHTML(den) + ''; } function rationalText(num, den) { var denOne = (den.length === 1); if (denOne) denOne = feq(den[0], fi(1)); if (denOne) return pfmtText(num); return '(' + pfmtText(num) + ')/(' + pfmtText(den) + ')'; }
/* ================= seeded RNG for generators ================= */ function mulberry32(seed) { var a = (seed >>> 0) || 1; return function () { a |= 0; a = (a + 0x6D2B79F5) | 0; var t = Math.imul(a ^ (a >>> 15), 1 | a); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; } function rint(rand, lo, hi) { return lo + Math.floor(rand() * (hi - lo + 1)); } function rpick(rand, arr) { return arr[Math.floor(rand() * arr.length)]; } function rpickNZ(rand, lo, hi) { var v = 0, guard = 0; while (v === 0) { if (guard >= 50) break; v = rint(rand, lo, hi); guard++; } return v === 0 ? 1 : v; } function rshuffle(rand, arr) { var a = arr.slice(), i, j, t; for (i = a.length - 1; i > 0; i--) { j = Math.floor(rand() * (i + 1)); t = a[i]; a[i] = a[j]; a[j] = t; } return a; }
return { igcd: igcd, fred: fred, fi: fi, fadd: fadd, fsub: fsub, fmul: fmul, fdiv: fdiv, fneg: fneg, feq: feq, fis0: fis0, ffmt: ffmt, fparse: fparse, pnorm: pnorm, pzero: pzero, pconst: pconst, pconstF: pconstF, pvar: pvar, pdeg: pdeg, pis0: pis0, padd: padd, psub: psub, pmul: pmul, pmulC: pmulC, ppow: ppow, peq: peq, peval: peval, pdivLin: pdivLin, pscaleInt: pscaleInt, pgcdAll: pgcdAll, parsePoly: parsePoly, parseRational: parseRational, parseDomainList: parseDomainList, domainSetEq: domainSetEq, pfmtText: pfmtText, pfmtHTML: pfmtHTML, linFactorHTML: linFactorHTML, escHTML: escHTML, divisors: divisors, factorQuad: factorQuad, factorQuadVerify: factorQuadVerify, intRootsOfPoly: intRootsOfPoly, rationalSimplify: rationalSimplify, rationalOp: rationalOp, fmtDomain: fmtDomain, rationalHTML: rationalHTML, rationalText: rationalText, mulberry32: mulberry32, rint: rint, rpick: rpick, rpickNZ: rpickNZ, rshuffle: rshuffle }; });
/*__WS_TEMPLATES__*/ /* ============================================================================ GenUtil — small random-generation helpers for practice/worksheet/game. Expects global FiboPoly. double-ampersand-free by construction. ============================================================================ */ var GenUtil = (function () { 'use strict'; var FP = FiboPoly; function ri(rand, lo, hi) { return FP.rint(rand, lo, hi); } function rsign(rand) { return rand() < 0.5 ? -1 : 1; } function intPoly(coeffsAsc) { return FP.pnorm(coeffsAsc.map(function (k) { return FP.fi(k); })); } /* random integer-coefficient poly of exact degree deg, coeffs in [lo,hi] */ function randPoly(rand, deg, lo, hi) { var c = [], d; for (d = 0; d < deg; d++) c.push(ri(rand, lo, hi)); c.push(FP.rpickNZ(rand, lo, hi)); return intPoly(c); } /* factor pairs of n (n != 0): [[d, n/d], ...] over positive divisors */ function factorPairs(n) { var out = [], ds = FP.divisors(n), i; for (i = 0; i < ds.length; i++) out.push([ds[i], n / ds[i]]); return out; } /* random nonzero monomial k*x^d, k in [lo,hi], d in 1..2 */ function randMono(rand, lo, hi) { var k = FP.rpickNZ(rand, lo, hi), d = ri(rand, 1, 2), c = [], i; for (i = 0; i < d; i++) c.push(FP.fi(0)); c.push(FP.fi(k)); return FP.pnorm(c); } /* random binomial a*x + b; monicX -> a = +/-1 */ function randBinom(rand, lo, hi, monicX) { var a = monicX ? rsign(rand) : FP.rpickNZ(rand, lo, hi); var b = FP.rpickNZ(rand, lo, hi); return FP.pnorm([FP.fi(b), FP.fi(a)]); } return { ri: ri, rsign: rsign, intPoly: intPoly, randPoly: randPoly, factorPairs: factorPairs, randMono: randMono, randBinom: randBinom }; })(); if (typeof module === 'undefined') { } else { if (module.exports) module.exports = GenUtil; }
/* ============================================================================ Practice engine — shared across polynomial-ecosystem practice pages. Pure JavaScript, no DOM. Expects global FiboPoly (inlined earlier). double-ampersand-free by construction. ============================================================================ */ var PolyPractice = (function () { 'use strict'; var FP = FiboPoly;
/* ---------------- answer checkers ---------------- */ function normStr(s) { return String(s).toLowerCase().replace(/−/g, '-').replace(/\s+/g, ''); }
function checkPolyAnswer(expected, raw, requireParens) { var s = String(raw === undefined || raw === null ? '' : raw); if (requireParens) { if (s.indexOf('(') < 0) { return { status: 'invalid', msg: 'Write your answer in factored form with parentheses, like (x+2)(x+3).' }; } } var p = FP.parsePoly(s); if (!p) { return { status: 'invalid', msg: 'Could not read that as a polynomial. Use x for the variable and ^ for powers, like 4x^2-5x-1.' }; } if (FP.peq(p, expected)) return { status: 'ok' }; return { status: 'wrong' }; } function checkTextAnswer(expected, raw) { var a = normStr(raw); if (!a) return { status: 'invalid', msg: 'Type an answer first.' }; if (a === normStr(expected)) return { status: 'ok' }; return { status: 'wrong' }; } /* expected: simplified num/den polys + domain fraction array */ function checkRationalAnswer(expNum, expDen, expDomain, rawExpr, rawDom) { var r = FP.parseRational(String(rawExpr === undefined || rawExpr === null ? '' : rawExpr)); if (!r) { return { status: 'invalid', msg: 'Could not read the expression. Write it like x-3, (x+3)/2, or 3x/(x-1).' }; } var s = FP.rationalSimplify(r.num, r.den); if (!s.ok) return { status: 'invalid', msg: s.error }; if (!s.simplest) { return { status: 'wrong', msg: 'not-simplest' }; } var dom = FP.parseDomainList(String(rawDom === undefined || rawDom === null ? '' : rawDom)); if (!dom) { return { status: 'invalid', msg: 'List the excluded values separated by commas, like 0, -1. (They come from the ORIGINAL denominator.)' }; } var exprOk = FP.peq(s.num, expNum); if (exprOk) exprOk = FP.peq(s.den, expDen); var domOk = FP.domainSetEq(dom, expDomain); if (exprOk) { if (domOk) return { status: 'ok' }; } if (!exprOk) { if (!domOk) return { status: 'wrong', msg: 'both' }; return { status: 'wrong', msg: 'expr' }; } return { status: 'wrong', msg: 'domain' }; } function wrongFeedback(msg) { if (msg === 'not-simplest') { return 'That is equivalent, but it is not fully simplified — factor the top and bottom, cancel the common factor, and try again.'; } if (msg === 'expr') { return 'Your excluded values look right, but the simplified expression is not quite right yet.'; } if (msg === 'domain') { return 'The simplified expression is right, but the excluded values are not — they come from the ORIGINAL denominator, before anything cancels.'; } return 'Not quite — check both the simplified form and the excluded values.'; }
/* ---------------- problem finishing ---------------- */ function finishProblem(o) { /* o: {tag, kind, promptHTML, answerDisplayHTML, acceptText, hints[3], whySteps[], check} */ var why = '
- ' +
o.whySteps.map(function (s) { return '
- ' + s + '
'; }).join('') + '
' + '
' + '
'; return { tag: o.tag, kind: o.kind, promptHTML: o.promptHTML, hints: o.hints, whyHTML: why, answerDisplayHTML: o.answerDisplayHTML, acceptText: o.acceptText, check: o.check, key: o.key }; }
/* ---------------- session: scoring, streaks, no-repeat ---------------- */
function PracticeSession(seed, templates) {
this._rand = FP.mulberry32(seed >>> 0);
this._templates = templates;
this.usedKeys = {};
this.score = 0;
this.streak = 0;
this.problemNum = 0;
this.current = null;
this.wrongCount = 0;
this.revealed = false;
this._done = false;
}
PracticeSession.prototype._draw = function () {
var guard, t, pr;
for (guard = 0; guard < 150; guard++) {
t = this._templates[Math.floor(this._rand() * this._templates.length)];
pr = t.gen(this._rand);
if (this.usedKeys[pr.key]) continue;
this.usedKeys[pr.key] = 1;
pr.templateId = t.id;
pr.tag = t.tag;
return pr;
}
this.usedKeys = {};
t = this._templates[0];
pr = t.gen(this._rand);
pr.templateId = t.id;
pr.tag = t.tag;
return pr;
};
PracticeSession.prototype.next = function () {
this.current = this._draw();
this.wrongCount = 0;
this.revealed = false;
this._done = false;
this.problemNum++;
return this.current;
};
PracticeSession.prototype.submit = function (raw1, raw2) {
var pr = this.current;
if (!pr) return { status: 'none' };
if (this._done) return { status: 'already' };
var chk = pr.check(raw1, raw2);
if (chk.status === 'invalid') return { status: 'invalid', msg: chk.msg };
if (chk.status === 'ok') {
this._done = true;
var firstTry = (this.wrongCount === 0);
if (firstTry) firstTry = !this.revealed;
if (!this.revealed) {
this.streak++;
if (firstTry) this.score++;
}
return { status: 'correct', firstTry: firstTry, score: this.score, streak: this.streak, answerHTML: pr.answerDisplayHTML };
}
this.wrongCount++;
this.streak = 0;
if (this.wrongCount === 1) return { status: 'hint', hintHTML: pr.hints[0], streak: 0 };
if (this.wrongCount === 2) return { status: 'hint', hintHTML: pr.hints[1], streak: 0 };
this.revealed = true;
var fb = chk.msg ? wrongFeedback(chk.msg) : null;
return { status: 'why', whyHTML: pr.whyHTML, streak: 0, feedback: fb };
};
PracticeSession.prototype.reveal = function () {
if (!this.current) return null;
this.revealed = true;
return this.current.whyHTML;
};
return {
PracticeSession: PracticeSession,
finishProblem: finishProblem,
checkPolyAnswer: checkPolyAnswer,
checkTextAnswer: checkTextAnswer,
checkRationalAnswer: checkRationalAnswer,
wrongFeedback: wrongFeedback,
normStr: normStr
};
})();
if (typeof module === 'undefined') { }
else { if (module.exports) module.exports = PolyPractice; }
/* ============================================================================
W2Check — extra answer checkers for wave-2 practice/game templates.
Pure JavaScript, no DOM. Expects globals FiboPoly (FP) and Alg2.
Double-ampersand-free by construction.
============================================================================ */
var W2Check = (function () {
'use strict';
var FP = (typeof FiboPoly !== 'undefined') ? FiboPoly : null;
function bad(msg){ return { status: 'invalid', msg: msg }; }
/* numeric answer: expected fraction {n,d}; accepts int, a/b, decimal, mixed */
function checkNum(exp, raw){
var s = String(raw === undefined || raw === null ? '' : raw);
var f = FP.fparse(s);
if (!f) return bad('Could not read that as a number. Try an integer, a fraction like 3/2, or a decimal.');
if (FP.feq(f, exp)) return { status: 'ok' };
return { status: 'wrong' };
}
/* parse a comma/space separated list of numbers -> [frac] or null */
function parseNumList(raw){
var s = String(raw === undefined || raw === null ? '' : raw).replace(/−/g, '-');
var parts = s.split(/[,;]+/);
var out = [], i;
for (i = 0; i < parts.length; i++){
var t = parts[i].trim();
if (!t) continue;
/* allow "x = 2" or "x=2" style */
var m = /^[a-zA-Z]\s*=\s*(.+)$/.exec(t);
if (m) t = m[1].trim();
var f = FP.fparse(t);
if (!f) return null;
out.push(f);
}
return out.length ? out : null;
}
/* root set: expected array of fractions; order-insensitive, duplicates matter */
function checkRoots(expRoots, raw){
var got = parseNumList(raw);
if (!got) return bad('List the solutions separated by commas, like 2, -3.');
if (got.length !== expRoots.length) return { status: 'wrong' };
var used = [], i, j;
for (i = 0; i < expRoots.length; i++) used.push(false);
for (i = 0; i < got.length; i++){
var hit = false;
for (j = 0; j < expRoots.length; j++){
if (used[j]) continue;
if (FP.feq(got[i], expRoots[j])){ used[j] = true; hit = true; break; }
}
if (!hit) return { status: 'wrong' };
}
return { status: 'ok' };
}
/* ordered pair: expected [f1, f2]; accepts "(a, b)", "a, b", "a,b" */
function checkPair(exp1, exp2, raw){
var s = String(raw === undefined || raw === null ? '' : raw).replace(/−/g, '-').trim();
s = s.replace(/^\(/, '').replace(/\)$/, '');
var got = parseNumList(s);
if (!got) return bad('Type the ordered pair like (3, 2).');
if (got.length !== 2) return { status: 'wrong' };
if (FP.feq(got[0], exp1)) { if (FP.feq(got[1], exp2)) return { status: 'ok' }; }
return { status: 'wrong' };
}
/* factored form: expected polynomial; input must contain parens and expand to it */
function checkFactored(expPoly, raw){
var s = String(raw === undefined || raw === null ? '' : raw);
if (s.indexOf('(') < 0) return bad('Write the factored form with parentheses, like (x+2)(x-3).');
var p = FP.parsePoly(s);
if (!p) return bad('Could not read that as a polynomial. Use x and ^, like (x+2)(x-3).');
if (FP.peq(p, expPoly)) return { status: 'ok' };
return { status: 'wrong' };
}
/* "prime" / "no solution" style text answers with synonyms */
function checkWord(synonyms, raw){
var a = String(raw === undefined || raw === null ? '' : raw).toLowerCase().replace(/[\s._-]+/g, '');
if (!a) return bad('Type an answer first.');
var i;
for (i = 0; i < synonyms.length; i++){
if (a === synonyms[i].toLowerCase().replace(/[\s._-]+/g, '')) return { status: 'ok' };
}
return { status: 'wrong' };
}
/* multiple choice: expected letter */
function checkChoice(letter, raw){
var a = String(raw === undefined || raw === null ? '' : raw).toLowerCase().replace(/[\s.)(\-]+/g, '');
if (!a) return bad('Choose A, B, C, or D.');
if (a === letter.toLowerCase()) return { status: 'ok' };
if (a === 'a' || a === 'b' || a === 'c' || a === 'd') return { status: 'wrong' };
return bad('Choose A, B, C, or D.');
}
/* text with accepted variants */
function checkVariants(variants, raw){
var a = String(raw === undefined || raw === null ? '' : raw).toLowerCase().replace(/\s+/g, '');
if (!a) return bad('Type an answer first.');
var i;
for (i = 0; i < variants.length; i++){
if (a === String(variants[i]).toLowerCase().replace(/\s+/g, '')) return { status: 'ok' };
}
return { status: 'wrong' };
}
return {
checkNum: checkNum, parseNumList: parseNumList, checkRoots: checkRoots,
checkPair: checkPair, checkFactored: checkFactored, checkWord: checkWord,
checkChoice: checkChoice, checkVariants: checkVariants
};
})();
if (typeof module === 'undefined') { }
else { if (module.exports) module.exports = W2Check; }
/* Practice templates: factoring-trinomials-a1 (key trinom1). Pure math in gen(), no DOM.
Key formats:
T1_TWO|
var T = [];
T.push({ id: 'T1_TWO', tag: 'Find the two numbers', gen: function (rand) {
var m = randNZ(rand, -9, 9), n = randNZ(rand, -9, 9);
if (m === n) n = n > 0 ? n - 1 : n + 1;
if (n === 0) n = 1;
var p = m * n, s = m + n;
var key = 'T1_TWO|' + m + '|' + n;
return finish({
tag: 'Find the two numbers', kind: 'roots',
promptHTML: 'Find two numbers with product ' + p + ' and sum ' + s + '. Type them as m, n (either order).',
answerDisplayHTML: '' + m + ' and ' + n + '',
acceptText: m + ', ' + n,
hints: [
'List factor pairs of ' + p + ' (with signs!).',
'Which pair adds to ' + s + '?',
'Remember: c ' + (p > 0 ? '> 0' : '< 0') + ' means the numbers have ' + (p > 0 ? 'the same sign' : 'opposite signs') + '.'
],
whySteps: [
'Factor pairs of ' + p + ': test each sum.',
m + ' x ' + n + ' = ' + p + ' and ' + m + ' + ' + n + ' = ' + s + '.'
],
check: function (r1) { return WC.checkRoots([fi(m), fi(n)], r1); },
key: key
});
}});
T.push({ id: 'T1_FAC', tag: 'Factor x^2+bx+c', gen: function (rand) { var m = randNZ(rand, -9, 9), n = randNZ(rand, -9, 9); if (m === n) n = n > 0 ? n - 1 : n + 1; if (n === 0) n = 1; var b = m + n, c = m * n; var p = GU.intPoly([c, b, 1]); var key = 'T1_FAC|' + b + '|' + c; return finish({ tag: 'Factor x^2+bx+c', kind: 'factored', promptHTML: 'Factor: ' + pH(p) + '', answerDisplayHTML: '(x + ' + m + ')(x + ' + n + ')', acceptText: '(x+' + m + ')(x+' + n + ')', hints: [ 'Find two numbers: product ' + c + ', sum ' + b + '.', 'Sign check: c ' + (c > 0 ? '> 0' : '< 0') + ' → ' + signName(b, c) + '.', 'Write (x + m)(x + n) with your numbers.' ], whySteps: [ m + ' x ' + n + ' = ' + c + '; ' + m + ' + ' + n + ' = ' + b + '.', 'So ' + pT(p) + ' = (x+' + m + ')(x+' + n + ').' ], check: (function (pp) { return function (r1) { return WC.checkFactored(pp, r1); }; })(p), key: key }); }}); T.push({ id: 'T1_SIGN', tag: 'Name the sign pattern', gen: function (rand) { var m = randNZ(rand, -9, 9), n = randNZ(rand, -9, 9); if (m === n) n = n > 0 ? n - 1 : n + 1; if (n === 0) n = 1; var b = m + n, c = m * n; var ans = signName(b, c); var key = 'T1_SIGN|' + b + '|' + c; return finish({ tag: 'Name the sign pattern', kind: 'text', promptHTML: 'For x² + (' + b + ')x + (' + c + '), the two numbers in the factors are: both positive, both negative, or opposite signs?', answerDisplayHTML: '' + ans + '', acceptText: ans, hints: [ 'Look at c first: positive or negative?', 'c > 0 → same sign; c < 0 → opposite signs.', 'If same sign, b decides: b > 0 both positive, b < 0 both negative.' ], whySteps: [ 'c = ' + c + ' ' + (c > 0 ? '> 0 → same sign' : '< 0 → opposite signs') + '.', c > 0 ? 'b = ' + b + ' ' + (b > 0 ? '> 0' : '< 0') + ' → ' + ans + '.' : 'Answer: ' + ans + '.' ], check: function (r1) { return WC.checkVariants([ans], r1); }, key: key }); }}); T.push({ id: 'T1_GCF1', tag: 'GCF first, then factor', gen: function (rand) { var g = FP.rint(rand, 2, 5); var m = randNZ(rand, -8, 8), n = randNZ(rand, -8, 8); if (m === n) n = n > 0 ? n - 1 : n + 1; if (n === 0) n = 1; var p = FP.pmul(GU.intPoly([g]), GU.intPoly([m * n, m + n, 1])); var key = 'T1_GCF1|' + g + '|' + m + '|' + n; return finish({ tag: 'GCF first, then factor', kind: 'factored', promptHTML: 'Factor completely: ' + pH(p) + '', answerDisplayHTML: '' + g + '(x + ' + m + ')(x + ' + n + ')', acceptText: g + '(x+' + m + ')(x+' + n + ')', hints: [ 'The two-numbers method needs a = 1 — is there a GCF to pull first?', 'Pull out ' + g + ', then factor what remains.', 'Do not leave the GCF behind: it is part of the answer.' ], whySteps: [ 'GCF = ' + g + ': ' + pT(p) + ' = ' + g + '(x² + ' + (m + n) + 'x + ' + (m * n) + ').', 'Two numbers ' + m + ', ' + n + ' → ' + g + '(x+' + m + ')(x+' + n + ').' ], check: (function (pp) { return function (r1) { return WC.checkFactored(pp, r1); }; })(p), key: key }); }});
T.push({ id: 'T1_MISS', tag: 'Find the missing constant', gen: function (rand) { var m = randNZ(rand, -9, 9), n = randNZ(rand, -9, 9); if (n === 0) n = 2; var b = m + n, c = m * n; var key = 'T1_MISS|' + m + '|' + n; return finish({ tag: 'Find the missing constant', kind: 'num', promptHTML: 'If (x + ' + m + ')(x + n) = x² + ' + b + 'x + ' + c + ', what is n?', answerDisplayHTML: '' + n + '', acceptText: String(n), hints: [ 'Expand (x + ' + m + ')(x + n): the constant is ' + m + '·n.', 'So ' + m + '·n = ' + c + '.', 'Divide to find n.' ], whySteps: [ 'Constant term: ' + m + ' x n = ' + c + '.', 'n = ' + c + ' / ' + m + ' = ' + n + '.' ], check: function (r1) { return WC.checkNum(fi(n), r1); }, key: key }); }});
T.push({ id: 'T1_PRIME', tag: 'Prime or factorable?', gen: function (rand) { var prime = rand() < 0.5; var b, c; if (prime) { var opts = [[3, 5], [2, 7], [4, 11], [-3, 5], [1, 6], [-2, 3]]; var o = opts[FP.rint(rand, 0, 5)]; b = o[0]; c = o[1]; } else { var m = randNZ(rand, -8, 8), n = randNZ(rand, -8, 8); if (n === 0) n = 1; b = m + n; c = m * n; } var p = GU.intPoly([c, b, 1]); var key = 'T1_PRIME|' + b + '|' + c; var ans = prime ? 'prime' : 'factorable'; return finish({ tag: 'Prime or factorable?', kind: 'text', promptHTML: 'Is ' + pH(p) + ' prime or factorable over the integers?', answerDisplayHTML: '' + ans + '', acceptText: ans, hints: [ 'Hunt for two integers: product ' + c + ', sum ' + b + '.', 'List every factor pair of ' + c + ' (mind the signs).', 'No pair works → prime. One works → factorable.' ], whySteps: [ prime ? 'No integer pair multiplies to ' + c + ' and adds to ' + b + ' → prime.' : 'A pair works → factorable.' ], check: function (r1) { return WC.checkVariants([ans, prime ? 'not factorable' : 'factors'], r1); }, key: key }); }});
T.push({ id: 'T1_ZERO', tag: 'Solve by zero product', gen: function (rand) {
var m = randNZ(rand, -9, 9), n = randNZ(rand, -9, 9);
if (m === n) n = n > 0 ? n - 1 : n + 1;
if (n === 0) n = 1;
if (m === 0) m = -1;
var p = GU.intPoly([m * n, m + n, 1]);
var key = 'T1_ZERO|' + m + '|' + n;
return finish({
tag: 'Solve by zero product', kind: 'roots',
promptHTML: 'Solve: ' + pH(p) + ' = 0 — type the roots as r1, r2.',
answerDisplayHTML: '' + (-m) + ' and ' + (-n) + '',
acceptText: (-m) + ', ' + (-n),
hints: [
'Factor first: product ' + (m * n) + ', sum ' + (m + n) + '.',
'(x + ' + m + ')(x + ' + n + ') = 0.',
'Zero product: x + ' + m + ' = 0 or x + ' + n + ' = 0.'
],
whySteps: [
pT(p) + ' = (x+' + m + ')(x+' + n + ').',
'x = ' + (-m) + ' or x = ' + (-n) + '.'
],
check: function (r1) { return WC.checkRoots([fi(-m), fi(-n)], r1); },
key: key
});
}});
T.push({ id: 'T1_EXPAND', tag: 'Expand to check', gen: function (rand) { var m = randNZ(rand, -9, 9), n = randNZ(rand, -9, 9); if (n === 0) n = 1; var exp = GU.intPoly([m * n, m + n, 1]); var key = 'T1_EXPAND|' + m + '|' + n; return finish({ tag: 'Expand to check', kind: 'poly', promptHTML: 'Expand: (x + ' + m + ')(x + ' + n + ')', answerDisplayHTML: '' + pH(exp) + '', acceptText: pT(exp), hints: [ 'FOIL: First, Outer, Inner, Last.', 'The middle terms combine: ' + m + 'x + ' + n + 'x.', 'The constant is ' + m + ' x ' + n + '.' ], whySteps: [ 'FOIL: x² + ' + n + 'x + ' + m + 'x + ' + (m * n) + '.', 'Combined: ' + pT(exp) + '.' ], check: function (r1) { return PP.checkPolyAnswer(exp, r1, false); }, key: key }); }});
return T; })();
/*__WS_TEMPLATES_END__*/
var WS_DIFF = {easy: ['T1_TWO', 'T1_EXPAND', 'T1_FAC'], medium: ['T1_SIGN', 'T1_MISS', 'T1_GCF1'], hard: ['T1_PRIME', 'T1_ZERO', 'T1_FAC']};
/*__WORKSHEET_SRC__*/ /* ============================================================================ Worksheet core + glue (shared across team-E skills). buildSheet picks 20 problems (7 easy / 8 medium / 5 hard) from the practice templates with a seeded RNG; the glue renders the printable sheet. Expects globals: FiboPoly, PRACTICE_TEMPLATES. Double-ampersand-free. ============================================================================ */ /*__WORKSHEET_CORE__*/ var WorksheetCore = (function () { 'use strict'; function buildSheet(templates, diffMap, seed, FP) { var byId = {}, i; for (i = 0; i < templates.length; i++) byId[templates[i].id] = templates[i]; var rand = FP.mulberry32(seed >>> 0); var sections = [ { name: 'Easy — warm up', ids: diffMap.easy, count: 7 }, { name: 'Medium — core skills', ids: diffMap.medium, count: 8 }, { name: 'Hard — stretch', ids: diffMap.hard, count: 5 } ]; var used = {}, num = 0, out = []; sections.forEach(function (sec) { var items = [], guard = 0; while (items.length < sec.count) { guard++; if (guard > 400) break; var t = byId[sec.ids[Math.floor(rand() * sec.ids.length)]]; if (!t) break; var p; try { p = t.gen(rand); } catch (e) { continue; } if (used[p.key]) continue; used[p.key] = 1; num++; items.push({ num: num, tag: p.tag, kind: p.kind, promptHTML: p.promptHTML, answerHTML: p.answerDisplayHTML }); } out.push({ name: sec.name, items: items }); }); return { sections: out, seed: seed >>> 0 }; } return { buildSheet: buildSheet }; })(); if (typeof module === 'undefined') { } else { if (module.exports) module.exports = WorksheetCore; } /*__WORKSHEET_CORE_END__*/ /*__WORKSHEET_GLUE__*/ (function () { 'use strict'; function el(id) { return document.getElementById(id); } var seed = (Date.now() % 100000) + 1;
function esc(s) { return String(s); }
function render() { var sheet = WorksheetCore.buildSheet(PRACTICE_TEMPLATES, WS_DIFF, seed, FiboPoly); var probHTML = '', keyHTML = ''; sheet.sections.forEach(function (sec) { probHTML += '
'; keyHTML += '
'; }); probHTML += '
'; }); el('probWrap').innerHTML = probHTML; el('keyGrid').innerHTML = keyHTML; el('seedLine').textContent = 'Worksheet seed ' + sheet.seed + ' — generate again for a brand-new set.'; }
el('btnGen').addEventListener('click', function () { seed = Math.floor(Math.random() * 1000000000) + 1; var keyShown = !el('keySection').hasAttribute('hidden'); render(); if (keyShown) { el('keySection').removeAttribute('hidden'); el('btnKey').setAttribute('aria-pressed', 'true'); el('btnKey').textContent = 'Hide Answer Key'; } }); el('btnKey').addEventListener('click', function () { var ks = el('keySection'); var show = ks.hasAttribute('hidden'); if (show) { ks.removeAttribute('hidden'); } else { ks.setAttribute('hidden', ''); } el('btnKey').setAttribute('aria-pressed', show ? 'true' : 'false'); el('btnKey').textContent = show ? 'Hide Answer Key' : 'Show Answer Key'; }); el('btnPrint').addEventListener('click', function () { window.print(); }); render(); /*__WORKSHEET_GLUE_END__*/ })();
/*__WORKSHEET_SRC_END__*/