After the McGuffey Readers came the math: Ray's Arithmetic, the arithmetic series used alongside McGuffey in the 1800s. This post turns Book One, Ray's New Primary Arithmetic (1877), into a game for the kids, in the same style and the same ETI-Eloquence "Reed" voice as ABC Books. It runs at https://abcbooks.fyi/rays/ on tablets, phones and computers, and works offline once it has been opened. This post explains how it was built and how to rebuild it. Every block has a Copy button.
What the kids do
All seven of Ray's books, from Book 1 (Grades 1-2) up to Higher Algebra (Grade 11), stand on the shelf in order; the ones not built yet say "Coming soon". Book One has 89 short lessons. Each one is a tile on a map: the next lesson glows, stars show how it went, and each finished lesson puts a sticker in the kid's sticker book. The kid picked in ABC Books is picked here too.
- Counting (Lessons 1-4): tap each ball and count out loud; then each number from 11 to 100 with its tens (blue rods) and ones; then "find the number" (hear it, tap it) and tracing the figures with a finger.
- Numbers to be read and written (Lessons 5-6): tap a number to hear it while reading the book's list to a grown-up; hear a number and type it.
- The tables (adding, taking away, times and dividing, 1 to 10): "Learn the table" reads the book's own table ("3 and 4 are 7", "4 from 7 leaves 3", "2 in 6, 3 times") with a dot picture for each line. "Practice" then asks every fact against the clock: the best time is saved per kid, a fire shows a streak, and a missed fact comes back a few questions later.
- Story problems: the book's own questions ("Francis had 2 cents, and his mother gave him 1 cent more: how many had he then?"), read aloud, answered on a big number pad. A Counters tray gives beads to count with. Two misses show the answer and the book's own worked solution. Long lessons have Finish later.
- Counting by 2's to 9's: typed one number at a time.
- Say it instead of typing: the 🎤 button under the number pad listens for the answer ("twenty-four") with the same home speech server ABC Books uses, so nothing is sent to Google or Amazon. It hides itself on a device that can't listen.
- Money and measures (Lessons 79-89): the book's tables (cents and dimes, pence and shillings, pints and quarts, feet and yards, and more), then practice and story problems on them.
How the book became data
The text comes from the archive.org scan of the 1877 edition (search archive.org for "Ray's new primary arithmetic"; the scan is raysnewprimarya00raygoog, use its "FULL TEXT" download). The scan's text is rough: headings like "LESSON xvni.", numbers misread ("8" for "3"), and on some pages a whole column cut off or jumbled. Even the book's printed worked answers came out wrong in places ("which are 8 cents" for 3 cents).
So the book was typed up again by hand as primary.txt, lesson by lesson, with the answer after every question in braces:
== 13 | Addition | Adding 3
table: add 3
q: Julius had 3 cents, and he found 2 more: how many cents had he then? {5}
sol: Julius had then 3 cents and 2 cents, which are 5 cents.
q: How many are 3 and 5? {8} 4 and 7? {11}
- The tables are not typed at all:
table: add 3makes the book's table for 3 exactly, in the book's order. - Long drill pages use
{?}, and the build works the answer out ("How many are 5 + 9 + 8 - 4?"). - Counting questions use
{1..19 by 2}. make-book.ps1checks every answer that is plain arithmetic ("How many are 3 and 5?", "One from 9 leaves how many?", "Nine and how many make 17?", "How many twos in 8?") and refuses to build if one is wrong. Seven wrong answers planted on purpose were all caught.
Afterwards every page was proofread against a clean scan of the same 1877 book (a free PDF from raysarithmetic.wordpress.com): it filled every gap and fixed about 30 numbers the scan had misread. The result: 89 lessons, 1,099 questions, 1,845 answers.
The voice
Every line the app says is recorded ahead of time with Eloquence on the PC, like ABC Books (the Amazon Kids browser on the Fire tablets has no speech of its own): the book's questions and solutions, every table line in both forms ("3 and 4 are 7" and "How many are 3 and 4?"), the numbers 0 to 100, "Find 7.", and "The answer is 12." for every answer in the book. That's 2,882 clips at each of the two speeds (normal and faster), about 15 minutes to record here. The recorder program is eloq-render.ps1, the same one ABC Books uses; it drives the PC's own 32-bit Eloquence directly.
Where it runs
It's a plain web app (HTML and JavaScript, no build step) in the folder /var/www/abcbooks/rays on CT 306 (abc-web), the container that serves ABC Books with Caddy. The edge proxy, CT 108 (Caddy reverse proxy), already sends all of abcbooks.fyi to CT 306, so no new route was needed. The recordings are sent as WAV files, and CT 306 turns them into small MP3s with lame.
Files
Each block below writes one file into /root/rays-rebuild/ when pasted into a Proxmox host shell (Proxmox web UI, pve, Shell). Afterwards, copy that folder to the PC and sort the files into this layout:
C:\RaysArithmetic\source\primary.txtC:\RaysArithmetic\tools\:make-book.ps1,make-audio.ps1,eloq-render.ps1,make-icons.ps1C:\RaysArithmetic\public\:index.html,app.js,prompts.json,sw.js,manifest.webmanifest, plustrace.jsandhershey.jsfrom the ABC Books Primer post (the same tracing engine)C:\RaysArithmetic\proxmox\:push.sh,convert-audio.shC:\RaysArithmetic\:deploy.ps1,serve-local.ps1
PC (PowerShell), after pasting all the blocks, copies the folder to C:\rays-rebuild:
scp -o BatchMode=yes -r proxmox:/root/rays-rebuild C:\rays-rebuild
Proxmox host shell:
mkdir -p /root/rays-rebuild
primary.txt: the whole book, hand-corrected, with the answers.
cat > /root/rays-rebuild/primary.txt <<'EOF'
@book: primary1877 | Ray's New Primary Arithmetic | 1877 - Book One | #2f6f8f
# Ray's New Primary Arithmetic (1877), hand-corrected from the archive.org OCR (primary-1877.txt).
# tools/make-book.ps1 turns this into public/books/primary1877.json.
#
# == <n> | <part> | <title> a lesson (n = the book's lesson number)
# kind: count A B | numbers A B | read | write | facts special lesson types (default: problems)
# table: add N | sub N | mul N | div N the book's table for N (generated, exact)
# rule: 10 cents make 1 dime a line of a measures table (Lessons 79-88)
# note: ... the book's note to the teacher (shown small)
# q: question text {answer} more text {answer} a question; each {n} is one answer the child types
# sol: ... the book's worked solution for the question above
# Answers in {} are checked by make-book.ps1 wherever the question is plain arithmetic.
== 1 | Numbers and Figures | Counting 1 to 10
kind: count 1 10
note: The pupil points to each ball and says, one; one, two; one, two, three; and so on. Then he writes the figure for each number.
== 2 | Numbers and Figures | Counting 11 to 40
kind: numbers 11 40
note: In this lesson the numbering of objects is extended to 40.
== 3 | Numbers and Figures | Counting 41 to 70
kind: numbers 41 70
note: In this lesson the numbering of objects is extended to 70. Each exercise should include a review of the preceding ones.
== 4 | Numbers and Figures | Counting 71 to 100
kind: numbers 71 100
== 5 | Numbers and Figures | Numbers to be read
kind: read
note: Pupils should be taught to read figures readily from 1 to 100.
list: 0 1 10 11 2 20 12 21 22 3 30 13 31 23 32 33 4 40 14 41 24 42 34 43 44 5 50 15 51 25 52 35 53 45 54 55 6 60 16 61 26 62 36 63 46 64 56 65 66 7 70 17 71 27 72 37 73 47 74 57 75 67 76 77 8 80 18 81 28 82 38 83 48 84 58 85 68 86 78 87 88 9 90 19 91 29 92 39 93 49 94 59 95 69 96 79 97 89 98 99 100
== 6 | Numbers and Figures | Numbers to be written
kind: write
note: The pupils must be thoroughly exercised in writing numbers.
list: 0 1 10 2 20 3 30 4 40 5 50 6 60 7 70 8 80 9 90 11 12 21 13 31 14 41 15 51 16 61 17 71 18 81 19 91 22 23 32 24 42 25 52 26 62 27 72 28 82 29 92 33 34 43 35 53 36 63 37 73 38 83 39 93 44 45 54 46 64 47 74 48 84 49 94 55 56 65 57 75 58 85 59 95 66 67 76 68 86 69 96 77 78 87 79 97 88 89 98 99 100
== 7 | Oral Exercises | Counters: 1 to 5
note: These exercises are intended for use with the Numeral Frame or with counters of some kind: marbles, pebbles, kernels of corn, beans, or bits of pasteboard. The objects should be arranged in distinct groups, to represent each number indicated.
q: How many counters have we here? (The teacher shows 1 counter.) {1}
q: How many are 1 and 1? {2} One taken away from 2 leaves how many? {1} How many ones in 2? {2} How many are two times 1? {2}
q: How many are 2 and 1? {3} How many are 1 and 1 and 1? {3} How many are three times 1? {3}
q: One taken away from 3 leaves how many? {2} Two taken away from 3 leaves how many? {1} How many ones in 3? {3}
q: How many are 3 and 1? {4} How many are 2 and 2? {4} How many are 1 and 1 and 1 and 1? {4} How many are four times 1? {4} How many are two times 2? {4}
q: One taken from 4 leaves how many? {3} Two from 4 leaves how many? {2} Three from 4 leaves how many? {1} How many ones in 4? {4} How many twos in 4? {2}
q: How many are 4 and 1? {5} How many are 3 and 2? {5} How many are 1 and 1 and 1 and 1 and 1? {5} How many are five times 1? {5}
q: One from 5 leaves how many? {4} Two from 5 leaves how many? {3} Three from 5 leaves how many? {2} Four from 5 leaves how many? {1} How many ones in 5? {5}
== 8 | Oral Exercises | Counters: 6 to 8
q: How many are 5 and 1? {6} How many are 4 and 2? {6} How many are 3 and 3? {6}
q: How many are six times 1? {6} How many are three times 2? {6} How many are two times 3? {6}
q: One from 6 leaves how many? {5} Two from 6? {4} Three from 6? {3} Four from 6? {2} Five from 6? {1}
q: How many ones in 6? {6} How many twos in 6? {3} How many threes in 6? {2}
q: How many are 6 and 1? {7} How many are 5 and 2? {7} How many are 4 and 3? {7} How many are 3 and 4? {7}
q: How many are seven times 1? {7}
q: One from 7 leaves how many? {6} Two from 7? {5} Three from 7? {4} Four from 7? {3} Five from 7? {2} Six from 7? {1}
q: How many ones in 7? {7}
q: How many are 7 and 1? {8} How many are 6 and 2? {8} How many are 5 and 3? {8} How many are 4 and 4? {8} How many are 3 and 5? {8} How many are 2 and 6? {8}
q: How many are eight times 1? {8} How many are four times 2? {8} How many are two times 4? {8}
q: One from 8 leaves how many? {7} Two from 8? {6} Three from 8? {5} Four from 8? {4} Five from 8? {3} Six from 8? {2} Seven from 8? {1}
q: How many ones in 8? {8} How many twos in 8? {4} How many fours in 8? {2}
== 9 | Oral Exercises | Counters: 9 and 10
q: How many are 8 and 1? {9} How many are 7 and 2? {9} How many are 6 and 3? {9} How many are 5 and 4? {9} How many are 4 and 5? {9} How many are 3 and 6? {9} How many are 2 and 7? {9}
q: How many are nine times 1? {9} How many are three times 3? {9}
q: One from 9 leaves how many? {8} Two from 9? {7} Three from 9? {6} Four from 9? {5} Five from 9? {4} Six from 9? {3} Seven from 9? {2} Eight from 9? {1}
q: How many ones in 9? {9} How many threes in 9? {3}
q: How many are 9 and 1? {10} How many are 8 and 2? {10} How many are 7 and 3? {10} How many are 6 and 4? {10} How many are 5 and 5? {10}
q: How many are 2 and 8? {10} How many are 3 and 7? {10} How many are 4 and 6? {10}
q: How many are ten times 1? {10} How many are five times 2? {10} How many are two times 5? {10}
q: One from 10 leaves how many? {9} Two from 10? {8} Three from 10? {7} Four from 10? {6} Five from 10? {5} Six from 10? {4} Seven from 10? {3} Eight from 10? {2} Nine from 10? {1}
q: How many ones in 10? {10} How many twos in 10? {5} How many fives in 10? {2}
== 10 | Oral Exercises | Birds and flowers
note: In the book's picture, birds sit on a bush, a flock is landing, and two flocks fly far away.
q: How many birds are two birds and five birds? {7} How many birds are seven birds and four birds? {11}
q: How many are 2 and 5? {7} 7 and 4? {11}
q: How many birds are two birds and four birds? {6} How many are five birds and seven birds? {12}
q: How many are 2 and 4? {6} 5 and 7? {12}
q: There are three flowers on one branch and three on another: how many flowers on both branches? {6}
== 11 | Addition | Adding 1
table: add 1
q: Francis had 2 cents, and his mother gave him 1 cent more: how many had he then? {3}
sol: Francis had then 2 cents and 1 cent, which are 3 cents.
q: John had 1 raisin, and his sister gave him 3 raisins more: how many had he then? {4}
q: Mary had 4 pears, and her mother gave her 1 pear more: how many had she then? {5}
q: Jane had 1 cherry, and her brother gave her 5 cherries more: how many did she then have? {6}
q: George has 6 cents, and John has 1 cent: how many cents have both? {7}
q: William had 1 plum, and his cousin gave him 7 plums more: how many had he then? {8}
q: There were 8 oranges on a dish, and 1 more orange was placed on it: how many were then on the dish? {9}
q: Henry had 1 peach, and his mother gave him 9 more: how many had he then? {10}
q: How many are 10 cents and 1 cent? {11}
== 12 | Addition | Adding 2
table: add 2
q: Mary had two birds, and a friend gave her 2 more: how many birds had she then? {4}
sol: Mary had then 2 birds and 2 birds, which are 4 birds.
q: Daniel has 3 tops, and Francis has 2: how many tops have they both? {5}
q: John had 2 chestnuts, and found 4 more: how many did he then have? {6}
q: Helen had 5 apples, and her brother gave her 2 more: how many had she then? {7}
q: Ellen had 2 chickens, and her cousin gave her 6 more: how many had she then? {8}
q: John had 2 cakes, and his mother gave him 7 more: how many did he then have? {9}
q: Frank had 8 marbles, and found 2 more: how many did he then have? {10}
q: Harry caught 2 fishes, and Edward caught 9: how many did both catch? {11}
q: How many are 10 cents and 2 cents? {12}
== 13 | Addition | Adding 3
table: add 3
q: Julius had 3 cents, and he found 2 more: how many cents had he then? {5}
sol: Julius had then 3 cents and 2 cents, which are 5 cents.
q: Francis has 3 dimes in his hand, and 3 in his pocket: how many dimes has he? {6}
q: Emma has 4 apples: if her mother give her 3 more, how many apples will she have? {7}
q: There are 3 pears on one limb, and 5 on another: how many pears on both limbs? {8}
q: Mary has 6 pens, and Belle has 3: how many pens have both? {9}
q: Henry has 3 books, and Oliver has 7: how many books have both? {10}
q: Charles caught 8 rabbits, and Samuel caught 3: how many did both catch? {11}
q: How many are 3 cents and 9 cents? {12}
q: My pencil cost 10 cents, and my pen 3 cents: how much did both cost? {13}
== 14 | Addition | Adding 4
table: add 4
q: James had 4 pens, and he found 2 more: how many had he then? {6}
sol: James had then 4 pens and 2 pens, which are 6 pens.
q: Mary has 3 pins in one hand, and 4 in the other: how many pins has she in both? {7}
q: Francis has 4 chestnuts in his hand, and 4 in his pocket: how many has he in all? {8}
q: There are 5 horses in 1 field, and 4 in another: how many are there in both fields? {9}
q: Cora spent 4 cents for tape, and 6 cents for ribbon: how many cents did she spend? {10}
q: I had 7 apples, and bought 4 more: how many did I then have? {11}
q: If a lemon cost 4 cents, and an orange 8 cents, how much will both cost? {12}
q: I sold a calf for 9 dollars, and a sheep for 4 dollars: how much did I get for both? {13}
q: How many are 4 cents and 10 cents? {14}
== 15 | Addition | Adding 5
table: add 5
q: A hen has 5 black chickens and 2 white ones: how many chickens has she? {7}
sol: She has 5 chickens and 2 chickens, which are 7 chickens.
q: How many are 3 square blocks and 5 square blocks? {8}
q: I gave 5 cents for a whistle, and 4 cents for a top: how much did I give for both? {9}
q: Emma had 5 cakes, and her mother gave her 5 more: how many had she then? {10}
q: There are 6 chairs in one room, and 5 in another: how many chairs in both rooms? {11}
q: There are 5 boys in one class, and 7 in another: how many are there in both classes? {12}
q: If a lemon cost 8 cents, and an orange 5 cents, how much will both cost? {13}
q: There are 5 letters in my name, and 9 in yours: how many letters in both names? {14}
q: If you put 10 balls by the side of 5 balls, how many balls will there be? {15}
== 16 | Addition | Adding 6
table: add 6
q: A farmer has 6 cows in one field, and 2 in another: how many cows in both fields? {8}
sol: The farmer has 6 cows and 2 cows, which are 8 cows.
q: James has 3 marbles in one pocket, and 6 in another: how many has he in both? {9}
q: Francis had 6 cents, and Mary, 4 cents: how many cents had both? {10}
q: There are 5 pigs in one pen, and 6 in another: how many pigs in both pens? {11}
q: If you have 6 plums in each hand, how many plums will you have in both hands? {12}
q: Lucy gave 7 cents to one poor man, and 6 cents to another: how many cents did she give to both? {13}
q: A man had 6 horses, and bought 8 more: how many horses did he then have? {14}
q: A lady traveled 9 miles by water, and 6 miles by land: how far did she travel? {15}
q: How many are 10 days and 6 days? {16}
== 17 | Addition | Adding 7
table: add 7
q: If you place 7 marbles by the side of 2 marbles, how many will there be altogether? {9}
sol: There will be 7 marbles and 2 marbles, which are 9 marbles.
q: There are 3 sheep in one field, and 7 in another: how many sheep in both fields? {10}
q: There are 7 boys on one bench, and 4 on another: how many are there on both benches? {11}
q: There are 5 chairs in one room, and 7 in another: how many chairs in both rooms? {12}
q: Thomas had 7 apples, and his mother gave him 6 more: how many had he then? {13}
q: I bought a melon for 7 cents, and a squash for 7 cents: how much did both cost? {14}
q: I paid 8 cents for a slate, and 7 cents for some pencils: how many cents did I spend? {15}
q: Fanny had 7 roses, and she plucked 9 more: how many had she then? {16}
q: How many are 10 dollars and 7 dollars? {17}
== 18 | Addition | Adding 8
table: add 8
q: James has 8 nuts in his pocket, and 2 in his hand: how many nuts has he? {10}
sol: James has 8 nuts and 2 nuts, which are 10 nuts.
q: Mary has 3 pins in one hand, and 8 in the other: how many has she in both hands? {11}
q: There are 8 geese in one pond, and 4 in another: how many geese in both ponds? {12}
q: Thomas had 5 marbles, and has found 8 more: how many marbles has he now? {13}
q: Harvey found 8 eggs, and Thomas 6: how many eggs did both find? {14}
q: I gave 7 dollars for a vest, and 8 dollars for a coat: how much did both cost? {15}
q: I bought 8 yards of blue cloth, and 8 yards of black: how many yards did I buy altogether? {16}
q: Anna is 9 years old, and Alice is 8 years older than Anna: how old is Alice? {17}
q: How many are 8 cents and 10 cents? {18}
== 19 | Addition | Adding 9
table: add 9
q: A cat caught 9 mice one day, and 2 the next: how many did she catch in both days? {11}
sol: She caught 9 mice and 2 mice, which are 11 mice.
q: Mary gave 3 cents for paper, and 9 cents for a book: how much did both cost? {12}
q: Joseph caught 9 fishes in one pool, and 4 in another: how many did he catch? {13}
q: If five horses are in one field, and 9 in another, how many horses are in both fields? {14}
q: If you have 9 oranges, and buy 6 more, how many oranges will you then have? {15}
q: Charles had 7 plums, and John gave him 9: how many did he then have? {16}
q: Sarah had 9 buttons, and her aunt gave her 8 more: how many had she then? {17}
q: George and Henry have 9 cents each: how many cents have both? {18}
q: How many are 10 pounds and 9 pounds? {19}
== 20 | Addition | Adding 10
table: add 10
q: I paid 10 cents for ink, and 2 cents for paper: how much did I pay for both? {12}
sol: I paid for both 10 cents and 2 cents, which are 12 cents.
q: Mary gave 3 dollars for a dress, and 10 dollars for a shawl: what did she give for both? {13}
q: There are 10 trees in one row, and 4 in another: how many trees in both rows? {14}
q: In one pasture there are 5 cows, and in another 10: how many cows in both pastures? {15}
q: I bought 10 yards of blue ribbon, and 6 yards of white: how many yards of ribbon did I buy? {16}
q: There are 7 girls in one class, and 10 in another: how many girls in both classes? {17}
q: I received 10 dollars for peaches, and 8 dollars for plums: how much in all? {18}
q: Edwin found 9 nuts under one tree, and 10 under another: how many nuts did he find? {19}
q: Mary had 10 cents, and her mother gave her 10 more: how many did she then have? {20}
== 21 | Review | Addition review
q: How many are 2 and 4? {6}
sol: 2 and 4 are 6.
q: How many are 3 and 5? {8} 4 and 7? {11}
q: How many are 6 and 4? {10} 3 and 8? {11}
q: How many are 2 and 9? {11} 8 and 2? {10}
q: How many are 2 and 10? {12} 7 and 2? {9}
q: How many are 3 and 3? {6} 4 and 4? {8}
q: How many are 5 and 4? {9} 6 and 5? {11}
q: How many are 2 and 5? {7} 3 and 10? {13}
q: How many are 10 and 4? {14} 3 and 9? {12}
q: How many are 6 and 7? {13} 7 and 5? {12}
q: How many are 4 and 8? {12} 5 and 9? {14}
q: How many are 10 and 6? {16} 9 and 7? {16}
q: How many are 7 and 7? {14} 6 and 6? {12}
q: How many are 8 and 6? {14} 10 and 7? {17}
q: How many are 2 and 6? {8} 6 and 9? {15}
q: How many are 9 and 4? {13} 10 and 9? {19}
q: How many are 10 and 8? {18} 5 and 8? {13}
q: How many are 8 and 7? {15} 2 and 2? {4}
q: How many are 5 and 5? {10} 7 and 3? {10}
q: How many are 8 and 8? {16} 9 and 9? {18}
q: How many are 4 and 3? {7} 2 and 3? {5}
q: How many are 5 and 10? {15} 8 and 9? {17}
q: How many are 2 and 2 and 2? {6}
sol: 2 and 2 are 4; 4 and 2 are 6.
q: How many are 4 and 4 and 4? {12} 5 and 5 and 5? {15}
q: How many are 6 and 4 and 6? {16} 2 and 6 and 8? {16}
q: How many are 3 and 5 and 7? {15} 4 and 5 and 9? {18}
q: How many are 6 and 2 and 9? {17} 5 and 2 and 3? {10}
q: How many are 3 and 5 and 9? {17} 4 and 4 and 6? {14}
q: How many are 4 and 6 and 7? {17} 3 and 8 and 9? {20}
q: How many are 2 and 3 and 2? {7} 2 and 6 and 7? {15}
== 22 | Review | Adding three numbers
q: How many are 2 and 4 and 2? {8} 2 and 7 and 7? {16}
q: How many are 3 and 5 and 5? {13} 4 and 5 and 8? {17}
q: How many are 2 and 5 and 2? {9} 3 and 6 and 5? {14}
q: How many are 6 and 2 and 2? {10} 2 and 2 and 7? {11}
q: How many are 4 and 4 and 2? {10} 3 and 3 and 5? {11}
q: How many are 3 and 6 and 3? {12} 2 and 4 and 6? {12}
q: How many are 3 and 4 and 5? {12} 5 and 2 and 7? {14}
q: How many are 2 and 4 and 7? {13} 3 and 8 and 8? {19}
q: How many are 5 and 6 and 8? {19} 4 and 6 and 9? {19}
q: How many are 3 and 6 and 6? {15} 2 and 7 and 9? {18}
q: How many are 2 and 3 and 6? {11} 2 and 7 and 3? {12}
q: How many are 2 and 9 and 9? {20} 3 and 6 and 9? {18}
q: How many are 5 and 3 and 8? {16} 3 and 3 and 8? {14}
q: How many are 2 and 2 and 9? {13} 2 and 8 and 2? {12}
q: How many are 8 and 2 and 8? {18} 5 and 2 and 5? {12}
q: How many are 4 and 2 and 9? {15} 6 and 6 and 7? {19}
q: How many are 5 and 4 and 4? {13} 5 and 5 and 9? {19}
q: How many are 7 and 4 and 9? {20} 2 and 7 and 8? {17}
q: How many are 4 and 3 and 2? {9} 2 and 3 and 3? {8}
q: How many are 6 and 7 and 7? {20} 4 and 8 and 8? {20}
q: How many are 2 and 3 and 8? {13} 2 and 9 and 8? {19}
q: How many are 6 and 2 and 6? {14} 3 and 6 and 7? {16}
q: How many are 4 and 5 and 5? {14} 4 and 5 and 6? {15}
q: How many are 4 and 6 and 8? {18} 3 and 7 and 9? {19}
q: How many are 3 and 3 and 4? {10} 5 and 8 and 7? {20}
q: How many are 2 and 3 and 9? {14} 3 and 3 and 7? {13}
q: How many are 5 and 5 and 6? {16} 5 and 5 and 8? {18}
q: How many are 4 and 7 and 8? {19} 3 and 6 and 8? {17}
q: How many are 3 and 3 and 3? {9} 4 and 2 and 5? {11}
q: How many are 4 and 7 and 7? {18} 4 and 4 and 9? {17}
q: How many are 4 and 5 and 7? {16}
sol: 4 and 5 are 9, and 7 are 16.
q: How many are 3 and 7 and 7? {17} 2 and 4 and 8? {14}
q: How many are 5 and 6 and 7? {18} 3 and 4 and 4? {11}
q: How many are 3 and 7 and 8? {18} 2 and 5 and 9? {16}
q: How many are 5 and 6 and 9? {20} 2 and 5 and 8? {15}
q: How many are 3 and 3 and 9? {15} 6 and 4 and 3? {13}
q: How many are 3 and 4 and 7? {14} 4 and 4 and 7? {15}
q: How many are 5 and 7 and 7? {19} 3 and 4 and 8? {15}
q: How many are 6 and 6 and 8? {20} 4 and 4 and 8? {16}
q: How many are 2 and 5 and 6? {13} 3 and 4 and 9? {16}
q: Begin with 1 and count by 2's to 19. {1..19 by 2}
sol: 1, 3, 5, 7, 9, 11, 13, 15, 17, 19.
q: Begin with 1 and count by 3's to 19. {1..19 by 3}
q: Begin with 1 and count by 4's to 17. {1..17 by 4}
q: Begin with 1 and count by 5's to 16. {1..16 by 5}
q: Count by 2's to 20. {2..20 by 2} Count by 3's to 18. {3..18 by 3}
q: Count by 4's to 20. {4..20 by 4} Count by 5's to 20. {5..20 by 5}
q: Begin with 2 and count by 3's to 20. {2..20 by 3}
q: Begin with 2 and count by 4's to 18. {2..18 by 4}
q: Begin with 2 and count by 5's to 17. {2..17 by 5}
q: Begin with 3 and count by 4's to 19. {3..19 by 4}
== 23 | Addition | Story problems with three numbers
q: Mary paid 5 cents for ribbon, 4 cents for thread, and 3 cents for tape: how much did she pay for all? {12}
sol: Mary paid for all 5 cents and 4 cents and 3 cents, which are 12 cents.
q: Joseph caught 6 fishes, Samuel 3, and Henry 5: how many fishes did they all catch? {14}
q: A boy spent 7 cents for candy, 3 cents for cakes, and 6 cents for apples: how much did he spend? {16}
q: Six peaches and 3 peaches and 6 peaches are how many peaches? {15}
q: Eight dollars and two dollars and eight dollars are how many dollars? {18}
q: Jane has 2 pins, and Mary gives her 4; she then finds 7 more: how many pins has she? {13}
q: James has 4 marbles: he buys 6 at a store, and Edward gives him 7 more: how many marbles has he then? {17}
q: I buy a hat for 4 dollars, a vest for 5 dollars, and a coat for 10 dollars, when I find that I have spent all my money: how much money had I? {19}
q: A farmer sold a barrel of apples for three dollars, a tub of butter for nine dollars, and a load of wood for seven dollars: how much did he receive for all? {19}
q: I buy four apples for 7 cents, two pears for 3 cents, and five oranges for 10 cents: how much do I spend? {20}
q: I buy 2 apples of one man for 2 cents, 6 of another for 5 cents, and 7 of another for 10 cents: how many apples do I buy? {15} How many cents do I spend? {17}
q: John bought 5 pears for 6 cents, 3 pears for 4 cents, and 2 pears for 5 cents: how many pears did he buy? {10} How much did they all cost? {15}
== 24 | Subtraction | Owls and bats
note: In the book's picture, 3 owls sit on the branch of a pine tree, and 7 bats are flying.
q: There were three owls sitting on a tree; two of them flew away: how many were left? {1}
q: Three less 2 are how many? {1}
q: Seven bats were seen flying; four of them flew into a barn: how many were still outside? {3}
q: Seven less 4 are how many? {3}
q: There were nine leaves on one twig, but three of them fell off: how many leaves were left? {6}
q: Nine less 3 are how many? {6}
== 25 | Subtraction | Taking away 1
table: sub 1
q: Mary had 3 roses, and she gave 2 of them to Henry: how many had she left? {1}
sol: Mary had left 3 roses less 2 roses, which is 1 rose.
q: If 1 melon be taken from 4 melons, how many melons will remain? {3}
q: If you have 5 nuts, and give away 4 of them, how many will you then have? {1}
q: Thomas had 6 pigeons, but one of them died: how many were left? {5}
q: Charles had 7 marbles, and lost 6 of them: how many marbles had he left? {1}
q: Alice had 8 chickens, and 1 was killed: how many had she then? {7}
q: Jane had 9 cents, and spent 8 cents: how many cents had she left? {1}
q: If you take 9 apples from 10 apples, how many will remain? {1}
q: Ella had 10 plums, and she gave 1 to her sister: how many plums had she left? {9}
== 26 | Subtraction | Taking away 2
table: sub 2
q: Frank had 3 apples, and gave 1 to his brother: how many had he left? {2}
sol: Frank had left 3 apples less 1 apple, which are 2 apples.
q: William had 4 cents: after spending 2 cents for nuts, how many had he left? {2}
q: There were 5 birds in a tree: after 2 of them flew away, how many were left? {3}
q: Daniel caught 6 mice, but 2 of them got away: how many were left? {4}
q: Francis bought 7 oranges, and ate 2 of them: how many had he then? {5}
q: John had 8 marbles, but he lost 2 of them: how many remained? {6}
q: A hen had 9 chickens, but 2 of them died: how many were left? {7}
q: Margaret had 10 cakes, and gave 8 of them to her sister: how many had she left? {2}
q: If 2 yards be taken from 11 yards, how many yards will remain? {9}
== 27 | Subtraction | Taking away 3
table: sub 3
q: Mary had 4 oranges; after eating 3, how many did she have left? {1}
sol: Mary had left 4 oranges less 3 oranges, which is 1 orange.
q: Lucy had 5 ducks, but 2 of them died: how many had she then? {3}
q: Six persons are in a carriage: if 3 of them get out, how many will remain? {3}
q: Cora had 7 pins, and lost 4 of them: how many pins did she then have? {3}
q: Francis had 8 cents, and spent 3 of them: how many cents had he left? {5}
q: A hen had 9 chickens, but a hawk carried off 6 of them: how many were left? {3}
q: John had 10 lemons, and gave 3 to a poor sick boy: how many had he left? {7}
q: If 8 be taken from 11, how many will remain? {3}
q: Eliza had 12 cents, and spent 3 cents: how many cents had she left? {9}
== 28 | Subtraction | Taking away 4
table: sub 4
q: Henry has 5 apples: if he eat 4 of them, how many will he have left? {1}
sol: Henry will have left 5 apples less 4 apples, which is 1 apple.
q: Eliza had 6 birds in a cage: she let 2 of them out: how many remained in the cage? {4}
q: There were 7 ducks in a pond: 4 flew away: how many were left? {3}
q: There are 8 beans in a row: if you take 4 of them away, how many will remain? {4}
q: A window contained 9 panes of glass: a boy broke 5: how many were not broken? {4}
q: There were 10 trees standing in a field: a storm blew down 4: how many remained? {6}
q: Oliver is 4 years old, and Jane is 11: how much older is Jane than Oliver? {7}
q: If 8 oranges be taken from 12 oranges, how many will remain? {4}
q: A hen had 13 chickens, and 4 of them died: how many remained alive? {9}
== 29 | Subtraction | Taking away 5
table: sub 5
q: Francis had 6 oranges, and gave 5 of them away: how many had he left? {1}
sol: Francis had left 6 oranges less 5 oranges, which is 1 orange.
q: There are 7 crows in the field: if 2 of them fly away, how many will be left? {5}
q: Of 8 ships that went to sea, 5 were lost in a storm: how many remained? {3}
q: Lucy had 9 yards of ribbon, and gave 5 of them for a doll: how many yards had she left? {4}
q: Daniel, having 10 marbles, gave 5 of them to John: how many had he left? {5}
q: Father gave me 6 cents, and mother gave me enough more to make 11 cents: how many did she give me? {5}
q: I owe 12 cents, and have but 5: how many more must I get to pay the debt? {7}
q: I owed 13 dollars, and paid all but 5 dollars: how much did I pay? {8}
q: Mary had 14 nuts: she cracked and ate 9 of them: how many nuts has she left? {5}
== 30 | Subtraction | Taking away 6
table: sub 6
q: If James makes 7 marks on a slate, and then rubs out 1 of them, how many remain? {6}
sol: There remain 7 marks less 1 mark, which are 6 marks.
q: Mary had 8 cents, and spent 6 cents for a thimble: how much had she left? {2}
q: Henry had 9 raisins: after eating 6 of them, how many had he left? {3}
q: I bought 10 eggs in market, and broke 4 of them coming home: how many whole eggs remained? {6}
q: If 6 cents be taken from 11 cents, how many cents will remain? {5}
q: I paid 6 dollars for a coat, and sold it for 12 dollars: how much did I gain? {6}
q: I had 13 dozen eggs, and sold 6 dozen: how many dozen were left? {7}
q: John had 14 oranges, and gave away 8 of them: how many oranges had he then? {6}
q: Nine and how many make 15? {6} Six and how many make 14? {8}
== 31 | Subtraction | Taking away 7
table: sub 7
q: Charles had 8 cents, and spent 7 cents for candy: how much had he left? {1}
sol: Charles had left 8 cents less 7 cents, which is 1 cent.
q: A man bought a calf for 9 dollars, and paid 2 dollars down: how much remained unpaid? {7}
q: Ella bought 10 oranges, and gave her mother 7: how many had she left? {3}
q: A book, which cost 7 cents, was sold for 11 cents: how much was gained? {4}
q: Thomas had 12 marbles, and lost 5 of them: how many had he left? {7}
q: If 7 lemons be taken from 13 lemons, how many lemons will remain? {6}
q: Mary is 14 years old, and Anna is 7: how much older is Mary than Anna? {7}
q: A boy counted 15 birds on a tree: some of them flying away, he counted 8 remaining: how many flew away? {7}
q: Seven and how many make 15? {8} Nine and how many make 16? {7}
== 32 | Subtraction | Taking away 8
table: sub 8
q: Mary has 9 pecans: after eating 1 of them, how many will she have left? {8}
sol: Mary will have left 9 pecans less 1 pecan, which are 8 pecans.
q: Jane has 10 cents: if she give 8 cents for a book, how many cents will she still have? {2}
q: Susan bought 11 peaches, and gave Emma 8: how many had she left? {3}
q: I sold a ball for 12 cents, which cost me 8 cents: how much did I gain? {4}
q: Frank had 13 oranges, and gave 5 to Charles: how many did Frank then have? {8}
q: Samuel, having 14 marbles, lost 8 of them: how many marbles had he left? {6}
q: Henry bought 15 pens, and lost 7: how many pens had he remaining? {8}
q: If 16 persons are in a room, and 8 of them leave, how many remain? {8}
q: Nine and how many make 17? {8} Eight and how many make 15? {7}
== 33 | Subtraction | Taking away 9
table: sub 9
q: Thomas has 10 walnuts: after eating 9, how many will he have left? {1}
sol: Thomas will have left 10 walnuts less 9 walnuts, which is 1 walnut.
q: I had 11 cents, and spent 9 for a slate: how much had I remaining? {2}
q: Having 12 dollars, I spent 9 for a coat: how much had I then? {3}
q: Anna had 13 birds: 4 of them died: how many had she left? {9}
q: There were 14 horses in a field, but 9 of them got out: how many horses remained? {5}
q: Mary had 15 plums, and gave her brother 6: how many had she then? {9}
q: George had 16 marbles, and lost 9 of them: how many did he then have? {7}
q: A man had 17 horses, and sold 8 of them: how many had he then? {9}
q: I bought a kite for 9 cents, and sold it for 18 cents: how much did I make? {9}
== 34 | Subtraction | Taking away 10
table: sub 10
q: Lucy had 11 cents, and paid 10 for a book: how many cents had she left? {1}
sol: Lucy had left 11 cents less 10 cents, which is 1 cent.
q: I had 12 dollars: after spending part, I had 10 dollars left: how much did I spend? {2}
q: There are 13 pupils in school: if 3 of them leave, how many will remain? {10}
q: There were 14 trees standing in a field: 10 of them were cut down: how many remained? {4}
q: Charles had 15 marbles, and lost all but 5: how many did he lose? {10}
q: Henry had 16 cents, and spent 10: how many did he then have? {6}
q: I owe 17 dollars: if I pay all but 7, how many will I pay? {10}
q: I bought three oranges for 10 cents, and sold them for 18 cents: how much did I gain? {8}
q: 10 from 19 leaves how many? {9} 9 from 19 leaves how many? {10}
== 35 | Review | Subtraction review
q: How many are 4 less 2? {2}
sol: 4 less 2 are 2.
q: How many are 10 less 4? {6} 10 less 6? {4} 6 less 3? {3}
q: How many are 7 less 5? {2} 9 less 3? {6} 11 less 4? {7}
q: How many are 12 less 4? {8} 12 less 8? {4} 13 less 6? {7}
q: How many are 18 less 10? {8} 19 less 9? {10} 15 less 9? {6}
q: How many are 16 less 10? {6} 13 less 3? {10} 14 less 7? {7}
q: How many are 11 less 8? {3} 7 less 4? {3} 11 less 6? {5}
q: How many are 12 less 3? {9} 16 less 7? {9} 15 less 10? {5}
q: How many are 17 less 8? {9} 17 less 10? {7} 11 less 9? {2}
q: How many are 9 less 5? {4} 8 less 3? {5} 7 less 2? {5}
q: How many are 8 less 6? {2} 10 less 7? {3} 11 less 5? {6}
q: How many are 12 less 9? {3} 13 less 10? {3} 12 less 10? {2}
q: How many are 14 less 9? {5} 13 less 7? {6} 15 less 8? {7}
q: How many are 17 less 9? {8} 13 less 4? {9} 16 less 8? {8}
q: How many are 11 less 7? {4} 18 less 9? {9} 15 less 7? {8}
q: How many are 16 less 9? {7} 13 less 5? {8} 12 less 6? {6}
q: How many are 14 less 10? {4} 14 less 8? {6} 9 less 4? {5}
q: How many are 15 less 6? {9} 12 less 5? {7} 8 less 5? {3}
q: How many are 11 less 3? {8} 15 less 5? {10} 12 less 7? {5}
q: How many are 9 less 2? {7} 19 less 10? {9} 17 less 7? {10}
q: How many are 5 less 2? {3} 10 less 3? {7} 12 less 2? {10}
q: How many are 18 less 8? {10} 5 less 3? {2} 6 less 4? {2}
q: How many are 6 less 2? {4} 14 less 6? {8} 9 less 6? {3}
q: How many are 16 less 6? {10} 14 less 4? {10} 8 less 2? {6}
q: How many are 8 less 4? {4} 13 less 9? {4} 9 less 7? {2}
q: How many are 10 less 2? {8} 14 less 5? {9} 11 less 2? {9}
q: How many are 13 less 8? {5} 10 less 8? {2}
== 36 | Addition and Subtraction | Add, then take away
q: How many are 2 and 5, less 3? {?}
sol: 2 and 5 are 7; 7 less 3 are 4.
q: How many are 3 and 3, less 4? {?}
q: How many are 8 and 9, less 7? {?} 9 and 8, less 10? {?}
q: How many are 7 and 10, less 8? {?} 10 and 7, less 9? {?}
q: How many are 3 and 4, less 5? {?} 5 and 2, less 4? {?}
q: How many are 2 and 6, less 3? {?} 5 and 3, less 4? {?}
q: How many are 7 and 9, less 6? {?} 10 and 6, less 8? {?}
q: How many are 3 and 5, less 6? {?} 4 and 4, less 6? {?}
q: How many are 6 and 10, less 9? {?} 10 and 6, less 7? {?}
q: How many are 8 and 10, less 9? {?} 9 and 9, less 10? {?}
q: How many are 3 and 9, less 10? {?} 9 and 3, less 7? {?}
q: How many are 7 and 7, less 5? {?} 8 and 5, less 6? {?}
q: How many are 4 and 8, less 9? {?} 5 and 9, less 7? {?}
q: How many are 6 and 7, less 3? {?} 7 and 8, less 5? {?}
q: How many are 5 and 6, less 9? {?} 2 and 7, less 4? {?}
q: How many are 6 and 6, less 4? {?} 10 and 5, less 7? {?}
q: How many are 3 and 8, less 5? {?} 8 and 7, less 6? {?}
q: How many are 5 and 5, less 2? {?} 8 and 6, less 4? {?}
q: How many are 8 and 6, less 9? {?} 7 and 5, less 8? {?}
q: How many are 3 and 7, less 4? {?} 6 and 8, less 10? {?}
q: How many are 4 and 9, less 5? {?} 9 and 5, less 8? {?}
q: How many are 8 and 8, less 10? {?} 6 and 4, less 3? {?}
q: How many are 6 and 3, less 5? {?} 8 and 6, less 7? {?}
q: How many are 5 and 4, less 2? {?} 2 and 8, less 5? {?}
q: How many are 9 and 6, less 7? {?} 7 and 6, less 10? {?}
q: How many are 7 and 4, less 8? {?} 10 and 2, less 5? {?}
q: How many are 5 and 7, less 3? {?} 6 and 3, less 7? {?}
== 37 | Addition and Subtraction | Counting down and stories
q: Begin with 20, and subtract by 2's to 0. {20..0 by -2}
sol: 20, 18, 16, 14, 12, 10, 8, 6, 4, 2, 0.
q: Begin with 18, and subtract by 3's to 0. {18..0 by -3}
q: Begin with 20, and subtract by 4's to 0. {20..0 by -4}
q: Begin with 20, and subtract by 5's to 0. {20..0 by -5}
q: Begin with 18, and subtract by 6's to 0. {18..0 by -6}
q: Francis has 10 cents in two pockets; there are 4 cents in one: how many in the other? {6}
q: I think of two numbers that together make 8: one of them is 5: what is the other? {3}
q: Mary had 11 apples: she gave 4 to Lucy, and 5 to Nancy: how many had she left? {2}
sol: Mary gave away 4 apples and 5 apples, which are 9 apples; then, she had left 11 apples less 9 apples, which are 2 apples.
q: Emma had 15 cents: she paid 5 cents for thread, 2 cents for tape, and 3 cents for needles: how much had she left? {5}
q: I have 10 cents in one hand, and 5 in the other: if I take 3 cents from each hand, how many cents will I then have in both hands? {9}
q: Three numbers together make 18: the first number is 9, the second is 4: what is the third? {5}
q: I bought 16 oranges, and gave 6 to James: Henry afterwards gave me 8 more: how many oranges had I then? {18}
q: James has 8 marbles, and John has 7; Henry has 10 less than James and John together: how many marbles has Henry? {5}
q: Albert bought 10 apples: he sold 3, and ate 2: how many apples had he left? {5}
== 38 | Multiplication | Swallows and nests
note: In the book's picture, swallows fly in and out of a shed, with four nests over the door-way.
q: How many birds are three times four birds? {12}
q: How many are 3 times 4? {12}
q: There are four nests over the door-way: if there are four eggs in each nest, how many eggs are there in all the nests? {16}
q: How many are 4 times 4? {16}
q: Each swallow has two wings: how many wings have eight swallows? {16}
q: How many are 8 times 2? {16}
== 39 | Multiplication | Times 1
table: mul 1
q: John bought 2 figs at 1 cent each: how much did they cost? {2}
sol: They cost 2 times 1 cent, which are 2 cents.
q: Henry paid 1 cent for a slate pencil: at that rate, how much will 3 pencils cost? {3}
q: If apples cost 1 cent apiece, what will be the cost of 4 apples? {4}
q: If one yard of tape cost 1 cent, what will be the cost of 5 yards? {5}
q: What will be the cost of 6 yards of silk, if one yard cost 1 dollar? {6}
q: If a car travel 1 mile in a minute, how far will it travel in 7 minutes? {7}
q: If one toy book cost 1 cent, how much must Mary pay for 8 toy books? {8}
q: How much will 9 yards of tape cost, at 1 cent a yard? {9}
q: If eggs are worth 1 cent each, what will 10 eggs cost? {10}
== 40 | Multiplication | Times 2
table: mul 2
q: When peaches are selling at 2 cents each, how much will 2 peaches cost? {4}
sol: They will cost 2 times 2 cents, which are 4 cents.
q: Frank bought 2 plums, at 3 cents each: how much did they cost? {6}
q: How much must you pay for 4 yards of tape, at 2 cents a yard? {8}
q: How much will 2 pounds of meat cost, at 5 cents a pound? {10}
q: When lemons are selling at 6 cents each, how many cents will 2 lemons cost? {12}
q: Sarah bought 2 yards of ribbon, at 7 cents a yard: how many cents did they cost? {14}
q: William bought 8 sticks of candy, at 2 cents a stick: how many cents did they cost? {16}
q: Harry has 9 cents, and Emma has 2 times as many: how many cents has Emma? {18}
q: Jane bought 2 books at 10 cents each: how much did she pay for them? {20}
== 41 | Multiplication | Times 3
table: mul 3
q: If a pint of chestnuts cost 3 cents, how much will 2 pints cost? {6}
sol: They will cost 2 times 3 cents, which are 6 cents.
q: James bought 3 tops, at 3 cents each: what did he pay for them? {9}
q: If Thomas can walk 3 miles in an hour, how far can he walk in 4 hours? {12}
q: If 1 pear is worth 5 apples, how many apples are 3 pears worth? {15}
q: If 1 peach is worth 3 plums, how many plums are 6 peaches worth? {18}
q: How much will 7 yards of tape cost, if 1 yard cost 3 cents? {21}
q: If 1 orange cost 3 cents, how many cents will 8 oranges cost? {24}
q: If 1 pound of sugar cost 9 cents, how much will 3 pounds cost? {27}
q: A slate cost 10 cents, and a book 3 times as much: what did the book cost? {30}
== 42 | Multiplication | Times 4
table: mul 4
q: Lucy has 2 kittens, and each one has 4 feet: how many feet have both? {8}
sol: Both kittens have 2 times 4 feet, which are 8 feet.
q: Thomas has 3 pigeons, and James has 4 times as many: how many has James? {12}
q: Daniel bought 4 tops, at 4 cents each: what did they cost? {16}
q: When rice is 4 cents a pound, how much will 5 pounds cost? {20}
q: There are 4 quarters in one apple: how many quarters are there in 6 apples? {24}
q: Francis bought 7 oranges, at 4 cents each: what did they cost? {28}
q: If peaches are sold at 4 cents apiece, how much will 8 peaches cost? {32}
q: If a pound of starch cost 9 cents, what will 4 pounds cost? {36}
q: At 10 cents apiece, what will be the cost of 4 lead-pencils? {40}
== 43 | Multiplication | Times 5
table: mul 5
q: Francis bought 5 tops, at 2 cents each: how many cents did they cost? {10}
sol: Five tops cost 5 times 2 cents, which are 10 cents.
q: If James can walk 5 miles in 1 hour, how many miles can he walk in 3 hours? {15}
q: If 1 peach is worth 5 plums, how many plums are 4 peaches worth? {20}
q: Lucy has 5 hens, and each hen has 5 chickens: how many chickens have all the hens? {25}
q: Francis found 6 bird's nests, with 5 eggs in each: how many eggs in all? {30}
q: Daniel bought 5 oranges, at 7 cents each: how much did he pay for them? {35}
q: What will be the cost of 8 kites, at 5 cents apiece? {40}
q: If 1 pound of flour cost 5 cents, what will 9 pounds cost? {45}
q: If one street-car ticket cost 5 cents, what will 10 tickets cost? {50}
== 44 | Multiplication | Times 6
table: mul 6
q: If 1 dress can be made from 6 yards of calico, how many yards will it take to make 2 dresses? {12}
sol: For 2 dresses it will take 2 times 6 yards, which are 12 yards.
q: Mary has 3 hens, and each hen has 6 chickens: how many chickens are there in all? {18}
q: If there are 6 panes of glass in one window, how many panes are there in 4 windows? {24}
q: If 1 orange is worth 5 peaches, how many peaches are 6 oranges worth? {30}
q: John bought 6 quarts of plums, at 6 cents a quart: how much did they cost? {36}
q: If 1 quart of strawberries cost 7 cents, how much will 6 quarts cost? {42}
q: If a man eat 6 ounces of bread in 1 day, how many ounces will he eat in 8 days? {48}
q: What will be the cost of 6 lead-pencils, at 9 cents each? {54}
q: I bought 6 dozen eggs, at 10 cents a dozen: how much did they cost? {60}
== 45 | Multiplication | Times 7
table: mul 7
q: Sarah bought 2 thimbles, at 7 cents each: how much did both cost? {14}
sol: Both thimbles cost 2 times 7 cents, which are 14 cents.
q: Edward has 3 pockets, and has 7 marbles in each: how many marbles has he? {21}
q: There are 7 days in a week: how many days are there in 4 weeks? {28}
q: If one melon is worth 5 peaches, how many peaches are 7 melons worth? {35}
q: If a horse travel 7 miles in one hour, how many miles will he travel in 6 hours? {42}
q: If each of 7 benches will seat 7 boys, how many boys can be seated on them all? {49}
q: If Harry gives 7 marbles for one cent, how many must he give for 8 cents? {56}
q: If muslin is 9 cents a yard, how many cents will 7 yards cost? {63}
q: At 10 cents a yard, how much will 7 yards of ribbon cost? {70}
== 46 | Multiplication | Times 8
table: mul 8
q: James bought 2 melons, at 8 cents each: how many cents did they cost? {16}
sol: The melons cost 2 times 8 cents, which are 16 cents.
q: Each one of 3 boys caught 8 fishes: how many did they all catch? {24}
q: Nancy has 4 hens, and each hen has 8 chickens: how many chickens are there? {32}
q: There are 5 houses, each having 8 windows: how many windows in all the houses? {40}
q: If there are 8 pints in one gallon, how many pints are there in 6 gallons? {48}
q: Clara bought 8 spools of thread, at 7 cents each: how much did they cost? {56}
q: There are 8 quarts in 1 peck: how many quarts are there in 8 pecks? {64}
q: If one dozen apples cost 9 cents, how many cents will 8 dozen apples cost? {72}
q: If one comb cost 10 cents, what will be the cost of 8 combs? {80}
== 47 | Multiplication | Times 9
table: mul 9
q: Francis bought 2 knives, at 9 cents each: how many cents did they cost? {18}
sol: The knives cost 2 times 9 cents, which are 18 cents.
q: I bought 3 pounds of raisins, at 9 cents a pound: what did I pay for them? {27}
q: There are 9 panes of glass in each of 4 windows: how many panes in all? {36}
q: If one orange cost 5 cents, how much will 9 oranges cost? {45}
q: How many cents will I have to pay for 6 tops, at 9 cents each? {54}
q: Frank bought 9 lemons, at 7 cents each: how much did they cost? {63}
q: If a boy travel 8 miles in 1 hour, how far will he travel in 9 hours? {72}
q: How many cents must be paid for 9 yards of muslin, at 9 cents a yard? {81}
q: In one dime there are 10 cents: how many cents in 9 dimes? {90}
== 48 | Multiplication | Times 10
table: mul 10
q: I bought 10 pencils, at 2 cents each: how much did they cost? {20}
sol: The pencils cost 10 times 2 cents, which are 20 cents.
q: If George earn 3 dollars in 1 week, how much will he earn in 10 weeks? {30}
q: There are 4 pecks in 1 bushel: how many pecks are there in 10 bushels? {40}
q: If a man can eat 5 pounds of bread in 1 week, how much can he eat in 10 weeks? {50}
q: At 6 dollars a cord, what will 10 cords of wood cost? {60}
q: If 10 marbles are given for 1 cent, how many must be given for 7 cents? {70}
q: How many cents will pay for 10 oranges, at 8 cents each? {80}
q: What will 9 barrels of flour cost, at 10 dollars a barrel? {90}
q: At 10 dollars a yard, how much will 10 yards of cloth cost? {100}
== 49 | Review | Multiplication review
q: How many are 2 times 5? {10}
sol: 2 times 5 are 10.
q: How many are 3 times 4? {12} 2 times 9? {18}
q: How many are 10 times 2? {20} 3 times 3? {9}
q: How many are 4 times 2? {8} 5 times 4? {20}
q: How many are 8 times 2? {16} 2 times 2? {4}
q: How many are 3 times 10? {30} 5 times 6? {30}
q: How many are 6 times 2? {12} 8 times 3? {24}
q: How many are 4 times 10? {40} 7 times 3? {21}
q: How many are 7 times 4? {28} 5 times 5? {25}
q: How many are 3 times 5? {15} 6 times 3? {18}
q: How many are 4 times 9? {36} 6 times 4? {24}
q: How many are 9 times 3? {27} 4 times 8? {32}
q: How many are 4 times 4? {16} 9 times 5? {45}
q: How many are 5 times 7? {35} 10 times 8? {80}
q: How many are 8 times 6? {48} 7 times 10? {70}
q: How many are 6 times 6? {36} 3 times 8? {24}
q: How many are 9 times 7? {63} 6 times 9? {54}
q: How many are 5 times 8? {40} 7 times 7? {49}
q: How many are 10 times 5? {50} 6 times 10? {60}
q: How many are 7 times 6? {42} 9 times 10? {90}
q: How many are 8 times 7? {56} 9 times 9? {81}
q: How many are 8 times 9? {72} 10 times 10? {100}
q: How many are 3 times 2? {6} 7 times 2? {14}
q: How many are 5 times 3? {15} 6 times 7? {42}
q: How many are 3 times 8? {24} 9 times 6? {54}
q: How many are 8 times 5? {40} 7 times 9? {63}
q: How many are 10 times 9? {90} 9 times 8? {72}
q: How many are 2 times 3 times 3? {18}
sol: 3 times 3 are 9; 2 times 9 are 18.
q: How many are 4 times 2 times 2? {16}
q: How many are 2 times 3 times 4? {24}
q: How many are 2 times 2 times 5? {20}
q: How many are 5 times 2 times 3? {30}
q: How many are 3 times 2 times 6? {36}
q: How many are 2 times 2 times 6? {24}
q: How many are 2 times 3 times 7? {42}
q: How many are 2 times 2 times 7? {28}
q: How many are 4 times 2 times 8? {64}
q: How many are 3 times 3 times 4? {36}
q: How many are 2 times 4 times 6? {48}
q: How many are 2 times 2 times 10? {40}
q: How many are 3 times 2 times 10? {60}
q: How many are 4 times 2 times 5? {40}
q: How many are 4 times 2 times 4? {32}
q: How many are 3 times 2 times 8? {48}
q: How many are 4 times 2 times 10? {80}
q: How many are 2 times 5 times 7? {70}
q: How many are 3 times 3 times 10? {90}
q: How many are 5 times 2 times 8? {80}
q: How many are 2 times 5 times 9? {90}
q: How many are 5 times 2 times 6? {60}
q: How many are 2 times 3 times 9? {54}
q: How many are 2 times 2 times 9? {36}
q: How many are 3 times 3 times 5? {45}
q: How many are 3 times 3 times 6? {54}
q: How many are 2 times 5 times 10? {100}
q: How many are 4 times 2 times 9? {72}
q: How many are 3 times 3 times 7? {63}
q: How many are 3 times 3 times 8? {72}
== 50 | Review | Add, take away, then multiply
q: How many are 2 and 5, less 4, multiplied by 3? {9}
sol: 2 and 5 are 7; 7 less 4 are 3; 3 multiplied by 3 is 9.
q: How many are 3 and 5, less 4, multiplied by 2? {?}
q: How many are 4 and 6, less 5, multiplied by 10? {?}
q: How many are 5 and 5, less 4, multiplied by 7? {?}
q: How many are 6 and 5, less 4, multiplied by 7? {?}
q: How many are 7 and 6, less 5, multiplied by 4? {?}
q: How many are 8 and 7, less 6, multiplied by 3? {?}
q: How many are 2 and 6, less 5, multiplied by 4? {?}
q: How many are 3 and 6, less 5, multiplied by 7? {?}
q: How many are 4 and 7, less 6, multiplied by 5? {?}
q: How many are 5 and 7, less 6, multiplied by 8? {?}
q: How many are 6 and 6, less 5, multiplied by 2? {?}
q: How many are 7 and 7, less 6, multiplied by 8? {?}
q: How many are 8 and 8, less 7, multiplied by 9? {?}
q: How many are 2 and 7, less 6, multiplied by 5? {?}
q: How many are 4 and 8, less 7, multiplied by 9? {?}
q: How many are 3 and 7, less 6, multiplied by 5? {?}
q: How many are 5 and 8, less 7, multiplied by 9? {?}
q: How many are 2 and 8, less 7, multiplied by 6? {?}
q: How many are 3 and 9, less 8, multiplied by 6? {?}
q: How many are 7 and 10, less 9, multiplied by 2? {?}
q: How many are 2 and 9, less 8, multiplied by 7? {?}
q: How many are 3 and 8, less 7, multiplied by 9? {?}
q: How many are 4 and 10, less 9, multiplied by 8? {?}
q: How many are 6 and 10, less 9, multiplied by 8? {?}
q: How many are 6 and 8, less 7, multiplied by 9? {?}
q: How many are 4 and 9, less 8, multiplied by 6? {?}
== 51 | Review | Mixed questions
q: Joseph had 14 cents, and bought 2 oranges, at 5 cents each: how much money had he left? {4}
sol: Joseph paid for the oranges 2 times 5 cents, which are 10 cents; he had left 14 cents, less 10 cents, which are 4 cents.
q: James bought a calf for 8 dollars, and 3 sheep, at 4 dollars apiece: how much did he pay for all? {20}
q: George owed me 19 cents: he gave me 2 oranges, worth 5 cents each, and the remainder in money: how much money did I get? {9}
q: I bought 2 yards of cloth, at 4 dollars a yard, and 3 yards, at 2 dollars a yard: how much did all cost? {14}
q: A boy worked 4 weeks, at 4 dollars a week: he spent 5 dollars for a coat, and 2 dollars for a hat: how much money had he left? {9}
q: A man can dig 6 bushels of potatoes in one hour, and a boy 3 bushels: how many bushels can both dig in 8 hours? {72}
q: A farmer has 9 hogs: 4 of them die, and he sells the rest at 10 dollars apiece: how much did he get for them? {50}
q: Mary had 8 five-cent pieces, and lost 5 of them: how many cents had she left? {15}
q: A blacksmith shod 7 horses in one day, putting a shoe on each foot: how many shoes did he use? {28}
q: Kate received 10 cents one day, and 8 cents another: she then spent 4 cents for apples and 5 cents for candy, after which her father gave her six times as much as she had left: how much did her father give her? {54}
== 52 | Division | Ducks and geese
note: In the book's picture you can count five wild geese and twenty ducks.
q: If twenty ducks were divided into four flocks, each having the same number, how many ducks would there be in each flock? {5}
q: How many times is 5 contained in 20? {4}
q: If five wild geese have ten wings, how many wings has each goose? {2}
q: How many times is 5 contained in 10? {2}
q: There are four full-blown flowers on two branches: how many on each branch? {2}
== 53 | Division | Dividing by 2
table: div 2
q: How many apples, at 2 cents each, can you buy for 4 cents? {2}
sol: You can buy as many apples as 2 cents are contained times in 4 cents, which are 2 times. Hence you can buy 2 apples.
q: How many marbles, at 2 cents each, can you buy for 6 cents? {3}
q: How many lemons, at 4 cents each, can you buy for 8 cents? {2}
q: How many peaches, at 5 cents each, can you buy for 10 cents? {2}
q: How many yards of ribbon, at 2 cents a yard, can you buy for 12 cents? {6}
q: How many oranges, at 7 cents each, can you buy for 14 cents? {2}
q: How many tops, at 2 cents each, can you buy for 16 cents? {8}
q: How many kites, at 9 cents each, can you buy for 18 cents? {2}
q: How many books, at 10 cents apiece, can you buy for 20 cents? {2}
== 54 | Division | Dividing by 3
table: div 3
q: If you have 6 balls, how many groups, of 3 balls each, can you make out of them? {2}
sol: You can make as many groups as 3 balls are contained times in 6 balls, which are 2 times. Hence you can make 2 groups.
q: Jane paid 9 cents for ribbon, at 3 cents a yard: how many yards did she get? {3}
q: When pears are 4 cents each, how many can you buy for 12 cents? {3}
q: In one yard there are 3 feet: how many yards are there in 15 feet? {5}
q: If one orange is worth 3 lemons, how many oranges can you get for 18 lemons? {6}
q: I have 21 marbles in groups of 7 each: how many groups are there? {3}
q: How much cloth, at 8 dollars a yard, can you buy for 24 dollars? {3}
q: For 27 cents, I bought 9 peaches: how much did one peach cost? {3}
q: How many 3-cent postage-stamps can you buy for 30 cents? {10}
== 55 | Division | Dividing by 4
table: div 4
q: How many oranges, at 4 cents each, can you buy for 8 cents? {2}
sol: You can buy as many oranges as 4 cents are contained times in 8 cents, which are 2 times. Hence you can buy 2 oranges.
q: There are 4 quarts in one gallon: how many gallons are there in 12 quarts? {3}
q: If 16 apples be divided equally among 4 boys, how many will each have? {4}
q: There are 20 scholars sitting on 4 benches: how many scholars on each bench? {5}
q: If 4 sheets of paper make one copy-book, how many copy-books will 24 sheets make? {6}
q: If one top cost 7 cents, how many tops can be bought for 28 cents? {4}
q: At 4 cents each, how many peaches can you buy for 32 cents? {8}
q: At 9 cents each, how many cakes can you buy for 36 cents? {4}
q: If a spelling-book cost 10 cents, how many spelling-books can you buy for 40 cents? {4}
== 56 | Division | Dividing by 5
table: div 5
q: How many oranges, at 5 cents each, can you buy for 10 cents? {2}
sol: You can buy as many oranges as 5 cents are contained times in 10 cents, which are 2 times. Hence you can buy 2 oranges.
q: How many pencils, at 3 cents each, can you buy for 15 cents? {5}
q: How many toy-books, at 4 cents each, can you buy for 20 cents? {5}
q: How many pears, at 5 cents each, can you buy for 25 cents? {5}
q: How many melons, at 6 cents each, can you buy for 30 cents? {5}
q: In one week there are 7 days: how many weeks in 35 days? {5}
q: How many cakes, at 8 cents each, can you buy for 40 cents? {5}
q: How many tops, at 5 cents each, can you buy for 45 cents? {9}
q: How many slates, at 5 cents each, can be bought for 50 cents? {10}
== 57 | Division | Dividing by 6
table: div 6
q: How many quarts of milk, at 6 cents a quart, can you buy for 12 cents? {2}
sol: You can buy as many quarts as 6 cents are contained times in 12 cents, which are 2 times. Hence you can buy 2 quarts.
q: How many oranges, at 6 cents each, can you buy for 18 cents? {3}
q: There are 24 trees in 6 rows: how many trees in each row? {4}
q: How many pears, at 5 cents each, can you buy for 30 cents? {6}
q: How many pounds of flour, at 6 cents a pound, can be bought for 36 cents? {6}
q: How many lemons, at 7 cents each, can you buy for 42 cents? {6}
q: How many pencils, at 8 cents each, can you buy for 48 cents? {6}
q: How many rings, at 6 dimes each, can you buy for 54 dimes? {9}
q: A farmer sold 6 loads of hay for 60 dollars: what did he get for each load? {10}
== 58 | Division | Dividing by 7
table: div 7
q: If you divide 14 apples into piles, containing 7 apples each, how many piles will there be? {2}
sol: There will be as many piles as 7 apples are contained times in 14 apples, which are 2 times. Hence there will be 2 piles.
q: If a pine-apple cost 7 cents, how many pine-apples can you buy for 21 cents? {3}
q: At 7 cents each, how many melons can be bought for 28 cents? {4}
q: If 7 boys share 35 peaches equally, how many will each boy receive? {5}
q: A man traveled 42 miles in 6 hours: how far did he travel in 1 hour? {7}
q: I paid 49 cents for 7 quarts of strawberries: how much did I pay a quart? {7}
q: There are 56 trees in 8 rows: how many trees are there in one row? {7}
q: At 9 cents a yard, how many yards of muslin can you buy for 63 cents? {7}
q: I paid 70 dollars for 7 calves: how much did I pay for each calf? {10}
== 59 | Division | Dividing by 8
table: div 8
q: If in one peck there are 8 quarts, how many pecks are there in 16 quarts? {2}
sol: There are as many pecks as 8 quarts are contained times in 16 quarts, which are 2 times. Hence there are 2 pecks.
q: If one orange is worth 8 apples, how many oranges can you get for 24 apples? {3}
q: How many pencils, at 4 cents each, can you buy for 32 cents? {8}
q: If 5 yards of calico cost 40 cents, how much will 1 yard cost? {8}
q: If one cane cost 8 dimes, how many canes can be bought for 48 dimes? {6}
q: At 7 cents each, how many tops can you buy for 56 cents? {8}
q: If one peach is worth 8 plums, how many peaches are 64 plums worth? {8}
q: Harry paid 72 cents for 9 pears: how much did he pay for each? {8}
q: I gave 80 cents for 8 toy-books: what did they cost apiece? {10}
== 60 | Division | Dividing by 9
table: div 9
q: At 9 cents each, how many pencils can you buy for 18 cents? {2}
sol: You can buy as many pencils as 9 cents are contained times in 18 cents, which are 2 times. Hence you can buy 2 pencils.
q: If 3 pounds of meat cost 27 cents, how much will one pound cost? {9}
q: Melons were sold at the rate of 4 for 36 cents: how much was that apiece? {9}
q: A father divided 45 cents equally among his 5 children: how much did each child get? {9}
q: Mary gave 54 cents for 9 spools of thread: how much did she give for each spool? {6}
q: A boy rode 63 miles in 7 hours: how many miles did he ride in one hour? {9}
q: At 9 cents a yard, how many yards of ribbon can you buy for 72 cents? {8}
q: If 81 blocks be placed in 9 rows, how many blocks will there be in each row? {9}
q: How many books, at 10 cents each, can you buy for 90 cents? {9}
== 61 | Division | Dividing by 10
table: div 10
q: How many melons, at 10 cents each, can you buy for 20 cents? {2}
sol: You can buy as many melons as 10 cents are contained times in 20 cents, which are 2 times. Hence you can buy 2 melons.
q: If a quince is worth 10 apples, how many quinces can you get for 30 apples? {3}
q: At 4 cents each, how many pears can you buy for 40 cents? {10}
q: At 10 cents each, how many oranges can be bought for 50 cents? {5}
q: I paid 60 cents for eggs, at 10 cents a dozen: how many dozens did I get? {6}
q: How many coats, at 10 dollars each, can be bought for 70 dollars? {7}
q: How many kites, at 8 cents each, can I buy for 80 cents? {10}
q: How many balls, at 10 cents each, can be bought for 90 cents? {9}
q: In one dime there are 10 cents: how many dimes are there in 100 cents? {10}
== 62 | Review | Division review
q: How many times is 2 contained in 4? {?} 4 in 12? {?} 4 in 32? {?} 5 in 35? {?} 9 in 54? {?}
sol: 2 is contained in 4 two times.
q: 2 in 8? {?} 3 in 18? {?} 5 in 30? {?} 6 in 42? {?} 7 in 63? {?}
q: 3 in 6? {?} 2 in 20? {?} 6 in 18? {?} 4 in 40? {?} 8 in 64? {?}
q: 5 in 10? {?} 10 in 30? {?} 7 in 28? {?} 8 in 40? {?} 9 in 63? {?}
q: 3 in 27? {?} 6 in 12? {?} 8 in 32? {?} 10 in 50? {?} 10 in 40? {?}
q: 5 in 20? {?} 9 in 27? {?} 8 in 16? {?} 7 in 42? {?} 6 in 60? {?}
q: 10 in 70? {?} 4 in 16? {?} 3 in 30? {?} 2 in 18? {?} 4 in 20? {?}
q: 2 in 10? {?} 6 in 24? {?} 4 in 24? {?} 5 in 45? {?} 9 in 36? {?}
q: 10 in 20? {?} 7 in 21? {?} 5 in 15? {?} 10 in 60? {?} 6 in 48? {?}
q: 3 in 9? {?} 3 in 21? {?} 9 in 18? {?} 8 in 48? {?} 8 in 72? {?}
q: 6 in 36? {?} 10 in 80? {?} 8 in 24? {?} 10 in 90? {?} 9 in 72? {?}
q: 4 in 8? {?} 2 in 16? {?} 7 in 35? {?} 7 in 49? {?} 7 in 70? {?}
q: 2 in 12? {?} 5 in 25? {?} 6 in 30? {?} 5 in 50? {?} 9 in 81? {?}
q: 3 in 15? {?} 4 in 28? {?} 5 in 40? {?} 6 in 54? {?} 8 in 80? {?}
q: 2 in 14? {?} 3 in 24? {?} 9 in 45? {?} 9 in 90? {?} 8 in 56? {?}
q: 3 in 12? {?} 7 in 14? {?} 4 in 36? {?} 7 in 56? {?} 10 in 100? {?}
note: State how many times the first number is contained in the second.
q: How many are 10 and 4, less 8, multiplied by 3, divided by 9? {?}
sol: 10 and 4 are 14; 14 less 8 are 6; 6 multiplied by 3 is 18; 18 divided by 9 is 2.
q: How many are 9 and 9, less 10, multiplied by 3, divided by 6? {?}
q: How many are 3 and 6, less 7, multiplied by 8, divided by 4? {?}
q: How many are 2 and 10, less 4, multiplied by 7, divided by 8? {?}
q: How many are 10 and 6, less 9, multiplied by 5, divided by 7? {?}
q: How many are 3 and 5, less 2, multiplied by 5, divided by 6? {?}
q: How many are 2 and 5, less 3, multiplied by 9, divided by 6? {?}
q: How many are 10 and 7, less 8, multiplied by 6, divided by 9? {?}
q: How many are 2 and 4, less 3, multiplied by 7, divided by 3? {?}
q: How many are 4 and 7, less 6, multiplied by 8, divided by 5? {?}
q: How many are 6 and 4, less 3, multiplied by 9, divided by 7? {?}
q: How many are 3 and 10, less 8, multiplied by 9, divided by 5? {?}
q: How many are 6 and 9, less 10, multiplied by 10, divided by 5? {?}
q: How many are 10 and 8, less 9, multiplied by 10, divided by 9? {?}
q: How many are 8 and 7, less 6, multiplied by 4, divided by 6? {?}
q: How many are 5 and 9, less 8, multiplied by 5, divided by 10? {?}
q: How many are 3 and 10, less 5, multiplied by 4, divided by 8? {?}
q: How many are 6 and 3, less 4, multiplied by 8, divided by 4? {?}
q: How many are 9 and 5, less 10, multiplied by 6, divided by 8? {?}
== 63 | Review | Find one, then many
q: If 4 pens cost 8 cents, how much will one pen cost? {2} If one pen cost 2 cents, how much will 6 pens cost? {12}
q: If 6 peaches cost 24 cents, how much will one peach cost? {4} If one peach cost 4 cents, how much will 9 peaches cost? {36}
q: If 4 dozen eggs cost 20 cents, how much will 6 dozen cost? {30}
sol: If 4 dozen eggs cost 20 cents, 1 dozen eggs will cost as many cents as 4 is contained times in 20, which are 5; and 6 dozen will cost 6 times 5 cents, which are 30 cents.
q: If 3 lead-pencils cost 18 cents, how many cents will 5 pencils cost? {30}
q: If 4 pine-apples cost 32 cents, how many cents will 5 pine-apples cost? {40}
q: If 3 quarts of berries cost 21 cents, how many cents will 7 quarts cost? {49}
q: What will 63 marbles cost, if 14 marbles cost 2 cents? {9}
q: Henry spends 32 cents for pears, at 4 cents apiece, and 20 cents for pears, at 5 cents apiece: how many pears does he buy? {12}
q: Sarah spends 50 cents for lemons, at 5 cents apiece, and gives 6 of them to her sister: how many lemons has she left? {4}
q: Mary bought 7 cents worth of thread, and 10 cents worth of needles, giving the clerk 25 cents: how much change did she get? {8}
q: I bought 4 pencils, at 3 cents each, and gave the clerk 3 five-cent pieces: how many cents should I receive in change? {3}
== 64 | Signs | Plus, minus, times, divided by, equals
note: The sign + is read plus: add the two numbers. The sign - is read minus: take the number on the right from the number on the left. The sign x is read multiplied by. The sign / is read divided by. The sign = is read equals.
q: 11 + 1 = ? {?}
sol: 11 plus 1 equals 12.
q: 2 + 11 = ? {?} 11 + 3 = ? {?} 4 + 11 = ? {?} 11 + 5 = ? {?} 6 + 11 = ? {?} 11 + 7 = ? {?} 8 + 11 = ? {?} 11 + 9 = ? {?} 10 + 11 = ? {?}
q: 12 + 1 = ? {?} 2 + 12 = ? {?} 12 + 3 = ? {?} 4 + 12 = ? {?} 12 + 5 = ? {?} 6 + 12 = ? {?} 12 + 7 = ? {?} 8 + 12 = ? {?} 12 + 9 = ? {?} 10 + 12 = ? {?}
q: 13 + 1 = ? {?} 2 + 13 = ? {?} 13 + 3 = ? {?} 4 + 13 = ? {?} 13 + 5 = ? {?} 6 + 13 = ? {?} 13 + 7 = ? {?} 8 + 13 = ? {?} 13 + 9 = ? {?} 10 + 13 = ? {?}
q: 14 + 1 = ? {?} 2 + 14 = ? {?} 14 + 3 = ? {?} 4 + 14 = ? {?} 14 + 5 = ? {?} 6 + 14 = ? {?} 14 + 7 = ? {?} 8 + 14 = ? {?} 14 + 9 = ? {?} 10 + 14 = ? {?}
q: 15 + 1 = ? {?} 2 + 15 = ? {?} 15 + 3 = ? {?} 4 + 15 = ? {?} 15 + 5 = ? {?} 6 + 15 = ? {?} 15 + 7 = ? {?} 8 + 15 = ? {?} 15 + 9 = ? {?} 10 + 15 = ? {?}
== 65 | Signs | Adding past 20
q: How many are 16 + 1? {?} 2 + 16? {?} 16 + 3? {?} 4 + 16? {?} 16 + 5? {?} 6 + 16? {?} 16 + 7? {?} 8 + 16? {?} 16 + 9? {?} 10 + 16? {?}
q: How many are 17 + 1? {?} 2 + 17? {?} 17 + 3? {?} 4 + 17? {?} 17 + 5? {?} 6 + 17? {?} 17 + 7? {?} 8 + 17? {?} 17 + 9? {?} 10 + 17? {?}
q: How many are 18 + 1? {?} 2 + 18? {?} 18 + 3? {?} 4 + 18? {?} 18 + 5? {?} 6 + 18? {?} 18 + 7? {?} 8 + 18? {?} 18 + 9? {?} 10 + 18? {?}
q: How many are 19 + 1? {?} 2 + 19? {?} 19 + 3? {?} 4 + 19? {?} 19 + 5? {?} 6 + 19? {?} 19 + 7? {?} 8 + 19? {?} 19 + 9? {?} 10 + 19? {?}
q: How many are 20 + 2? {?} 4 + 30? {?} 40 + 6? {?} 8 + 50? {?} 60 + 10? {?} 3 + 70? {?} 80 + 5? {?} 7 + 90? {?} 90 + 9? {?}
q: How many are 21 + 2? {?} 31 + 4? {?} 6 + 41? {?} 41 + 8? {?} 10 + 41? {?} 3 + 51? {?} 61 + 5? {?} 7 + 71? {?} 81 + 9? {?} 9 + 91? {?}
q: How many are 22 + 2? {?} 4 + 32? {?} 42 + 6? {?} 8 + 52? {?} 62 + 10? {?} 3 + 72? {?} 82 + 5? {?} 7 + 92? {?} 92 + 8? {?}
q: How many are 23 + 2? {?} 4 + 33? {?} 43 + 6? {?} 8 + 53? {?} 63 + 10? {?} 3 + 73? {?} 83 + 5? {?} 7 + 93? {?}
q: How many are 24 + 2? {?} 4 + 34? {?} 44 + 6? {?} 8 + 54? {?} 64 + 10? {?} 3 + 74? {?} 84 + 5? {?} 6 + 94? {?}
q: How many are 25 + 2? {?} 4 + 35? {?} 45 + 6? {?} 8 + 55? {?} 65 + 10? {?} 3 + 75? {?} 85 + 5? {?} 5 + 95? {?}
q: How many are 26 + 2? {?} 4 + 36? {?} 46 + 6? {?} 8 + 56? {?} 66 + 10? {?} 3 + 76? {?} 86 + 5? {?} 4 + 96? {?}
q: How many are 27 + 2? {?} 4 + 37? {?} 47 + 6? {?} 8 + 57? {?} 67 + 10? {?} 3 + 77? {?} 87 + 5? {?} 3 + 97? {?}
q: How many are 28 + 2? {?} 4 + 38? {?} 48 + 6? {?} 8 + 58? {?} 68 + 10? {?} 3 + 78? {?} 88 + 5? {?} 2 + 98? {?}
== 66 | Review | Adding to 100
q: How many are 29 + 2? {?} 4 + 39? {?} 49 + 6? {?} 8 + 59? {?} 69 + 10? {?} 3 + 79? {?} 89 + 5? {?} 7 + 89? {?}
q: How many are 24 + 3? {?} 34 + 5? {?} 44 + 7? {?} 54 + 9? {?} 64 + 2? {?} 74 + 4? {?} 84 + 6? {?} 85 + 8? {?}
q: How many are 25 + 6? {?} 3 + 35? {?} 45 + 5? {?} 7 + 55? {?} 65 + 9? {?} 2 + 75? {?} 85 + 4? {?} 8 + 86? {?}
q: How many are 26 + 6? {?} 8 + 36? {?} 46 + 3? {?} 5 + 56? {?} 66 + 7? {?} 9 + 76? {?} 86 + 2? {?} 4 + 87? {?}
q: How many are 27 + 7? {?} 5 + 37? {?} 47 + 3? {?} 2 + 57? {?} 67 + 4? {?} 6 + 77? {?} 87 + 8? {?} 10 + 88? {?}
q: How many are 27 + 9? {?} 8 + 28? {?} 38 + 5? {?} 9 + 48? {?} 58 + 7? {?} 2 + 68? {?} 78 + 4? {?} 6 + 88? {?}
q: How many are 27 + 10? {?} 8 + 29? {?} 39 + 9? {?} 7 + 49? {?} 59 + 5? {?} 6 + 69? {?} 79 + 2? {?} 4 + 89? {?}
q: How many are 23 + 3? {?} 5 + 33? {?} 43 + 7? {?} 9 + 53? {?} 63 + 2? {?} 4 + 73? {?} 83 + 6? {?} 6 + 93? {?}
q: How many are 24 + 5? {?} 2 + 34? {?} 44 + 4? {?} 6 + 54? {?} 64 + 8? {?} 9 + 74? {?} 84 + 7? {?} 4 + 94? {?}
q: How many are 35 + 5? {?} 10 + 45? {?} 55 + 9? {?} 8 + 65? {?} 75 + 7? {?} 6 + 85? {?} 95 + 3? {?} 2 + 96? {?}
q: How many are 26 + 8? {?} 6 + 36? {?} 46 + 4? {?} 10 + 56? {?} 66 + 8? {?} 7 + 76? {?} 86 + 6? {?} 4 + 95? {?}
q: How many are 27 + 5? {?} 3 + 37? {?} 47 + 7? {?} 5 + 57? {?} 67 + 9? {?} 7 + 77? {?} 87 + 10? {?} 92 + 6? {?}
q: How many are 28 + 7? {?} 9 + 38? {?} 48 + 8? {?} 6 + 58? {?} 68 + 9? {?} 5 + 78? {?} 88 + 7? {?} 9 + 88? {?}
q: How many are 29 + 9? {?} 7 + 39? {?} 49 + 8? {?} 6 + 59? {?} 69 + 3? {?} 4 + 79? {?} 89 + 2? {?} 8 + 91? {?}
q: How many are 27 + 8? {?} 6 + 37? {?} 47 + 4? {?} 7 + 57? {?} 67 + 8? {?} 2 + 77? {?} 87 + 9? {?} 78 + 9? {?} 8 + 79? {?} 89 + 7? {?}
== 67 | Review | Story problems to 50
q: I had 15 cents, and Charles gave me 5 more: how many cents had I then? {20}
q: My slate cost 12 cents, and my primer, 10 cents: how much did both cost? {22}
q: Mary paid 20 cents for a reader, and 5 cents for a pencil: how much did she pay for both? {25}
q: Frank's coat cost 14 dollars, and his boots 5 dollars: how much did both cost? {19}
q: Harry is 12 years old, and Susan is 9: how many years in both their ages? {21}
q: Frank had 16 cents, and his aunt gave him 9 more: how many cents did he then have? {25}
q: John owes me 13 cents, and Samuel, 10 cents: how much do both owe me? {23}
q: I bought a whip for 18 cents: at what price must I sell it to make 6 cents? {24}
q: Harvey is now 17 years old: in 10 years from this time, how old will he be? {27}
q: In a school, there are 19 boys and 10 girls: how many pupils in the school? {29}
q: Mary had 36 chickens, and she bought 4 more: how many had she then? {40}
q: Oliver had 17 ducks, and his mother gave him 4 more: how many had he then? {21}
q: Cora spent 47 cents for books, and 4 cents for pens: how much did she spend? {51}
q: Edwin has 8 oranges more than Anna, and Anna has 19: how many has Edwin? {27}
q: George bought a sled for 27 cents, and paid 5 cents to have it repaired: how much did the sled cost him? {32}
q: Forty and 10 are how many? {50}
== 68 | Review | Taking away, bigger numbers
q: Thomas had 15 marbles, and lost 4: how many had he then? {11}
q: Oscar had 16 cents, and spent 3: how many had he left? {13}
q: Daniel, having 17 plums, gave his sister 4 of them: how many did he then have? {13}
q: Charles bought 18 peaches, and gave 5 to a poor man: how many had he left? {13}
q: Six and how many make 19? {13} 7 and how many make 20? {13}
q: If you have 20 cents, and spend 6, how many will you have left? {14}
q: Sarah had 31 needles, and lost 2: how many had she left? {29}
q: A boy had 33 chickens, and sold 4: how many had he remaining? {29}
q: Lucy had 35 eggs, and broke 6 of them: how many had she then? {29}
q: Henry had 10 cents, and his mother gave him enough more to make 40 cents: how much did she give him? {30}
q: William had 56 cents, and spent all but 7 of them for school-books: how many cents did he spend? {49}
q: Frank gathered 43 quarts of chestnuts: after selling part of them, he had only 8 quarts left: how many quarts did he sell? {35}
q: Harry Lee owed me 53 cents: he has paid me 6 cents: how many cents does he yet owe me? {47}
q: Thomas had 43 marbles, and gave 10 of them to his brother Charles: how many had he remaining? {33}
q: How many are 65 less 9? {56}
== 69 | Signs | Plus and minus together
q: How many are 5 + 9 + 3 - 4? {?}
sol: 5 plus 9 plus 3 minus 4, equals 13.
q: How many are 6 + 10 + 4 - 3? {?}
q: How many are 4 + 9 + 5 - 6? {?}
q: How many are 7 + 4 + 10 - 3? {?}
q: How many are 10 + 9 + 8 - 4? {?}
q: How many are 9 + 8 + 8 - 6? {?}
q: How many are 4 + 9 + 7 - 6? {?}
q: How many are 9 + 6 + 8 - 7? {?}
q: How many are 9 + 7 + 8 - 4? {?}
q: How many are 18 + 8 + 10 - 9? {?}
q: How many are 9 + 9 + 9 - 3? {?}
q: How many are 13 + 10 + 3 - 4? {?}
q: How many are 17 + 9 + 2 - 7? {?}
q: How many are 11 + 10 + 9 - 5? {?}
q: How many are 21 + 8 + 8 - 7? {?}
q: How many are 19 + 9 + 6 - 5? {?}
q: How many are 10 + 8 + 6 - 9? {?}
q: How many are 18 + 9 + 10 - 6? {?}
q: How many are 21 + 9 + 10 - 8? {?}
q: How many are 17 + 10 + 8 - 9? {?}
q: How many are 20 + 10 + 4 - 6? {?}
q: How many are 30 + 6 + 4 - 7? {?}
q: How many are 39 + 4 + 8 - 6? {?}
q: How many are 47 + 6 + 9 - 7? {?}
q: How many are 54 + 9 + 10 - 8? {?}
q: How many are 50 + 3 + 7 - 4? {?}
q: How many are 68 + 8 + 10 - 6? {?}
q: How many are 80 + 4 + 5 - 6? {?}
q: How many are 84 + 4 + 8 - 7? {?}
== 70 | Review | Spending money
q: Frank had 19 cents, and spent 5: how many cents had he left? {14}
q: Henry had 25 cents: he spent 4 cents for a top, and 6 cents for a kite: how many cents has he left? {15}
q: Mother gave me 15 cents, and father gave me enough more to make 25 cents: how much did father give me? {10}
q: I had 22 oranges: I gave 4 to brother Charles, and 6 to sister Mary: how many did I give away, {10} and how many did I have left? {12}
q: Thomas had 45 cents: he paid 5 cents for ink, and 10 cents for a copy-book: how many cents had he left? {30}
q: My uncle gave me 35 cents: I bought a pen-knife for 20 cents, and a spelling-book for 10 cents: how many cents had I left? {5}
q: Frank had 40 cents: he paid 10 cents for three oranges, and 18 cents for five lemons: how many cents has he remaining? {12}
q: Charles is 4 years old, and his father is 32 years old: in how many years will Charles be as old as his father is now? {28}
q: I paid 75 cents for a pair of skates, and 10 cents for a book: how much more did the skates cost than the book? {65}
q: Harry had 40 cents given him on Christmas day: he spent 10 cents for toys, and 26 cents for books: how many cents had he remaining? {4}
q: Mary had 50 cents: she gave 25 cents for a reader, 10 cents for a slate, and 5 cents for a sponge: how many cents did she pay for all, {40} and how many had she left? {10}
== 71 | Signs | Times, then take away
q: How many are 3 x 4 - 5? {?}
sol: 3 multiplied by 4 equals 12, and 12 minus 5 equals 7.
q: How many are 6 x 6 - 7? {?} 7 x 7 - 8? {?}
q: How many are 9 x 9 - 10? {?} 3 x 5 - 4? {?}
q: How many are 4 x 5 - 6? {?} 3 x 4 - 8? {?}
q: How many are 8 x 5 - 10? {?} 6 x 3 - 2? {?}
q: How many are 6 x 8 - 4? {?} 6 x 10 - 3? {?}
q: How many are 5 x 10 - 7? {?} 9 x 4 - 5? {?}
q: How many are 7 x 6 - 3? {?} 4 x 8 - 5? {?}
q: How many are 2 x 10 - 5? {?} 9 x 7 - 10? {?}
q: How many are 10 x 3 - 8? {?} 8 x 9 - 5? {?}
q: How many are 2 x 3 - 4? {?} 4 x 6 - 3? {?}
q: How many are 6 x 2 - 5? {?} 3 x 8 - 7? {?}
q: How many are 2 x 4 - 8? {?} 9 x 6 - 10? {?}
q: How many are 10 x 7 - 2? {?} 2 x 5 - 3? {?}
q: How many are 3 x 9 - 7 + 1? {?}
q: How many are 10 x 9 - 8 + 5? {?}
q: How many are 8 x 8 - 9 + 2? {?}
q: How many are 3 x 7 - 3 + 5? {?}
q: How many are 5 x 6 - 9 + 4? {?}
q: How many are 5 x 7 - 6 + 7? {?}
q: How many are 10 x 4 - 6 + 3? {?}
q: How many are 7 x 4 - 3 + 8? {?}
q: How many are 2 x 9 - 7 + 9? {?}
q: How many are 5 x 9 - 6 + 1? {?}
q: How many are 8 x 7 - 9 + 6? {?}
q: How many are 2 x 2 - 4 + 10? {?}
q: How many are 2 x 7 - 9 + 5? {?}
q: How many are 10 x 10 - 6 + 3? {?}
== 72 | Review | How many times? (to 32)
q: Four are how many times 2? {2}
sol: 4 are 2 times 2.
q: Six are how many times 2? {3} How many times 3? {2}
q: Eight are how many times 2? {4} How many times 4? {2}
q: Nine are how many times 3? {3}
q: Ten are how many times 2? {5} How many times 5? {2}
q: Twelve are how many times 2? {6} How many times 3? {4} 4? {3} 6? {2}
q: Fourteen are how many times 2? {7} How many times 7? {2}
q: Fifteen are how many times 3? {5} How many times 5? {3}
q: Sixteen are how many times 2? {8} How many times 4? {4} 8? {2}
q: Eighteen are how many times 2? {9} How many times 3? {6} 6? {3} 9? {2}
q: Twenty are how many times 2? {10} How many times 4? {5} 5? {4} 10? {2}
q: Twenty-one are how many times 3? {7} How many times 7? {3}
q: Twenty-four are how many times 3? {8} How many times 4? {6} 6? {4} 8? {3}
q: Twenty-five are how many times 5? {5}
q: Twenty-seven are how many times 3? {9} How many times 9? {3}
q: Twenty-eight are how many times 4? {7} How many times 7? {4}
q: Thirty are how many times 3? {10} How many times 5? {6} 6? {5} 10? {3}
q: Thirty-two are how many times 4? {8} How many times 8? {4}
== 73 | Review | How many times? (to 100)
q: Thirty-five are how many times 5? {7} How many times 7? {5}
q: Thirty-six are how many times 4? {9} How many times 6? {6} 9? {4}
q: Forty are how many times 4? {10} How many times 5? {8} 8? {5} 10? {4}
q: Forty-two are how many times 6? {7} How many times 7? {6}
q: Forty-five are how many times 5? {9} How many times 9? {5}
q: Forty-eight are how many times 6? {8} How many times 8? {6}
q: Forty-nine are how many times 7? {7}
q: Fifty are how many times 5? {10} How many times 10? {5}
q: Fifty-four are how many times 6? {9} How many times 9? {6}
q: Fifty-six are how many times 7? {8} How many times 8? {7}
q: Sixty are how many times 6? {10} How many times 10? {6}
q: Sixty-three are how many times 7? {9} How many times 9? {7}
q: Sixty-four are how many times 8? {8}
q: Seventy are how many times 7? {10} How many times 10? {7}
q: Seventy-two are how many times 8? {9} How many times 9? {8}
q: Eighty are how many times 8? {10} How many times 10? {8}
q: Eighty-one are how many times 9? {9}
q: Ninety are how many times 9? {10} How many times 10? {9}
q: One hundred are how many times 10? {10}
== 74 | Review | Mixed questions
q: George bought 2 peaches, at 3 cents each, and 2 oranges, at 5 cents each: how many cents did he pay for all? {16}
q: Edwin has 4 oranges, and Thomas has 3 times as many as Edwin: how many oranges have both? {16}
q: Anna is 6 years old, and Jane is twice as old as Anna, and 2 years more: how old is Jane? {14} How many years in both their ages? {20}
q: How many pine-apples, at 9 cents each, can you buy for 27 cents? {3} for 45 cents? {5} for 63 cents? {7} for 72 cents? {8}
q: I bought 10 cents worth of lemons, giving 2 cents for each lemon: how many lemons did I purchase? {5}
q: If 3 men can do a certain piece of work in 5 days, how many men can do the same work in 1 day? {15}
q: If a man can travel 90 miles in 9 hours, how many miles can he travel in 1 hour? {10} In what time could he travel 20 miles? {2}
q: Samuel bought 3 books, at 10 cents each, and a toy for 7 cents: how many cents did he pay for all? {37}
q: If 4 men can do a certain piece of work in 9 days, in how many days can 1 man do the same work? {36}
q: If 10 peaches are worth 1 orange, how many oranges are 60 peaches worth? {6} How many are 80 peaches worth? {8} 100 peaches? {10}
q: I bought 3 pounds of raisins, at 8 cents a pound, and 2 oranges for 10 cents: how much did all cost? {34}
q: What will 8 cords of wood cost, at seven dollars a cord? {56}
== 75 | Review | Buying and selling
q: Charles and Henry had each 10 marbles: Charles gave 6 of his to Henry: how many did Charles have then? {4} How many did Henry have? {16}
q: William Jones owed me 20 cents: he gave me 3 peaches, worth 4 cents each, and an orange, worth 5 cents: how much was then due? {3}
q: I bought 3 oranges, at 5 cents each, and 2 lemons, at 4 cents each: how many cents did I pay for all? {23}
q: When 3 lemons were selling for 15 cents, John gave 1 lemon and 5 cents in money for a book: what was the value of the book? {10}
q: Harry bought 3 rabbits for 30 cents, and sold them for 39 cents: how many cents did he gain on the 3 rabbits? {9} On each rabbit? {3}
q: I bought 3 dozen eggs, at 6 cents a dozen, and sold them at 8 cents a dozen: how much did I make? {6}
q: Frank bought 4 flags, at 10 cents each, and 3, at 3 cents each: how many flags did he buy, {7} and how much did they cost? {49}
q: John gave 30 cents in money and 3 peaches, worth 5 cents each, for a sled: how much did the sled cost? {45}
q: I paid 25 cents for five pounds of meat, and 10 cents for a melon: how much did I pay for all? {35}
q: Mary bought 3 quarts of chestnuts, at 10 cents a quart, and a doll for 20 cents: how much did all cost? {50}
q: Frank bought 5 books, at 7 cents each, and sold them at 9 cents each: how many cents did he make? {10}
q: I bought 3 dozen pens, at 10 cents a dozen, and some paper for 18 cents: how much did all cost me? {48}
q: A drover bought 10 sheep, at 3 dollars a head, and sold them for 5 dollars a head: how much did he gain? {20}
== 76 | Review | Paying with other things
q: James bought 3 lemons, at 2 cents each, and paid for them with oranges, at 3 cents each: how many oranges did it take? {2}
q: Mary bought 3 yards of ribbon, at 4 cents a yard: how many cents did it cost? {12} She paid for it with two-cent coins: how many coins did it take? {6}
q: Daniel bought 8 tops, at 2 cents each, and paid for them with oranges, at 4 cents each: how many oranges were required? {4}
q: Francis bought 9 marbles, at 2 cents each, and paid for them with tops, at 3 cents each: how many tops did it take? {6}
q: Sarah bought 4 thimbles, at 5 cents each, and paid for them with cherries, at 10 cents a quart: how many quarts did she give? {2}
q: A man bought 8 yards of cloth, at 3 dollars a yard, and paid for it with flour, at 4 dollars a barrel: how many barrels did it take? {6}
q: Thomas bought 3 oranges, at 5 cents each, and paid for them with chestnuts, at 3 cents a quart: how many quarts did he give? {5}
q: I bought 4 barrels of flour, at 10 dollars a barrel, and paid for it with apples, at 5 dollars a barrel: how many barrels of apples did I give? {8}
q: I bought 3 pounds of raisins, at 8 cents a pound, and paid for them with melons, at 6 cents each: how many melons did I give? {4}
q: How many oranges, at 6 cents each, will pay for 10 lemons, at 3 cents each? {5}
q: How many pears, at 8 cents each, would it take to buy 4 pine-apples, at 10 cents each? {5}
== 77 | Review | Trading and working
q: How many pine-apples, at 10 cents each, will pay for 5 peaches, at 6 cents each? {3}
q: How many pencils, at 5 cents each, will pay for 4 books, at 10 cents each? {8}
q: How many kites, at 4 cents each, will pay for 6 lemons, at 6 cents each? {9}
q: How many peaches, at 5 cents each, will pay for 4 slates, at 10 cents each? {8}
q: I bought 3 quarts of strawberries, at 10 cents a quart, and paid for them with chestnuts, at 6 cents a quart: how many quarts did it take? {5}
q: I bought 5 dozen figs, at 8 cents a dozen, and paid for them with pears, at 4 cents each: how many pears did I give? {10}
q: If 4 men can mow a field of grass in 5 days, in how many days can 10 men mow the same field? {2}
q: How much cloth, at 6 dollars a yard, will pay for 4 barrels of flour, at 9 dollars a barrel? {6}
q: How many slates, at 3 dimes each, will cost as much as 2 geographies, at 6 dimes each? {4}
q: How many bottles of ink, at 8 cents each, will pay for 10 oranges, at 4 cents each? {5}
q: How many oranges, at 4 cents each, will pay for 2 books, at 10 cents each? {5}
q: If 6 men can do a piece of work in 8 days, how long will it take 4 men to perform it? {12}
q: If 7 barrels of flour cost 84 dollars, what will 5 barrels cost? {60}
q: An orchard contains 8 rows of trees, and has 6 trees in each row; if the same number of trees were placed in 4 rows, how many would there be in a row? {12}
== 78 | Review | Counting by 2's to 9's
q: Begin with 2, and add by 2's to 100. {2..100 by 2}
q: Begin with 100, and subtract by 2's to 0. {100..0 by -2}
q: Begin with 3, and add by 3's to 99. {3..99 by 3}
q: Begin with 99, and subtract by 3's to 0. {99..0 by -3}
q: Begin with 4, and add by 4's to 100. {4..100 by 4}
q: Begin with 100, and subtract by 4's to 0. {100..0 by -4}
q: Begin with 5, and add by 5's to 100. {5..100 by 5}
q: Begin with 100, and subtract by 5's to 0. {100..0 by -5}
q: Begin with 6, and add by 6's to 96. {6..96 by 6}
q: Begin with 96, and subtract by 6's to 0. {96..0 by -6}
q: Begin with 7, and add by 7's to 98. {7..98 by 7}
q: Begin with 98, and subtract by 7's to 0. {98..0 by -7}
q: Begin with 8, and add by 8's to 96. {8..96 by 8}
q: Begin with 96, and subtract by 8's to 0. {96..0 by -8}
q: Begin with 9, and add by 9's to 99. {9..99 by 9}
q: Begin with 99, and subtract by 9's to 0. {99..0 by -9}
q: Begin with 1, and add by 2's to 99. {1..99 by 2}
q: Begin with 100, and subtract by 3's to 1. {100..1 by -3}
q: Begin with 1, and add by 3's to 100. {1..100 by 3}
q: Begin with 97, and subtract by 4's to 1. {97..1 by -4}
q: Begin with 1, and add by 4's to 97. {1..97 by 4}
q: Begin with 96, and subtract by 5's to 1. {96..1 by -5}
q: Begin with 1, and add by 5's to 96. {1..96 by 5}
q: Begin with 97, and subtract by 6's to 1. {97..1 by -6}
q: Begin with 1, and add by 6's to 97. {1..97 by 6}
q: Begin with 100, and subtract by 6's to 4. {100..4 by -6}
q: Begin with 1, and add by 7's to 99. {1..99 by 7}
q: Begin with 100, and subtract by 7's to 2. {100..2 by -7}
q: Begin with 1, and add by 8's to 97. {1..97 by 8}
q: Begin with 100, and subtract by 8's to 4. {100..4 by -8}
q: Begin with 1, and add by 9's to 100. {1..100 by 9}
== 79 | Tables | United States money
rule: 10 cents make 1 dime
rule: 10 dimes make 1 dollar
rule: 10 dollars make 1 eagle
q: How many cents in 3 dimes? {30}
sol: In 1 dime there are 10 cents; then, in 3 dimes there are 3 times 10 cents, which are 30 cents.
q: How many dimes in 3 dollars? {30} In 7 dollars? {70}
q: How many dimes in 60 cents? {6}
sol: In 10 cents there is 1 dime; then, in 60 cents there are as many dimes as 10 is contained times in 60, which are 6.
q: How many dollars in 6 eagles? {60} In 8? {80} In 4? {40}
q: How many dollars in 40 dimes? {4} In 60? {6} In 90? {9}
q: How many dimes in 1 eagle? {100} In 8 dollars? {80}
q: If 1 lemon cost 6 cents, how many dimes will 5 lemons cost? {3}
q: James bought 8 pencils, at 5 cents each: how many dimes did they cost? {4}
q: How many dollars will pay for 5 bushels of wheat, at 10 dimes a bushel? {5}
q: Jane spent 10 cents in one store, 3 dimes in another, and 5 dimes in another: how many cents did she spend altogether? {90} How many dimes? {9}
q: Henry spent 6 dimes for handkerchiefs, 25 cents for collars, and gave 1 dollar to the clerk in payment: how many cents in change did he receive? {15}
== 80 | Tables | English money
rule: 4 farthings make 1 penny
rule: 12 pence make 1 shilling
rule: 20 shillings make 1 pound
q: How many farthings in 3 pence? {12}
sol: In 1 penny there are 4 farthings; then, in 3 pence there are 3 times 4 farthings, which are 12 farthings.
q: How many shillings in 60 pence? {5}
sol: In 12 pence there is 1 shilling; then, in 60 pence there are as many shillings as 12 is contained times in 60, which are 5.
q: How many pence in 6 shillings? {72} In 8? {96} In 7? {84}
q: How many shillings in 2 pounds? {40} In 4? {80} In 5? {100}
q: How many pounds in 40 shillings? {2} In 60? {3} In 100? {5}
q: If a geography cost 4 shillings, how many pounds will 10 geographies cost? {2}
q: A boy had 9 pence; his father gave him enough to make 2 shillings: how many pence did he receive? {15}
q: How many books, at 6 shillings each, can be bought for 3 pounds? {10}
q: How many tops, at 3 pence each, can be bought for 2 shillings? {8}
q: If 10 yards of cloth costs one pound, how many shillings does 1 yard cost? {2}
q: A boy in London spent 12 pence in one store, 12 shillings in another, and 7 shillings in another: how many pounds did he spend? {1}
q: Jane spent 8 pence for prunes, 16 pence for rice, and 5 shillings for sugar, giving the clerk 1 pound in payment: how many shillings in change did she receive? {13}
== 81 | Tables | Troy weight
note: Troy weight is used for gold, silver and jewels.
rule: 24 grains make 1 penny-weight
rule: 20 penny-weights make 1 ounce
rule: 12 ounces make 1 pound
q: How many ounces in 5 pounds? {60}
sol: In 1 pound there are 12 ounces; then, in 5 pounds there are 5 times 12 ounces, which are 60 ounces.
q: How many pounds in 96 ounces? {8}
sol: In 12 ounces there is 1 pound; then, in 96 ounces there are as many pounds as 12 is contained times in 96, which are 8.
q: How many penny-weights in 3 ounces? {60} In 5? {100}
q: How many ounces in 60 penny-weights? {3} In 80? {4}
q: How many grains in 2 penny-weights? {48}
q: How many penny-weights in 48 grains? {2}
q: If 1 penny-weight of silver is worth 3 dimes, how many dimes is 1 ounce worth? {60}
q: A miner had 40 penny-weights of gold in one bag, 6 ounces in another, and 4 ounces in another: how many ounces did he have in all? {12}
q: If an ounce of gold is worth 3 pounds sterling, how many pounds sterling is 1 pound of gold worth? {36}
q: Bought a gold chain for 20 dollars, paying 2 dollars a penny-weight for it: how many penny-weights did it weigh? {10}
q: If 1 penny-weight of gold is worth 3 penny-weights of silver, how many ounces of silver are worth 1 pound of gold? {36}
q: A man owed 3 pounds of silver: at one time he paid 14 ounces; at another, 20 ounces; and at another, 20 penny-weights: how many ounces did he still owe? {1}
== 82 | Tables | Avoirdupois weight
note: Avoirdupois weight is used for everyday things, like sugar, hay and coal.
rule: 16 ounces make 1 pound
rule: 100 pounds make 1 hundred-weight
rule: 2000 pounds make 1 ton
q: How many ounces in 2 pounds? {32} In 3? {48}
q: How many pounds in 32 ounces? {2} In 64? {4}
q: How many ounces in 4 pounds? {64} In 5? {80}
q: How many pounds in 48 ounces? {3} In 80? {5}
q: How many pounds in 2 tons? {4000} In 3? {6000}
q: How many tons in 4000 pounds? {2} In 6000? {3}
q: I bought 20 ounces of rice in one store, and 12 ounces in another: how many pounds did I buy at both stores? {2}
q: James bought 3 pounds of brown sugar and 1 pound of white sugar: how many ounces did he buy? {64}
q: A farmer sold 4000 pounds of hay to one man, and 8000 pounds to another: how many tons did he sell? {6}
q: Frederick caught one fish that weighed 48 ounces, and another that weighed 32 ounces: how many pounds did the two fishes weigh? {5}
q: I bought 1 pound of figs for 20 cents, and sold them for 3 cents an ounce: how much did I get for my figs? {48} How many cents did I make? {28}
q: An express company received 4 barrels of fish, weighing 1 hundred-weight each, and charged 2 cents a pound for transportation: how many cents were their charges? {800}
q: How many pounds in 6 hundred-weight? {600}
q: How many pounds in 5 tons? {10000}
q: If you buy 5 pounds of coffee, how many ounces do you get? {80}
== 83 | Tables | Dry measure
note: Dry measure is used for grain, fruit and vegetables.
rule: 2 pints make 1 quart
rule: 8 quarts make 1 peck
rule: 4 pecks make 1 bushel
q: How many pints in 2 quarts? {4} In 3? {6} In 4? {8}
q: How many quarts in 12 pints? {6} In 14? {7} In 16? {8}
q: How many pecks in 2 bushels? {8} In 3? {12} In 5? {20}
q: How many bushels in 16 pecks? {4} In 24? {6} In 32? {8}
q: How many pints in 2 pecks? {32} In 3? {48}
q: How many quarts in 2 bushels? {64} In 3? {96}
q: If 1 pint of meal cost 2 cents, what will 7 quarts cost? {28}
q: If 3 pecks of apples cost 24 cents, what will 1 quart cost? {1}
q: I bought 10 quarts of oats of one man, 9 of another, and 5 of another: how many pecks did I buy? {3}
q: Henry bought 2 bushels of peaches, and sold 6 pecks of them: how many quarts of peaches had he left? {16}
q: James bought 3 bushels of apples for 6 dollars, and sold them for 1 dollar a peck: how much did he get for his apples? {12} How many dollars did he make? {6}
q: Henry bought 6 quarts of berries for 30 cents, and 10 quarts for 40 cents: how many pecks did he buy? {2} How much did all the berries cost? {70}
q: A farmer bought 12 bushels of seed for 60 dollars: he sold 35 dollars worth to a neighbor at the same price per bushel that he gave: how many bushels did he sell? {7}
q: If I feed my horse 4 quarts of oats three times a day: how many pecks will be required for six days? {9}
== 84 | Tables | Liquid measure
note: Liquid or wine measure is used for milk, oil, molasses and other liquids.
rule: 4 gills make 1 pint
rule: 2 pints make 1 quart
rule: 4 quarts make 1 gallon
q: How many gills in 4 pints? {16} In 6? {24} In 8? {32}
q: How many pints in 20 gills? {5} In 28? {7} In 36? {9}
q: How many pints in 6 quarts? {12} In 8? {16} In 10? {20}
q: How many quarts in 12 pints? {6} In 16? {8} In 18? {9}
q: How many quarts in 5 gallons? {20} In 7? {28} In 9? {36}
q: How many gallons in 16 quarts? {4} In 24? {6} In 32? {8}
q: If 6 gills of olive-oil cost 60 cents, what will 1 pint cost? {40}
q: If 8 quarts of milk cost 48 cents, what will 1 pint cost? {3}
q: At 80 cents a gallon, what will 1 pint of molasses cost? {10}
q: My cook buys 8 quarts of milk every week, at 4 cents a pint. How much does the milk cost a week? {64}
q: I bought 6 quarts of vinegar at one time, 7 at another, and 11 at another: how many gallons did I buy? {6}
q: Frederick bought 4 gallons of wine, at 2 dollars a gallon: he sold it at 1 dollar a quart: how much did he make on his wine? {8}
q: A grocer bought 10 gallons of kerosene for 3 dollars, and sold it for 40 cents a gallon: how many cents did he make on each gallon? {10}
q: How many quarts in 30 pints of milk? {15}
q: If a gallon of syrup costs 1 dollar, how many pints do you get for 5 dollars? {40}
== 85 | Tables | Long measure
rule: 12 inches make 1 foot
rule: 3 feet make 1 yard
rule: 5 and a half yards, or 16 and a half feet, make 1 rod
rule: 320 rods make 1 mile
q: How many inches in 3 feet? {36} In 5? {60} In 7? {84}
q: How many feet in 24 inches? {2} In 48? {4} In 96? {8}
q: How many feet in 4 yards? {12} In 8? {24} In 10? {30}
q: How many yards in 9 feet? {3} In 15? {5} In 27? {9}
q: How many rods in 2 miles? {640} In 3? {960}
q: One desk is 2 feet in length, another 3 feet: how many inches longer is one desk than the other? {12}
q: A man made a sidewalk 40 feet long, and a walk to the front door 14 feet long: how many yards of walk did he make? {18}
q: Robert put weather-strips on 5 windows, 4 yards being required for each window: how many feet of weather-strips did he put on all the windows? {60}
q: I have one piece of cloth, 1 yard and 10 inches in length, another is 26 inches in length: how many yards of cloth in both pieces? {2}
q: John can jump 50 inches, and his brother can jump ten inches farther than John: how many feet can his brother jump? {5}
q: How many bricks, each 8 inches long, will it take to make a single course of bricks 2 yards long? {9}
q: A stairway consists of 12 steps, each 8 inches in height: what is the entire rise in feet? {8}
q: How many miles in 960 rods? {3}
q: How many inches in 10 yards? {360}
== 86 | Tables | Square measure
rule: 144 square inches make 1 square foot
rule: 9 square feet make 1 square yard
rule: 30 and a quarter square yards make 1 square rod
rule: 160 square rods make 1 acre
rule: 640 acres make 1 square mile
q: How many square feet in 3 square yards? {27} In 5? {45}
q: How many square yards in 36 square feet? {4} In 72? {8}
q: How many square rods in 2 acres? {320} In 3? {480}
q: How many acres in 320 square rods? {2} In 480? {3}
q: How many square feet in a blackboard 9 feet long and 3 feet wide? {27}
q: A floor is 5 yards long and 4 yards wide: how many square yards does it contain? {20}
q: One closet measures 36 square feet, and another 45 square feet: how many square yards in both closets? {9}
q: Jane wishes to carpet a room that measures 15 feet in length and 12 feet in width: how many yards of carpet, 1 yard wide, must she buy? {20}
q: How much will it cost to plaster a wall 6 feet high and 9 feet long, at 7 cents a square yard? {42}
q: A fern-case, 2 feet square, rests on a stand 1 yard square: how many square feet of the surface of the stand are not occupied by the fern-case? {5}
q: How many square feet in a walk that is 1 yard wide and 4 yards long? {36}
q: How many square feet of lumber in a board that is 24 inches wide and 16 feet long? {32}
q: How many square miles in 1280 acres? {2}
q: How many square feet in 10 square yards? {90}
== 87 | Tables | Apothecaries weight, cubic measure and time
note: Apothecaries weight is used for mixing medicines. A pile of wood 8 feet long, 4 feet wide and 4 feet high contains a cord. The months: January 31 days, February 28 or 29, March 31, April 30, May 31, June 30, July 31, August 31, September 30, October 31, November 30, December 31.
rule: 20 grains make 1 scruple
rule: 3 scruples make 1 dram
rule: 8 drams make 1 ounce
rule: 12 ounces make 1 pound
rule: 1728 cubic inches make 1 cubic foot
rule: 27 cubic feet make 1 cubic yard
rule: 128 cubic feet make 1 cord
rule: 60 seconds make 1 minute
rule: 60 minutes make 1 hour
rule: 24 hours make 1 day
rule: 365 days and 6 hours make 1 year
rule: 100 years make 1 century
rule: 7 days make 1 week
rule: 4 weeks make 1 month
rule: 12 calendar months make 1 year
== 88 | Tables | Circles, paper and other tables
note: A book whose sheets are folded so each sheet makes 2 leaves, or 4 pages, is a folio; 4 leaves or 8 pages, a quarto; 8 leaves or 16 pages, an octavo; 12 leaves or 24 pages, a duodecimo.
rule: 60 seconds make 1 minute of a circle
rule: 60 minutes make 1 degree
rule: 360 degrees make 1 circle
rule: 24 sheets make 1 quire
rule: 20 quires make 1 ream
rule: 12 things make 1 dozen
rule: 12 dozen make 1 gross
rule: 20 things make 1 score
rule: 6 feet make 1 fathom
rule: 4 inches make 1 hand
rule: 4 rods make 1 chain
rule: 196 pounds of flour make 1 barrel
rule: 200 pounds of beef or pork make 1 barrel
rule: 60 pounds of wheat make 1 bushel
rule: 56 pounds of corn make 1 bushel
== 89 | Review | Review of tables
q: How many cents in 5 dimes? {50} In 3? {30} In 7? {70}
q: How many pence in 40 farthings? {10} In 32? {8}
q: How many eagles in 30 dollars? {3} In 50? {5} In 70? {7}
q: How many shillings in 3 pounds? {60} In 5? {100} In 4? {80}
q: How many penny-weights in 4 ounces? {80} In 2? {40}
q: How many pounds Troy in 36 ounces? {3} In 60? {5}
q: How many ounces avoirdupois in 2 pounds? {32} In 3? {48}
q: How many pounds avoirdupois in 32 ounces? {2} In 48? {3}
q: How many quarts in 6 pecks? {48} In 8? {64} In 10? {80}
q: How many bushels in 20 pecks? {5} In 28? {7} In 36? {9}
q: How many quarts in 7 gallons? {28} In 6? {24} In 8? {32}
q: How many gallons in 16 quarts? {4} In 20? {5} In 36? {9}
q: How many feet in 7 yards? {21} In 10? {30} In 12? {36}
q: How many square yards in 45 square feet? {5} In 63? {7}
q: How many scruples in 9 drams? {27} In 8? {24} In 10? {30}
q: How many cubic feet in 2 cubic yards? {54}
q: How many cubic yards in 54 cubic feet? {2}
q: How many hours in 2 days? {48} In 3? {72}
q: How many days in 48 hours? {2} In 72? {3}
q: How many weeks in 7 months? {28} In 12? {48}
q: How many minutes in 2 degrees? {120} In 3? {180}
q: How many quires in 4 reams of paper? {80} In 3? {60}
q: How many pens in 1 gross? {144}
q: How many feet in 7 fathoms? {42} In 5? {30} In 9? {54}
q: How many feet tall is a horse 15 hands high? {5}
q: How many chains in 24 rods? {6} In 28? {7} In 36? {9}
q: How many farthings in 2 pounds? {1920} In 10? {9600}
q: How many ounces in 80 penny-weights? {4}
q: How many sheets of paper in 2 quires? {48} In 2 reams? {960}
q: How many are 3 score and 10? {70}
EOF
make-book.ps1: turns primary.txt into public\books\primary1877.json: makes the tables, works out {?}, checks the answers.
cat > /root/rays-rebuild/make-book.ps1 <<'EOF'
# Builds public/books/<id>.json from a hand-corrected book in source/<Book>.txt (format at the top of source/primary.txt).
# Generates every table exactly, works out {?} answers, and checks typed answers wherever the question is plain
# arithmetic ("How many are 3 and 5?", "One from 9?", "Nine and how many make 17?", "How many twos in 8?").
# Usage: powershell -ExecutionPolicy Bypass -File RaysArithmetic\tools\make-book.ps1 [-Book primary|intellectual|...]
param([string]$Book = 'primary')
$root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
$src = Get-Content "$root\source\$Book.txt" -Encoding UTF8
# the book's id, title, shelf line and color: "@book: primary1877 | Ray's New Primary Arithmetic | 1877 - Book One | #2f6f8f"
$head = $src | Where-Object { $_ -match '^@book: ' } | Select-Object -First 1
if (-not $head) { throw "source\$Book.txt has no @book: line" }
$bk = ($head -replace '^@book: ', '') -split '\s*\|\s*'
$script:errors = New-Object System.Collections.ArrayList
# ---------- numbers ----------
$ones = @{ naught = 0; zero = 0; one = 1; two = 2; three = 3; four = 4; five = 5; six = 6; seven = 7; eight = 8; nine = 9; ten = 10; eleven = 11; twelve = 12; thirteen = 13; fourteen = 14; fifteen = 15; sixteen = 16; seventeen = 17; eighteen = 18; nineteen = 19 }
$tens = @{ twenty = 20; thirty = 30; forty = 40; fifty = 50; sixty = 60; seventy = 70; eighty = 80; ninety = 90 }
$plural = @{ ones = 1; twos = 2; threes = 3; fours = 4; fives = 5; sixes = 6; sevens = 7; eights = 8; nines = 9; tens = 10 }
function WordNum([string]$w) {
$w = $w.ToLower()
if ($w -match '^\d+$') { return [int]$w }
if ($w -eq 'one hundred' -or $w -eq 'hundred') { return 100 }
if ($ones.ContainsKey($w)) { return $ones[$w] }
if ($tens.ContainsKey($w)) { return $tens[$w] }
if ($w -match '^(\w+)-(\w+)$' -and $tens.ContainsKey($Matches[1]) -and $ones.ContainsKey($Matches[2])) { return $tens[$Matches[1]] + $ones[$Matches[2]] }
return $null
}
$numRx = '\b(?:\d+|one hundred|(?:twenty|thirty|forty|fifty|sixty|seventy|eighty|ninety)(?:-(?:one|two|three|four|five|six|seven|eight|nine))?|naught|zero|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|one|two|three|four|five|six|seven|eight|nine|ten)\b'
# Plain arithmetic in a question part -> its value, or $null when the part isn't plain arithmetic.
# Reads left to right, the way the book works them ("2 and 5 are 7; 7 less 3 are 4"); every x comes first in the book.
function Calc([string]$t) {
$s = ' ' + $t.ToLower() + ' '
$s = $s -replace '[?:]', ' ' -replace '\s*=\s*', ' ' -replace ',', ' '
# "Twelve are how many times 3?" -> 12 / 3
# "What are 2/3 of 9?" -> 6
if ($s -match '^\s*(?:what (?:are|is)\s+)?(\d+)/(\d+) of (\d+)\s*$') { $v = [int]$Matches[3] * [int]$Matches[1]; if ($v % [int]$Matches[2]) { return $null }; return [int]($v / [int]$Matches[2]) }
if ($s -match "^\s*($numRx)\s+(?:are|is) how many times\s+($numRx)\s*$") { $a = WordNum $Matches[1]; $b = WordNum $Matches[2]; if ($a % $b) { return $null }; return [int]($a / $b) }
$s = $s -replace '\bare how many\b', ' ' -replace '\bhow many (are|is)\b', ' ' -replace '\bhow many\b', ' '
# "N from M leaves how many" / "N from M" / "N taken away from M"
if ($s -match "^\s*($numRx)\s+(?:taken\s+)?(?:away\s+)?from\s+($numRx)\s*(?:leaves)?\s*$") { return (WordNum $Matches[2]) - (WordNum $Matches[1]) }
# "N and how many make M"
if ($s -match "^\s*($numRx)\s+and\s+make\s+($numRx)\s*$") { return (WordNum $Matches[2]) - (WordNum $Matches[1]) }
# "how many twos in 8"
if ($s -match "^\s*(ones|twos|threes|fours|fives|sixes|sevens|eights|nines|tens)\s+in\s+($numRx)\s*$") { return [int]((WordNum $Matches[2]) / $plural[$Matches[1]]) }
# "times is 2 contained in 4" / "2 in 4"
if ($s -match "^\s*(?:times is\s+)?($numRx)\s+(?:contained\s+)?in\s+($numRx)\s*$") { return [int]((WordNum $Matches[2]) / (WordNum $Matches[1])) }
$s = $s -replace '\bmultiplied by\b', ' x ' -replace '\bdivided by\b', ' / ' -replace '\btimes\b', ' x ' -replace '\btime\b', ' x ' -replace '\bplus\b', ' + ' -replace '\bminus\b', ' - ' -replace '\bless\b', ' - ' -replace '\band\b', ' + '
$tok = @([regex]::Matches($s, "$numRx|[-+x/]") | ForEach-Object { $_.Value })
$rest = ([regex]::Replace($s, "$numRx|[-+x/]", ' ')).Trim()
if ($rest -or $tok.Count -lt 3 -or $tok.Count % 2 -eq 0) { return $null }
$v = WordNum $tok[0]; if ($null -eq $v) { return $null }
for ($i = 1; $i -lt $tok.Count; $i += 2) {
$b = WordNum $tok[$i + 1]; if ($null -eq $b) { return $null }
switch ($tok[$i]) { '+' { $v += $b } '-' { $v -= $b } 'x' { $v *= $b } '/' { if ($v % $b) { return $null }; $v = $v / $b } default { return $null } }
}
return [int]$v
}
# ---------- exact fractions ----------
# {~ 1/2 + 1/4} works the answer out exactly: whole numbers, a/b, mixed numbers written 1_3/4, + - * / and ( ).
# A fraction is written without spaces (3/4); division between numbers needs spaces (12 / 3).
# The answer is written the book's way: 3/4, 2 1/3, 5.
function Gcd([long]$a, [long]$b) { $a = [Math]::Abs($a); $b = [Math]::Abs($b); while ($b) { $t = $a % $b; $a = $b; $b = $t }; [long][Math]::Max($a, 1) }
function Rat([long]$n, [long]$d) { if ($d -lt 0) { $n = -$n; $d = -$d }; $g = Gcd $n $d; ,@([long]($n / $g), [long]($d / $g)) }
function RatStr($r) {
$n = $r[0]; $d = $r[1]
if ($d -eq 1) { return "$n" }
$w = [long][Math]::Truncate($n / $d); $f = $n - $w * $d
if ($w) { "$w $([Math]::Abs($f))/$d" } else { "$n/$d" }
}
function EvalFrac([string]$e) {
$script:tk = @([regex]::Matches($e, '\d+_\d+/\d+|\d+/\d+|\d+|[-+*/()]') | ForEach-Object { $_.Value }); $script:ti = 0
function Atom {
$x = $script:tk[$script:ti]; $script:ti++
if ($x -eq '(') { $v = Sum; $script:ti++; return ,$v }
if ($x -match '^(\d+)_(\d+)/(\d+)$') { return ,(Rat ([long]$Matches[1] * [long]$Matches[3] + [long]$Matches[2]) ([long]$Matches[3])) }
if ($x -match '^(\d+)/(\d+)$') { return ,(Rat ([long]$Matches[1]) ([long]$Matches[2])) }
if ($x -match '^\d+$') { return ,(Rat ([long]$x) 1) }
throw "bad token '$x' in $e"
}
function Prod {
$v = Atom
while ($script:ti -lt $script:tk.Count -and $script:tk[$script:ti] -match '^[*/]$') {
$op = $script:tk[$script:ti]; $script:ti++; $b = Atom
if ($op -eq '*') { $v = Rat ($v[0] * $b[0]) ($v[1] * $b[1]) } else { $v = Rat ($v[0] * $b[1]) ($v[1] * $b[0]) }
}
,$v
}
function Sum {
$v = Prod
while ($script:ti -lt $script:tk.Count -and $script:tk[$script:ti] -match '^[-+]$') {
$op = $script:tk[$script:ti]; $script:ti++; $b = Prod
$n = $(if ($op -eq '+') { $v[0] * $b[1] + $b[0] * $v[1] } else { $v[0] * $b[1] - $b[0] * $v[1] })
$v = Rat $n ($v[1] * $b[1])
}
,$v
}
$r = Sum
if ($script:ti -ne $script:tk.Count) { throw "couldn't read all of: $e" }
RatStr $r
}
# ---------- the book's tables ----------
function Table([string]$op, [int]$n) {
$f = New-Object System.Collections.ArrayList
# t = the book's line, q = the same fact asked as a question (the app's drill and the voice use both)
function F($t, $a, $b, $r) {
$q = switch ($op) { 'add' { "How many are $a and $b?" } 'sub' { "$a from $b leaves how many?" } 'mul' { if ($a -eq 1) { "How many is 1 time $b?" } else { "How many are $a times $b?" } } 'div' { "How many times is $a contained in $b?" } }
[void]$f.Add([ordered]@{ t = $t; q = $q; a = $a; b = $b; r = $r })
}
switch ($op) {
'add' { foreach ($k in 1..10) { F "$n and $k are $($n + $k)" $n $k ($n + $k); if ($k -ne $n) { F "$k and $n are $($n + $k)" $k $n ($n + $k) } } }
'sub' { F "$n from $n leaves 0" $n $n 0
foreach ($k in 1..9) { $m = $n + $k; F "$n from $m leaves $k" $n $m $k; if ($k -ne $n) { F "$k from $m leaves $n" $k $m $n } } }
'mul' { if ($n -eq 1) { foreach ($k in 2..10) { F "1 time $k is $k" 1 $k $k; F "$k times 1 are $k" $k 1 $k } }
else { F "$n times 1 are $n" $n 1 $n; foreach ($k in 2..10) { F "$k times $n are $($k * $n)" $k $n ($k * $n); if ($k -ne $n) { F "$n times $k are $($k * $n)" $n $k ($k * $n) } } } }
'div' { F "$n in $n, 1 time" $n $n 1
foreach ($k in 2..10) { $m = $n * $k; F "$n in $m, $k times" $n $m $k; if ($k -ne $n) { F "$k in $m, $n times" $k $m $n } } }
}
return ,$f.ToArray()
}
# ---------- questions ----------
# display: the book's x and / become real signs; spoken: signs become words for the voice
$TIMES = " $([char]0x00D7) "; $DIV = " $([char]0x00F7) "; $MINUS = " $([char]0x2212) " # plain-ASCII script: no BOM needed
function Display([string]$t) { ($t -replace '(?<=\d) x (?=\d)', $TIMES -replace '(?<=\d) / (?=\d)', $DIV -replace '(?<=\d) - (?=\d)', $MINUS) }
# fractions said the book's way: "3/4" -> "3 fourths", "2 1/2" -> "2 and 1 half"
$FRN = @{ 2 = 'half'; 3 = 'third'; 4 = 'fourth'; 5 = 'fifth'; 6 = 'sixth'; 7 = 'seventh'; 8 = 'eighth'; 9 = 'ninth'; 10 = 'tenth'; 11 = 'eleventh'; 12 = 'twelfth'; 13 = 'thirteenth'; 14 = 'fourteenth'; 15 = 'fifteenth'; 16 = 'sixteenth'; 17 = 'seventeenth'; 18 = 'eighteenth'; 19 = 'nineteenth'; 20 = 'twentieth'; 100 = 'hundredth' }
function FracWords([int]$n, [int]$d) {
$name = $FRN[$d]; if (-not $name) { $name = "$($d)th" }
if ($n -gt 1) { $name = $(if ($name -eq 'half') { 'halves' } else { $name + 's' }) }
"$n $name"
}
function SayFractions([string]$t) {
$t = [regex]::Replace($t, '(?<![\d/])(\d+) (\d+)/(\d+)(?![\d/])', { param($m) "$($m.Groups[1].Value) and $(FracWords $m.Groups[2].Value $m.Groups[3].Value)" })
[regex]::Replace($t, '(?<![\d/])(\d+)/(\d+)(?![\d/])', { param($m) FracWords $m.Groups[1].Value $m.Groups[2].Value })
}
function Spoken([string]$t) { $t = SayFractions $t; ($t -replace '(?<=\d) x (?=\d)', ' times ' -replace '(?<=\d) / (?=\d)', ' divided by ' -replace '(?<=\d) - (?=\d)', ' minus ' -replace '(?<=\d) \+ (?=\d)', ' plus ' -replace '= \?', 'equals what?') }
function Question([string]$q, [string]$where) {
$parts = New-Object System.Collections.ArrayList
$pos = 0
foreach ($m in [regex]::Matches($q, '\{([^}]*)\}')) {
$seg = $q.Substring($pos, $m.Index - $pos).Trim(); $pos = $m.Index + $m.Length
$ans = $m.Groups[1].Value.Trim()
$p = [ordered]@{ t = (Display $seg) }
if ($ans -match '^(-?\d+)\.\.(-?\d+) by (-?\d+)$') {
$a = [int]$Matches[1]; $b = [int]$Matches[2]; $st = [int]$Matches[3]
$seq = @(); for ($v = $a; ($st -gt 0 -and $v -le $b) -or ($st -lt 0 -and $v -ge $b); $v += $st) { $seq += $v }
if ($seq[-1] -ne $b) { [void]$script:errors.Add("$where : count by $st from $a never lands on $b") }
$p.seq = $seq
} elseif ($ans -match '^\d+(\|\d+)+$') { # more than one right answer, e.g. {100|1} = 100 cents or 1 dollar
$all = @($ans -split '\|' | ForEach-Object { [int]$_ }); $p.a = $all[0]; $p.alt = @($all | Select-Object -Skip 1)
} elseif ($ans -match '^~\s*(.+)$') { # worked out exactly: {~ 1/2 + 1/4} -> 3/4
try { $v = EvalFrac $Matches[1]; if ($v -match '^\d+$') { $p.a = [int]$v } else { $p.f = $v } } catch { [void]$script:errors.Add("$where : $_") }
} elseif ($ans -match '^=(.+)$') { # exact written form, e.g. {=1/2} for "reduce to lowest terms"; {=3/6|1/2} more than one
$ex = @($Matches[1] -split '\|' | ForEach-Object { $_.Trim() }); $p.f = $ex[0]; $p.ex = $ex
} elseif ($ans -eq 'open') { # answered out loud to a grown-up (e.g. "Give two numbers which make 10")
$p.open = $true
} elseif ($ans -match '^(\d+ )?\d+/\d+$' -or $ans -match '^\d*\.\d+$') { # fraction, mixed number or decimal
$p.f = $ans
} elseif ($ans -eq '?') {
$v = Calc $seg
if ($null -eq $v) { [void]$script:errors.Add("$where : can't work out {?} for '$seg'") } else { $p.a = $v }
} elseif ($ans -match '^\d+$') {
$p.a = [int]$ans
$v = Calc $seg
if ($null -ne $v -and $v -ne $p.a) { [void]$script:errors.Add("$where : '$seg' is $v, not $ans") }
} else { [void]$script:errors.Add("$where : bad answer {$ans}") }
if ((Spoken $seg) -ne $seg) { $p.s = Spoken $seg }
[void]$parts.Add($p)
}
$tail = $q.Substring($pos).Trim()
if ($tail) { if ($parts.Count) { $parts[$parts.Count - 1].t += ' ' + $tail } else { [void]$script:errors.Add("$where : question has no {answer}") } }
return [ordered]@{ parts = $parts.ToArray() }
}
# ---------- lessons ----------
$lessons = New-Object System.Collections.ArrayList
$L = $null; $lastQ = $null; $qn = 0
foreach ($line in $src) {
if ($line -match '^\s*(#|$)' -or $line -match '^@book: ') { continue }
if ($line -match '^== (\d+) \| ([^|]+) \| (.+)$') {
$L = [ordered]@{ n = [int]$Matches[1]; part = $Matches[2].Trim(); title = $Matches[3].Trim(); kind = 'problems' }
$L.review = $L.part -eq 'Review'
[void]$lessons.Add($L); $lastQ = $null; $qn = 0; continue
}
if (-not $L) { continue }
$where = "Lesson $($L.n)"
if ($line -match '^kind: (count|numbers) (\d+) (\d+)$') { $L.kind = $Matches[1]; $L.from = [int]$Matches[2]; $L.to = [int]$Matches[3] }
elseif ($line -match '^kind: (read|write)$') { $L.kind = $Matches[1] }
elseif ($line -match '^list: (.+)$') { $L.list = @($Matches[1] -split '\s+' | ForEach-Object { [int]$_ }) }
elseif ($line -match '^note: (.+)$') { $L.note = $Matches[1] }
elseif ($line -match '^table: (add|sub|mul|div) (\d+)$') { $L.op = $Matches[1]; $L.table = Table $Matches[1] ([int]$Matches[2]) }
elseif ($line -match '^table: \+(add|sub|mul|div) (\d+) (\d+)$') { # Book Two style "2 + 1 = 3" ... "2 + 12 = 14"; several add up
$L.op = $Matches[1]; $op = $Matches[1]; $n = [int]$Matches[2]; $max = [int]$Matches[3]
$rows = New-Object System.Collections.ArrayList; if ($L.table) { [void]$rows.AddRange(@($L.table)) }
foreach ($k in 1..$max) {
switch ($op) {
'add' { $r = $n + $k; [void]$rows.Add([ordered]@{ t = "$n + $k = $r"; q = "How many are $n and $k?"; a = $n; b = $k; r = $r }) }
'sub' { $m = $n + $k - 1; $r = $k - 1; [void]$rows.Add([ordered]@{ t = "$m - $n = $r"; q = "$n from $m leaves how many?"; a = $n; b = $m; r = $r }) }
'mul' { $r = $n * $k; [void]$rows.Add([ordered]@{ t = "$k x $n = $r"; q = "How many are $k times $n?"; a = $k; b = $n; r = $r }) } # the book's columns: 1 x 4, 2 x 4 ... 12 x 4
'div' { $m = $n * $k; [void]$rows.Add([ordered]@{ t = "$m / $n = $k"; q = "How many times is $n contained in $m?"; a = $n; b = $m; r = $k }) }
}
}
foreach ($f in $rows) { $f.t = Display $f.t }
$L.table = $rows.ToArray()
}
elseif ($line -match '^rule: (\d+) (.+) make 1 (.+)$') {
if (-not $L.rules) { $L.rules = New-Object System.Collections.ArrayList }
$r = [ordered]@{ t = "$($Matches[1]) $($Matches[2]) make 1 $($Matches[3])"; q = "How many $($Matches[2]) make 1 $($Matches[3])?"; n = [int]$Matches[1]; a = $Matches[2]; b = $Matches[3] }
if ($r.a -match '\d|^and ') { $r.quiz = $false } # "365 days and 6 hours": show it, don't quiz it
[void]$L.rules.Add($r)
}
elseif ($line -match '^q: (.+)$') {
$qn++; if (-not $L.qs) { $L.qs = New-Object System.Collections.ArrayList }
$lastQ = Question $Matches[1] "$where q$qn"; [void]$L.qs.Add($lastQ)
}
elseif ($line -match '^sol: (.+)$') { if ($lastQ) { $lastQ.sol = $Matches[1] } else { [void]$script:errors.Add("$where : sol without a question") } }
else { [void]$script:errors.Add("$where : can't read line '$line'") }
}
foreach ($L in $lessons) {
if ($L.rules) { $L.rules = $L.rules.ToArray() }
if ($L.qs) { $L.qs = $L.qs.ToArray() }
if ($L.kind -eq 'problems' -and -not $L.qs -and -not $L.rules -and -not $L.table) { [void]$script:errors.Add("Lesson $($L.n) : no questions") }
}
$nums = @($lessons | ForEach-Object { $_.n })
for ($i = 0; $i -lt $nums.Count; $i++) { if ($nums[$i] -ne $i + 1) { [void]$script:errors.Add("lesson numbers out of order at $($nums[$i])"); break } }
if ($script:errors.Count) { "PROBLEMS:"; $script:errors; exit 1 }
$out = [ordered]@{ id = $bk[0]; title = $bk[1]; sub = $bk[2]; color = $bk[3]; lessons = $lessons.ToArray() }
New-Item -ItemType Directory -Force "$root\public\books" | Out-Null
[IO.File]::WriteAllText("$root\public\books\$($bk[0]).json", ($out | ConvertTo-Json -Depth 10 -Compress), (New-Object Text.UTF8Encoding $false))
$q = ($lessons | ForEach-Object { @($_.qs).Count } | Measure-Object -Sum).Sum
$a = ($lessons | ForEach-Object { foreach ($x in @($_.qs)) { if ($x) { $x.parts.Count } } } | Measure-Object -Sum).Sum
"$($lessons.Count) lessons, $q questions, $a answers -> public\books\$($bk[0]).json"
EOF
make-audio.ps1: records every line with Eloquence at both speeds and writes public\audio\primary1877.json.
cat > /root/rays-rebuild/make-audio.ps1 <<'EOF'
# Records everything Ray's Arithmetic says with the PC's ETI-Eloquence (Reed, the same voice as ABC Books) at each
# speed, then writes public/audio/<id>.json (what the app asks for -> clip id). WAVs land in tools/out/s<speed>/,
# shared by every book (a line two books both say is recorded once).
# Usage: powershell -ExecutionPolicy Bypass -File RaysArithmetic\tools\make-audio.ps1 [-Id primary1877|intellectual|...]
# The keys must match app.js: prompts, "lesson:N", "n:N", "find:N", "ans:N", and the book's own texts.
param([string]$Id = 'primary1877', [int[]]$Speeds = @(60, 75))
$root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
$book = Get-Content "$root\public\books\$Id.json" -Raw -Encoding UTF8 | ConvertFrom-Json
$P = Get-Content "$root\public\prompts.json" -Raw -Encoding UTF8 | ConvertFrom-Json
$kids = 'Layla', 'Lavon', 'Lilly'
$say = New-Object System.Collections.Specialized.OrderedDictionary ([StringComparer]::Ordinal)
function Add($key, $spoken) { if ($key -and -not $say.Contains([string]$key)) { $say[[string]$key] = $(if ($spoken) { $spoken } else { [string]$key }) } }
foreach ($pr in $P.PSObject.Properties) { if ($pr.Value -like '*{kid}*') { foreach ($k in $kids) { Add ($pr.Value -replace '\{kid\}', $k) } } else { Add $pr.Value } }
foreach ($k in $kids) { Add "Great job, $k!" }
foreach ($n in 0..100) { Add "n:$n" "$n."; Add "find:$n" "Find $n." }
# "2 1/2" -> "2 and 1 half", "3/4" -> "3 fourths": how the book says fractions
function Frac([string]$f) {
$den = @{ 2 = 'half'; 3 = 'third'; 4 = 'fourth'; 5 = 'fifth'; 6 = 'sixth'; 7 = 'seventh'; 8 = 'eighth'; 9 = 'ninth'; 10 = 'tenth'; 11 = 'eleventh'; 12 = 'twelfth'; 20 = 'twentieth'; 100 = 'hundredth' }
if ($f -match '^\d*\.\d+$') { return $f }
$w = ''; if ($f -match '^(\d+) (.+)$') { $w = "$($Matches[1]) and "; $f = $Matches[2] }
$n, $d = $f -split '/'; $name = $den[[int]$d]; if (-not $name) { $name = "$($d)th" }
if ($name -eq 'half' -and [int]$n -gt 1) { $name = 'halve' }
return "$w$n $name$(if ([int]$n -gt 1) { 's' })"
}
$answers = New-Object 'System.Collections.Generic.SortedSet[int]'
foreach ($L in $book.lessons) {
Add "lesson:$($L.n)" "Lesson $($L.n). $($L.title)."
foreach ($v in @($L.list)) { if ($null -ne $v) { [void]$answers.Add([int]$v) } }
foreach ($f in @($L.table)) { if ($f) { Add $f.t; Add $f.q; [void]$answers.Add([int]$f.r) } }
foreach ($r in @($L.rules)) { if ($r) { Add $r.t; Add $r.q } }
foreach ($q in @($L.qs)) {
if (-not $q) { continue }
foreach ($p in $q.parts) {
$s = if ($p.s) { $p.s } else { $p.t }; Add $s
if ($null -ne $p.a) { [void]$answers.Add([int]$p.a) }
if ($p.f) { Add "ans:$($p.f)" "The answer is $(Frac $p.f)." }
foreach ($v in @($p.seq)) { if ($null -ne $v) { [void]$answers.Add([int]$v); if ($v -gt 100) { Add "n:$v" "$v." } } }
}
if ($q.sol) { Add $q.sol }
}
}
foreach ($a in $answers) { Add "ans:$a" "The answer is $a." }
Add "Answer out loud, then tap Next."
"$($say.Count) clips per speed"
$sha = [Security.Cryptography.SHA1]::Create()
function Id($s) { (($sha.ComputeHash([Text.Encoding]::UTF8.GetBytes($s)) | Select-Object -First 6 | ForEach-Object { $_.ToString('x2') }) -join '') }
$items = New-Object System.Collections.Specialized.OrderedDictionary ([StringComparer]::Ordinal)
foreach ($k in $say.Keys) { $items[$k] = Id $say[$k] }
$ps32 = 'C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe'
foreach ($sp in $Speeds) {
$dir = "$root\tools\out\s$sp"; New-Item -ItemType Directory -Force $dir | Out-Null
$jobs = @(); $seen = @{}
foreach ($k in $say.Keys) { $cid = $items[$k]; if (-not $seen[$cid] -and -not (Test-Path "$dir\$cid.wav")) { $seen[$cid] = 1; $jobs += [ordered]@{ text = $say[$k]; out = "$dir\$cid.wav" } } }
"speed $sp : $($jobs.Count) to record"
for ($i = 0; $i -lt $jobs.Count; $i += 400) { # batches, so a long run shows progress
$jf = "$dir\jobs.json"; ConvertTo-Json @($jobs[$i..([Math]::Min($i + 399, $jobs.Count - 1))]) | Set-Content -Encoding utf8 $jf
& $ps32 -NoProfile -ExecutionPolicy Bypass -File "$root\tools\eloq-render.ps1" -Jobs $jf -Speed $sp | Out-Null
" $([Math]::Min($i + 400, $jobs.Count)) / $($jobs.Count)"
}
Remove-Item "$dir\*.wav.json" -ErrorAction SilentlyContinue # word times aren't used here
}
$out = [ordered]@{ speeds = $Speeds; items = $items }
New-Item -ItemType Directory -Force "$root\public\audio" | Out-Null
[IO.File]::WriteAllText("$root\public\audio\$Id.json", ($out | ConvertTo-Json -Depth 4 -Compress), (New-Object Text.UTF8Encoding $false))
"manifest: $root\public\audio\$Id.json"
EOF
eloq-render.ps1: talks to the PC's 32-bit Eloquence (C:\Windows\Speech\Eloq\eci.dll) and writes WAV files.
cat > /root/rays-rebuild/eloq-render.ps1 <<'EOF'
# Renders text with the PC's own ETI-Eloquence (C:\Windows\Speech\Eloq\eci.dll, 32-bit) to WAV
# plus word start times (ms). Must run in 32-bit PowerShell:
# C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -File eloq-render.ps1 -Jobs jobs.json
# jobs.json: [{ "text": "A cat and a rat.", "out": "C:\\...\\l1-5.wav" }, ...] -> writes out + out.json (word times)
param(
[Parameter(Mandatory)] [string]$Jobs,
[int]$Voice = 1, # 1 = Reed (Eloquence default male)
[int]$Speed = 50, # eciSpeed 0-250
[int]$Pitch = -1, # eciPitchBaseline 0-100, -1 = voice default
[int]$Rate = 1 # eciSampleRate: 0=8000, 1=11025, 2=22050
)
if ([IntPtr]::Size -ne 4) { throw 'Run this with 32-bit PowerShell (SysWOW64).' }
$env:PATH = 'C:\Windows\Speech\Eloq;' + $env:PATH
Add-Type -TypeDefinition @'
using System; using System.IO; using System.Collections.Generic; using System.Runtime.InteropServices;
public static class Eloq {
public delegate int Cb(IntPtr h, int msg, int lParam, IntPtr data);
[DllImport("eci.dll")] static extern IntPtr eciNew();
[DllImport("eci.dll")] static extern IntPtr eciDelete(IntPtr h);
[DllImport("eci.dll")] static extern int eciSetParam(IntPtr h, int p, int v);
[DllImport("eci.dll")] static extern int eciCopyVoice(IntPtr h, int from, int to);
[DllImport("eci.dll")] static extern int eciGetVoiceParam(IntPtr h, int voice, int p);
[DllImport("eci.dll")] static extern int eciSetVoiceParam(IntPtr h, int voice, int p, int v);
[DllImport("eci.dll", CharSet = CharSet.Ansi)] static extern int eciAddText(IntPtr h, string t);
[DllImport("eci.dll")] static extern int eciInsertIndex(IntPtr h, int i);
[DllImport("eci.dll")] static extern int eciSynthesize(IntPtr h);
[DllImport("eci.dll")] static extern int eciSynchronize(IntPtr h);
[DllImport("eci.dll")] static extern void eciRegisterCallback(IntPtr h, Cb cb, IntPtr data);
[DllImport("eci.dll")] static extern int eciSetOutputBuffer(IntPtr h, int size, IntPtr buf);
const int BUF = 256; // small, so word marks line up with the audio (marks arrive before the buffer is flushed)
static IntPtr buf; static List<short> pcm; static List<int> marks; static Cb keep;
static int OnMsg(IntPtr h, int msg, int lp, IntPtr d) {
if (msg == 0) { var s = new short[lp]; Marshal.Copy(buf, s, 0, lp); pcm.AddRange(s); } // eciWaveformBuffer
else if (msg == 2) { while (marks.Count <= lp) marks.Add(-1); marks[lp] = pcm.Count; } // eciIndexReply
return 1; // eciDataProcessed
}
public static int SampleRate(int r) { return r == 0 ? 8000 : r == 2 ? 22050 : 11025; }
// returns word start times in ms; writes a 16-bit mono WAV
public static int[] Render(string text, string wav, int voice, int speed, int pitch, int rate) {
IntPtr h = eciNew(); if (h == IntPtr.Zero) throw new Exception("eciNew failed (eci.dll / enu.syn not found?)");
buf = Marshal.AllocHGlobal(BUF * 2); pcm = new List<short>(); marks = new List<int>(); keep = OnMsg;
eciRegisterCallback(h, keep, IntPtr.Zero); eciSetOutputBuffer(h, BUF, buf);
eciSetParam(h, 5, rate); // eciSampleRate
eciSetParam(h, 1, 1); // eciInputType: annotated text
eciCopyVoice(h, voice, 0);
eciSetVoiceParam(h, 0, 6, speed); // eciSpeed
if (pitch >= 0) eciSetVoiceParam(h, 0, 2, pitch); // eciPitchBaseline
string[] words = text.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < words.Length; i++) { eciInsertIndex(h, i); eciAddText(h, words[i] + " "); }
eciInsertIndex(h, words.Length);
eciSynthesize(h); eciSynchronize(h); eciDelete(h); Marshal.FreeHGlobal(buf);
int sr = SampleRate(rate);
using (var w = new BinaryWriter(File.Create(wav))) {
int n = pcm.Count * 2;
w.Write(new[] { 'R','I','F','F' }); w.Write(36 + n); w.Write(new[] { 'W','A','V','E','f','m','t',' ' });
w.Write(16); w.Write((short)1); w.Write((short)1); w.Write(sr); w.Write(sr * 2); w.Write((short)2); w.Write((short)16);
w.Write(new[] { 'd','a','t','a' }); w.Write(n); foreach (var s in pcm) w.Write(s);
}
var t = new int[words.Length + 1];
for (int i = 0; i <= words.Length; i++) t[i] = i < marks.Count && marks[i] >= 0 ? (int)(marks[i] * 1000L / sr) : -1;
return t;
}
}
'@
$list = Get-Content $Jobs -Raw | ConvertFrom-Json
foreach ($j in $list) {
$t = [Eloq]::Render($j.text, $j.out, $Voice, $Speed, $Pitch, $Rate)
ConvertTo-Json @{ text = $j.text; words = $t } -Compress | Set-Content -Encoding utf8 ($j.out + '.json')
"$($j.out) words(ms): $($t -join ',')"
}
EOF
index.html: the screens and the dark look.
cat > /root/rays-rebuild/index.html <<'EOF'
<!doctype html>
<html lang="en"><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta name="theme-color" content="#121212">
<title>Ray's Arithmetic</title>
<link rel="icon" type="image/png" sizes="192x192" href="icon-192.png">
<link rel="manifest" href="manifest.webmanifest">
<link rel="apple-touch-icon" href="icon-192.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Ray's Math">
<style>
:root{color-scheme:dark;--paper:#121212;--ink:#ececec;--soft:#a8a8a8;--accent:#2f8fbf;--warm:#d9733f;--good:#3fae6e;--bad:#ff6b5e;--board:#1d3527;--card:#1e1e1e;--line:#6a6a6a;--hi:#1f3340;--a:#f0a03c;--b:#5ab4f0}
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{margin:0;height:100%;background:var(--paper);color:var(--ink);font-family:Georgia,"Times New Roman",serif;overflow:hidden;user-select:none;-webkit-user-select:none;-webkit-text-size-adjust:100%;text-size-adjust:100%}
.screen{position:absolute;inset:0;display:none;flex-direction:column;padding:10px 16px}
.screen.on{display:flex}
h1{margin:4px 0 10px;font-size:clamp(26px,6vmin,44px);text-align:center;letter-spacing:.04em}
h2{margin:4px 0;font-size:clamp(19px,4.2vmin,30px);text-align:center}
button{font:inherit;font-size:clamp(18px,4vmin,26px);border:3px solid var(--line);background:var(--card);color:var(--ink);border-radius:14px;padding:8px 16px;min-height:50px;cursor:pointer}
button:active{transform:scale(.96)}
button.big{background:var(--accent);color:#fff;border-color:#164a63;font-size:clamp(21px,4.6vmin,30px);padding:12px 26px}
button.ghost{border-color:transparent;background:transparent}
button:disabled{opacity:.35}
.row{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;align-items:center}
.grow{flex:1;min-height:0;position:relative}
.kids button{min-width:30vmin;font-size:clamp(24px,6vmin,40px)}
.kids button.sel{background:var(--accent);border-color:var(--accent);color:#fff}
#home{overflow-y:auto}
.shelf{display:flex;gap:3vmin;justify-content:center;flex-wrap:wrap;margin-top:3vmin;padding:8px}
.book{width:min(34vmin,210px);aspect-ratio:3/4;border-radius:6px 14px 14px 6px;color:#fff;padding:14px;display:flex;flex-direction:column;justify-content:space-between;box-shadow:inset 10px 0 0 rgba(0,0,0,.18),0 4px 10px rgba(0,0,0,.25);border:none;text-align:left}
.book b{font-size:clamp(18px,4vmin,26px)}.book small{font-size:clamp(13px,2.6vmin,17px);opacity:.9}
.book .bp{font-size:clamp(14px,3vmin,19px);font-weight:bold;background:rgba(0,0,0,.35);border-radius:8px;padding:4px 8px;align-self:flex-start}
.book.soon{filter:grayscale(.6);opacity:.6}
.book.cur{outline:5px solid #ffd166;outline-offset:3px;animation:pulse 1.6s infinite}
.map{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(22vmin,120px),1fr));grid-auto-rows:min(24vmin,120px);gap:12px;overflow-y:auto;padding:6px;align-content:start}
.tile{height:100%;min-height:0;border-radius:18px;display:flex;flex-direction:column;align-items:center;justify-content:center;font-size:clamp(22px,6vmin,38px);padding:4px;line-height:1.1}
.tile small{font-size:clamp(11px,2.4vmin,15px);color:var(--soft)}
.tile .st{font-size:clamp(14px,3vmin,20px);color:#e0a000;letter-spacing:2px}
.tile.next{background:var(--hi);border-color:var(--accent);animation:pulse 1.4s infinite}
.tile.locked{opacity:.35}
.tile.rev{border-style:dashed}
@keyframes pulse{50%{transform:scale(1.05)}}
.top{display:flex;align-items:center;gap:8px}
.top .t{flex:1;text-align:center;font-size:clamp(17px,3.8vmin,26px)}
.steps{display:flex;gap:6px;justify-content:center;margin:4px 0}
.steps i{width:clamp(18px,4vmin,28px);height:clamp(18px,4vmin,28px);border-radius:50%;border:3px solid var(--soft);font-style:normal;font-size:12px;display:flex;align-items:center;justify-content:center}
.steps i.done{background:var(--good);border-color:var(--good)}.steps i.cur{border-color:var(--accent);background:var(--hi)}
.hint{font-size:clamp(15px,3.2vmin,20px);color:var(--soft);text-align:center;min-height:1.4em}
.toast{position:fixed;left:50%;top:40%;transform:translate(-50%,-50%);background:#2a2a2a;border:3px solid var(--accent);color:#fff;padding:18px 28px;border-radius:18px;font-size:clamp(24px,6vmin,40px);display:none;z-index:9;text-align:center}
.stars{font-size:clamp(40px,12vmin,90px);color:#e0a000;text-align:center;letter-spacing:6px}
.parent{font-size:15px;color:var(--soft)}
.col{display:flex;flex-direction:column;align-items:center;gap:1.2vmin;height:100%;overflow-y:auto;justify-content:center}
.big-n{font-size:clamp(70px,22vmin,170px);line-height:1;font-weight:bold}
.word{font-size:clamp(22px,5vmin,36px);color:var(--soft)}
.fact{font-size:clamp(30px,8vmin,64px);text-align:center;line-height:1.15}
.fact .sm{display:block;font-size:clamp(16px,3.4vmin,22px);color:var(--soft)}
.dots{display:flex;flex-wrap:wrap;gap:1.4vmin;justify-content:center;align-items:center;max-width:94vw}
.grp{display:grid;grid-template-columns:repeat(5,auto);gap:.8vmin;padding:1vmin;border-radius:10px;border:2px dashed transparent}
.grp.box{border-color:var(--line)}
.dot{width:clamp(14px,3.6vmin,26px);height:clamp(14px,3.6vmin,26px);border-radius:50%;background:var(--a)}
.dot.b{background:var(--b)}.dot.x{background:#444;position:relative}.dot.x::after{content:'';position:absolute;left:-2px;right:-2px;top:45%;height:3px;background:var(--bad);transform:rotate(-35deg)}
.op{font-size:clamp(26px,6vmin,44px);color:var(--soft)}
.chips{display:flex;flex-wrap:wrap;gap:6px;justify-content:center;max-height:24vh;overflow-y:auto}
.chip{font-size:clamp(14px,3vmin,19px);min-height:38px;padding:4px 10px;border-radius:10px}
.chip.cur{background:var(--hi);border-color:var(--accent)}.chip.seen{border-color:var(--good)}
.balls{display:flex;flex-wrap:wrap;gap:2vmin;justify-content:center;max-width:90vw}
.ball{width:clamp(44px,11vmin,80px);height:clamp(44px,11vmin,80px);border-radius:50%;border:none;background:radial-gradient(circle at 35% 30%,#ffd9a8,var(--warm) 60%,#7a3412);padding:0;min-height:0;font-size:clamp(18px,4vmin,28px);color:#fff;font-weight:bold}
.ball.on{background:radial-gradient(circle at 35% 30%,#b8f0c8,var(--good) 60%,#1b5a35)}
.rods{display:flex;gap:1vmin;align-items:flex-end;justify-content:center;flex-wrap:wrap}
.rod{display:grid;grid-template-rows:repeat(10,1fr);gap:2px;height:clamp(110px,26vmin,190px);width:clamp(14px,3.4vmin,22px)}
.rod i{background:var(--b);border-radius:3px}
.choices{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:2vmin;width:min(90vw,520px)}
.choices button{font-size:clamp(40px,12vmin,86px);min-height:clamp(80px,18vmin,140px)}
.choices button.ok{background:var(--good);border-color:var(--good);color:#fff}.choices button.no{animation:shake .4s}
@keyframes shake{25%{transform:translateX(-8px)}75%{transform:translateX(8px)}}
.grid{display:flex;flex-wrap:wrap;gap:8px;justify-content:center;overflow-y:auto;max-height:100%;align-content:flex-start;padding:4px}
.grid button{min-width:clamp(56px,14vmin,90px);font-size:clamp(24px,6vmin,40px)}
.grid button.seen{border-color:var(--good)}
/* question + number pad */
.qa{display:flex;flex-direction:column;align-items:center;gap:1vmin;height:100%;min-height:0}
.q{font-size:clamp(20px,4.6vmin,34px);line-height:1.35;text-align:center;overflow-y:auto;max-height:42%;padding:4px 10px;border-radius:12px}
.q .pt{opacity:.55}.q .pt.cur{opacity:1;background:var(--hi);border-radius:8px;padding:0 4px}
.q .got{display:inline-block;background:var(--good);color:#fff;border-radius:8px;padding:0 8px;margin:0 4px;font-weight:bold}
.q .miss{background:var(--warm)}
.pic{font-size:clamp(30px,8vmin,56px);line-height:1}
.expr{font-size:clamp(46px,13vmin,100px);font-weight:bold;line-height:1.05;text-align:center}
.micb{min-width:min(86vw,360px);font-size:clamp(20px,4.6vmin,30px)}.micb.live{background:var(--bad);border-color:var(--bad);color:#fff;animation:pulse 1s infinite}
.expr.long{font-size:clamp(28px,7vmin,52px)}
.entry{min-width:3.4em;min-height:1.3em;font-size:clamp(36px,9vmin,64px);text-align:center;border-bottom:4px solid var(--accent);font-weight:bold;letter-spacing:.05em}
.entry.no{animation:shake .4s;border-color:var(--bad)}.entry.ok{color:var(--good)}
.pad{display:grid;grid-template-columns:repeat(3,1fr);gap:1.2vmin;width:min(86vw,360px)}
.pad button{font-size:clamp(24px,6vmin,38px);min-height:clamp(46px,9.5vmin,70px);padding:0}
.pad button.okb{background:var(--good);border-color:var(--good);color:#fff}
.seq{display:flex;flex-wrap:wrap;gap:6px;justify-content:center;max-height:22vh;overflow-y:auto}
.seq span{min-width:2.2em;text-align:center;border:2px solid var(--line);border-radius:8px;padding:2px 6px;font-size:clamp(18px,4vmin,28px)}
.seq span.got{border-color:var(--good);color:var(--good)}.seq span.cur{border-color:var(--accent);background:var(--hi)}
.sol{font-size:clamp(16px,3.4vmin,22px);color:#ffd166;text-align:center;max-width:90vw}
.tray{position:fixed;left:50%;bottom:2vh;transform:translateX(-50%);background:#1a1a1a;border:3px solid var(--accent);border-radius:16px;padding:10px;z-index:20;display:none;flex-direction:column;align-items:center;gap:8px}
.tray.on{display:flex}
.frame{display:grid;grid-template-columns:repeat(10,1fr);gap:5px}
.frame button{width:clamp(26px,6.5vmin,44px);height:clamp(26px,6.5vmin,44px);min-height:0;border-radius:50%;padding:0;border-width:2px}
.frame button.on{background:var(--a);border-color:var(--a)}
.timer{font-size:clamp(16px,3.4vmin,22px);color:var(--soft)}
.stk{font-size:clamp(40px,10vmin,72px)}
.stickers{display:flex;flex-wrap:wrap;gap:10px;justify-content:center;overflow-y:auto;font-size:clamp(34px,9vmin,60px)}
.stickers span{width:1.5em;height:1.5em;display:flex;align-items:center;justify-content:center;border:2px dashed var(--line);border-radius:12px}
.stickers span.got{border:2px solid #e0a000;background:#2a2410}
canvas{position:absolute;inset:0;width:100%;height:100%;touch-action:none;border-radius:14px}
.paper{background:#1a1a1a;border:3px solid var(--line);border-radius:14px}
.note{font-size:clamp(13px,2.8vmin,17px);color:var(--soft);text-align:center;max-width:90vw}
</style>
</head><body>
<section id="home" class="screen on">
<h1>Ray's Arithmetic</h1>
<h2>Who is doing math today?</h2>
<div class="row kids" id="kids"></div>
<div class="shelf" id="shelf"></div>
<div class="row" style="margin-top:auto"><button class="ghost parent" id="speedBtn">Voice: normal</button><button class="ghost parent" id="toStickers">Sticker book</button></div>
</section>
<section id="map" class="screen">
<div class="top"><button onclick="Rays.go('home')">◀</button><div class="t" id="mapTitle"></div></div>
<div class="map grow" id="tiles"></div>
</section>
<section id="lesson" class="screen">
<div class="top"><button onclick="Rays.leave()">◀</button><div class="t" id="lesTitle"></div><button id="sayBtn" title="Say it again">🔊</button></div>
<div class="steps" id="steps"></div>
<h2 id="stepTitle"></h2>
<div class="grow" id="stage"></div>
<div class="hint" id="hint"></div>
<div class="row" id="controls"></div>
</section>
<section id="stickers" class="screen">
<div class="top"><button onclick="Rays.go('home')">◀</button><div class="t" id="stkTitle">Sticker book</div></div>
<div class="stickers grow" id="stkList"></div>
</section>
<div class="tray" id="tray"><div class="frame" id="frame"></div><div class="row"><b id="trayN">0</b><button id="trayClear">Clear</button><button id="trayClose">Close</button></div></div>
<div class="toast" id="toast"></div>
<script src="hershey.js"></script>
<script src="trace.js"></script>
<script src="app.js"></script>
<script>if ('serviceWorker' in navigator) navigator.serviceWorker.register('sw.js').catch(() => {});</script>
</body></html>
EOF
app.js: the game.
cat > /root/rays-rebuild/app.js <<'EOF'
// Ray's Arithmetic: gamified Ray's New Primary Arithmetic (1877). Progress is saved per kid in localStorage ('rays').
// Same look and voice as ABC Books; the kid picked in ABC Books (same site) is picked here too.
const Rays = (() => {
const $ = id => document.getElementById(id);
const KIDS = ['Layla', 'Lavon', 'Lilly'];
// Ray's series in order (raysarithmetic.wordpress.com/rays-free-arithmetic). A book is playable once books/<id>.json exists.
const SHELF = [
{ id: 'primary1877', title: "Ray's New Primary Arithmetic", sub: 'Book 1 · Grades 1-2', color: '#2f6f8f' },
{ id: 'intellectual', title: "Ray's New Intellectual Arithmetic", sub: 'Book 2 · Grades 3-4', color: '#6a4c93' },
{ id: 'practical', title: "Ray's New Practical Arithmetic", sub: 'Book 3 · Grades 5-6', color: '#2e7d4f', soon: true },
{ id: 'higher', title: "Ray's New Higher Arithmetic", sub: 'Book 4 · Grades 7-8', color: '#a0522d', soon: true },
{ id: 'geometry', title: "Ray's Geometry and Trigonometry", sub: 'Book 5 · Grades 9-10', color: '#7a6a1f', soon: true },
{ id: 'algebra1', title: "Ray's New Elementary Algebra", sub: 'Book 6 · Grades 9-10', color: '#8f2f4f', soon: true },
{ id: 'algebra2', title: "Ray's New Higher Algebra", sub: 'Book 7 · Grade 11', color: '#3b4a8f', soon: true }
];
const BOOKS = {};
let P = {};
const STICKERS = ['🦊', '🐢', '🦉', '🐝', '🦋', '🐞', '🐸', '🐧', '🦄', '🐬', '🦁', '🐼', '🐨', '🐙', '🦀', '🐳', '🦒', '🦓', '🐘', '🦔', '🐿️', '🦜', '🐠', '🐤', '🐰', '🐻', '🐯', '🐮', '🐷', '🐵', '🌻', '🌈', '⭐', '🚂', '🚀', '⛵', '🎈', '🍎', '🍓', '🍩'];
const STEPS = {
count: { name: 'Count', icon: '●' }, numbers: { name: 'Numbers', icon: '#' }, find: { name: 'Find the number', icon: '👂' },
say: { name: 'Read the numbers', icon: '💬' }, write: { name: 'Write the numbers', icon: '✎' }, trace: { name: 'Trace the numbers', icon: '1' },
learn: { name: 'Learn the table', icon: '📖' }, rules: { name: 'Learn the table', icon: '📖' }, drill: { name: 'Practice', icon: '⚡' },
problems: { name: 'Story problems', icon: '?' }
};
// ---------- storage ----------
let S = load();
function load() {
let s = null; try { s = JSON.parse(localStorage.getItem('rays')); } catch (e) {}
s = s || {};
s.progress = s.progress || {}; s.pos = s.pos || {}; s.best = s.best || {}; s.stickers = s.stickers || {};
if (!s.speed) s.speed = 60;
if (!s.kid) { // the kid picked in ABC Books or BlockCraft on this site
for (const k of ['abcbooks', 'blockcraft_cfg']) { try { const c = JSON.parse(localStorage.getItem(k)); const n = c && (c.kid || c.name); if (KIDS.includes(n)) { s.kid = n; break; } } catch (e) {} }
}
return s;
}
function save() { try { localStorage.setItem('rays', JSON.stringify(S)); } catch (e) {} }
const kidMap = obj => (obj[S.kid] = obj[S.kid] || {});
const prog = b => (kidMap(S.progress)[b] = kidMap(S.progress)[b] || {});
// ---------- sound + voice (recorded Eloquence clips, else the device voice) ----------
let AC;
function beep(good) {
try {
AC = AC || new (window.AudioContext || window.webkitAudioContext)();
const o = AC.createOscillator(), g = AC.createGain(), t = AC.currentTime;
o.type = 'triangle'; o.connect(g); g.connect(AC.destination);
const notes = good ? [523, 659, 784] : [220, 180];
notes.forEach((f, i) => o.frequency.setValueAtTime(f, t + i * 0.1));
g.gain.setValueAtTime(0.25, t); g.gain.exponentialRampToValueAtTime(0.001, t + notes.length * 0.1 + 0.2);
o.start(t); o.stop(t + notes.length * 0.1 + 0.25);
} catch (e) {}
}
const hasTTS = 'speechSynthesis' in window;
let voice = null;
function pickVoice() { if (!hasTTS) return; const v = speechSynthesis.getVoices().filter(v => /^en/i.test(v.lang)); voice = v.find(v => /female|samantha|zira|aria|jenny|google us/i.test(v.name)) || v[0] || null; }
if (hasTTS) { pickVoice(); speechSynthesis.onvoiceschanged = pickVoice; }
let AUDIO = null; // audio/primary1877.json: spoken text -> clip id (tools/make-audio.ps1)
const clip = new Audio(); clip.preload = 'auto';
let clipDone = null, tok = 0, unlocked = false;
const blobs = new Map();
function clipURL(url) { // fetched whole and played from memory: works offline and on Safari
if (!blobs.has(url)) blobs.set(url, fetch(url).then(r => { if (!r.ok) throw new Error(r.status); return r.blob(); }).then(b => URL.createObjectURL(b)));
const p = blobs.get(url); p.catch(() => blobs.delete(url)); return p;
}
const speedOf = () => AUDIO.speeds.includes(S.speed) ? S.speed : AUDIO.speeds[0];
const urlFor = key => { const c = AUDIO && AUDIO.items[key]; return c ? `audio/s${speedOf()}/${c}.mp3` : null; };
function stopAll() { tok++; clip.pause(); if (clipDone) { const d = clipDone; clipDone = null; d(); } if (hasTTS) speechSynthesis.cancel(); }
function playURL(u, my) {
return new Promise(res => {
clipURL(u).then(b => {
if (my !== tok) return res();
clipDone = res; clip.muted = false; clip.src = b;
clip.onended = clip.onerror = () => { if (clipDone) { const d = clipDone; clipDone = null; d(); } };
clip.play().catch(() => clip.onended());
}).catch(() => res());
});
}
// speak(...keys): one clip per key, played one after the other; a key with no clip uses the device voice
async function speak(...keys) {
stopAll(); const my = tok;
for (const k of keys) {
if (my !== tok || k == null || k === '') continue;
const u = urlFor(String(k));
if (u) await playURL(u, my); else await tts(String(k).replace(/^(n|find|ans|lesson):/, ''));
}
}
function tts(text) {
return new Promise(res => {
if (!hasTTS) return setTimeout(res, 300);
const u = new SpeechSynthesisUtterance(text); if (voice) u.voice = voice; u.rate = 0.85;
u.onend = u.onerror = res; speechSynthesis.speak(u);
});
}
const sayNum = n => speak('n:' + n);
window.addEventListener('pointerdown', () => { // iOS: unlock audio inside the first tap
if (AC && AC.state === 'suspended') AC.resume();
const u = urlFor(P.yes); if (unlocked || !u) return; unlocked = true;
clipURL(u).then(b => { if (!clip.src) { clip.muted = true; clip.src = b; clip.play().then(() => { clip.pause(); clip.muted = false; }).catch(() => { clip.muted = false; }); } }).catch(() => {});
}, { passive: true });
// ---------- helpers ----------
function toast(t, ms) { const e = $('toast'); e.textContent = t; e.style.display = 'block'; clearTimeout(toast.t); toast.t = setTimeout(() => e.style.display = 'none', ms || 1100); }
function el(tag, cls, html) { const e = document.createElement(tag); if (cls) e.className = cls; if (html != null) e.innerHTML = html; return e; }
function btn(label, fn, cls) { const b = el('button', cls, label); b.onclick = fn; return b; }
const shuffle = a => { a = a.slice(); 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; };
const range = (a, b) => Array.from({ length: b - a + 1 }, (_, i) => a + i);
const starStr = n => '★'.repeat(n) + '☆'.repeat(3 - n);
const starsFor = (miss, n) => miss === 0 ? 3 : miss <= Math.max(2, Math.round(n * 0.15)) ? 2 : 1;
const wait = ms => new Promise(r => setTimeout(r, ms));
function go(id) { document.querySelectorAll('.screen').forEach(s => s.classList.toggle('on', s.id === id)); if (id === 'home') home(); if (id === 'stickers') stickers(); }
const ONES = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen'];
const TENS = ['', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety'];
const words = n => n < 20 ? ONES[n] : n === 100 ? 'one hundred' : TENS[Math.floor(n / 10)] + (n % 10 ? '-' + ONES[n % 10] : '');
// pictures: dots for a fact (a and b in two colors, crossed out when taken away, rows for times, groups for division)
function dotsFor(op, a, b) {
const box = el('div', 'dots');
const grp = (n, cls, boxed) => { const g = el('div', 'grp' + (boxed ? ' box' : '')); for (let i = 0; i < n; i++) g.appendChild(el('i', 'dot ' + (cls || ''))); return g; };
if (op === 'add') { box.append(grp(a), el('span', 'op', '+'), grp(b, 'b')); }
else if (op === 'sub') { const g = el('div', 'grp'); for (let i = 0; i < b; i++) g.appendChild(el('i', 'dot ' + (i >= b - a ? 'x' : ''))); box.appendChild(g); }
else if (op === 'mul') { if (a * b > 100) return box; for (let i = 0; i < a; i++) box.appendChild(grp(b, i % 2 ? 'b' : '', true)); }
else if (op === 'div') { if (b > 100) return box; for (let i = 0; i < b / a; i++) box.appendChild(grp(a, i % 2 ? 'b' : '', true)); }
return box;
}
const SIGN = { add: '+', sub: '−', mul: '×', div: '÷' };
const expr = (op, f) => op === 'sub' ? `${f.b} − ${f.a}` : op === 'div' ? `${f.b} ÷ ${f.a}` : `${f.a} ${SIGN[op]} ${f.b}`;
// a picture for a story problem, from the first thing it counts
const PICS = [['cent|dime|money', '💰'], ['dollar', '💵'], ['apple', '🍎'], ['pear', '🍐'], ['peach', '🍑'], ['orange', '🍊'], ['lemon', '🍋'], ['cherr', '🍒'], ['plum|raisin|fig', '🍇'], ['melon', '🍈'], ['pine-apple', '🍍'], ['strawberr|berr', '🍓'], ['nut|chestnut|pecan|walnut', '🌰'],
['marble', '🔵'], ['top', '🎯'], ['kite', '🎏'], ['ball', '⚽'], ['doll', '🧸'], ['bird|swallow|crow|pigeon', '🐦'], ['owl', '🦉'], ['bat', '🦇'], ['duck', '🦆'], ['geese|goose', '🦢'], ['chicken|hen', '🐔'], ['fish', '🐟'], ['horse', '🐴'], ['cow|calf|calves', '🐄'], ['sheep', '🐑'], ['pig|hog', '🐖'], ['rabbit', '🐇'], ['cat|kitten', '🐈'], ['mice|mouse', '🐭'],
['egg', '🥚'], ['cake|candy', '🍬'], ['milk', '🥛'], ['book|primer|reader|geograph', '📚'], ['pen|pencil|slate', '✏️'], ['ribbon|tape|cloth|calico|muslin|silk', '🎀'], ['tree|orchard', '🌳'], ['flower|rose', '🌹'], ['leaves|leaf', '🍃'], ['ship|boat', '⛵'], ['car', '🚃'], ['mile|travel', '🛤️'], ['hour|minute', '⏰'], ['day|week|month|year', '📅'], ['wood|cord', '🌲'], ['flour|wheat|corn|oats|seed', '🌾'],
['boy|girl|pupil|scholar|men|man|persons', '🧒'], ['window|pane', '🏠'], ['chair|bench', '💺'], ['foot|feet|inch|yard|rod', '📏'], ['pound|ounce|ton', '⚖️'], ['quart|pint|gallon|gill|peck|bushel', '🍶']];
function picFor(text) { const t = text.toLowerCase(); let best = null, at = 1e9; for (const [rx, e] of PICS) { const m = t.match(new RegExp('\\b(' + rx + ')')); if (m && m.index < at) { at = m.index; best = e; } } return best || '🧮'; }
// ---------- spoken answers ----------
// The kid can say the answer. First choice: our own Vosk server (CT 307, /api/recognize, shared with ABC Books),
// told to listen only for number words. Fallback: the browser's own recognizer. No mic at all: the button hides.
const NUM_WORDS = 'zero oh one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty thirty forty fifty sixty seventy eighty ninety hundred thousand and a';
const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
const GUM = !!(window.isSecureContext && navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
let micOK = GUM || !!SR, useServer = GUM;
const W1 = { zero: 0, oh: 0, one: 1, two: 2, three: 3, four: 4, for: 4, five: 5, six: 6, seven: 7, eight: 8, ate: 8, nine: 9, ten: 10, eleven: 11, twelve: 12, thirteen: 13, fourteen: 14, fifteen: 15, sixteen: 16, seventeen: 17, eighteen: 18, nineteen: 19 };
const W10 = { twenty: 20, thirty: 30, forty: 40, fifty: 50, sixty: 60, seventy: 70, eighty: 80, ninety: 90 };
// "nine thousand six hundred" -> 9600; "four two" (digits one at a time) -> 42; "7" -> 7
function wordsToNumber(words) {
// "a hundred" = one hundred; a stray "a" (Vosk often hears it for "one"/"and" in fast speech) alone is one
const w = words.join(' ').toLowerCase().replace(/-/g, ' ').split(/\s+/).filter(x => x && x !== 'and').map((x, i, all) => x === 'a' ? (all.length === 1 || /^(hundred|thousand)$/.test(all[i + 1] || '') ? 'one' : '') : x).filter(Boolean);
if (!w.length) return null;
if (w.every(x => /^\d+$/.test(x))) return parseInt(w.join(''), 10);
if (w.length > 1 && w.every(x => x in W1 && W1[x] < 10)) return parseInt(w.map(x => W1[x]).join(''), 10);
let total = 0, cur = 0, any = false;
for (const x of w) {
if (/^\d+$/.test(x)) { cur += parseInt(x, 10); any = true; }
else if (x in W1 || x in W10) { // "nineteen twenty" (said like a year) -> 1920; "twenty four" -> 24
const v = x in W1 ? W1[x] : W10[x];
cur = v >= 10 && cur % 100 ? cur * 100 + v : cur + v; any = true;
}
else if (x === 'hundred') { cur = (cur || 1) * 100; any = true; }
else if (x === 'thousand') { total += (cur || 1) * 1000; cur = 0; any = true; }
else return null; // a word that isn't a number: didn't understand
}
return any ? total + cur : null;
}
let stopListening = () => {};
async function hearNumber() {
if (useServer) {
try { const w = await listenServer(7000); return w ? wordsToNumber(w) : null; }
catch (e) { useServer = false; if (!SR) { micOK = false; return null; } }
}
const alts = await listenBrowser(7000);
for (const a of alts) { const n = wordsToNumber(a); if (n != null) return n; }
return null;
}
// records until the kid stops talking (1.2 s quiet) or the time runs out (same as ABC Books)
async function listenServer(timeoutMs) {
let stream;
try { stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true } }); }
catch (e) { micOK = false; return null; }
const ctx = new (window.AudioContext || window.webkitAudioContext)();
try { await ctx.resume(); } catch (e) {}
const src = ctx.createMediaStreamSource(stream), proc = ctx.createScriptProcessor(4096, 1, 1);
const chunks = []; let spoke = false, quiet = 0; const t0 = performance.now();
await new Promise(res => {
let done = false; const fin = () => { if (!done) { done = true; res(); } };
stopListening = fin;
proc.onaudioprocess = e => {
const d = e.inputBuffer.getChannelData(0); chunks.push(new Float32Array(d));
let s = 0; for (let i = 0; i < d.length; i++) s += d[i] * d[i];
if (Math.sqrt(s / d.length) > 0.015) { spoke = true; quiet = 0; } else if (spoke) quiet += d.length / ctx.sampleRate * 1000;
if ((spoke && quiet > 1200) || performance.now() - t0 > timeoutMs) fin();
};
src.connect(proc); proc.connect(ctx.destination);
});
stopListening = () => {};
proc.disconnect(); src.disconnect(); stream.getTracks().forEach(t => t.stop());
const rate = ctx.sampleRate; ctx.close();
if (!spoke) return null;
const r = await fetch('/api/recognize?near=0&words=' + encodeURIComponent(NUM_WORDS), { method: 'POST', headers: { 'Content-Type': 'application/octet-stream' }, body: to16k(chunks, rate) });
if (!r.ok) throw new Error('speech ' + r.status);
const j = await r.json();
return j.words || [];
}
function to16k(chunks, rate) { // Float32 at the device rate -> 16 kHz 16-bit PCM (what Vosk wants)
const n = chunks.reduce((a, c) => a + c.length, 0), all = new Float32Array(n); let o = 0;
for (const c of chunks) { all.set(c, o); o += c.length; }
const ratio = rate / 16000, m = Math.floor(n / ratio), out = new Int16Array(m);
for (let i = 0; i < m; i++) {
const a = Math.floor(i * ratio), b = Math.min(n, Math.max(a + 1, Math.floor((i + 1) * ratio))); let s = 0;
for (let k = a; k < b; k++) s += all[k];
out[i] = Math.max(-1, Math.min(1, s / (b - a))) * 32767;
}
return out.buffer;
}
function listenBrowser(timeoutMs) {
return new Promise(res => {
if (!SR) return res([]);
const r = new SR(); r.lang = 'en-US'; r.interimResults = false; r.maxAlternatives = 5;
let best = [], done = false;
const fin = () => { if (done) return; done = true; try { r.stop(); } catch (e) {} res(best); };
stopListening = fin;
r.onresult = e => { best = [...e.results[0]].map(a => a.transcript.split(/\s+/)); fin(); };
r.onerror = e => { if (/not-allowed|service-not-allowed/.test(e.error || '')) micOK = false; fin(); };
r.onend = fin;
try { r.start(); } catch (e) { micOK = false; return fin(); }
setTimeout(fin, timeoutMs);
});
}
// number pad (big buttons; a computer keyboard works too; 🎤 to say the answer)
function numPad(onOK) {
const wrap = el('div', 'col'); wrap.style.height = 'auto'; wrap.style.gap = '1vmin';
const entry = el('div', 'entry', ' '); let val = '';
const show = () => { entry.innerHTML = val || ' '; entry.classList.remove('no', 'ok'); };
const pad = el('div', 'pad');
let fracMode = false; // answers like 3/4 or 2 1/2: extra keys, and OK hands back the text
const press = k => {
if (k === '⌫') val = val.slice(0, -1);
else if (k === 'OK') { if (val.trim()) onOK(fracMode ? val.trim() : parseInt(val, 10)); return; }
else if (k === 'and') { if (fracMode && val && !/[ /.]/.test(val)) val += ' '; }
else if (k === '/') { if (fracMode && val && !val.endsWith(' ') && !/[/.]/.test(val)) val += '/'; }
else if (k === '.') { if (fracMode && !/[ /.]/.test(val)) val += '.'; }
else if (val.length < 9) val += k;
show();
};
['1', '2', '3', '4', '5', '6', '7', '8', '9', '⌫', '0', 'OK'].forEach(k => pad.appendChild(btn(k, () => press(k), k === 'OK' ? 'okb' : '')));
const fracRow = el('div', 'pad'); fracRow.style.display = 'none';
fracRow.append(btn('/', () => press('/')), btn('and', () => press('and')), btn('.', () => press('.')));
const onKey = e => { if (/^[0-9]$/.test(e.key)) press(e.key); else if (e.key === '/' || e.key === '.') press(e.key); else if (e.key === ' ' && fracMode) { e.preventDefault(); press('and'); } else if (e.key === 'Backspace') press('⌫'); else if (e.key === 'Enter') press('OK'); };
window.addEventListener('keydown', onKey);
const mic = btn('🎤 Say it', async () => {
if (mic.classList.contains('live')) return stopListening();
stopAll(); mic.classList.add('live'); mic.textContent = '🎤 Listening…';
const n = await hearNumber();
mic.classList.remove('live'); mic.textContent = '🎤 Say it';
if (!micOK) { mic.style.display = 'none'; return toast('This device cannot listen. Type the answer.', 2000); }
if (n == null) return toast('I did not catch that. Say it again!', 1500);
val = String(n); show(); onOK(n);
}, 'micb');
if (!micOK) mic.style.display = 'none';
wrap.append(entry, pad, fracRow, mic);
return { el: wrap, entry, frac(on) { fracMode = !!on; fracRow.style.display = on ? '' : 'none'; mic.style.display = on || !micOK ? 'none' : ''; }, show(on) { wrap.style.display = on ? '' : 'none'; }, clear() { val = ''; show(); }, set(v, cls) { val = String(v); show(); if (cls) entry.classList.add(cls); }, bad() { entry.classList.remove('no'); void entry.offsetWidth; entry.classList.add('no'); }, stop() { stopListening(); window.removeEventListener('keydown', onKey); } };
}
// fractions compare by value: 5/2 = 2 1/2 = 2.5
function fracVal(s) {
s = String(s).trim(); let m;
if ((m = s.match(/^(\d+) (\d+)\/(\d+)$/))) return +m[1] + m[2] / m[3];
if ((m = s.match(/^(\d+)\/(\d+)$/))) return m[1] / m[2];
return /^\d*\.?\d+$/.test(s) ? parseFloat(s) : NaN;
}
const sameVal = (a, b) => Math.abs(fracVal(a) - fracVal(b)) < 1e-9;
// counters tray: tap beads to count with
function initTray() {
const f = $('frame'); f.innerHTML = '';
for (let i = 0; i < 30; i++) f.appendChild(btn('', e => { e.currentTarget.classList.toggle('on'); count(); }));
const count = () => { $('trayN').textContent = f.querySelectorAll('.on').length; };
$('trayClear').onclick = () => { f.querySelectorAll('.on').forEach(b => b.classList.remove('on')); count(); };
$('trayClose').onclick = () => $('tray').classList.remove('on');
}
// ---------- home, map, stickers ----------
function home() {
const k = $('kids'); k.innerHTML = '';
KIDS.forEach(n => k.appendChild(btn(n, () => { S.kid = n; save(); home(); speak(P.greet.replace('{kid}', n)); }, S.kid === n ? 'sel' : '')));
const sb = $('speedBtn'); sb.textContent = 'Voice: ' + (S.speed === 75 ? 'faster' : 'normal');
sb.onclick = () => { S.speed = S.speed === 75 ? 60 : 75; save(); home(); speak(P.yes); };
const sh = $('shelf'); sh.innerHTML = '';
// the kid's current book glows: the first open book they haven't finished
const doneIn = id => S.kid && BOOKS[id] ? Object.values(prog(id)).filter(x => x && x.stars).length : 0;
const cur = S.kid && SHELF.find(b => BOOKS[b.id] && doneIn(b.id) < BOOKS[b.id].lessons.length);
SHELF.forEach(b => {
const B = BOOKS[b.id], n = B ? B.lessons.length : 0, d = doneIn(b.id);
const pr = !B ? 'Coming soon' : S.kid ? (d >= n ? '★ Finished!' : d ? `Lesson ${d + 1} of ${n}` : `Lessons: ${n}`) : '';
const e = btn(`<b>${b.title}</b><span class="bp">${pr}</span><small>${b.sub}</small>`, () => {
if (!B) return toast('Coming soon!');
if (!S.kid) return toast('Pick your name first!');
openBook(b.id);
}, 'book' + (!B ? ' soon' : '') + (cur && cur.id === b.id ? ' cur' : ''));
e.style.background = b.color; sh.appendChild(e);
});
}
let book, bookId;
function openBook(id) {
bookId = id; book = BOOKS[id]; $('mapTitle').textContent = `${S.kid}: ${book.title}`;
const t = $('tiles'); t.innerHTML = ''; const p = prog(id); let nextSet = false;
book.lessons.forEach((L, i) => {
const st = (p[L.n] || {}).stars || 0, prevDone = i === 0 || (p[book.lessons[i - 1].n] || {}).stars;
const isNext = !st && prevDone && !nextSet; if (isNext) nextSet = true;
const tile = btn(`${L.n}<small>${L.part}</small><span class="st">${st ? starStr(st) : ''}</span>`, () => {
if (!prevDone && !st) return toast('Finish lesson ' + book.lessons[i - 1].n + ' first');
startLesson(L);
}, 'tile' + (isNext ? ' next' : '') + (!prevDone && !st ? ' locked' : '') + (L.review ? ' rev' : ''));
tile.title = L.title; t.appendChild(tile);
});
go('map'); speak(P.map);
const nx = t.querySelector('.next'); if (nx) nx.scrollIntoView({ block: 'center' });
}
const stkKey = (b, n) => b === 'primary1877' ? n : `${b}:${n}`; // Book One keeps its first keys
function stickers() { // the sticker book for the book the kid is in (Book One when none is open yet)
const g = $('stkList'); g.innerHTML = ''; const got = kidMap(S.stickers), id = bookId || 'primary1877', B = BOOKS[id];
const n = B ? B.lessons.filter(L => got[stkKey(id, L.n)]).length : 0;
$('stkTitle').textContent = `${S.kid || ''} sticker book: ${B ? B.title : ''}, ${n} of ${B ? B.lessons.length : 0}`;
(B ? B.lessons : []).forEach(L => { const v = got[stkKey(id, L.n)]; const s = el('span', v ? 'got' : '', v || ''); s.title = 'Lesson ' + L.n; g.appendChild(s); });
}
$('toStickers').onclick = () => { if (!S.kid) return toast('Pick your name first!'); go('stickers'); };
// ---------- lesson flow ----------
let L, steps, stepIdx, scores, cleanup = null, sayAgain = null, stepTok = 0;
function stepsFor(L) {
switch (L.kind) {
case 'count': return ['count', 'find', 'trace'];
case 'numbers': return ['numbers', 'find', 'trace'];
case 'read': return ['say', 'find'];
case 'write': return ['write', 'trace'];
}
const s = [];
if (L.table) s.push('learn', 'drill');
if (L.rules) s.push('rules'); if (L.rules && L.rules.some(r => r.quiz !== false) && !L.table) s.push('drill');
if (L.qs) s.push('problems');
return s;
}
function startLesson(lesson) {
L = lesson; steps = stepsFor(L); scores = {}; stepIdx = 0;
$('lesTitle').textContent = `Lesson ${L.n}: ${L.title}`;
go('lesson'); runStep(true);
}
function leave() { stopStep(); openBook(bookId); }
function stopStep() { stopAll(); $('tray').classList.remove('on'); if (cleanup) { cleanup(); cleanup = null; } }
function runStep(first) {
stopStep();
const id = steps[stepIdx];
const dots = $('steps'); dots.innerHTML = '';
steps.forEach((x, i) => dots.appendChild(el('i', i < stepIdx ? 'done' : i === stepIdx ? 'cur' : '', STEPS[x].icon)));
if (!id) return finish();
$('stepTitle').textContent = STEPS[id].name; $('stage').innerHTML = ''; $('controls').innerHTML = ''; $('hint').textContent = P[id] || '';
sayAgain = null; const my = ++stepTok;
const intro = (first ? speak('lesson:' + L.n, P[id]) : speak(P[id]));
$('sayBtn').onclick = () => sayAgain ? sayAgain() : speak(P[id]);
({ count: stepCount, numbers: stepNumbers, find: stepFind, say: stepSay, write: stepWrite, trace: stepTrace, learn: stepLearn, rules: stepRules, drill: stepDrill, problems: stepProblems })[id](intro.then(() => my === stepTok));
}
function next(stars) { scores[steps[stepIdx]] = stars; stepIdx++; runStep(); }
const afterIntro = (intro, fn) => intro.then(ok => { if (ok) fn(); });
// Lesson 1: count the balls, one tap per ball
function stepCount(intro) {
let n = L.from;
const st = $('stage'); const col = el('div', 'col'); st.appendChild(col);
function show() {
col.innerHTML = ''; let c = 0;
const balls = el('div', 'balls'); const big = el('div', 'big-n', ' '); const w = el('div', 'word', ' ');
for (let i = 0; i < n; i++) balls.appendChild(btn('', e => {
const b = e.currentTarget; if (b.classList.contains('on')) return;
b.classList.add('on'); b.textContent = ++c; sayNum(c);
if (c === n) { big.textContent = n; w.textContent = words(n); setTimeout(() => { beep(true); speak('n:' + n).then(() => wait(700)).then(() => { if (++n > L.to) next(3); else show(); }); }, 650); }
}, 'ball'));
col.append(balls, big, w);
$('hint').textContent = `Tap each ball and count. (${n - L.from + 1} of ${L.to - L.from + 1})`;
}
sayAgain = () => speak(P.count);
show();
}
// Lessons 2-4: each number with its tens and ones
function stepNumbers(intro) {
let n = L.from; const st = $('stage'); const col = el('div', 'col'); st.appendChild(col);
let auto = false;
function show() {
col.innerHTML = '';
col.append(el('div', 'big-n', n), el('div', 'word', words(n)));
const rods = el('div', 'rods');
for (let t = 0; t < Math.floor(n / 10); t++) { const r = el('div', 'rod'); for (let i = 0; i < 10; i++) r.appendChild(el('i')); rods.appendChild(r); }
const ones = el('div', 'grp'); for (let i = 0; i < n % 10; i++) ones.appendChild(el('i', 'dot')); rods.appendChild(ones);
col.appendChild(rods);
col.appendChild(el('div', 'note', `${Math.floor(n / 10)} tens and ${n % 10} ones`));
$('hint').textContent = `${n - L.from + 1} of ${L.to - L.from + 1}`;
return sayNum(n);
}
async function playAll() { auto = true; playB.disabled = true; while (auto && n <= L.to) { await show(); await wait(700); if (!auto) break; if (n < L.to) n++; else break; } auto = false; playB.disabled = false; nextB.disabled = false; }
const prevB = btn('◀', () => { auto = false; if (n > L.from) { n--; show(); } });
const fwdB = btn('▶', () => { auto = false; if (n < L.to) { n++; show(); } else nextB.disabled = false; });
const playB = btn('▶ Play all', playAll, 'big'), nextB = btn('Next step ▶', () => next(3));
nextB.disabled = true; $('controls').append(prevB, playB, fwdB, nextB);
sayAgain = show; cleanup = () => { auto = false; };
col.append(el('div', 'big-n', n), el('div', 'word', words(n)));
afterIntro(intro, playAll);
}
// hear a number, tap it
function stepFind(intro) {
const pool = L.list || range(L.from, L.to);
const rounds = shuffle(pool).slice(0, 8); let r = 0, miss = 0, busy = false, target;
const st = $('stage'); const col = el('div', 'col'); st.appendChild(col);
const grid = el('div', 'choices'); col.appendChild(grid);
function round() {
if (r >= rounds.length) { toast('Great job! ⭐'); return setTimeout(() => next(starsFor(miss, rounds.length)), 900); }
target = rounds[r++]; grid.innerHTML = '';
const opts = shuffle([target, ...shuffle(pool.filter(x => x !== target)).slice(0, 3)]);
opts.forEach(v => grid.appendChild(btn(v, e => {
if (busy) return; const b = e.currentTarget;
if (v === target) { busy = true; beep(true); b.classList.add('ok'); setTimeout(() => { busy = false; round(); }, 800); }
else { miss++; beep(false); b.classList.remove('no'); void b.offsetWidth; b.classList.add('no'); }
})));
$('hint').textContent = `Find the number you hear (${r} of ${rounds.length})`;
sayAgain = () => speak('find:' + target); sayAgain();
}
afterIntro(intro, round);
}
// Lesson 5: numbers to be read, out loud to a grown-up
function stepSay(intro) {
const g = el('div', 'grid'); $('stage').appendChild(g);
L.list.forEach(v => g.appendChild(btn(v, e => { e.currentTarget.classList.add('seen'); sayNum(v); })));
$('controls').appendChild(btn('Done ✓', () => next(3), 'big'));
}
// Lesson 6: numbers to be written: hear it, type it
function stepWrite(intro) {
const items = shuffle(L.list).slice(0, 12); let i = 0, miss = 0, tries = 0;
const col = el('div', 'col'); $('stage').appendChild(col);
const pad = numPad(v => {
if (v === items[i]) { beep(true); pad.set(v, 'ok'); tries = 0; setTimeout(() => { i++; ask(); }, 700); }
else if (++tries < 2) { miss++; beep(false); pad.bad(); speak(P.tryAgain, 'n:' + items[i]); }
else { miss++; pad.set(items[i], 'ok'); speak('ans:' + items[i]); tries = 0; setTimeout(() => { i++; ask(); }, 1800); }
});
col.appendChild(pad.el);
function ask() {
if (i >= items.length) return next(starsFor(miss, items.length));
pad.clear(); $('hint').textContent = `Write the number you hear (${i + 1} of ${items.length})`;
sayAgain = () => sayNum(items[i]); sayAgain();
}
cleanup = () => pad.stop();
afterIntro(intro, ask);
}
// trace the figures (same engine as ABC Books)
function stepTrace(intro) {
const nums = L.kind === 'count' ? ['1 2 3', '4 5 6', '7 8 9', '10'] : L.kind === 'write' ? ['0 1 2 3 4', '5 6 7 8 9', '10 20 30'] : (() => { const r = range(L.from, L.to), out = []; for (let i = 0; i < r.length && out.length < 4; i += 4) out.push(r.slice(i, i + 4).join(' ')); return out; })();
let i = 0, misses = 0, tracer;
const box = el('div', 'grow paper'); box.style.height = '100%'; $('stage').appendChild(box);
const cv = el('canvas'); box.appendChild(cv);
ABCTrace.Tracer.onMiss = why => { misses++; beep(false); $('hint').textContent = why === 'start' ? 'Start at the green dot!' : 'Follow the arrow all the way.'; };
function page() {
if (tracer) tracer.stop();
$('hint').textContent = `Page ${i + 1} of ${nums.length}: start at the green dot and follow the arrow.`;
tracer = ABCTrace.Tracer(cv, nums[i], 'print', () => { beep(true); toast('⭐', 700); setTimeout(() => { if (++i < nums.length) page(); else next(misses < 3 ? 3 : misses < 8 ? 2 : 1); }, 800); });
}
$('controls').appendChild(btn('Skip page', () => { if (++i < nums.length) page(); else next(1); }, 'ghost parent'));
setTimeout(page, 50);
const onRes = () => page(); window.addEventListener('resize', onRes);
cleanup = () => { tracer && tracer.stop(); window.removeEventListener('resize', onRes); };
}
// the book's table, one line at a time with its picture
function stepLearn(intro) { learnList(L.table.map(f => ({ t: f.t, pic: () => dotsFor(L.op, f.a, f.b), ex: `${expr(L.op, f)} = ${f.r}` })), intro); }
function stepRules(intro) { learnList(L.rules.map(r => ({ t: r.t, pic: () => el('div', 'pic', picFor(r.t)) })), intro); }
function learnList(items, intro) {
const col = el('div', 'col'); $('stage').appendChild(col);
const face = el('div', 'col'); face.style.height = 'auto'; const chips = el('div', 'chips'); col.append(face, chips);
let i = 0, auto = false; const seen = new Set();
const chipEls = items.map((it, k) => { const c = btn(it.t, () => { auto = false; i = k; show(); }, 'chip'); chips.appendChild(c); return c; });
function show() {
face.innerHTML = ''; const it = items[i];
const f = el('div', 'fact', it.t); if (it.ex) f.appendChild(el('span', 'sm', it.ex));
face.append(f, it.pic());
chipEls.forEach((c, k) => c.classList.toggle('cur', k === i)); chipEls[i].classList.add('seen'); seen.add(i);
chipEls[i].scrollIntoView({ block: 'nearest' });
if (seen.size === items.length) nextB.disabled = false;
return speak(it.t);
}
async function playAll() { auto = true; playB.disabled = true; for (i = 0; i < items.length && auto; i++) { await show(); await wait(600); } i = Math.min(i, items.length - 1); auto = false; playB.disabled = false; nextB.disabled = false; }
const playB = btn('▶ Read to me', playAll, 'big'), nextB = btn('Next ▶', () => next(3));
nextB.disabled = true; $('controls').append(playB, nextB);
sayAgain = show; cleanup = () => { auto = false; };
face.appendChild(el('div', 'fact', items[0].t));
$('hint').textContent = 'Tap ▶ Read to me, or tap each line.';
afterIntro(intro, playAll);
}
// practice: every fact in the table (or every rule), timed; a missed one comes back later
function stepDrill(intro) {
const items = L.table ? L.table.map(f => ({ q: f.q, ex: expr(L.op, f) + ' = ?', a: f.r, say: f.t, pic: () => dotsFor(L.op, f.a, f.b) }))
: L.rules.filter(r => r.quiz !== false).map(r => ({ q: r.q, ex: `? ${r.a} = 1 ${r.b}`, a: r.n, say: r.t, pic: () => el('div', 'pic', picFor(r.t)) }));
let queue = shuffle(items), done = 0, miss = 0, tries = 0, streak = 0, t0 = 0, cur;
const key = `${bookId}:${L.n}`, best = kidMap(S.best);
const col = el('div', 'qa'); $('stage').appendChild(col);
const top = el('div', 'row'); const timer = el('div', 'timer'); const fire = el('div', 'timer'); top.append(timer, fire);
const ex = el('div', 'expr'); const qd = el('div', 'note'); const picBox = el('div'); picBox.style.minHeight = '0';
const pad = numPad(check);
col.append(top, ex, qd, picBox, pad.el);
const showB = btn('👀 Show me', () => { picBox.innerHTML = ''; picBox.appendChild(cur.pic()); }, 'ghost');
$('controls').appendChild(showB);
const tick = setInterval(() => { if (t0) timer.textContent = `⏱ ${Math.round((Date.now() - t0) / 1000)}s` + (best[key] ? ` (best ${best[key]}s)` : ''); }, 500);
function ask() {
if (!queue.length) {
const secs = Math.round((Date.now() - t0) / 1000);
let msg = `${secs} seconds!`;
if (!best[key] || secs < best[key]) { if (best[key]) msg += ' New best time! 🏆'; best[key] = secs; save(); }
toast(msg, 1800); return setTimeout(() => next(starsFor(miss, items.length)), 1800);
}
cur = queue[0]; tries = 0; pad.clear(); picBox.innerHTML = '';
ex.textContent = cur.ex; ex.className = 'expr' + (cur.ex.length > 12 ? ' long' : ''); qd.textContent = cur.q;
$('hint').textContent = `${done + 1} of ${items.length}`;
sayAgain = () => speak(cur.q); sayAgain();
if (!t0) t0 = Date.now();
}
function check(v) {
if (v === cur.a) {
beep(true); pad.set(v, 'ok'); streak++; fire.textContent = streak >= 3 ? `🔥 ${streak}` : '';
queue.shift(); done++; setTimeout(ask, 600);
} else if (++tries < 2) { beep(false); pad.bad(); miss++; streak = 0; fire.textContent = ''; speak(P.tryAgain); }
else { // show it, say the book's line, and ask it again later
pad.set(cur.a, 'ok'); picBox.innerHTML = ''; picBox.appendChild(cur.pic());
const it = queue.shift(); queue.splice(Math.min(3, queue.length), 0, it);
speak(cur.say).then(() => wait(600)).then(ask);
}
}
cleanup = () => { clearInterval(tick); pad.stop(); };
afterIntro(intro, ask);
}
// story problems: the book's questions, one answer at a time; two misses shows the answer and the book's solution
function stepProblems(intro) {
const qs = L.qs, posKey = `${bookId}:${L.n}`, pos = kidMap(S.pos);
let qi = pos[posKey] || 0, pi = 0, tries = 0, miss = 0, si = 0, busy = false;
if (qi >= qs.length) qi = 0;
const col = el('div', 'qa'); $('stage').appendChild(col);
const pic = el('div', 'pic'); const qd = el('div', 'q'); const seq = el('div', 'seq'); const sol = el('div', 'sol');
const pad = numPad(check);
col.append(pic, qd, seq, sol, pad.el);
initTray();
$('controls').append(btn('🧮 Counters', () => $('tray').classList.toggle('on')), btn('Finish later', () => { pos[posKey] = qi; save(); leave(); }, 'ghost parent'));
if (L.note) $('hint').textContent = L.note;
const Q = () => qs[qi], Pt = () => Q().parts[pi];
function render() {
const q = Q(); qd.innerHTML = '';
q.parts.forEach((p, k) => {
qd.appendChild(el('span', 'pt' + (k === pi ? ' cur' : k < pi ? '' : ''), p.t + ' '));
if (k < pi) qd.appendChild(el('span', 'got' + (p.missed ? ' miss' : ''), p.seq || p.open ? '✓' : (p.f || p.a)));
if (k < q.parts.length - 1) qd.append(' ');
});
pic.textContent = picFor(q.parts[0].t);
seq.innerHTML = '';
const p = Pt(); if (p && p.seq) p.seq.forEach((v, k) => seq.appendChild(el('span', k < si ? 'got' : k === si ? 'cur' : '', k < si ? v : '?')));
const cur = seq.querySelector('.cur'); if (cur) cur.scrollIntoView({ block: 'nearest' });
}
function ask(sayIt) {
if (qi >= qs.length) { pos[posKey] = 0; save(); return next(starsFor(miss, qs.reduce((n, q) => n + q.parts.length, 0))); }
tries = 0; pad.clear(); sol.textContent = '';
const p = Pt(); if (p.seq && si === 0) si = 1; // the first number of a count is given
pad.frac(p.f); pad.show(!p.open); openB.style.display = p.open ? '' : 'none';
render();
$('hint').textContent = `Question ${qi + 1} of ${qs.length}`;
sayAgain = () => speak(p.s || p.t, p.open ? 'Answer out loud, then tap Next.' : null);
if (sayIt !== false) sayAgain();
}
function advance() {
busy = false; const q = Q();
if (++pi < q.parts.length) { si = 0; return ask(); }
if (q.sol && !q.shown) { q.shown = true; sol.textContent = q.sol; }
qi++; pi = 0; si = 0; pos[posKey] = qi; save();
setTimeout(() => ask(), q.sol && sol.textContent ? 600 : 0);
}
function check(v) {
if (busy) return; const p = Pt();
const want = p.seq ? p.seq[si] : p.f || p.a;
const norm = s => String(s).trim().replace(/\s+/g, ' ');
if (p.ex ? p.ex.includes(norm(v)) : p.f ? sameVal(v, p.f) : v === want || (!p.seq && p.alt && p.alt.includes(v))) {
beep(true); pad.set(v, 'ok'); tries = 0;
if (p.seq) { if (++si >= p.seq.length) { busy = true; render(); return setTimeout(() => { si = 0; advance(); }, 700); } setTimeout(() => { pad.clear(); render(); }, 350); return; }
busy = true; speak(P.yes); setTimeout(advance, 900); return;
}
miss++;
if (++tries < 2) { beep(false); pad.bad(); speak(P.tryAgain); return; }
// second miss: show the answer, say it, and the book's solution when there is one
busy = true; pad.set(want, 'ok'); tries = 0;
const q = Q(), last = !p.seq && pi === q.parts.length - 1;
if (last && q.sol) { sol.textContent = q.sol; q.shown = true; }
if (p.seq) { si++; if (si >= p.seq.length) { si = 0; p.missed = true; return speak('ans:' + want).then(() => advance()); } return speak('n:' + want).then(() => { busy = false; pad.clear(); render(); }); }
p.missed = true;
speak('ans:' + want, last && q.sol ? q.sol : null).then(() => wait(800)).then(advance);
}
// a question answered out loud to a grown-up: no typing, just Next
const openB = btn('Next ▶', () => { if (busy) return; busy = true; beep(true); setTimeout(advance, 300); }, 'big'); openB.style.display = 'none';
col.appendChild(openB);
cleanup = () => pad.stop();
afterIntro(intro, () => ask());
}
function finish() {
const vals = Object.values(scores), stars = Math.max(1, Math.round(vals.reduce((a, b) => a + b, 0) / Math.max(1, vals.length)));
const p = prog(bookId), old = (p[L.n] || {}).stars || 0;
p[L.n] = { stars: Math.max(old, stars), at: Date.now() };
const got = kidMap(S.stickers), sk = stkKey(bookId, L.n); const sticker = got[sk] || STICKERS[(L.n * 7 + SHELF.findIndex(b => b.id === bookId) * 3) % STICKERS.length]; got[sk] = sticker;
save();
$('stepTitle').textContent = `${S.kid}, you finished Lesson ${L.n}!`;
$('stage').innerHTML = `<div class="col"><div class="stars">${starStr(stars)}</div><div class="stk">${sticker}</div><div class="note">A new sticker for your sticker book!</div></div>`;
$('hint').textContent = ''; $('controls').innerHTML = '';
$('controls').append(btn('Lessons', () => openBook(bookId), 'big'), btn('Sticker book', () => go('stickers')));
beep(true); speak(`Great job, ${S.kid}!`, P.done);
}
Promise.all([
fetch('prompts.json').then(r => r.json()).then(j => { P = j; }),
...SHELF.filter(b => !b.soon).map(b => fetch(`books/${b.id}.json`).then(r => r.json()).then(j => { BOOKS[b.id] = j; })),
...SHELF.filter(b => !b.soon).map(b => fetch(`audio/${b.id}.json`).then(r => r.ok ? r.json() : null).then(j => {
if (!j) return; if (!AUDIO) AUDIO = { speeds: j.speeds, items: {} }; Object.assign(AUDIO.items, j.items);
}).catch(() => {}))
]).then(home, () => toast('Could not load the book. Check the internet.', 4000));
return { go, leave, debug: () => ({ S, L, steps, stepIdx, scores }), wordsToNumber };
})();
EOF
prompts.json: the spoken instructions.
cat > /root/rays-rebuild/prompts.json <<'EOF'
{
"home": "Who is doing math today? Tap your name. Then tap the book.",
"greet": "Hi, {kid}! Tap the book to start.",
"map": "Pick a lesson. The glowing one is next.",
"count": "Count the balls. Tap each ball, and count out loud.",
"numbers": "Learn the numbers. Listen, and say each number with me.",
"find": "Find the number. Listen, then tap the number you hear.",
"say": "Read the numbers. Say each number out loud to a grown-up. Tap a number to hear it.",
"write": "Write the numbers. Listen, then type the number you hear, and tap OK.",
"trace": "Time to trace the numbers. Start at the green dot, and follow the arrow.",
"learn": "Learn the table. Listen, and say it with me.",
"rules": "Learn the table. Listen, and say it with me.",
"drill": "Practice time. Type the answer, then tap OK. Try to beat your best time!",
"problems": "Story problems. Listen to each question. Type the answer, then tap OK. Tap the counters if you need help.",
"yes": "Yes!",
"tryAgain": "Not quite. Try again.",
"done": "You finished the lesson! You earned a sticker."
}
EOF
sw.js: offline support.
cat > /root/rays-rebuild/sw.js <<'EOF'
// Offline support. App files: network first (so updates show), cached copy when offline.
// Voice clips (audio/*.mp3) never change for a given name: cache first, saved the first time they play.
const SHELL = 'rays-shell-v6', CLIPS = 'rays-clips-v1';
const FILES = ['./', 'index.html', 'app.js', 'prompts.json', 'trace.js', 'hershey.js', 'books/primary1877.json', 'audio/primary1877.json', 'books/intellectual.json', 'audio/intellectual.json', 'icon-192.png', 'manifest.webmanifest'];
self.addEventListener('install', e => { e.waitUntil(caches.open(SHELL).then(c => Promise.all(FILES.map(f => c.add(f).catch(() => {})))).then(() => self.skipWaiting())); });
self.addEventListener('activate', e => {
e.waitUntil(caches.keys().then(ks => Promise.all(ks.filter(k => k.startsWith('rays-') && k !== SHELL && k !== CLIPS).map(k => caches.delete(k)))).then(() => self.clients.claim()));
});
self.addEventListener('fetch', e => {
const r = e.request; if (r.method !== 'GET' || new URL(r.url).origin !== location.origin) return;
if (r.url.endsWith('.mp3')) {
e.respondWith(caches.open(CLIPS).then(c => c.match(r).then(hit => hit || fetch(r).then(res => { if (res.ok && res.status === 200) c.put(r, res.clone()); return res; }))));
return;
}
e.respondWith(fetch(r).then(res => { if (res.ok) caches.open(SHELL).then(c => c.put(r, res.clone())); return res; }).catch(() => caches.match(r)));
});
EOF
manifest.webmanifest: lets it be added to a home screen like an app.
cat > /root/rays-rebuild/manifest.webmanifest <<'EOF'
{
"name": "Ray's Arithmetic",
"short_name": "Ray's Math",
"description": "Ray's New Primary Arithmetic (1877) as games: count, learn the tables, practice, story problems.",
"start_url": "./",
"scope": "./",
"display": "standalone",
"orientation": "any",
"background_color": "#121212",
"theme_color": "#121212",
"icons": [
{ "src": "icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any maskable" },
{ "src": "icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" }
]
}
EOF
make-icons.ps1: draws the app icon ("1+2").
cat > /root/rays-rebuild/make-icons.ps1 <<'EOF'
# Draws the Ray's Arithmetic app icons (public/icon-192.png, icon-512.png) with Windows' System.Drawing.
# Usage: powershell -ExecutionPolicy Bypass -File RaysArithmetic\tools\make-icons.ps1
$root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
Add-Type -AssemblyName System.Drawing
foreach ($s in 192, 512) {
$b = New-Object Drawing.Bitmap $s, $s; $g = [Drawing.Graphics]::FromImage($b)
$g.SmoothingMode = 'AntiAlias'; $g.TextRenderingHint = 'AntiAlias'
$g.Clear([Drawing.Color]::FromArgb(24, 52, 66))
$pen = New-Object Drawing.Pen ([Drawing.Color]::FromArgb(240, 160, 60)), ($s * 0.05)
$g.DrawRectangle($pen, $s * 0.03, $s * 0.03, $s * 0.94, $s * 0.94)
$sf = New-Object Drawing.StringFormat; $sf.Alignment = 'Center'; $sf.LineAlignment = 'Center'
$f = New-Object Drawing.Font 'Georgia', ($s * 0.27), ([Drawing.FontStyle]::Bold), ([Drawing.GraphicsUnit]::Pixel)
$g.DrawString('1+2', $f, [Drawing.Brushes]::WhiteSmoke, (New-Object Drawing.RectangleF 0, ($s * -0.07), $s, $s), $sf)
$f2 = New-Object Drawing.Font 'Georgia', ($s * 0.11), ([Drawing.FontStyle]::Italic), ([Drawing.GraphicsUnit]::Pixel)
$g.DrawString("Ray's", $f2, (New-Object Drawing.SolidBrush ([Drawing.Color]::FromArgb(255, 233, 168))), (New-Object Drawing.RectangleF 0, ($s * 0.22), $s, $s), $sf)
$b.Save("$root\public\icon-$s.png", [Drawing.Imaging.ImageFormat]::Png); $g.Dispose(); $b.Dispose()
}
"wrote icons"
EOF
deploy.ps1: copies everything to the Proxmox host and runs push.sh.
cat > /root/rays-rebuild/deploy.ps1 <<'EOF'
# Deploy Ray's Arithmetic to CT 306 (abc-web) at abcbooks.fyi/rays/. Run from anywhere:
# powershell -ExecutionPolicy Bypass -File RaysArithmetic\deploy.ps1 (app files only)
# powershell -ExecutionPolicy Bypass -File RaysArithmetic\deploy.ps1 -Audio (also every recorded clip; CT 306 converts new ones to MP3)
param([switch]$Audio)
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$tmp = Join-Path $env:TEMP 'rays-deploy'; New-Item -ItemType Directory -Force $tmp | Out-Null
tar -cf "$tmp\site.tar" -C (Join-Path $here 'public') .
$files = @("$tmp\site.tar", "$here\proxmox\push.sh", "$here\proxmox\convert-audio.sh")
if ($Audio) { # only the recordings CT 306 doesn't have as MP3 yet
$list = New-Object System.Collections.Generic.List[string]
foreach ($sp in 's60', 's75') {
$have = New-Object 'System.Collections.Generic.HashSet[string]'
foreach ($h in @(ssh -o BatchMode=yes proxmox "pct exec 306 -- ls /var/www/abcbooks/rays/audio/$sp" 2>$null)) { [void]$have.Add(($h -replace '\.mp3$', '')) }
Get-ChildItem (Join-Path $here "tools\out\$sp") -Filter *.wav | Where-Object { -not $have.Contains($_.BaseName) } | ForEach-Object { $list.Add("$sp/$($_.Name)") }
}
"new recordings to send: $($list.Count)"
if ($list.Count) { [IO.File]::WriteAllLines("$tmp\audio.txt", $list); tar -cf "$tmp\audio.tar" -C (Join-Path $here 'tools\out') -T "$tmp\audio.txt"; $files += "$tmp\audio.tar" }
}
ssh -o BatchMode=yes proxmox "mkdir -p /root/rays"
ssh -o BatchMode=yes proxmox "test -f /root/rays/audio.tar && unlink /root/rays/audio.tar; true"
scp -o BatchMode=yes @files proxmox:/root/rays/
ssh -o BatchMode=yes proxmox "bash /root/rays/push.sh"
Remove-Item $tmp -Recurse -Force
EOF
push.sh (Proxmox host): unpacks the app into CT 306 and converts new recordings.
cat > /root/rays-rebuild/push.sh <<'EOF'
#!/bin/bash
# Run on the Proxmox host after copying site.tar (+ audio.tar) and these scripts to /root/rays/.
# Ray's Arithmetic lives next to ABC Books on CT 306 (abc-web): /var/www/abcbooks/rays = abcbooks.fyi/rays/
# (and games.503mail.com/abcbooks/rays/). ABC Books' own deploy never deletes this folder.
set -e
CT=306
DST=/var/www/abcbooks/rays
pct exec $CT -- mkdir -p $DST
pct push $CT /root/rays/site.tar /tmp/rays.tar
pct exec $CT -- tar -xf /tmp/rays.tar -C $DST --no-same-owner
pct exec $CT -- unlink /tmp/rays.tar
if [ -f /root/rays/audio.tar ]; then
pct push $CT /root/rays/convert-audio.sh /root/rays-convert-audio.sh
pct push $CT /root/rays/audio.tar /tmp/raysaudio.tar
pct exec $CT -- mkdir -p /tmp/raysaudio
pct exec $CT -- tar -xf /tmp/raysaudio.tar -C /tmp/raysaudio --no-same-owner
pct exec $CT -- unlink /tmp/raysaudio.tar
pct exec $CT -- bash /root/rays-convert-audio.sh
fi
pct exec $CT -- chmod -R u=rwX,go=rX $DST
pct exec $CT -- du -sh $DST
echo RAYS-DONE
EOF
convert-audio.sh (runs inside CT 306): WAV to MP3, skipping clips already done.
cat > /root/rays-rebuild/convert-audio.sh <<'EOF'
#!/bin/bash
# Runs INSIDE CT 306: converts the recorded WAV clips in /tmp/raysaudio/s<speed>/ to MP3 under
# /var/www/abcbooks/rays/audio/s<speed>/ (skips clips already converted).
set -e
SRC=/tmp/raysaudio
DST=/var/www/abcbooks/rays/audio
n=0
for d in $SRC/s*/; do
rel=${d#$SRC/}
mkdir -p "$DST/$rel"
for w in "$d"*.wav; do
m="$DST/$rel$(basename "$w" .wav).mp3"
if [ ! -f "$m" ]; then lame --quiet -V 6 "$w" "$m"; n=$((n+1)); fi
done
done
rm -r $SRC
echo "converted $n clips"
EOF
serve-local.ps1: a tiny web server for trying it on the PC at http://localhost:8129/.
cat > /root/rays-rebuild/serve-local.ps1 <<'EOF'
# Tiny static file server for testing Ray's Arithmetic locally: http://localhost:8129/
$root = Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Path) "public"
$l = New-Object System.Net.HttpListener
$l.Prefixes.Add('http://localhost:8129/')
$l.Start()
$types = @{ '.html' = 'text/html; charset=utf-8'; '.png' = 'image/png'; '.js' = 'text/javascript'; '.css' = 'text/css'; '.json' = 'application/json'; '.mp3' = 'audio/mpeg'; '.webmanifest' = 'application/manifest+json' }
while ($l.IsListening) {
$ctx = $l.GetContext()
$path = [Uri]::UnescapeDataString($ctx.Request.Url.AbsolutePath)
if ($path.EndsWith('/')) { $path += 'index.html' }
$file = Join-Path $root $path.TrimStart('/')
if (Test-Path $file -PathType Leaf) {
$bytes = [IO.File]::ReadAllBytes($file)
$ext = [IO.Path]::GetExtension($file)
if ($types[$ext]) { $ctx.Response.ContentType = $types[$ext] }
$ctx.Response.Headers.Add('Cache-Control', 'no-store')
$ctx.Response.OutputStream.Write($bytes, 0, $bytes.Length)
} else { $ctx.Response.StatusCode = 404 }
$ctx.Response.Close()
}
EOF
Rebuild, step by step
The PC needs ETI-Eloquence installed (it comes with some screen readers; the 32-bit eci.dll must be in C:\Windows\Speech\Eloq). CT 306 (abc-web) must already serve ABC Books, as set up in the ABC Books posts.
1. Build the book data
PC (PowerShell). It prints "89 lessons, 1099 questions, 1845 answers", or a list of problems if an answer is wrong:
powershell -ExecutionPolicy Bypass -File C:\RaysArithmetic\tools\make-book.ps1
2. Record the voice
PC (PowerShell), about 15 minutes:
powershell -ExecutionPolicy Bypass -File C:\RaysArithmetic\tools\make-audio.ps1
3. Icons
PC (PowerShell):
powershell -ExecutionPolicy Bypass -File C:\RaysArithmetic\tools\make-icons.ps1
4. Put it on the server
PC (PowerShell). -Audio also sends the recordings (about 260 MB of WAV; only needed when they change). It ends with "RAYS-DONE":
powershell -ExecutionPolicy Bypass -File C:\RaysArithmetic\deploy.ps1 -Audio
5. Check it
Open https://abcbooks.fyi/rays/ , tap a name, tap the book, and play Lesson 1. To try it on the PC first, run serve-local.ps1 and open http://localhost:8129/ .
Troubleshooting
- The app is silent: the voice list
audio/primary1877.jsonor the MP3s are missing. Proxmox web UI, pve, Shell:
pct exec 306 -- ls /var/www/abcbooks/rays/audio/s60 | wc -l
Healthy: 2882 with Book One alone, more once other books are added (they share clips; the same count in s75). If it is lower, run step 4 again with -Audio.
- make-book.ps1 lists problems: each line names the lesson and question, for example
Lesson 21 q2 : '4 and 7?' is 11, not 12. Fix that answer inprimary.txt. - An old version shows after an update: the offline copy. Change the
rays-shell-vnumber insw.jsto the next number, deploy, then reload the page twice. - Web server log (CT 306 console, or Proxmox host shell): healthy is "serving initial configuration" and no errors.
pct exec 306 -- journalctl -u caddy -n 50 --no-pager
- A tracing page is skipped right away: the page was hidden (another tab in front) while it loaded; go back to the lesson.
Notes
-
Book 2 has its own post: Ray's New Intellectual Arithmetic.
-
Kids' progress, best times and stickers are saved in the browser on each device (
raysin local storage), not on the server.