diff --git a/manual/add-note.php b/manual/add-note.php
index 1398e5d006..ef9d63d509 100644
--- a/manual/add-note.php
+++ b/manual/add-note.php
@@ -1,11 +1,15 @@
:
- ? |
+ question; ?>?
(Example: nine) |
-
-
-
+
+
+
|
diff --git a/manual/spam_challenge.php b/manual/spam_challenge.php
deleted file mode 100644
index 15bf6a5dd7..0000000000
--- a/manual/spam_challenge.php
+++ /dev/null
@@ -1,66 +0,0 @@
-
@@ -158,13 +162,13 @@
diff --git a/src/Spam/Challenge.php b/src/Spam/Challenge.php
new file mode 100644
index 0000000000..3a3466990e
--- /dev/null
+++ b/src/Spam/Challenge.php
@@ -0,0 +1,160 @@
+ 'zero',
+ 1 => 'one',
+ 2 => 'two',
+ 3 => 'three',
+ 4 => 'four',
+ 5 => 'five',
+ 6 => 'six',
+ 7 => 'seven',
+ 8 => 'eight',
+ 9 => 'nine',
+ ];
+
+ private const CHALLENGES = [
+ // name, print, generator
+ 'max' => [
+ 'max',
+ [
+ self::class,
+ 'print_prefix',
+ ],
+ ],
+ 'min' => [
+ 'min',
+ [
+ self::class,
+ 'print_prefix',
+ ],
+ ],
+ 'minus' => [
+ [
+ self::class,
+ 'minus',
+ ],
+ [
+ self::class,
+ 'print_infix',
+ ],
+ [
+ self::class,
+ 'gen_minus',
+ ],
+ ],
+ 'plus' => [
+ [
+ self::class,
+ 'plus',
+ ],
+ [
+ self::class,
+ 'print_infix',
+ ],
+ [
+ self::class,
+ 'gen_plus',
+ ],
+ ],
+ ];
+
+ private function __construct(
+ public string $function,
+ public string $argumentOne,
+ public string $argumentTwo,
+ public string $question,
+ ) {
+ }
+
+ public static function create(): self
+ {
+ $function = array_rand(self::CHALLENGES);
+
+ $challenge = self::CHALLENGES[$function];
+
+ $a = mt_rand(0, 9);
+ $argumentOne = self::NUMBERS[$a];
+
+ $b = isset($challenge[2]) ? $challenge[2]($a) : mt_rand(0, 9);
+ $argumentTwo = self::NUMBERS[$b];
+
+ $question = $challenge[1](
+ $function,
+ $argumentOne,
+ $argumentTwo,
+ );
+
+ return new self(
+ $function,
+ $argumentOne,
+ $argumentTwo,
+ $question,
+ );
+ }
+
+ public static function isValidAnswer(
+ string $function,
+ string $argumentOne,
+ string $argumentTwo,
+ string $answer,
+ ): bool {
+ if (!array_key_exists($function, self::CHALLENGES)) {
+ return false;
+ }
+
+ $challenge = self::CHALLENGES[$function];
+
+ $a = array_search($argumentOne, self::NUMBERS, true);
+
+ if (!is_int($a)) {
+ return false;
+ }
+
+ $b = array_search($argumentTwo, self::NUMBERS, true);
+
+ if (!is_int($b)) {
+ return false;
+ }
+
+ $expected = self::NUMBERS[$challenge[0]($a, $b)];
+
+ return $expected === $answer;
+ }
+
+ private static function plus(int $a, int $b): int
+ {
+ return $a + $b;
+ }
+
+ private static function gen_plus(int $a): int
+ {
+ return mt_rand(0, 9 - $a);
+ }
+
+ private static function minus(int $a, int $b): int
+ {
+ return $a - $b;
+ }
+
+ private static function gen_minus(int $a): int
+ {
+ return mt_rand(0, $a);
+ }
+
+ private static function print_infix(string $name, string $a, string $b): string
+ {
+ return "$a $name $b";
+ }
+
+ private static function print_prefix(string $name, string $a, string $b): string
+ {
+ return "$name($a, $b)";
+ }
+}
diff --git a/tests/test-answer.phpt b/tests/Spam/Challenge/is-valid-answer.phpt
similarity index 94%
rename from tests/test-answer.phpt
rename to tests/Spam/Challenge/is-valid-answer.phpt
index 235d68cd76..e948e166be 100644
--- a/tests/test-answer.phpt
+++ b/tests/Spam/Challenge/is-valid-answer.phpt
@@ -1,9 +1,13 @@
--TEST--
-test_answer() returns true when answer to spam challenge is valid
+Challenge::isValidAnswer() returns true when answer to spam challenge is valid
--FILE--
$function,
- 'argumentOne' => $argumentOne,
- 'argumentTwo' => $argumentTwo,
- 'question' => $question,
+ 'function' => $challenge->function,
+ 'argumentOne' => $challenge->argumentOne,
+ 'argumentTwo' => $challenge->argumentTwo,
+ 'question' => $challenge->question,
];
}, range(1, 20));