Parcourir la source

Fix Multiplication, Division and Rounding.

Gregory Huxley il y a 1 semaine
Parent
commit
882b5a298c
3 fichiers modifiés avec 72 ajouts et 17 suppressions
  1. 5 3
      README.md
  2. 3 2
      TK2.php
  3. 64 12
      questionGenerators/math/numeracy2.php

+ 5 - 3
README.md

@@ -66,13 +66,15 @@ generator in `questionGenerators/`. A subject unlocks at a difficulty threshold:
 | numbers (missing number), spelling | 0 |
 | addition | 10,000 |
 | subtraction | 20,000 |
+| rounding (money to the nearest 5 cents) | 50,000 |
 | elements (periodic table) | 150,000 |
 | multiplication | 200,000 |
+| division | 300,000 |
 | anagrams | 4,000,000 |
 
-Division, rounding, worded problems and the geography/history/health subjects exist or are
-stubbed but are switched off (the `0` in `$subjectsList` in `TK2.php`). To switch one on, change
-that `0` to a `1`. Worded problems (`word_problems`) would unlock at 250,000 and use whole-number
+Worded problems and the geography/history/health subjects exist or are stubbed but are switched
+off (the `0` in `$subjectsList` in `TK2.php`). To switch one on, change that `0` to a `1`; to change
+when a subject appears, change its number. Worded problems (`word_problems`) would unlock at 250,000 and use whole-number
 answers only: pillow cases, sponges, an elephant's dinner, sandwiches, marbles, a day at the beach,
 cupcakes and sugar bags. The numbers get bigger as the student's accuracy and number of answers grow.
 

+ 3 - 2
TK2.php

@@ -25,6 +25,7 @@
  *  - showMessage(): user name escaped
  *  - someone with neither the student nor the teacher class now sees a
  *    message instead of an empty page
+ *  - division and rounding are switched on (rounding at 50,000, division at 300,000)
  *  - new word_problems subject (switched off: see $subjectsList); the old
  *    worded_* subjects were mapped to the spelling generator and are gone
  */
@@ -39,8 +40,8 @@ class TK2 {
             ['addition', 1, 10000], // Numeracy adding
             ['subtraction', 1, 20000], // Numeracy minus'
             ['multiplication', 1, 200000], // Numeracy times'
-            ['division', 0, 0], // Numeracy deviding
-            ['rounding', 0, 0], // Numeracy rounding
+            ['division', 1, 300000], // Numeracy deviding
+            ['rounding', 1, 50000], // Numeracy rounding (money to the nearest 5 cents)
             ['word_problems', 0, 250000], // Worded maths problems: change the 0 to a 1 to switch them on
         ],
         // English

+ 64 - 12
questionGenerators/math/numeracy2.php

@@ -18,6 +18,14 @@
  *  - user/subject are escaped in SQL, and the hard-coded `e107.` database
  *    name is replaced by MPREFIX
  *  - the previous tkLastQuestion row is removed before inserting the new one
