5 use Illuminate\Support\Facades\Storage;
9 public function addMessage(ChatLog $msg) {
10 $this->addText($msg->text_content);
13 public function addText($text) {
14 $tokens = $this->tokenize($text);
15 if (empty($tokens)) return;
17 foreach ($tokens as $num => $token) {
19 $this->addTransition([], $token);
21 $start = max(0, $num - $this->size - 1);
23 for ($i = $start; $i < $end; ++$i) {
24 $this->addTransition(array_slice($tokens, $i, $end - $i), $token);
25 if ($end - $i < 5) break;
31 public function compile() {
32 foreach ($this->transitions as $key => $value) {
33 $this->transitions[$key] = $this->index($this->transitions[$key]);
34 if (empty($this->transitions[$key])) {
35 unset($this->transitions[$key]);
40 public function generate($limit = 100) {
43 while (strlen($generated) < $limit) {
44 $next = $this->randomNext($tokens);
45 if ($next === '') break;
52 public function saveAs($name) {
54 'size' => $this->size,
55 'transitions' => $this->transitions,
57 Storage::disk('chatlib')->put($name.'.json', json_encode($data));
60 public function loadFrom($name) {
61 $data = json_decode(Storage::disk('chatlib')->get($name.'.json'), true);
62 $this->size = $data['size'];
63 $this->transitions = $data['transitions'];
66 private function index($arr) {
69 foreach ($arr as $key => $entry) {
70 $weight = $entry['count'];
71 if ($weight == 1) continue;
75 if (is_array(end($entry['examples']))) {
77 $examples = $entry['examples'];
78 } else if ($key === ' ') {
79 $examples = [[' ', 0, 1]];
82 foreach ($entry['examples'] as $example => $subweight) {
84 $subsum += $subweight;
85 $examples[] = [$example, $sublower, $subsum];
88 $result[] = [$key, $lower, $sum, $examples];
93 private function randomNext($tokens) {
94 $cnt = count($tokens);
95 for ($size = min($this->size, $cnt); $size > 0; --$size) {
96 $cmb = $this->generalize(array_slice($tokens, -$size));
97 if (isset($this->transitions[$cmb])) {
98 $pick = $this->pick($this->transitions[$cmb]);
99 if (!is_null($pick)) {
100 return $this->exampleOf($pick);
107 private function pick($options) {
108 if (empty($options)) return null;
109 $max = end($options)[2];
110 $num = random_int(0, $max);
112 $max_index = count($options) - 1;
113 while ($min_index < $max_index) {
114 $cur_index = intval(($min_index + $max_index) / 2);
115 $cur_low = $options[$cur_index][1];
116 $cur_high = $options[$cur_index][2];
117 if ($cur_low > $num) {
118 $max_index = $cur_index;
119 } else if ($cur_high < $num) {
120 $min_index = $cur_index + 1;
122 $min_index = $cur_index;
126 return $options[$min_index];
129 private function addTransition($state, $next) {
130 $cmb = $this->generalize($state);
131 if (!isset($this->transitions[$cmb])) {
132 $this->transitions[$cmb] = [];
134 $this->increment($this->transitions[$cmb], $next);
137 private function increment(&$which, $token) {
138 $generalized = $this->generalize([$token]);
139 if (!isset($which[$generalized])) {
140 $which[$generalized] = [
144 $which[$generalized]['examples'][$token] = 1;
146 ++$which[$generalized]['count'];
147 if (!isset($which[$generalized]['examples'][$token])) {
148 $which[$generalized]['examples'][$token] = 1;
150 ++$which[$generalized]['examples'][$token];
155 private function tokenize($str) {
156 return array_values(array_filter(preg_split('/\b/u', $str), function($token) {
157 if ($token === '') return false;
158 if (preg_match('/cheer\d+/u', strtolower($token))) return false;
163 private function generalize($tokens) {
165 foreach ($tokens as $token) {
166 $replaced = preg_replace('/\d+/u', '0', $token);
167 $replaced = preg_replace('/\s+/u', ' ', $replaced);
168 $replaced = preg_replace('/(.)\1{2,}/u', '$1$1', $replaced);
169 $replaced = strtolower($replaced);
170 foreach ($this->aliases as $canonical => $variants) {
171 if (in_array($replaced, $variants)) {
172 $replaced = $canonical;
175 if ($replaced === $canonical) {
181 foreach ($this->categories as $category => $patterns) {
182 foreach ($patterns as $pattern) {
183 $str = preg_replace('/\b'.$pattern.'\b/u', '%'.strtoupper($category).'%', $str);
189 private function exampleOf($pick) {
190 $example = $this->pick($pick[3]);
195 private $transitions = [];
198 'chest' => ['kiste'],
199 'einen' => ['n', 'nen'],
200 'musik' => ['mukke'],
201 'schade' => ['schad'],
204 private $categories = [
207 'holysm0notlikethis',
287 'goat(buster|ie?|y)?',
384 'thieve\'?s\'? ?town',
390 '(big|small|retro|generic) ?keys?',
404 '(gloves?|mitts|handschuhe?)',
405 '(half|quarter) ?magic',
421 '(red|green|blue) ?(goo|potion)',
422 '(red|green|blue|baby) ?mail',
423 '(red|blue|bu|boo|good|bad|both)merang',
425 '(gro(ss|ß)er? |kleiner? )?schlüssel',
434 'zelda_location' => [
436 'bumper( cave)?( ledge)?',
437 '(hyrule)? ?castle ?(tower)?',
445 '((back|front) of )?escape',
450 '(light|dark) ?world',
453 '(dark )?(death )?mountain',
455 'pyramid( fairy)?( ledge)?',