diff --git a/src/app.tsx b/src/app.tsx index e2c0195..e4b0323 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,16 +1,25 @@ +import { useState } from 'preact/hooks' import { GCF } from './gcf' import { LCM } from './lcm' +import { TimesPractice } from './times' import Navbar from './navbar' import { BrowserRouter, Navigate, Routes, Route } from 'react-router-dom' import './app.css' export function App() { + const [ maxNum, setMaxNum ] = useState(200) + + function onMaxNumSet(newMax : number) { + setMaxNum(newMax) + } + return <> - + } /> - } /> + } /> + } /> } /> diff --git a/src/navbar.tsx b/src/navbar.tsx index ab79b90..6483fe0 100644 --- a/src/navbar.tsx +++ b/src/navbar.tsx @@ -1,13 +1,29 @@ import { useState } from 'preact/hooks' import { NavLink } from 'react-router-dom' -function Navbar() { +export type NavbarSettings = { + maxNum : number + onMaxNumChanged : (newMaxNum : number) => void +} + +function Navbar({maxNum, onMaxNumChanged} : NavbarSettings) { const [ show, setShow ] = useState(false); + const [ displayedMax, setDisplayedMax ] = useState(maxNum) const handleNavClick = () : void => { setShow(false) } + const handleSliderInput = (e : Event) : void => { + const newMaxNum = Number((e.target as HTMLInputElement).value) + onMaxNumChanged(newMaxNum) + } + + const onSliderChange = (e : Event) : void => { + const newMaxNum = Number((e.target as HTMLInputElement).value) + setDisplayedMax(newMaxNum) + } + return ( diff --git a/src/prob.ts b/src/prob.ts new file mode 100644 index 0000000..e15f9e4 --- /dev/null +++ b/src/prob.ts @@ -0,0 +1,16 @@ +namespace Prob { + export type Probability = [ number, T ] + type NonEmptyArray = [ T, ...T[] ] + export type ProbabilitySet = NonEmptyArray> + export const chooseRandom = (probabilities : ProbabilitySet) : T => { + let r = Math.random(), accum = 0 + for (let i = 0; i < probabilities.length; i++) { + if ((r >= accum) && (r < accum + probabilities[i][0])) + return probabilities[i][1] + accum += probabilities[i][0] + } + return probabilities[probabilities.length - 1][1] + } +} + +export default Prob diff --git a/src/times.css b/src/times.css new file mode 100644 index 0000000..c6f0186 --- /dev/null +++ b/src/times.css @@ -0,0 +1,25 @@ +.solution { + padding: 16px; + border-radius: 12px; + background: white; + color: black; + clear: both; + display: flex; + justify-content: center; +} + +.timesCircle { + cursor: pointer; + min-width: 2.0em; + min-height: 1.5em; +} + +.timesCircleLit { + color: lightblue; +} + +.timesSolution, +.timesRows { + width: 40px;; +} + diff --git a/src/times.tsx b/src/times.tsx new file mode 100644 index 0000000..0ab22f1 --- /dev/null +++ b/src/times.tsx @@ -0,0 +1,99 @@ +import { useEffect, useState } from 'preact/hooks' +import './times.css' + +interface TimesPracticeParams { + maxNum : number +} + +interface SolutionParams { + rows : number + cols : number +} + +function Solution({rows,cols}:SolutionParams) { + const [coords, setCoords] = useState<[number,number]|null>(null) + return ( +
+ + + {Array(rows).fill(0).map((_, i)=>({i === 0 ? : <>}{Array(cols).fill(0).map((_, j)=>)}{i === 0 ? : <>}))} +
×{coords !== null ? coords[1] + 1 : cols}
{coords !== null ? coords[0] + 1 : rows}setCoords([i,j])} onMouseOut={()=>setCoords(null)}>●={coords !== null ? String((coords[0] + 1) * (coords[1] + 1)) : rows * cols}
+
+ ) +} + +export function TimesPractice({maxNum} : TimesPracticeParams) { + + const getRandomInt = (max : number) : number => Math.floor(Math.random() * max + 1) + + const chooseFactors = () : [number, number] => { + return [ getRandomInt(12), getRandomInt(12) ] + } + + const doCheck = (e : Event) : void => { + e.preventDefault() + if (factors === null) return + const rightAnswer = factors[0] * factors[1] + const yourAnswer = (document.getElementById("inlineFormInputResponse") as HTMLInputElement).value + if (rightAnswer == Number(yourAnswer)) { + setCorrect(true) + setFeedback(true) + } else { + setFeedback(true) + } + } + + const doNext = (e : Event) : void => { + e.preventDefault() + setFeedback(false) + setCorrect(false) + setFactors(chooseFactors()) + setSolution(null) + document.forms[0].reset() + } + + const doSolution = (e : Event) : void => { + e.preventDefault() + if (factors === null) return + if (solution === null) { + var answerBox = document.getElementById("inlineFormInputResponse") as HTMLInputElement + answerBox.value = String(factors[0] * factors[1]) + setSolution() + setCorrect(true) + } else { + setSolution(null) + } + } + + useEffect(()=>{ + setFactors(chooseFactors()) + }, [ maxNum ]) + + const [factors, setFactors] = useState<[number,number]|null>(null) + const [correct, setCorrect] = useState(false) + const [feedback, setFeedback] = useState(false) + const [solution, setSolution] = useState(null) + + return ( +
+

Multiplication Practice

+ {factors === null ?

Loading...

:

What is {factors[0]} × {factors[1]}?

} +
+
+ +
+ +
+
+
+ + +
+
+
+
{correct ? "Correct" : "Try again!"}
+
+ {solution !== null ? solution : <>} +
+ ) +}