]> git.localhorst.tv Git - alttp.git/blobdiff - app/Models/ChatLib.php
respond to whispers
[alttp.git] / app / Models / ChatLib.php
index d6d4f56e71bc57ffef6d545cc8f2913402070fa2..d6a7ffcbbe7906bef5ae3e25b40cdf0e080cad71 100644 (file)
@@ -2,59 +2,82 @@
 
 namespace App\Models;
 
+use Illuminate\Support\Facades\Storage;
+
 class ChatLib {
 
-       public function addMessage($msg) {
-               $tokens = array_values(array_filter(preg_split('/\b/', $msg->text_content)));
-               if (empty($tokens)) return;
-               $tokens [] = '';
-               foreach ($tokens as $num => $token) {
-                       if ($num === 0) {
-                               $this->addStart($token);
-                       } else if ($num === 1) {
-                               $this->addOne($tokens[0], $token);
-                       } else if ($num === 2) {
-                               $this->addOne($tokens[1], $token);
-                               $this->addTwo($tokens[0], $tokens[1], $token);
-                       } else {
-                               $this->addOne($tokens[$num - 1], $token);
-                               $this->addTwo($tokens[$num - 2], $tokens[$num - 1], $token);
-                               $this->addThree($tokens[$num - 3], $tokens[$num - 2], $tokens[$num - 1], $token);
+       public function __construct($size = 3) {
+               $this->size = $size;
+               $converted = [];
+               foreach ($this->categories as $category => $patterns) {
+                       $converted_patterns = [];
+                       foreach ($patterns as $pattern) {
+                               $converted_patterns[] = '/\b'.$pattern.'\b/u';
                        }
+                       $converted[strtoupper($category)] = $converted_patterns;
                }
+               $this->categories = $converted;
        }
 
-       public function compile() {
-               $this->start = $this->index($this->start);
-               foreach ($this->one as $key => $value) {
-                       $this->one[$key] = $this->index($this->one[$key]);
+       public function addMessage(ChatLog $msg, ChatLog $previous = null) {
+               if ($msg->isReply()) {
+                       $this->addText($msg->text_content, $msg->getReplyParent());
+               } else if (!is_null($previous)) {
+                       $this->addText($msg->text_content, $previous->text_content);
+               } else {
+                       $this->addText($msg->text_content);
+               }
+       }
+
+       public function addText($text, $context = '') {
+               $tokens = $this->tokenize($text);
+               for ($i = 0; $i < count($tokens) - $this->size; ++$i) {
+                       $this->addTransition(array_slice($tokens, $i, $this->size), $tokens[$i + $this->size]);
                }
-               foreach ($this->two as $key => $value) {
-                       $this->two[$key] = $this->index($this->two[$key]);
+               if (!empty($context)) {
+                       $tokens = $this->tokenizeWithContext($text, $context);
+                       $size = min($this->size - 1, count($tokens) - $this->size);
+                       for ($i = 0; $i < $size; ++$i) {
+                               $this->addTransition(array_slice($tokens, $i, $this->size), $tokens[$i + $this->size]);
+                       }
                }
-               foreach ($this->three as $key => $value) {
-                       $this->three[$key] = $this->index($this->three[$key]);
+       }
+
+       public function compile() {
+               foreach ($this->transitions as $key => $values) {
+                       $this->transitions[$key] = $this->index($values);
                }
        }
 
-       public function generate($limit = 50) {
-               $tokens = [];
-               $start = $this->randomStart();
-               $tokens[] = $start;
-               $generated = $start;
-               while (strlen($generated) < $limit) {
-                       $next = $this->randomNext($tokens);
-                       if (empty($next)) break;
-                       $tokens[] = $next;
-                       $generated .= $next;
+       public function generate($context = null) {
+               if (!is_null($context)) {
+                       $tokens = $this->tokenizeWithContext('', $context);
+                       $generated = $this->loop($tokens);
+                       if (!empty($generated)) {
+                               return $generated;
+                       }
                }
-               return $generated;
+               $tokens = $this->tokenize('');
+               return $this->loop($tokens);
+       }
+
+       public function saveAs($name) {
+               $data = [
+                       'size' => $this->size,
+                       'transitions' => $this->transitions,
+               ];
+               Storage::disk('chatlib')->put($name.'.json', json_encode($data));
+       }
+
+       public function loadFrom($name) {
+               $data = json_decode(Storage::disk('chatlib')->get($name.'.json'), true);
+               $this->size = $data['size'];
+               $this->transitions = $data['transitions'];
        }
 
        private function index($arr) {
                $result = [];
                $sum = 0;
-               asort($arr);
                foreach ($arr as $key => $weight) {
                        $lower = $sum;
                        $sum += $weight;
@@ -63,42 +86,36 @@ class ChatLib {
                return $result;
        }
 
-       private function randomStart() {
-               return $this->pick($this->start);
+       private function loop($tokens) {
+               while (count($tokens) < 50) {
+                       $next = $this->randomNext($tokens);
+                       if ($next === ' ') break;
+                       $tokens[] = $next;
+               }
+               return $this->untokenize($tokens);
        }
 
        private function randomNext($tokens) {
-               $cnt = count($tokens);
-               if ($cnt >= 3) {
-                       $cmb = $tokens[$cnt - 3].$tokens[$cnt - 2].$tokens[$cnt - 1];
-                       if (isset($this->three[$cmb])) {
-                               return $this->pick($this->three[$cmb]);
-                       }
-               }
-               if ($cnt >= 2) {
-                       $cmb = $tokens[$cnt - 2].$tokens[$cnt - 1];
-                       if (isset($this->two[$cmb])) {
-                               return $this->pick($this->two[$cmb]);
-                       }
-               }
-               if ($cnt >= 1) {
-                       $cmb = $tokens[$cnt - 1];
-                       if (isset($this->one[$cmb])) {
-                               return $this->pick($this->one[$cmb]);
-                       }
-               }
-               return '';
+               $key = $this->makeKey($tokens);
+               if (!isset($this->transitions[$key])) return ' ';
+               $pick = $this->pick($this->transitions[$key]);
+               return $pick[0];
        }
 
        private function pick($options) {
-               $max = end($options)[2];
+               if (empty($options)) return null;
+               $max = end($options)[2] - 1;
                $num = random_int(0, $max);
+               return static::search($options, $num);
+       }
+
+       public static function search($options, $num) {
                $min_index = 0;
                $max_index = count($options) - 1;
                while ($min_index < $max_index) {
                        $cur_index = intval(($min_index + $max_index) / 2);
                        $cur_low = $options[$cur_index][1];
-                       $cur_high = $options[$cur_index][2];
+                       $cur_high = $options[$cur_index][2] - 1;
                        if ($cur_low > $num) {
                                $max_index = $cur_index;
                        } else if ($cur_high < $num) {
@@ -108,56 +125,328 @@ class ChatLib {
                                break;
                        }
                }
-               return $options[$min_index][0];
+               return $options[$min_index];
        }
 
-       private function addStart($token) {
-               if (empty($token)) return;
-               if (!isset($this->start[$token])) {
-                       $this->start[$token] = 1;
+       private function addTransition($tokens, $next) {
+               $key = $this->makeKey($tokens);
+               if (!isset($this->transitions[$key])) {
+                       $this->transitions[$key] = [];
+               }
+               if (!isset($this->transitions[$key][$next])) {
+                       $this->transitions[$key][$next] = 1;
                } else {
-                       ++$this->start[$token];
+                       ++$this->transitions[$key][$next];
                }
        }
 
-       private function addOne($one, $token) {
-               if (!isset($this->one[$one])) {
-                       $this->one[$one] = [];
-               }
-               if (!isset($this->one[$one][$token])) {
-                       $this->one[$one][$token] = 1;
-               } else {
-                       ++$this->one[$one][$token];
-               }
+       private function splitText($text) {
+               if (trim($text) === '') return [];
+               return preg_split('/\s+/u', $text);
        }
 
-       private function addTwo($one, $two, $token) {
-               $cmb = $one.$two;
-               if (!isset($this->two[$cmb])) {
-                       $this->two[$cmb] = [];
-               }
-               if (!isset($this->two[$cmb][$token])) {
-                       $this->two[$cmb][$token] = 1;
-               } else {
-                       ++$this->two[$cmb][$token];
+       private function makeKey($tokens) {
+               $key = $this->joinText(array_slice($tokens, $this->size * -1));
+               $key = mb_strtolower($key);
+               $key = str_replace(['.', ',', ':', ';', '!', '?', '^', '+', '-', '"', "'", '(', ')', '[', ']'], '', $key);
+               $key = preg_replace('/\d+/u', '0', $key);
+               foreach ($this->categories as $category => $patterns) {
+                       $key = preg_replace($patterns, $category, $key);
                }
+               return $key;
        }
 
-       private function addThree($one, $two, $three, $token) {
-               $cmb = $one.$two.$three;
-               if (!isset($this->three[$cmb])) {
-                       $this->three[$cmb] = [];
+       private function joinText($tokens) {
+               return implode(' ', $tokens);
+       }
+
+       private function untokenize($tokens) {
+               return $this->joinText(array_slice($tokens, $this->size));
+       }
+
+       private function tokenize($text) {
+               $tokens = $this->splitText($text);
+               $combined = array_merge(array_fill(0, $this->size, ' '), $tokens);
+               if (!empty($tokens)) {
+                       $combined[] = ' ';
                }
-               if (!isset($this->three[$cmb][$token])) {
-                       $this->three[$cmb][$token] = 1;
-               } else {
-                       ++$this->three[$cmb][$token];
+               return $combined;
+       }
+
+       private function tokenizeWithContext($text, $context) {
+               $combined = $this->tokenize($text);
+               $context_tokens = array_slice($this->splitText($context), $this->size * -1 + 1);
+               for ($i = 0; $i < count($context_tokens); ++$i) {
+                       $combined[$this->size - $i - 2] = $context_tokens[count($context_tokens) - $i - 1];
                }
+               return $combined;
        }
 
-       private $start = [];
-       private $one = [];
-       private $two = [];
-       private $three = [];
+       private $size;
+       private $transitions = [];
+
+       private $categories = [
+               'fail' => [
+                       'failfish',
+                       'holysm0notlikethis',
+                       'notlikethis',
+                       'tetobridge0',
+                       'vinter0clown',
+               ],
+
+               'hype' => [
+                       'dergoaparty',
+                       'dinodance',
+                       'elemen0party',
+                       'muftaahype',
+                       'luckwuhype',
+                       'olliwahype',
+                       'osora0umbrihype',
+                       'partyhat',
+                       'peepocheer',
+                       'rei0hype',
+                       'sakayahype',
+                       'tetotroete',
+                       'ticknaboargeil0',
+                       'ticknahype0',
+               ],
+
+               'kappa' => [
+                       'kappa(claus|hd)?',
+               ],
+
+               'jam' => [
+                       '(cat|dog|rat)jam',
+                       'kanash0jam',
+                       'rei0jamers',
+                       'samusdance',
+               ],
+
+               'lol' => [
+                       ':d',
+                       'boothi0lul',
+                       'kekw',
+                       'lol',
+                       'lul',
+                       'rei0lul',
+                       'samusgrin',
+                       'ticknaauslachen',
+                       'xd',
+               ],
+
+               'love' => [
+                       '<3',
+                       'duden0love',
+                       'exec0love',
+                       'krawal0heart',
+                       'lodanzhug',
+                       'luckwulove',
+                       'luvsign',
+                       'muftaal',
+                       'osora0love',
+                       'peepoexcitedhug',
+                       'spirit0love',
+                       'svenkalove',
+                       'ticknaherz',
+               ],
+
+               'name' => [
+                       'baba',
+                       'baka',
+                       'bobe?r',
+                       'brog(i|or)',
+                       'cfate',
+                       'danny',
+                       'danzi+',
+                       'daruck',
+                       'dennsen',
+                       'dimez',
+                       'divi',
+                       'dud(en|i+)',
+                       'ele',
+                       'eri(ror)?',
+                       '(name)?faker',
+                       'fetti+',
+                       'gamma(chuu)?',
+                       'goat(buster|ie?|y)?',
+                       'hitsu(yan)?',
+                       'holy',
+                       'jem',
+                       'kala(marino)?',
+                       'kromb',
+                       'koval',
+                       'kum(i|o|p)',
+                       'lanux',
+                       'len(esha|chen)',
+                       'leya+',
+                       'magno',
+                       'malmo',
+                       'markam',
+                       'micha',
+                       'mimsy',
+                       'muf(fy|taay)',
+                       'murd(elizer|i+)',
+                       'nami',
+                       'nula',
+                       'onio',
+                       'paulinche',
+                       'phaaze',
+                       'ralen',
+                       'ramond',
+                       'ray(vis)?',
+                       'schulzer',
+                       'skunk(ner)?',
+                       'skipsy',
+                       'soli+',
+                       'sven(ka+)?',
+                       'tantalus',
+                       'teto',
+                       'thalanee?',
+                       'tick(i+|naldo|y+)',
+                       'tofu',
+                       'tr[i0]x+',
+                       'vin(nie?|ny|ter)',
+                       'xall',
+                       'yasi',
+               ],
+
+               'pog' => [
+                       'bumble0Pog',
+                       'komodohype',
+                       'pog',
+                       'pogchamp',
+                       'poggers',
+                       'satono0pog',
+               ],
+
+               'run' => [
+                       'dennsenboots',
+                       'lodanzrun',
+                       'ticknaldosprint',
+                       'vinter0run',
+               ],
+
+               'wave' => [
+                       'dennsenhi',
+                       'dergoawave',
+                       'falcnwavehi',
+                       'heyguys',
+                       'holysm0heyguys',
+                       'muftaahey',
+                       'rei0wave',
+                       'sayuri0wave',
+                       'shindi0wave',
+                       'svenkawave',
+                       'wuschlwave',
+               ],
+
+               'zb' => [
+                       'aga(hnim)?',
+                       'armos( knights)?',
+                       'arrghus',
+                       'blind',
+                       'ganon(dorf)?',
+                       'helma',
+                       'kholdstare',
+                       'lanmo(las)?',
+                       'moldorm',
+                       'mothula',
+                       'mott[ei]',
+                       'trinexx',
+                       'vit(reous|ty)',
+               ],
+
+               'zd' => [
+                       'eastern',
+                       'desert( palace)?',
+                       'gt',
+                       'hera',
+                       'ice ?(palace)?',
+                       '(misery )?mire',
+                       'pod',
+                       'skull ?woods',
+                       'swamp',
+                       'thieve\'?s\'? ?town',
+                       'tr',
+                       'tt',
+               ],
+
+               'zi' => [
+                       '(big|small|retro|generic) ?keys?',
+                       'b[oö]gen',
+                       'bombos',
+                       'boots',
+                       'bottle',
+                       'bows?',
+                       'bugnet',
+                       'byrna',
+                       'cape',
+                       'ether',
+                       'flasche',
+                       'flippers',
+                       'fl[uö]te',
+                       'frod',
+                       '(gloves?|mitts|handschuhe?)',
+                       '(half|quarter) ?magic',
+                       'hammer',
+                       'hookshot',
+                       '(ice|fire) ?rod',
+                       'lampe?',
+                       'laser ?bridge',
+                       'mearl',
+                       'mirror',
+                       'moon ?pearl',
+                       'mushroom',
+                       'ocarina',
+                       'pilz',
+                       'powder',
+                       'puder',
+                       'quake',
+                       '(red|blue) ?cane',
+                       '(red|green|blue) ?(goo|potion)',
+                       '(red|green|blue|baby) ?mail',
+                       '(red|blue|bu|boo|good|bad|both)merang',
+                       'schaufel',
+                       '(gro(ss|ß)er? |kleiner? )?schlüssel',
+                       'schwert',
+                       'shovel',
+                       'silvers',
+                       'somaria',
+                       'spiegel',
+                       'sword',
+               ],
+
+               'zl' => [
+                       'big chest',
+                       'bumper( cave)?( ledge)?',
+                       '(hyrule)? ?castle ?(tower)?',
+                       'catfish',
+                       'cave 0?',
+                       'chest ?game',
+                       'cutscene ?chest',
+                       'damm',
+                       'desert( ledge)?',
+                       'dig(ging)? ?game',
+                       '((back|front) of )?escape',
+                       'gyl',
+                       'hobo',
+                       'hook ?(shot) cave',
+                       'lava ?chest',
+                       '(light|dark) ?world',
+                       'lss',
+                       'magic bat',
+                       '(dark )?(death )?mountain',
+                       'ped(estal)?',
+                       'pyramid( fairy)?( ledge)?',
+                       'red bomb',
+                       'sahasrahla',
+                       'sasha',
+                       'sick kid',
+                       'stumpy',
+                       'tile ?room',
+                       'torch',
+                       'zora( ledge)?',
+               ],
+       ];
 
 }