]> git.localhorst.tv Git - alttp.git/blob - app/Models/ChatLib.php
adlib chat
[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($msg) {
10                 $tokens = $this->tokenize($msg->text_content);
11                 if (empty($tokens)) return;
12                 $tokens[] = '';
13                 foreach ($tokens as $num => $token) {
14                         if ($num === 0) {
15                                 $this->addTransition([], $token);
16                         } else {
17                                 $start = max(0, $num - $this->size);
18                                 $end = $num;
19                                 for ($i = $start; $i < $end; ++$i) {
20                                         $this->addTransition(array_slice($tokens, $i, $end - $i), $token);
21                                         if ($end - $i < 3) break;
22                                 }
23                         }
24                 }
25         }
26
27         public function compile() {
28                 foreach ($this->transitions as $key => $value) {
29                         $this->transitions[$key] = $this->index($this->transitions[$key]);
30                         if (empty($this->transitions[$key])) {
31                                 unset($this->transitions[$key]);
32                         }
33                 }
34         }
35
36         public function generate($limit = 100) {
37                 $tokens = [];
38                 $generated = '';
39                 while (strlen($generated) < $limit) {
40                         $next = $this->randomNext($tokens);
41                         if (empty($next)) break;
42                         $tokens[] = $next;
43                         $generated .= $next;
44                 }
45                 return $generated;
46         }
47
48         public function saveAs($name) {
49                 $data = [
50                         'size' => $this->size,
51                         'transitions' => $this->transitions,
52                 ];
53                 Storage::disk('chatlib')->put($name.'.json', json_encode($data));
54         }
55
56         public function loadFrom($name) {
57                 $data = json_decode(Storage::disk('chatlib')->get($name.'.json'), true);
58                 $this->size = $data['size'];
59                 $this->transitions = $data['transitions'];
60         }
61
62         private function index($arr) {
63                 $result = [];
64                 $sum = 0;
65                 foreach ($arr as $key => $entry) {
66                         $weight = $entry['count'];
67                         if ($weight == 1) continue;
68                         $lower = $sum;
69                         $sum += $weight;
70                         $examples = [];
71                         if (is_array(end($entry['examples']))) {
72                                 // already processed
73                                 $examples = $entry['examples'];
74                         } else {
75                                 $subsum = 0;
76                                 foreach ($entry['examples'] as $example => $subweight) {
77                                         $sublower = $subsum;
78                                         $subsum += $subweight * $subweight;
79                                         $examples[] = [$example, $sublower, $subsum];
80                                 }
81                         }
82                         $result[] = [$key, $lower, $sum, $examples];
83                 }
84                 return $result;
85         }
86
87         private function randomNext($tokens) {
88                 $cnt = count($tokens);
89                 for ($size = min($this->size, $cnt); $size >= 0; --$size) {
90                         $cmb = $this->generalize(array_slice($tokens, $cnt - $size, $size));
91                         if (isset($this->transitions[$cmb])) {
92                                 $pick = $this->pick($this->transitions[$cmb]);
93                                 if (!is_null($pick)) {
94                                         return $this->exampleOf($pick);
95                                 }
96                         }
97                 }
98                 return '';
99         }
100
101         private function pick($options) {
102                 if (empty($options)) return null;
103                 $max = end($options)[2];
104                 $num = random_int(0, $max);
105                 $min_index = 0;
106                 $max_index = count($options) - 1;
107                 while ($min_index < $max_index) {
108                         $cur_index = intval(($min_index + $max_index) / 2);
109                         $cur_low = $options[$cur_index][1];
110                         $cur_high = $options[$cur_index][2];
111                         if ($cur_low > $num) {
112                                 $max_index = $cur_index;
113                         } else if ($cur_high < $num) {
114                                 $min_index = $cur_index + 1;
115                         } else {
116                                 $min_index = $cur_index;
117                                 break;
118                         }
119                 }
120                 return $options[$min_index];
121         }
122
123         private function addTransition($state, $next) {
124                 $cmb = $this->generalize($state);
125                 if (!isset($this->transitions[$cmb])) {
126                         $this->transitions[$cmb] = [];
127                 }
128                 $this->increment($this->transitions[$cmb], $next);
129         }
130
131         private function increment(&$which, $token) {
132                 $generalized = $this->generalize([$token]);
133                 if (!isset($which[$generalized])) {
134                         $which[$generalized] = [
135                                 'count' => 1,
136                                 'examples' => [],
137                         ];
138                         $which[$generalized]['examples'][$token] = 1;
139                 } else {
140                         ++$which[$generalized]['count'];
141                         if (!isset($which[$generalized]['examples'][$token])) {
142                                 $which[$generalized]['examples'][$token] = 1;
143                         } else {
144                                 ++$which[$generalized]['examples'][$token];
145                         }
146                 }
147         }
148
149         private function tokenize($str) {
150                 return array_values(array_filter(preg_split('/\b/u', $str), function($token) {
151                         if (empty($token)) return false;
152                         if (preg_match('/cheer\d+/u', strtolower($token))) return false;
153                         return true;
154                 }));
155         }
156
157         private function generalize($tokens) {
158                 $str = '';
159                 foreach ($tokens as $token) {
160                         $replaced = preg_replace('/\d+/', '0', $token);
161                         $replaced = strtolower($replaced);
162                         $str .= empty($replaced) ? $token : $replaced;
163                 }
164                 return $str;
165         }
166
167         private function exampleOf($pick) {
168                 $example = $this->pick($pick[3]);
169                 return $example[0];
170         }
171
172         private $size = 5;
173         private $transitions = [];
174
175 }