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