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