5 use Illuminate\Support\Facades\Storage;
9 public function __construct($size = 3) {
12 foreach ($this->categories as $category => $patterns) {
13 $converted_patterns = [];
14 foreach ($patterns as $pattern) {
15 $converted_patterns[] = '/\b'.$pattern.'\b/u';
17 $converted[strtoupper($category)] = $converted_patterns;
19 $this->categories = $converted;
22 public function addMessage(ChatLog $msg, ChatLog $previous = null) {
23 if ($msg->isReply()) {
24 $this->addText($msg->text_content, $msg->getReplyParent());
25 } else if (!is_null($previous)) {
26 $this->addText($msg->text_content, $previous->text_content);
28 $this->addText($msg->text_content);
32 public function addText($text, $context = '') {
33 $tokens = $this->tokenize($text);
34 for ($i = 0; $i < count($tokens) - $this->size; ++$i) {
35 $this->addTransition(array_slice($tokens, $i, $this->size), $tokens[$i + $this->size]);
37 if (!empty($context)) {
38 $tokens = $this->tokenizeWithContext($text, $context);
39 $size = min($this->size - 1, count($tokens) - $this->size);
40 for ($i = 0; $i < $size; ++$i) {
41 $this->addTransition(array_slice($tokens, $i, $this->size), $tokens[$i + $this->size]);
46 public function compile() {
47 foreach ($this->transitions as $key => $values) {
48 $this->transitions[$key] = $this->index($values);
52 public function generate($context = null) {
53 if (!is_null($context)) {
54 $tokens = $this->tokenizeWithContext('', $context);
55 $generated = $this->loop($tokens);
56 if (!empty($generated)) {
60 $tokens = $this->tokenize('');
61 return $this->loop($tokens);
64 public function saveAs($name) {
66 'size' => $this->size,
67 'transitions' => $this->transitions,
69 Storage::disk('chatlib')->put($name.'.json', json_encode($data));
72 public function loadFrom($name) {
73 $data = json_decode(Storage::disk('chatlib')->get($name.'.json'), true);
74 $this->size = $data['size'];
75 $this->transitions = $data['transitions'];
78 private function index($arr) {
81 foreach ($arr as $key => $weight) {
84 $result[] = [$key, $lower, $sum];
89 private function loop($tokens) {
90 while (count($tokens) < 50) {
91 $next = $this->randomNext($tokens);
92 if ($next === ' ') break;
95 return $this->untokenize($tokens);
98 private function randomNext($tokens) {
99 $key = $this->makeKey($tokens);
100 if (!isset($this->transitions[$key])) return ' ';
101 $pick = $this->pick($this->transitions[$key]);
105 private function pick($options) {
106 if (empty($options)) return null;
107 $max = end($options)[2] - 1;
108 $num = random_int(0, $max);
109 return static::search($options, $num);
112 public static function search($options, $num) {
114 $max_index = count($options) - 1;
115 while ($min_index < $max_index) {
116 $cur_index = intval(($min_index + $max_index) / 2);
117 $cur_low = $options[$cur_index][1];
118 $cur_high = $options[$cur_index][2] - 1;
119 if ($cur_low > $num) {
120 $max_index = $cur_index;
121 } else if ($cur_high < $num) {
122 $min_index = $cur_index + 1;
124 $min_index = $cur_index;
128 return $options[$min_index];
131 private function addTransition($tokens, $next) {
132 $key = $this->makeKey($tokens);
133 if (!isset($this->transitions[$key])) {
134 $this->transitions[$key] = [];
136 if (!isset($this->transitions[$key][$next])) {
137 $this->transitions[$key][$next] = 1;
139 ++$this->transitions[$key][$next];
143 private function splitText($text) {
144 if (trim($text) === '') return [];
145 return preg_split('/\s+/u', $text);
148 private function makeKey($tokens) {
149 $key = $this->joinText(array_slice($tokens, $this->size * -1));
150 $key = mb_strtolower($key);
151 $key = str_replace(['.', ',', ':', ';', '!', '?', '^', '+', '-', '"', "'", '(', ')', '[', ']'], '', $key);
152 $key = preg_replace('/\d+/u', '0', $key);
153 foreach ($this->categories as $category => $patterns) {
154 $key = preg_replace($patterns, $category, $key);
159 private function joinText($tokens) {
160 return implode(' ', $tokens);
163 private function untokenize($tokens) {
164 return $this->joinText(array_slice($tokens, $this->size));
167 private function tokenize($text) {
168 $tokens = $this->splitText($text);
169 $combined = array_merge(array_fill(0, $this->size, ' '), $tokens);
170 if (!empty($tokens)) {
176 private function tokenizeWithContext($text, $context) {
177 $combined = $this->tokenize($text);
178 $context_tokens = array_slice($this->splitText($context), $this->size * -1 + 1);
179 for ($i = 0; $i < count($context_tokens); ++$i) {
180 $combined[$this->size - $i - 2] = $context_tokens[count($context_tokens) - $i - 1];
186 private $transitions = [];
188 private $categories = [
191 'holysm0notlikethis',
271 'goat(buster|ie?|y)?',
369 'thieve\'?s\'? ?town',
375 '(big|small|retro|generic) ?keys?',
389 '(gloves?|mitts|handschuhe?)',
390 '(half|quarter) ?magic',
406 '(red|green|blue) ?(goo|potion)',
407 '(red|green|blue|baby) ?mail',
408 '(red|blue|bu|boo|good|bad|both)merang',
410 '(gro(ss|ß)er? |kleiner? )?schlüssel',
421 'bumper( cave)?( ledge)?',
422 '(hyrule)? ?castle ?(tower)?',
430 '((back|front) of )?escape',
435 '(light|dark) ?world',
438 '(dark )?(death )?mountain',
440 'pyramid( fairy)?( ledge)?',