| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- <?php
- /**
- * Anagram question generator.
- *
- * Changes from the original:
- * - word selection used `year >= difficulty`, i.e. beginners got every word
- * and strong players only the hardest; now `year <= difficulty` (same
- * direction as spelling2, and consistent with the min(...) caps below)
- * - falls back to any word when nothing exists at the user's level, and shows
- * a message instead of crashing when the word list is empty
- * - the scramble is never the original word (up to 10 reshuffles)
- * - multibyte-safe letter splitting, escaped output
- * - user escaped in SQL; hard-coded `e107.` database name replaced by MPREFIX
- * - only the data the checker needs is stored (server-side via TkAnswerStore)
- * - previous tkLastQuestion row removed before inserting the new one
- */
- class anagrams {
- private $user;
- /**
- * @param string $user User to supply the question for.
- */
- public function __construct($user) {
- $this->user = $user;
- }
- public function getanagram() {
- $year = max(1, (int) ceil($this->difficulty()));
- $question = $this->pickWord($year);
- if ($question === null) {
- $question = $this->pickWord(13); // nothing at this level: fall back to any word
- }
- if ($question === null) {
- e107::getRender()->tablerender('Anagrams', 'No anagram words are available yet.');
- return;
- }
- $questionHtml = $this->anagramsHtml($question['word']);
- $db = e107::getDb();
- $db->delete('tkLastQuestion', "user = '" . $db->escape($this->user) . "'");
- $db->insert('tkLastQuestion', ['user' => $this->user, 'LastQuestionHtml' => base64_encode($questionHtml)]);
- e107::getRender()->tablerender('Anagrams', $questionHtml);
- }
- /**
- * Pick a word at or below $year, preferring words this user has answered
- * least in the last month.
- *
- * @return array|null Row with `word`, or null when none found.
- */
- private function pickWord($year) {
- $db = e107::getDb();
- $row = $db->retrieve(
- "SELECT `word`, IFNULL(`cnt`, 0) AS Ncnt FROM
- (SELECT `word` FROM `" . MPREFIX . "tk_wordList`
- WHERE `year` <= " . (int) $year . " AND `usage` != '' AND CHAR_LENGTH(`word`) > 3) AS wList
- LEFT JOIN
- (SELECT `answer`, COUNT(`answer`) AS `cnt` FROM `" . MPREFIX . "tkReport`
- WHERE `answereddatestamp` > UNIX_TIMESTAMP(NOW() - INTERVAL 1 MONTH)
- AND `username` = '" . $db->escape($this->user) . "'
- AND `subject` = 'anagrams' GROUP BY `answer`) AS Ans
- ON wList.`word` = Ans.`answer`
- ORDER BY IFNULL(`cnt`, 0), RAND() LIMIT 1"
- );
- return (is_array($row) && !empty($row['word'])) ? $row : null;
- }
- /**
- * Build the anagram form for $word. The expected answer is stored
- * server-side; the form only carries a token.
- */
- private function anagramsHtml($word) {
- $letters = preg_split('//u', $word, -1, PREG_SPLIT_NO_EMPTY);
- $shuffled = $letters;
- for ($i = 0; $i < 10; $i++) {
- shuffle($shuffled);
- if ($shuffled !== $letters) {
- break;
- }
- }
- $token = TkAnswerStore::issue($this->user, ['answer1' => $word, 'question' => $shuffled, 'type' => 'anagrams']);
- $frm = e107::getForm();
- $fString = $frm->open('questionForm', 'post', '', ['autocomplete' => 'off', 'class' => 'formclass']);
- $fString .= $frm->label('<h4>Anagrams</h4>') . "</br>\n";
- $fString .= $frm->label('Unjumble the letters to find the word.') . "\n";
- $fString .= '</br>';
- foreach ($shuffled as $letter) {
- $fString .= $frm->label(htmlspecialchars($letter) . " ") . "\n";
- }
- $fString .= '</br></br>';
- $fString .= $frm->text('a1', '', 80, ['placeholder' => 'Write answer here!', 'autocomplete' => "off", 'spellcheck' => "false"]) . "\n";
- $fString .= $frm->hidden('checkerData', $token, ['id' => 'checkerData']);
- $fString .= $frm->hidden('subject', 'anagrams', ['id' => 'subject']);
- $fString .= '</br>' . "\n";
- $fString .= $frm->button('Answer', 'Check answer') . "\n";
- $fString .= $frm->close();
- $fString .= '</br></br>';
- return $fString;
- }
- /**
- * Difficulty (word year level 1-13) from the last month of answers.
- * Capped while the user has few answers.
- *
- * @return float
- */
- private function difficulty() {
- $db = e107::getDb();
- $stats = $db->retrieve(
- "SELECT (SUM(correct)/COUNT(correct)*13) AS percent, COUNT(correct) AS count
- FROM `" . MPREFIX . "tkReport`
- WHERE answereddatestamp >= UNIX_TIMESTAMP(NOW() - INTERVAL 1 MONTH)
- AND `username` = '" . $db->escape($this->user) . "' AND `subject` = 'anagrams'"
- );
- if (!is_array($stats)) {
- return 1;
- }
- $count = (int) $stats['count'];
- $percent = (float) $stats['percent'];
- if ($count >= 30) {
- return $percent;
- } elseif ($count >= 25) {
- return min($percent, 7);
- } elseif ($count >= 20) {
- return min($percent, 5);
- } elseif ($count >= 10) {
- return min($percent, 3);
- }
- return 1;
- }
- }
|