]> git.localhorst.tv Git - alttp.git/blob - app/Models/ChatLib.php
slightly improved message generation
[alttp.git] / app / Models / ChatLib.php
1 <?php
2
3 namespace App\Models;
4
5 class ChatLib {
6
7         public function addMessage($msg) {
8                 $tokens = array_values(array_filter(preg_split('/\s+/u', $msg->text_content)));
9                 if (empty($tokens)) return;
10                 $tokens [] = '';
11                 foreach ($tokens as $num => $token) {
12                         if ($num === 0) {
13                                 $this->addStart($token);
14                         }
15                         if ($num > 0) {
16                                 $this->addOne($tokens[$num - 1], $token);
17                         }
18                         if ($num > 1) {
19                                 $this->addTwo($tokens[$num - 2], $tokens[$num - 1], $token);
20                         }
21                         if ($num > 2) {
22                                 $this->addThree($tokens[$num - 3], $tokens[$num - 2], $tokens[$num - 1], $token);
23                         }
24                         if ($num > 3) {
25                                 $this->addFour($tokens[$num - 4], $tokens[$num - 3], $tokens[$num - 2], $tokens[$num - 1], $token);
26                         }
27                         if ($num > 4) {
28                                 $this->addFive($tokens[$num - 5], $tokens[$num - 4], $tokens[$num - 3], $tokens[$num - 2], $tokens[$num - 1], $token);
29                         }
30                 }
31         }
32
33         public function compile() {
34                 $this->start = $this->index($this->start);
35                 foreach ($this->one as $key => $value) {
36                         $this->one[$key] = $this->index($this->one[$key]);
37                         if (empty($this->one[$key])) {
38                                 unset($this->one[$key]);
39                         }
40                 }
41                 foreach ($this->two as $key => $value) {
42                         $this->two[$key] = $this->index($this->two[$key]);
43                         if (empty($this->two[$key])) {
44                                 unset($this->two[$key]);
45                         }
46                 }
47                 foreach ($this->three as $key => $value) {
48                         $this->three[$key] = $this->index($this->three[$key]);
49                         if (empty($this->three[$key])) {
50                                 unset($this->three[$key]);
51                         }
52                 }
53                 foreach ($this->four as $key => $value) {
54                         $this->four[$key] = $this->index($this->four[$key]);
55                         if (empty($this->four[$key])) {
56                                 unset($this->four[$key]);
57                         }
58                 }
59                 foreach ($this->five as $key => $value) {
60                         $this->five[$key] = $this->index($this->five[$key]);
61                         if (empty($this->five[$key])) {
62                                 unset($this->five[$key]);
63                         }
64                 }
65         }
66
67         public function generate($limit = 75) {
68                 $tokens = [];
69                 $start = $this->randomStart();
70                 $tokens[] = $start;
71                 $generated = $start;
72                 while (strlen($generated) < $limit) {
73                         $next = $this->randomNext($tokens);
74                         if (empty($next)) break;
75                         $tokens[] = $next;
76                         $generated .= ' '.$next;
77                 }
78                 return $generated;
79         }
80
81         private function index($arr) {
82                 $result = [];
83                 $sum = 0;
84                 foreach ($arr as $key => $entry) {
85                         $weight = $entry['count'];
86                         if ($weight == 1) continue;
87                         $lower = $sum;
88                         $sum += intval(pow($weight, 1.4));
89                         $examples = [];
90                         if (is_array(end($entry['examples']))) {
91                                 // already processed
92                                 $examples = $entry['examples'];
93                         } else {
94                                 $subsum = 0;
95                                 foreach ($entry['examples'] as $example => $subweight) {
96                                         $sublower = $subsum;
97                                         $subsum += $subweight * $subweight;
98                                         $examples[] = [$example, $sublower, $subsum];
99                                 }
100                         }
101                         $result[] = [$key, $lower, $sum, $examples];
102                 }
103                 return $result;
104         }
105
106         private function randomStart() {
107                 $pick = $this->pick($this->start);
108                 if (is_null($pick)) return '';
109                 return $this->exampleOf($pick);
110         }
111
112         private function randomNext($tokens) {
113                 $cnt = count($tokens);
114                 $picks = [];
115                 if ($cnt >= 5) {
116                         $cmb = $this->generalize(array_slice($tokens, $cnt - 5, 5));
117                         if (isset($this->five[$cmb])) {
118                                 $pick = $this->pick($this->five[$cmb]);
119                                 if (!is_null($pick)) {
120                                         $picks[$pick[0]] = [
121                                                 'count' => 10,
122                                                 'examples' => $pick[3],
123                                         ];
124                                 }
125                         }
126                 }
127                 if ($cnt >= 4) {
128                         $cmb = $this->generalize(array_slice($tokens, $cnt - 4, 4));
129                         if (isset($this->four[$cmb])) {
130                                 $pick = $this->pick($this->four[$cmb]);
131                                 if (!is_null($pick)) {
132                                         $picks[$pick[0]] = [
133                                                 'count' => 12,
134                                                 'examples' => $pick[3],
135                                         ];
136                                 }
137                         }
138                 }
139                 if ($cnt >= 3) {
140                         $cmb = $this->generalize(array_slice($tokens, $cnt - 3, 3));
141                         if (isset($this->three[$cmb])) {
142                                 $pick = $this->pick($this->three[$cmb]);
143                                 if (!is_null($pick)) {
144                                         $picks[$pick[0]] = [
145                                                 'count' => 14,
146                                                 'examples' => $pick[3],
147                                         ];
148                                 }
149                         }
150                 }
151                 if ($cnt >= 2) {
152                         $cmb = $this->generalize(array_slice($tokens, $cnt - 2, 2));
153                         if (isset($this->two[$cmb])) {
154                                 $pick = $this->pick($this->two[$cmb]);
155                                 if (!is_null($pick)) {
156                                         $picks[$pick[0]] = [
157                                                 'count' => 4,
158                                                 'examples' => $pick[3],
159                                         ];
160                                 }
161                         }
162                 }
163                 if ($cnt >= 1) {
164                         $cmb = $this->generalize(array_slice($tokens, $cnt - 1, 1));
165                         if (isset($this->one[$cmb])) {
166                                 $pick = $this->pick($this->one[$cmb]);
167                                 if (!is_null($pick)) {
168                                         $picks[$pick[0]] = [
169                                                 'count' => 2,
170                                                 'examples' => $pick[3],
171                                         ];
172                                 }
173                         }
174                 }
175                 if (empty($picks)) return '';
176                 $picks = $this->index($picks);
177                 $pick = $this->pick($picks);
178                 return $this->exampleOf($pick);
179         }
180
181         private function pick($options) {
182                 if (empty($options)) return null;
183                 $max = end($options)[2];
184                 $num = random_int(0, $max);
185                 $min_index = 0;
186                 $max_index = count($options) - 1;
187                 while ($min_index < $max_index) {
188                         $cur_index = intval(($min_index + $max_index) / 2);
189                         $cur_low = $options[$cur_index][1];
190                         $cur_high = $options[$cur_index][2];
191                         if ($cur_low > $num) {
192                                 $max_index = $cur_index;
193                         } else if ($cur_high < $num) {
194                                 $min_index = $cur_index + 1;
195                         } else {
196                                 $min_index = $cur_index;
197                                 break;
198                         }
199                 }
200                 return $options[$min_index];
201         }
202
203         private function addStart($token) {
204                 if (empty($token)) return;
205                 $this->increment($this->start, $token);
206         }
207
208         private function addOne($one, $token) {
209                 $cmb = $this->generalize([$one]);
210                 if (!isset($this->one[$cmb])) {
211                         $this->one[$cmb] = [];
212                 }
213                 $this->increment($this->one[$cmb], $token);
214         }
215
216         private function addTwo($one, $two, $token) {
217                 $cmb = $this->generalize([$one, $two]);
218                 if (!isset($this->two[$cmb])) {
219                         $this->two[$cmb] = [];
220                 }
221                 $this->increment($this->two[$cmb], $token);
222         }
223
224         private function addThree($one, $two, $three, $token) {
225                 $cmb = $this->generalize([$one, $two, $three]);
226                 if (!isset($this->three[$cmb])) {
227                         $this->three[$cmb] = [];
228                 }
229                 $this->increment($this->three[$cmb], $token);
230         }
231
232         private function addFour($one, $two, $three, $four, $token) {
233                 $cmb = $this->generalize([$one, $two, $three, $four]);
234                 if (!isset($this->four[$cmb])) {
235                         $this->four[$cmb] = [];
236                 }
237                 $this->increment($this->four[$cmb], $token);
238         }
239
240         private function addFive($one, $two, $three, $four, $five, $token) {
241                 $cmb = $this->generalize([$one, $two, $three, $four, $five]);
242                 if (!isset($this->five[$cmb])) {
243                         $this->five[$cmb] = [];
244                 }
245                 $this->increment($this->five[$cmb], $token);
246         }
247
248         private function increment(&$which, $token) {
249                 $generalized = $this->generalize([$token]);
250                 if (!isset($which[$generalized])) {
251                         $which[$generalized] = [
252                                 'count' => 1,
253                                 'examples' => [],
254                         ];
255                         $which[$generalized]['examples'][$token] = 1;
256                 } else {
257                         ++$which[$generalized]['count'];
258                         if (!isset($which[$generalized]['examples'][$token])) {
259                                 $which[$generalized]['examples'][$token] = 1;
260                         } else {
261                                 ++$which[$generalized]['examples'][$token];
262                         }
263                 }
264         }
265
266         private function generalize($tokens) {
267                 $str = '';
268                 foreach ($tokens as $token) {
269                         $replaced = preg_replace('/\W/u', '', $token);
270                         $replaced = preg_replace('/\d+/', '0', $replaced);
271                         $replaced = strtolower(trim($replaced));
272                         $str .= empty($replaced) ? $token : $replaced;
273                 }
274                 return $str;
275         }
276
277         private function exampleOf($pick) {
278                 $example = $this->pick($pick[3]);
279                 return $example[0];
280         }
281
282         private $start = [];
283         private $one = [];
284         private $two = [];
285         private $three = [];
286         private $four = [];
287         private $five = [];
288
289 }