+ *  - division has its own number ranges (times-table style, whole answers). It used
+ *    getNumbers(), which gave a new student "1 / 1 = 1" every time, then jumped to
+ *    four-digit dividends around 800 answers a month and fell back to 1 / 1 after 1200
+ *  - rounding no longer asks about amounts that are already a multiple of 5 cents
+ *  - multiplication uses the same ranges as division (the two stay in step). It used
+ *    getNumbers() with its count/4 and accuracy/6 scaling, which started at 0 x 1, grew up
+ *    to 100 x 100, jumped to about 250 x 250 at 800 answers a month and fell back to
+ *    single digits after 1200
  */
 class numeracy2 {
 
@@ -93,15 +101,10 @@ class numeracy2 {
      * Two random whole numbers, larger first.
      */
     private function getNumbers($difficulty, $sign) {
-        //TODO add finer grain control for multiplication here
+        // Used for addition and subtraction. Multiplication and division have their own ranges.
         $count = (float) $difficulty['count'];
         $percent = (float) $difficulty['percent'];
 
-        if ($sign == 'x' || $sign == '/') {
-            $count = floor($count / 4);
-            $percent = $percent / 6;
-        }
-
         if ($count >= 300) {
             $max = (int) pow($count, ($percent / 100) * 2);
         } elseif ($count >= 200) {
@@ -123,6 +126,10 @@ class numeracy2 {
     }
 
     private function basic($sign, $difficulty) {
+        if ($sign === 'x') {
+            return $this->Multiplication($difficulty);
+        }
+
         $numbers = $this->getNumbers($difficulty, $sign);
         $firstNumber = $numbers[0];
         $secondNumber = $numbers[1];
@@ -130,19 +137,61 @@ class numeracy2 {
         $solution = [
             '+' => $firstNumber + $secondNumber,
             '-' => $firstNumber - $secondNumber,
-            'x' => $firstNumber * $secondNumber,
         ];
 
         return ['answer1' => $solution[$sign], 'question' => [$firstNumber, $sign, $secondNumber], 'type' => $this->subSubject];
     }
 
+    /**
+     * Multiplication, times-table style: one factor goes up to 15 and the other up
+     * to 40 (answers up to 600) as the student's accuracy and number of answers
+     * grow. Both are at least 2, the bigger one first. Same ranges as Division.
+     */
+    private function Multiplication($difficulty) {
+        $level = $this->level($difficulty);
+        $numbers = $this->flipNumbers($this->scaled(2, 15, $level), $this->scaled(2, 40, $level));
+
+        return [
+            'answer1' => $numbers[0] * $numbers[1],
+            'question' => [$numbers[0], 'x', $numbers[1]],
+            'type' => $this->subSubject,
+        ];
+    }
+
+    /**
+     * Division with whole-number answers, times-table style: the divisor goes up
+     * to 15 and the answer up to 40 (dividends up to 600) as the student's
+     * accuracy and number of answers grow. Both are at least 2.
+     */
     private function Division($difficulty) {
-        $numbers = $this->getNumbers($difficulty, '/');
-        $quotient = max(1, $numbers[0]);
-        $divisor = max(1, $numbers[1]);
-        $dividend = $quotient * $divisor;
+        $level = $this->level($difficulty);
+        $divisor = $this->scaled(2, 15, $level);
+        $quotient = $this->scaled(2, 40, $level);
 
-        return ['answer1' => $quotient, 'question' => [$dividend, '/', $divisor], 'type' => $this->subSubject];
+        return [
+            'answer1' => $quotient,
+            'question' => [$divisor * $quotient, '/', $divisor],
+            'type' => $this->subSubject,
+        ];
+    }
+
+    /**
+     * How hard to make the numbers, 0-100: this month's accuracy, scaled up over
+     * the first 30 answers so one lucky answer doesn't jump to the hardest numbers.
+     */
+    private function level($difficulty) {
+        $percent = max(0.0, min(100.0, (float) $difficulty['percent']));
+        return $percent * min(1.0, ((int) $difficulty['count']) / 30);
+    }
+
+    /**
+     * A random whole number from $min up to $maxAtFull. At level 0 the top of the
+     * range is a quarter of the way there; at level 100 it is all of it.
+     */
+    private function scaled($min, $maxAtFull, $level) {
+        $span = max(0, $maxAtFull - $min);
+        $max = $min + (int) ceil($span * (0.25 + 0.75 * $level / 100));
+        return mt_rand($min, max($min, $max));
     }
 
     /**
@@ -152,6 +201,9 @@ class numeracy2 {
     private function Rounding($difficulty) {
         $maxCents = (5 + (int) floor($difficulty['percent'] / 10) * 2) * 100;
         $cents = mt_rand(100, $maxCents);
+        if ($cents % 5 === 0) {
+            $cents += mt_rand(1, 4); // already a multiple of 5 cents: nothing to round
+        }
         $roundedCents = (int) (round($cents / 5) * 5);
 
         return [