Skill: percentages
Percent Sprint
90 seconds, 3 lives. Convert between fractions, decimals, and percents against the clock.
Percent Sprint
90 seconds, 3 lives. Convert between fractions, decimals, and percents against the clock.
- 1You have 90 seconds and 3 lives.
- 2Each correct answer scores 10 points, plus a streak bonus up to +50.
- 3A wrong answer costs one life and resets your streak.
- 4The sprint ends when time runs out or all lives are gone.
40. Fractions: 3/5.// __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 gcd(a, b){ a = Math.abs(a); b = Math.abs(b); while (b){ var t = a % b; a = b; b = t; } return a || 1; }
function fnum(v, p){
var neg = v < 0, s = String(Math.abs(v));
while (s.length <= p) s = '0' + s;
var ip = s.slice(0, s.length - p), fp = s.slice(s.length - p).replace(/0+$/, '');
return (neg ? '-' : '') + (fp ? ip + '.' + fp : ip);
}
function mhtml(s){ return '' + s + ''; }
function parseNum(s){
s = String(s).trim().replace(/,/g, '').replace(/%$/, '');
var m = /^(-?\d+(?:\.\d+)?)\s*\/\s*(-?\d+(?:\.\d+)?)$/.exec(s);
if (m){ var d = parseFloat(m[2]); if (d === 0) return null; return parseFloat(m[1]) / d; }
if (!/^-?(\d+(\.\d+)?|\.\d+)$/.test(s)) return null;
return parseFloat(s);
}
var T = [];
/* easy: decimal -> percent */
T.push({id:'dec-pct', name:'Decimal to percent', level:'easy', gen:function(rng){
var p = ri(rng, 1, 3), v = ri(rng, 1, 999);
var dec = fnum(v, p), pct = v * Math.pow(10, 2 - p);
return {q:'Convert ' + mhtml(dec) + ' to a percent. (Enter just the number.)',
a:String(pct), av:pct, alt:[],
data:{kind:'d2p', v:v, p:p},
hint:'Multiply by 100: slide the point two places right.',
steps:['Percent means per hundred: multiply ' + dec + ' by 100.',
'Slide the point two places right: ' + pct + '%']};
}});
/* easy: percent -> decimal */
T.push({id:'pct-dec', name:'Percent to decimal', level:'easy', gen:function(rng){
var pdec = ri(rng, 0, 1);
var P = pdec === 0 ? ri(rng, 1, 99) : pick(rng, [5, 25, 75, 125, 375, 625]);
var dec = fnum(P, 2 + pdec);
return {q:'Convert ' + mhtml(P / Math.pow(10, pdec) + '%') + ' to a decimal.',
a:dec, av:P / Math.pow(10, 2 + pdec), alt:[],
data:{kind:'p2d', P:P, pdec:pdec},
hint:'Divide by 100: slide the point two places left.',
steps:['Divide by 100: slide the point two places left.',
'' + dec + '']};
}});
/* medium: fraction -> percent */
var FRACS = [[1,2],[1,4],[3,4],[1,5],[2,5],[3,5],[4,5],[1,8],[3,8],[5,8],[7,8],[1,10],[3,10],[7,10],[9,10],[1,20],[3,20],[7,20],[9,20]];
T.push({id:'frac-pct', name:'Fraction to percent', level:'medium', gen:function(rng){
var f = pick(rng, FRACS), n = f[0], d = f[1];
var pct = n / d * 100;
return {q:'Convert ' + mhtml(n + '/' + d) + ' to a percent. (Enter just the number.)',
a:String(pct), av:pct, alt:[],
data:{kind:'f2p', n:n, d:d},
hint:'Divide ' + n + ' ÷ ' + d + ' first, then slide the point two places right.',
steps:[mhtml(n + ' ÷ ' + d + ' = ' + (n / d)),
'Slide right two places: ' + pct + '%']};
}});
/* medium: percent -> simplified fraction */
T.push({id:'pct-frac', name:'Percent to fraction', level:'medium', gen:function(rng){
var p = pick(rng, [10, 20, 25, 30, 40, 45, 50, 60, 75, 80, 90]);
var g = gcd(p, 100);
var fn = p / g, fd = 100 / g;
return {q:'Write ' + mhtml(p + '%') + ' as a fraction in lowest terms. (Like 3/5.)',
a:fn + '/' + fd, av:p / 100, alt:[String(p / 100)],
data:{kind:'p2f', p:p},
hint:'Write it over 100, then divide top and bottom by their greatest common factor.',
steps:[mhtml(p + '% = ' + p + '/100'),
'Simplify (÷' + g + '): ' + fn + '/' + fd + '']};
}});
/* easy: benchmark percent of a number */
T.push({id:'bench', name:'Benchmark percents', level:'easy', gen:function(rng){
var b = pick(rng, [50, 25, 10, 1]);
var N = b === 50 ? 2 * ri(rng, 5, 60) : b === 25 ? 4 * ri(rng, 5, 40) : b === 10 ? 10 * ri(rng, 4, 40) : 100 * ri(rng, 1, 9);
var ans = b * N / 100;
var why = b === 50 ? 'half of ' + N : b === 25 ? 'a quarter of ' + N : b === 10 ? 'move the point one place left' : 'move the point two places left';
return {q:'Find ' + mhtml(b + '%') + ' of ' + mhtml(N) + ' mentally.',
a:String(ans), av:ans, alt:[],
data:{kind:'bench', b:b, N:N},
hint:'Use the benchmark: ' + why + '.',
steps:['Benchmark: ' + b + '% means ' + why + '.',
'' + ans + '']};
}});
/* medium: percent of a number */
T.push({id:'pct-of', name:'Percent of a number', level:'medium', gen:function(rng){
var p = pick(rng, [5, 10, 15, 20, 25, 30, 35, 40, 60, 75, 80, 90]);
var N = 20 * ri(rng, 2, 25);
var ans = p * N / 100;
return {q:'Find ' + mhtml(p + '%') + ' of ' + mhtml(N) + '.',
a:String(ans), av:ans, alt:[],
data:{kind:'pctof', p:p, N:N},
hint:'Convert ' + p + '% to ' + (p / 100) + ' first, then multiply.',
steps:[mhtml(p + '% = ' + (p / 100)),
mhtml((p / 100) + ' × ' + N + ' = ' + ans + '')]};
}});
/* hard: what percent? */
T.push({id:'what-pct', name:'What percent?', level:'hard', gen:function(rng){
var pct = pick(rng, [10, 20, 25, 30, 40, 50, 60, 75, 80]);
var whole = pick(rng, [40, 50, 60, 80, 100, 120, 160, 200]);
var part = pct * whole / 100;
return {q:'What percent of ' + mhtml(whole) + ' is ' + mhtml(part) + '? (Enter just the number.)',
a:String(pct), av:pct, alt:[],
data:{kind:'whatpct', part:part, whole:whole},
hint:'Divide part by whole, then convert the decimal to a percent.',
steps:[mhtml(part + ' ÷ ' + whole + ' = ' + (part / whole)),
'Convert to a percent: ' + pct + '%']};
}});
function checkAnswer(user, canonical, av){
var u = parseNum(user);
if (u === null || av === null || av === undefined) return false;
return Math.abs(u - av) < 1e-9;
}
return {templates:T, checkAnswer:checkAnswer, parseNum:parseNum};
})();
// __QCORE_END__
// __GAME_BEGIN__
var GAME_API = (function(){
'use strict';
var DURATION = 90;
function create(){
return {score:0, streak:0, best:0, lives:3, answered:0, correct:0, over:false, reason:''};
}
function onCorrect(s){
s.answered++; s.correct++; s.streak++;
if (s.streak > s.best) s.best = s.streak;
s.score += 10 + 5 * Math.min(s.streak - 1, 8);
return s;
}
function onWrong(s){
s.answered++; s.streak = 0; s.lives--;
if (s.lives <= 0){ s.over = true; s.reason = 'lives'; }
return s;
}
function onTimeout(s){ s.over = true; s.reason = 'time'; return s; }
function accuracy(s){ return s.answered === 0 ? 0 : Math.round(100 * s.correct / s.answered); }
return {create:create, onCorrect:onCorrect, onWrong:onWrong, onTimeout:onTimeout,
accuracy:accuracy, DURATION:DURATION};
})();
// __GAME_END__
/* Shared arcade engine (Wave 2C). Requires QGEN and GAME_API globals. */ (function(){ 'use strict'; var S = null, cur = null, curT = null, lastIdx = -1, timerId = null, timeLeft = 0; function $(id){ return document.getElementById(id); } function esc(s){ return String(s).replace(/\x26/g,'&').replace(//g,'>'); } function showScreen(id){ document.querySelectorAll('.screen').forEach(function(el){ el.classList.remove('show'); }); $(id).classList.add('show'); } function hearts(n){ var out = ''; for (var i = 0; i < 3; i++){ out += ''; } return out; } function hud(){ $('hScore').textContent = S.score; $('hStreak').textContent = S.streak; $('hLives').innerHTML = hearts(S.lives); $('hTime').textContent = timeLeft; var tb = $('hTimeBox'); if (timeLeft <= 15) tb.classList.add('timer-low'); else tb.classList.remove('timer-low'); var pct = Math.max(0, Math.min(100, 100 * timeLeft / GAME_API.DURATION)); $('timeFill').style.width = pct + '%'; } function newQ(){ var Ts = QGEN.templates, i = 0, guard = 0; if (Ts.length > 1){ 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; } curT = Ts[i]; cur = curT.gen(Math.random); $('qType').textContent = curT.name; $('qPrompt').innerHTML = cur.q; $('gAnswer').value = ''; $('gAnswer').disabled = false; hideFb(); $('gAnswer').focus(); } function showFb(ok, title, why){ var f = $('gFb'); f.className = 'feedback show ' + (ok ? 'correct' : 'wrong'); f.innerHTML = '
'; } function hideFb(){ var f = $('gFb'); f.className = 'feedback'; f.innerHTML = ''; } function submit(){ if (!S || S.over) return; var user = $('gAnswer').value; if (!user.trim()) return; var good = false; try { good = QGEN.checkAnswer(user, cur.a, cur.av); } catch(e){ good = false; } if (good){ GAME_API.onCorrect(S); showFb(true, 'Correct! +' + (10 + 5 * Math.min(S.streak - 1, 8)) + ' pts', 'Answer: ' + esc(cur.a) + ''); } else { GAME_API.onWrong(S); showFb(false, 'Missed — the answer was ' + esc(cur.a), cur.hint); } hud(); if (S.over){ endGame(); return; } $('gAnswer').disabled = true; setTimeout(function(){ if (!S.over) newQ(); }, good ? 650 : 1400); } function tick(){ timeLeft--; hud(); if (timeLeft <= 0){ GAME_API.onTimeout(S); endGame(); } } function start(){ S = GAME_API.create(); timeLeft = GAME_API.DURATION; showScreen('scrPlay'); hud(); newQ(); if (timerId) clearInterval(timerId); timerId = setInterval(tick, 1000); } function endGame(){ if (timerId){ clearInterval(timerId); timerId = null; } showScreen('scrOver'); var reason = S.reason === 'time' ? 'Time ran out.' : 'Out of lives.'; $('overReason').textContent = reason + ' Scores are session-only and reset when you start again.'; $('stScore').textContent = S.score; $('stCorrect').textContent = S.correct + '/' + S.answered; $('stStreak').textContent = S.best; $('stAcc').textContent = GAME_API.accuracy(S) + '%'; } document.addEventListener('DOMContentLoaded', function(){ $('gStart').addEventListener('click', start); $('gAgain').addEventListener('click', start); $('gSubmit').addEventListener('click', submit); $('gAnswer').addEventListener('keydown', function(e){ if (e.key === 'Enter') submit(); }); }); })();