]> git.localhorst.tv Git - alttp.git/blob - app/Models/ChatLib.php
f4ab93f4798b28ebc87383b94f68e75728bbc7c9
[alttp.git] / app / Models / ChatLib.php
1 <?php
2
3 namespace App\Models;
4
5 use Illuminate\Support\Facades\Storage;
6
7 class ChatLib {
8
9         public function addMessage(ChatLog $msg) {
10                 $this->addText($msg->text_content);
11         }
12
13         public function addText($text) {
14                 $tokens = $this->tokenize($text);
15                 if (empty($tokens)) return;
16                 $tokens[] = '';
17                 foreach ($tokens as $num => $token) {
18                         if ($num === 0) {
19                                 $this->addTransition([], $token);
20                         } else {
21                                 $start = max(0, $num - $this->size - 1);
22                                 $end = $num;
23                                 for ($i = $start; $i < $end; ++$i) {
24                                         $this->addTransition(array_slice($tokens, $i, $end - $i), $token);
25                                         if ($end - $i < 5) break;
26                                 }
27                         }
28                 }
29         }
30
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]);
36                         }
37                 }
38         }
39
40         public function generate($limit = 100) {
41                 $tokens = [''];
42                 $generated = '';
43                 while (strlen($generated) < $limit) {
44                         $next = $this->randomNext($tokens);
45                         if ($next === '') break;
46                         $tokens[] = $next;
47                         $generated .= $next;
48                 }
49                 return $generated;
50         }
51
52         public function saveAs($name) {
53                 $data = [
54                         'size' => $this->size,
55                         'transitions' => $this->transitions,
56                 ];
57                 Storage::disk('chatlib')->put($name.'.json', json_encode($data));
58         }
59
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'];
64         }
65
66         private function index($arr) {
67                 $result = [];
68                 $sum = 0;
69                 foreach ($arr as $key => $entry) {
70                         $weight = $entry['count'];
71                         if ($weight == 1) continue;
72                         $lower = $sum;
73                         $sum += $weight;
74                         $examples = [];
75                         if (is_array(end($entry['examples']))) {
76                                 // already processed
77                                 $examples = $entry['examples'];
78                         } else if ($key === ' ') {
79                                 $examples = [[' ', 0, 1]];
80                         } else {
81                                 $subsum = 0;
82                                 foreach ($entry['examples'] as $example => $subweight) {
83                                         $sublower = $subsum;
84                                         $subsum += $subweight;
85                                         $examples[] = [$example, $sublower, $subsum];
86                                 }
87                         }
88                         $result[] = [$key, $lower, $sum, $examples];
89                 }
90                 return $result;
91         }
92
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);
101                                 }
102                         }
103                 }
104                 return '';
105         }
106
107         private function pick($options) {
108                 if (empty($options)) return null;
109                 $max = end($options)[2];
110                 $num = random_int(0, $max);
111                 $min_index = 0;
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;
121                         } else {
122                                 $min_index = $cur_index;
123                                 break;
124                         }
125                 }
126                 return $options[$min_index];
127         }
128
129         private function addTransition($state, $next) {
130                 $cmb = $this->generalize($state);
131                 if (!isset($this->transitions[$cmb])) {
132                         $this->transitions[$cmb] = [];
133                 }
134                 $this->increment($this->transitions[$cmb], $next);
135         }
136
137         private function increment(&$which, $token) {
138                 $generalized = $this->generalize([$token]);
139                 if (!isset($which[$generalized])) {
140                         $which[$generalized] = [
141                                 'count' => 1,
142                                 'examples' => [],
143                         ];
144                         $which[$generalized]['examples'][$token] = 1;
145                 } else {
146                         ++$which[$generalized]['count'];
147                         if (!isset($which[$generalized]['examples'][$token])) {
148                                 $which[$generalized]['examples'][$token] = 1;
149                         } else {
150                                 ++$which[$generalized]['examples'][$token];
151                         }
152                 }
153         }
154
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;
159                         return true;
160                 }));
161         }
162
163         private function generalize($tokens) {
164                 $str = '';
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;
173                                         break;
174                                 }
175                                 if ($replaced === $canonical) {
176                                         break;
177                                 }
178                         }
179                         $str .= $replaced;
180                 }
181                 foreach ($this->categories as $category => $patterns) {
182                         foreach ($patterns as $pattern) {
183                                 $str = preg_replace('/\b'.$pattern.'\b/u', '%'.strtoupper($category).'%', $str);
184                         }
185                 }
186                 return $str;
187         }
188
189         private function exampleOf($pick) {
190                 $example = $this->pick($pick[3]);
191                 return $example[0];
192         }
193
194         private $size = 7;
195         private $transitions = [];
196
197         private $aliases = [
198                 'chest' => ['kiste'],
199                 'einen' => ['n', 'nen'],
200                 'musik' => ['mukke'],
201                 'schade' => ['schad'],
202         ];
203
204         private $categories = [
205                 'fail' => [
206                         'failfish',
207                         'holysm0notlikethis',
208                         'notlikethis',
209                         'tetobridge0',
210                         'vinter0clown',
211                 ],
212
213                 'hype' => [
214                         'dergoaparty',
215                         'dinodance',
216                         'elemen0party',
217                         'muftaahype',
218                         'luckwuhype',
219                         'olliwahype',
220                         'osora0umbrihype',
221                         'partyhat',
222                         'peepocheer',
223                         'rei0hype',
224                         'sakayahype',
225                         'tetotroete',
226                         'ticknaboargeil0',
227                         'ticknahype0',
228                 ],
229
230                 'kappa' => [
231                         'kappa(claus|hd)?',
232                 ],
233
234                 'jam' => [
235                         '(cat|dog|rat)jam',
236                         'kanash0jam',
237                         'rei0jamers',
238                         'samusdance',
239                 ],
240
241                 'lol' => [
242                         ':d',
243                         'boothi0lul',
244                         'kekw',
245                         'lol',
246                         'lul',
247                         'rei0lul',
248                         'samusgrin',
249                         'ticknaauslachen',
250                         'xd',
251                 ],
252
253                 'love' => [
254                         '<3',
255                         'duden0love',
256                         'exec0love',
257                         'krawal0heart',
258                         'lodanzhug',
259                         'luckwulove',
260                         'luvsign',
261                         'muftaal',
262                         'osora0love',
263                         'peepoexcitedhug',
264                         'spirit0love',
265                         'svenkalove',
266                         'ticknaherz',
267                 ],
268
269                 'name' => [
270                         'baba',
271                         'baka',
272                         'bobe?r',
273                         'brog(i|or)',
274                         'cfate',
275                         'danny',
276                         'danzi+',
277                         'daruck',
278                         'dennsen',
279                         'dimez',
280                         'divi',
281                         'dud(en|i+)',
282                         'ele',
283                         'eri(ror)?',
284                         '(name)?faker',
285                         'fetti+',
286                         'gamma(chuu)?',
287                         'goat(buster|ie?|y)?',
288                         'hitsu(yan)?',
289                         'holy',
290                         'jem',
291                         'kala(marino)?',
292                         'kromb',
293                         'koval',
294                         'kum(i|o|p)',
295                         'lanux',
296                         'len(esha|chen)',
297                         'leya+',
298                         'magno',
299                         'malmo',
300                         'markam',
301                         'micha',
302                         'mimsy',
303                         'muf(fy|taay)',
304                         'murd(elizer|i+)',
305                         'nami',
306                         'nula',
307                         'onio',
308                         'paulinche',
309                         'phaaze',
310                         'ralen',
311                         'ramond',
312                         'ray(vis)?',
313                         'schulzer',
314                         'skunk(ner)?',
315                         'skipsy',
316                         'soli+',
317                         'sven(ka+)?',
318                         'tantalus',
319                         'teto',
320                         'thalanee?',
321                         'tick(i+|naldo|y+)',
322                         'tofu',
323                         'tr[i0]x+',
324                         'vin(nie?|ny|ter)',
325                         'xall',
326                         'yasi',
327                 ],
328
329                 'pog' => [
330                         'bumble0Pog',
331                         'komodohype',
332                         'pog',
333                         'pogchamp',
334                         'poggers',
335                         'satono0pog',
336                 ],
337
338                 'run' => [
339                         'dennsenboots',
340                         'lodanzrun',
341                         'ticknaldosprint',
342                         'vinter0run',
343                 ],
344
345                 'wave' => [
346                         'dennsenhi',
347                         'dergoawave',
348                         'heyguys',
349                         'holysm0heyguys',
350                         'muftaahey',
351                         'rei0wave',
352                         'sayuri0wave',
353                         'shindi0wave',
354                         'svenkawave',
355                         'wuschlwave',
356                 ],
357
358                 'zelda_boss' => [
359                         'aga(hnim)?',
360                         'armos( knights)?',
361                         'arrghus',
362                         'blind',
363                         'ganon(dorf)?',
364                         'helma',
365                         'kholdstare',
366                         'lanmo(las)?',
367                         'moldorm',
368                         'mothula',
369                         'mott[ei]',
370                         'trinexx',
371                         'vit(reous|ty)',
372                 ],
373
374                 'zelda_dungeon' => [
375                         'eastern',
376                         'desert( palace)?',
377                         'gt',
378                         'hera',
379                         'ice ?(palace)?',
380                         '(misery )?mire',
381                         'pod',
382                         'skull ?woods',
383                         'swamp',
384                         'thieve\'?s\'? ?town',
385                         'tr',
386                         'tt',
387                 ],
388
389                 'zelda_item' => [
390                         '(big|small|retro|generic) ?keys?',
391                         'b[oö]gen',
392                         'bombos',
393                         'boots',
394                         'bottle',
395                         'bows?',
396                         'bugnet',
397                         'byrna',
398                         'cape',
399                         'ether',
400                         'flasche',
401                         'flippers',
402                         'fl[uö]te',
403                         'frod',
404                         '(gloves?|mitts|handschuhe?)',
405                         '(half|quarter) ?magic',
406                         'hammer',
407                         'hookshot',
408                         '(ice|fire) ?rod',
409                         'lampe?',
410                         'laser ?bridge',
411                         'mearl',
412                         'mirror',
413                         'moon ?pearl',
414                         'mushroom',
415                         'ocarina',
416                         'pilz',
417                         'powder',
418                         'puder',
419                         'quake',
420                         '(red|blue) ?cane',
421                         '(red|green|blue) ?(goo|potion)',
422                         '(red|green|blue|baby) ?mail',
423                         '(red|blue|bu|boo|good|bad|both)merang',
424                         'schaufel',
425                         '(gro(ss|ß)er? |kleiner? )?schlüssel',
426                         'schwert',
427                         'shovel',
428                         'silvers',
429                         'somaria',
430                         'spiegel',
431                         'sword',
432                 ],
433
434                 'zelda_location' => [
435                         'big chest',
436                         'bumper( cave)?( ledge)?',
437                         '(hyrule)? ?castle ?(tower)?',
438                         'catfish',
439                         'cave 0?',
440                         'chest ?game',
441                         'cutscene ?chest',
442                         'damm',
443                         'desert( ledge)?',
444                         'dig(ging)? ?game',
445                         '((back|front) of )?escape',
446                         'gyl',
447                         'hobo',
448                         'hook ?(shot) cave',
449                         'lava ?chest',
450                         '(light|dark) ?world',
451                         'lss',
452                         'magic bat',
453                         '(dark )?(death )?mountain',
454                         'ped(estal)?',
455                         'pyramid( fairy)?( ledge)?',
456                         'red bomb',
457                         'sahasrahla',
458                         'sasha',
459                         'sick kid',
460                         'stumpy',
461                         'tile ?room',
462                         'torch',
463                         'zora( ledge)?',
464                 ],
465         ];
466
467 }