310 lines
13 KiB
PHP
310 lines
13 KiB
PHP
<?php
|
|
// This file is part of the Studyplan plugin for Moodle
|
|
//
|
|
// Moodle is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Moodle is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
|
|
/**
|
|
*
|
|
* @package local_treestudyplan
|
|
* @copyright 2023 P.M. Kuipers
|
|
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
|
|
namespace local_treestudyplan\local;
|
|
|
|
use Exception;
|
|
|
|
class gradegenerator {
|
|
private $table = [];
|
|
|
|
private static $loremipsum = [
|
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
|
"Etiam scelerisque ligula porttitor velit sollicitudin blandit.",
|
|
"Praesent laoreet nisi id lacus laoreet volutpat.",
|
|
"Donec rutrum tortor tempus lectus malesuada, ut pretium eros vehicula.",
|
|
"Phasellus vulputate tortor vehicula mauris porta ultricies.",
|
|
"Ut et lacus sit amet nisl facilisis elementum.",
|
|
"Vestibulum ut mauris ac justo tincidunt hendrerit.",
|
|
"Fusce congue nulla quis elit facilisis malesuada.",
|
|
"Aenean ornare eros placerat ipsum fringilla, sed imperdiet felis imperdiet.",
|
|
"Ut malesuada risus ultricies arcu dapibus, quis lobortis eros maximus.",
|
|
"Nam ullamcorper dolor ac est tristique, vel blandit tortor tristique.",
|
|
"Quisque quis lorem vitae leo lobortis posuere.",
|
|
"Nulla ac enim consectetur, rhoncus eros sed, malesuada enim.",
|
|
"Vestibulum lobortis lacus ac dolor pulvinar, gravida tincidunt dolor bibendum.",
|
|
"Maecenas fringilla urna eget sem bibendum, non lacinia lorem tempus.",
|
|
"Nullam quis metus sagittis, pharetra orci eget, ultrices nunc.",
|
|
"Morbi et ante at ipsum sodales porta.",
|
|
"Morbi vel neque in urna vestibulum vestibulum eu quis lectus.",
|
|
"Nam consequat dolor at enim vestibulum, ac gravida nisl consequat.",
|
|
"Phasellus ac libero vestibulum, vulputate tellus at, viverra dui.",
|
|
"Vivamus venenatis magna a nunc cursus, eget laoreet velit malesuada.",
|
|
"Cras fermentum velit vitae tellus sodales, vulputate semper purus porta.",
|
|
"Cras ultricies orci in est elementum, at laoreet erat tempus.",
|
|
"In non magna et lorem sagittis sollicitudin sit amet et est.",
|
|
"Etiam vitae augue ac turpis volutpat iaculis a vitae enim.",
|
|
"Integer pharetra quam ac tortor porta dignissim.",
|
|
"Pellentesque ullamcorper neque vitae ligula rhoncus accumsan.",
|
|
"Nullam in lectus sit amet est faucibus elementum vitae vel risus.",
|
|
"Aenean vehicula libero ut convallis blandit.",
|
|
"Aenean id mi facilisis, tristique enim vel, egestas lorem.",
|
|
"Mauris suscipit dui eget neque gravida, vel pellentesque leo gravida.",
|
|
"Quisque quis elit at velit maximus viverra ultricies in nisi.",
|
|
"Vivamus et orci nec magna hendrerit egestas sed quis arcu.",
|
|
"Suspendisse semper tortor sed justo iaculis volutpat.",
|
|
"Praesent interdum dolor nec ultricies imperdiet.",
|
|
"Vivamus tristique justo quis tellus commodo, at faucibus justo auctor.",
|
|
"Praesent pharetra tellus vel nunc mattis pharetra.",
|
|
"Cras a dui quis arcu rutrum ullamcorper sit amet et sem.",
|
|
"Aenean porttitor risus ac enim tempor posuere.",
|
|
"Mauris bibendum augue ac vehicula mattis.",
|
|
"Vestibulum nec justo vehicula, euismod enim sed, convallis magna.",
|
|
"Praesent ultrices elit vitae velit dignissim dignissim.",
|
|
"Curabitur vehicula velit vitae tortor consequat consectetur sit amet at leo.",
|
|
"Sed lobortis neque a magna facilisis aliquam.",
|
|
"Phasellus a libero in sem aliquam varius.",
|
|
"Mauris tincidunt ligula a risus efficitur euismod.",
|
|
"Sed pharetra diam ac neque tempus convallis.",
|
|
"Donec at ipsum elementum ex hendrerit laoreet mollis non elit.",
|
|
"Praesent eu arcu sollicitudin, fermentum tellus at, blandit dolor.",
|
|
"Curabitur in lectus consequat, bibendum ligula vitae, semper lacus.",
|
|
"Aenean eu risus non sem pretium dictum.",
|
|
"Praesent nec risus vestibulum quam venenatis tempor.",
|
|
"Nullam rhoncus ex a quam egestas, eu auctor enim lobortis.",
|
|
"Nam luctus ante id lacus scelerisque, quis blandit ante elementum.",
|
|
|
|
];
|
|
|
|
private function generatedfeedback() {
|
|
if (file_exists("/usr/games/fortune")) {
|
|
// Get a fortune if it is available.
|
|
return shell_exec("/usr/games/fortune -n 160 -e disclaimer literature science pratchett wisdom education");
|
|
} else {
|
|
// Get a random loremipsum string.
|
|
return self::$loremipsum[rand(0, count(self::$loremipsum) - 1)];
|
|
}
|
|
}
|
|
|
|
public function __construct() {
|
|
|
|
}
|
|
|
|
public function addstudent(string $student) {
|
|
if (!array_key_exists($student, $this->table)) {
|
|
$this->table[$student] = [
|
|
"intelligence" => rand(70, 100),
|
|
"endurance" => rand(70, 100),
|
|
"skills" => [],
|
|
];
|
|
}
|
|
}
|
|
|
|
public function addskill(string $student, string $skill) {
|
|
$this->addstudent($student);
|
|
if (!array_key_exists($skill, $this->table[$student]["skills"])) {
|
|
$int = $this->table[$student]["intelligence"];
|
|
$end = $this->table[$student]["endurance"];
|
|
$this->table[$student]["skills"][$skill] = [
|
|
"intelligence" => min(100, $int + rand(-30, 30)),
|
|
"endurance" => min(100, $end + rand(-10, 10)),
|
|
];
|
|
}
|
|
}
|
|
|
|
// Below is mostly just for reference in the json file.
|
|
public function add_username_info(string $student, $firstname, $lastname) {
|
|
$this->addstudent($student);
|
|
$this->table[$student]["firstname"] = $firstname;
|
|
$this->table[$student]["lastname"] = $lastname;
|
|
}
|
|
|
|
public function generateraw($student, $skill, $count ) {
|
|
$this->addskill($student, $skill);
|
|
$int = $this->table[$student]["skills"][$skill]["intelligence"];
|
|
$end = $this->table[$student]["skills"][$skill]["endurance"];
|
|
|
|
$results = [];
|
|
$gaveup = false;
|
|
for ($i = 0; $i < $count; $i++) {
|
|
$r = new \stdClass;
|
|
if ($gaveup) {
|
|
$r->done = !$gaveup;
|
|
} else {
|
|
$r->done = (rand(0, $end) > 20); // Determine if the assignment was done.
|
|
}
|
|
if ($r->done) {
|
|
$score = rand(0, $int);
|
|
$r->result = ($score > 20); // Determine if the assignment was successful.
|
|
if (!$r->result) {
|
|
$r->failed = !($score > 10);
|
|
}
|
|
} else {
|
|
$r->result = false; // Make sure a result property is always there.
|
|
$r->failed = true;
|
|
}
|
|
// Aways generate a little feedback.
|
|
$r->fb = $this->generatedfeedback();
|
|
$results[] = $r;
|
|
|
|
if (!$gaveup && $i >= 3) {
|
|
/* There is a slight chance the students with low endurance
|
|
for this course will stop with this course's work entirely.
|
|
*/
|
|
$gaveup = (rand(0, $end) < 15);
|
|
}
|
|
}
|
|
return $results;
|
|
}
|
|
|
|
public function generate($student, $skill, array $gradeinfos ) {
|
|
global $DB;
|
|
$table = "local_treestudyplan_gradecfg";
|
|
|
|
$rlist = [];
|
|
$gen = $this->generateraw($student, $skill, count($gradeinfos));
|
|
|
|
for ($i = 0; $i < count($gradeinfos); $i++) {
|
|
$g = $gradeinfos[$i];
|
|
$gi = $g->get_gradeitem();
|
|
$gr = $gen[$i];
|
|
|
|
// First get the configured interpretation for this scale or grade.
|
|
$scale = $gi->load_scale();
|
|
if ( isset($scale)) {
|
|
$gradecfg = $DB->get_record($table, ["scale_id" => $scale->id]);
|
|
} else if ($gi->grademin == 0) {
|
|
$gradecfg = $DB->get_record($table, ["grade_points" => $gi->grademax]);
|
|
} else {
|
|
$gradecfg = null;
|
|
}
|
|
|
|
// Next generate the grade.
|
|
if ($gradecfg) {
|
|
if (!$gr->done) {
|
|
// INCOMPLETE.
|
|
// Fair chance of teacher forgetting to set incomplete to "no evidence".
|
|
$grade = 0;
|
|
$r = (object)["gi" => $g, "grade" => $grade, "fb" => "" ];
|
|
} else if (!$gr->result) {
|
|
$grade = rand($gradecfg->min_progress, $gradecfg->min_completed - 1 );
|
|
$r = (object)["gi" => $g, "grade" => $grade, "fb" => $gr->fb ];
|
|
} else {
|
|
// COMPLETED.
|
|
$r = (object)["gi" => $g, "grade" => rand( $gradecfg->min_completed, $gi->grademax ), "fb" => $gr->fb ];
|
|
}
|
|
|
|
$r->gradetext = $r->grade;
|
|
if ( isset($scale)) {
|
|
$scaleitems = $scale->load_items();
|
|
if ($r->grade > 0) {
|
|
$r->gradetext = trim($scale->get_nearest_item($r->grade));
|
|
} else {
|
|
$r->gradetext = "-";
|
|
}
|
|
}
|
|
|
|
} else if ($gi->gradepass > 0) {
|
|
if (!$gr->done) {
|
|
// INCOMPLETe or FAILED.
|
|
$grade = rand(0, $gi->gradepass / 2);
|
|
$r = (object)["gi" => $g, "grade" => $grade, "fb" => ($grade > 0) ? $gr->fb : "" ];
|
|
} else if (!$gr->result) {
|
|
// PROGRESS.
|
|
$r = (object)["gi" => $g, "grade" => rand( round($gi->gradepass / 2), $gi->gradepass - 1 ), "fb" => $gr->fb ];
|
|
} else {
|
|
// COMPLETED.
|
|
$r = (object)["gi" => $g, "grade" => rand( $gi->gradepass, $gi->grademax ), "fb" => $gr->fb ];
|
|
}
|
|
|
|
$r->gradetext = $r->grade;
|
|
} else {
|
|
// Blind assumptions if nothing is provided.
|
|
// Over 55% of range is completed.
|
|
// Under 35% is not done.
|
|
$range = floatval($gi->grademax - $gi->grademin);
|
|
|
|
if (!$gr->done) {
|
|
// INCOMPLETe or FAILED.
|
|
$grade = rand(0, round($range * 0.35) - 1);
|
|
$r = (object)[
|
|
"gi" => $g,
|
|
"grade" => $gi->grademin + $grade,
|
|
"fb" => ($grade > 0) ? $gr->fb : ""
|
|
];
|
|
} else if (!$gr->result) {
|
|
// PROGRESS.
|
|
$r = (object)[
|
|
"gi" => $g,
|
|
"grade" => $gi->grademin + rand(round($range * 0.35), round($range * 0.55) - 1 ),
|
|
"fb" => $gr->fb
|
|
];
|
|
} else {
|
|
// COMPLETED.
|
|
$r = (object)[
|
|
"gi" => $g,
|
|
"grade" => $gi->grademin + rand(round($range * 0.55) , $range ),
|
|
"fb" => $gr->fb
|
|
];
|
|
}
|
|
|
|
$r->gradetext = $r->grade;
|
|
}
|
|
|
|
$rlist[] = $r;
|
|
|
|
}
|
|
|
|
return $rlist;
|
|
}
|
|
|
|
public function getstats($student) {
|
|
return $this->table[$student];
|
|
}
|
|
public function serialize(): ?string {
|
|
return json_encode([
|
|
"table" => $this->table], JSON_PRETTY_PRINT);
|
|
}
|
|
|
|
public function unserialize(string $data): void {
|
|
$o = json_decode($data, true);
|
|
$this->table = $o["table"];
|
|
}
|
|
|
|
public function to_file(string $filename) {
|
|
$filename = self::expand_tilde($filename);
|
|
file_put_contents($filename, $this->serialize());
|
|
}
|
|
|
|
public function from_file(string $filename) {
|
|
$filename = self::expand_tilde($filename);
|
|
if (file_exists($filename)) {
|
|
try {
|
|
$json = file_get_contents($filename);
|
|
$this->unserialize($json);
|
|
} catch (Exception $x) {
|
|
cli_problem("ERROR loading from file");
|
|
throw $x; // Throw X up again to show the output.
|
|
}
|
|
}
|
|
}
|
|
|
|
private static function expand_tilde($path) {
|
|
if (function_exists('posix_getuid') && strpos($path, '~') !== false) {
|
|
$info = posix_getpwuid(posix_getuid());
|
|
$path = str_replace('~', $info['dir'], $path);
|
|
}
|
|
|
|
return $path;
|
|
}
|
|
|
|
}
|