Skill: multi-step-equations
Practice: Multi-Step Equations
Collect, combine, conquer — variables on both sides, parentheses, fractions, decimals, and the two special endings.
1 Practice
Untimed practice with hints and full worked solutions. Every problem is generated fresh.
How to enter answers
- Enter just the value:
4,-3. (x=4also works.) - For special cases type
no solutionorinfinite. - Fractions like
1/2are accepted where a decimal answer appears.
2 Problem types
The generator mixes these types at random.
Variables both sides
Subtract the smaller x-term first.
Both sides, trickier
Negatives and bigger coefficients.
Parentheses first
Distribute, then collect.
Special cases
0 = 4 or 0 = 0?
Fractions
Multiply by the denominator.
Decimals
Same rules, decimal arithmetic.
// __QCORE_BEGIN__
var QGEN = (function(){
'use strict';
function ri(rng, lo, hi){ return lo + Math.floor(rng() * (hi - lo + 1)); }
function pick(rng, a){ return a[Math.floor(rng() * a.length)]; }
function mhtml(s){ return '' + s + ''; }
function sgn(n){ return n < 0 ? '−' + (-n) : String(n); }
function showX(a){ return a === 1 ? 'x' : a === -1 ? '−x' : sgn(a) + 'x'; }
function sideHtml(a, b){
var xs = showX(a);
if (b === 0) return xs;
return xs + (b > 0 ? ' + ' + b : ' − ' + (-b));
}
function fracHtml(n, d){
return '' + n + '' + d + '';
}
var T = [];
function eqBoth(rng, a, b, c, x, level){
var d = (a - c) * x + b;
var q = {q:'Solve: ' + mhtml(sideHtml(a, b) + ' = ' + sideHtml(c, d)) + '.',
a:String(x), av:x, alt:['x=' + x, 'x = ' + x],
data:{kind:'eqm', form:'both', a:a, b:b, c:c, d:d, x:x},
hint:'Subtract the smaller x-term from both sides, then solve the two-step.'};
var kx = a - c, kn = d - b;
q.steps = [
'Move x-terms together: ' + mhtml(showX(kx) + ' = ' + kn),
'Divide by ' + kx + ': x = ' + x + '.',
'Check: ' + (a * x + b) + ' = ' + (c * x + d) + '.'];
return q;
}
/* easy: small coefficients, positive */
T.push({id:'both1', name:'Variables both sides', level:'easy', gen:function(rng){
var x = ri(rng, 1, 6), a = ri(rng, 2, 5), c = ri(rng, 1, a - 1), b = ri(rng, 0, 5);
return eqBoth(rng, a, b, c, x, 'easy');
}});
/* medium: negatives, bigger */
T.push({id:'both2', name:'Both sides, trickier', level:'medium', gen:function(rng){
var x = ri(rng, -5, 5);
if (x === 0) x = 3;
var a = pick(rng, [2, 3, 4, 5, -2, -3]);
var c = pick(rng, [1, 2, 3, 4, -1, -2]);
if (c === a) c = 1;
var b = ri(rng, -6, 6);
return eqBoth(rng, a, b, c, x, 'medium');
}});
/* medium: distribute first */
T.push({id:'paren', name:'Parentheses first', level:'medium', gen:function(rng){
var k = ri(rng, 2, 3), bi = ri(rng, 1, 3), ci = ri(rng, -4, 4);
if (ci === 0) ci = 2;
var x = ri(rng, 1, 6), d = ri(rng, 1, k * bi - 1);
var e = k * (bi * x + ci) - d * x;
var lhs = k + '(' + sideHtml(bi, ci) + ')';
return {q:'Solve: ' + mhtml(lhs + ' = ' + sideHtml(d, e)) + '.',
a:String(x), av:x, alt:['x=' + x, 'x = ' + x],
data:{kind:'eqm', form:'paren', a:k * bi, b:k * ci, c:d, d:e, x:x},
hint:'Distribute the ' + k + ' first, then collect x-terms.',
steps:['Distribute: ' + mhtml(sideHtml(k * bi, k * ci) + ' = ' + sideHtml(d, e)),
'Collect: ' + mhtml(showX(k * bi - d) + ' = ' + (e - k * ci)),
'Answer: x = ' + x + '. Check: ' + (k * (bi * x + ci)) + ' = ' + (d * x + e) + '.']};
}});
/* hard: no solution or infinite */
T.push({id:'special', name:'Special cases', level:'hard', gen:function(rng){
var kind = pick(rng, ['none', 'infinite']);
var a = ri(rng, 2, 5), b = a * ri(rng, 1, 3);
if (kind === 'none'){
var d2 = b + pick(rng, [1, 2, 3, -2, -3]);
return {q:'How many solutions does ' + mhtml(sideHtml(a, b) + ' = ' + sideHtml(a, d2)) + ' have? (Answer: no solution or infinitely many.)',
a:'no solution', av:null, alt:['none', 'no solutions'],
data:{kind:'eqm', form:'special', a:a, b:b, c:a, d:d2, special:'none'},
hint:'Subtract ' + showX(a) + ' from both sides. What is left?',
steps:['Subtract ' + showX(a) + ' from both sides: ' + mhtml(b + ' = ' + d2),
'False statement — no solution.']};
}
return {q:'How many solutions does ' + mhtml(sideHtml(a, b) + ' = ' + a + '(' + sideHtml(1, b / a) + '))') + ' have? (Answer: no solution or infinitely many.)',
a:'infinitely many', av:null, alt:['infinite', 'infinite solutions', 'all real numbers'],
data:{kind:'eqm', form:'special', a:a, b:b, c:a, d:b, special:'infinite'},
hint:'Distribute the right side. Do the two sides match exactly?',
steps:['Distribute: right side = ' + mhtml(sideHtml(a, b)),
'Identical sides — infinitely many solutions.']};
}});
/* hard: fraction coefficient */
T.push({id:'frac', name:'Fractions', level:'hard', gen:function(rng){
var qn = pick(rng, [2, 3, 4]), pn = ri(rng, 1, qn - 1);
var x = ri(rng, 2, 6) * qn;
var b = ri(rng, 1, 6), c = Math.round(pn * x / qn) + b;
return {q:'Solve: ' + fracHtml(pn, qn) + mhtml('x + ' + b + ' = ' + c) + '.',
a:String(x), av:x, alt:['x=' + x, 'x = ' + x],
data:{kind:'eqm', form:'frac', a:pn / qn, b:b, c:0, d:c, x:x},
hint:'Multiply every term by ' + qn + ' to clear the fraction.',
steps:['Multiply by ' + qn + ': ' + mhtml(pn + 'x + ' + (b * qn) + ' = ' + (c * qn)),
'Subtract ' + (b * qn) + ', divide by ' + pn + ': x = ' + x + '.',
'Check: ' + (pn * x / qn) + ' + ' + b + ' = ' + c + '.']};
}});
/* hard: decimals */
T.push({id:'dec', name:'Decimals', level:'hard', gen:function(rng){
var x = ri(rng, 1, 5);
var p = pick(rng, [0.5, 1.5, 2.5]), q = p + pick(rng, [1, 2]);
var b = ri(rng, 1, 6);
/* equation: p x + b = q x + e -> e = p*x + b - q*x */
var e2 = Math.round((p * x + b - q * x) * 10) / 10;
function f(n){ return String(Math.round(n * 10) / 10); }
return {q:'Solve: ' + mhtml(f(p) + 'x + ' + b + ' = ' + f(q) + 'x + ' + f(e2)) + '.',
a:String(x), av:x, alt:['x=' + x, 'x = ' + x],
data:{kind:'eqm', form:'dec', a:p, b:b, c:q, d:e2, x:x},
hint:'Subtract the smaller decimal x-term from both sides.',
steps:['Collect x-terms: ' + mhtml(f(q - p) + 'x = ' + f(b - e2)),
'Divide: x = ' + x + '.',
'Check: ' + f(p * x + b) + ' = ' + f(q * x + e2) + '.']};
}});
function norm(s){ return String(s).trim().toLowerCase().replace(/\s+/g, ''); }
function checkAnswer(user, canonical, av, alt){
var s = norm(user), c0 = norm(canonical);
if (c0 === 'nosolution'){
if (s === 'nosolution' || s === 'none' || s === 'nosolutions') return true;
}
if (c0 === 'infinitelymany'){
if (s === 'infinitelymany' || s === 'infinite' || s === 'infinitesolutions' || s === 'allrealnumbers' || s === 'allreal' || s === 'allx') return true;
}
s = s.replace(/^x=/, '');
for (var i = 0; i < (alt || []).length; i++){
if (s === norm(alt[i]).replace(/^x=/, '')) return true;
}
if (!/^-?(\d+(\.\d+)?|\.\d+)$/.test(s)) return false;
if (av === null || av === undefined) return false;
return Math.abs(parseFloat(s) - av) < 1e-9;
}
return {templates:T, checkAnswer:checkAnswer};
})();
// __QCORE_END__
/* Shared practice engine (Wave 2C). Requires QGEN global from the skill core. */ (function(){ 'use strict'; var correct = 0, streak = 0, total = 0, attempts = 0, cur = null, curT = null, lastIdx = -1; function $(id){ return document.getElementById(id); } function esc(s){ return String(s).replace(/\x26/g,'&').replace(//g,'>'); } function pickTemplate(){ var Ts = QGEN.templates, i, guard = 0; if (Ts.length === 1) return {t: Ts[0], i: 0}; i = Math.floor(Math.random() * Ts.length); for (guard = 0; guard < 11; guard++){ if (i !== lastIdx) break; i = Math.floor(Math.random() * Ts.length); } lastIdx = i; return {t: Ts[i], i: i}; } function newQuestion(){ var p = pickTemplate(); curT = p.t; cur = curT.gen(Math.random); attempts = 0; $('pType').textContent = curT.name; $('pPrompt').innerHTML = cur.q; $('pAnswer').value = ''; $('pAnswer').disabled = false; $('pCheck').disabled = false; hide($('pFb')); hide($('pHintBox')); hide($('pWhy')); $('pSol').style.display = 'none'; $('pNext').style.display = 'none'; updateStats(); $('pAnswer').focus(); } function show(el){ el.classList.add('show'); } function hide(el){ el.classList.remove('show'); } function updateStats(){ $('pStats').innerHTML = 'Correct ' + correct + ' of ' + total + '' + ' · Streak ' + streak + ''; } function feedback(ok, html){ var f = $('pFb'); f.className = 'fb show ' + (ok ? 'ok' : 'no'); f.innerHTML = html; } function check(){ if (!cur) return; var user = $('pAnswer').value; if (!user.trim()){ feedback(false, 'Enter an answer first.'); return; } var good = false; try { good = QGEN.checkAnswer(user, cur.a, cur.av, cur.alt || []); } catch(e){ good = false; } total++; if (good){ correct++; streak++; feedback(true, 'Correct. ' + esc(cur.a)); showSolution(false); $('pAnswer').disabled = true; $('pCheck').disabled = true; $('pNext').style.display = ''; $('pSol').style.display = 'none'; } else { streak = 0; attempts++; if (attempts >= 2){ feedback(false, 'Not quite. The answer is ' + esc(cur.a) + '. Study the steps below, then try the next one.'); showSolution(true); $('pAnswer').disabled = true; $('pCheck').disabled = true; $('pNext').style.display = ''; } else { feedback(false, 'Not quite — try again. Use the hint if you need it. The answer is ' + esc(cur.a) + ' only after a second miss.'); $('pSol').style.display = ''; } } updateStats(); } function showHint(){ if (!cur) return; var h = $('pHintBox'); h.innerHTML = 'Hint: ' + cur.hint; show(h); } function showSolution(force){ if (!cur) return; var w = $('pWhy'); var lis = cur.steps.map(function(s){ return '
'; }).join(''); w.innerHTML = '
- ' + lis + '
' + '
'; show(w); if (force){ $('pSol').style.display = 'none'; } } document.addEventListener('DOMContentLoaded', function(){ $('pCheck').addEventListener('click', check); $('pAnswer').addEventListener('keydown', function(e){ if (e.key === 'Enter') check(); }); $('pHint').addEventListener('click', showHint); $('pSol').addEventListener('click', function(){ showSolution(false); $('pSol').style.display='none'; }); $('pNext').addEventListener('click', newQuestion); $('pSkip').addEventListener('click', function(){ streak = 0; newQuestion(); }); newQuestion(); }); })();