]> git.localhorst.tv Git - alttp.git/blob - app/Models/ChatLib.php
c89ef941be5f4c2802eb89c92cc2be6dd7bcfd9d
[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 < 4) 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 {
79                                 $subsum = 0;
80                                 foreach ($entry['examples'] as $example => $subweight) {
81                                         $sublower = $subsum;
82                                         $subsum += $subweight;
83                                         $examples[] = [$example, $sublower, $subsum];
84                                 }
85                         }
86                         $result[] = [$key, $lower, $sum, $examples];
87                 }
88                 return $result;
89         }
90
91         private function randomNext($tokens) {
92                 $cnt = count($tokens);
93                 for ($size = min($this->size, $cnt); $size > 0; --$size) {
94                         $cmb = $this->generalize(array_slice($tokens, -$size));
95                         if (isset($this->transitions[$cmb])) {
96                                 $pick = $this->pick($this->transitions[$cmb]);
97                                 if (!is_null($pick)) {
98                                         return $this->exampleOf($pick);
99                                 }
100                         }
101                 }
102                 return '';
103         }
104
105         private function pick($options) {
106                 if (empty($options)) return null;
107                 $max = end($options)[2];
108                 $num = random_int(0, $max);
109                 $min_index = 0;
110                 $max_index = count($options) - 1;
111                 while ($min_index < $max_index) {
112                         $cur_index = intval(($min_index + $max_index) / 2);
113                         $cur_low = $options[$cur_index][1];
114                         $cur_high = $options[$cur_index][2];
115                         if ($cur_low > $num) {
116                                 $max_index = $cur_index;
117                         } else if ($cur_high < $num) {
118                                 $min_index = $cur_index + 1;
119                         } else {
120                                 $min_index = $cur_index;
121                                 break;
122                         }
123                 }
124                 return $options[$min_index];
125         }
126
127         private function addTransition($state, $next) {
128                 $cmb = $this->generalize($state);
129                 if (!isset($this->transitions[$cmb])) {
130                         $this->transitions[$cmb] = [];
131                 }
132                 $this->increment($this->transitions[$cmb], $next);
133         }
134
135         private function increment(&$which, $token) {
136                 $generalized = $this->generalize([$token]);
137                 if (!isset($which[$generalized])) {
138                         $which[$generalized] = [
139                                 'count' => 1,
140                                 'examples' => [],
141                         ];
142                         $which[$generalized]['examples'][$token] = 1;
143                 } else {
144                         ++$which[$generalized]['count'];
145                         if (!isset($which[$generalized]['examples'][$token])) {
146                                 $which[$generalized]['examples'][$token] = 1;
147                         } else {
148                                 ++$which[$generalized]['examples'][$token];
149                         }
150                 }
151         }
152
153         private function tokenize($str) {
154                 return array_values(array_filter(preg_split('/\b/u', $str), function($token) {
155                         if (empty($token)) return false;
156                         if (preg_match('/cheer\d+/u', strtolower($token))) return false;
157                         return true;
158                 }));
159         }
160
161         private function generalize($tokens) {
162                 $str = '';
163                 foreach ($tokens as $token) {
164                         $replaced = preg_replace('/\d+/', '0', $token);
165                         $replaced = strtolower($replaced);
166                         $str .= $replaced;
167                 }
168                 return $str;
169         }
170
171         private function exampleOf($pick) {
172                 $example = $this->pick($pick[3]);
173                 return $example[0];
174         }
175
176         private $size = 5;
177         private $transitions = [];
178
179 }