1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
|
;;; wasp-friend --- "friend" -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
(require 'dash)
(require 's)
(require 'cl-lib)
(require 'wasp-utils)
(require 'wasp-chat)
(require 'wasp-ai)
(require 'wasp-twitch)
;;;; Buffer and mode
(defcustom w/friend-buffer "*wasp-friend*"
"Name of buffer used to display \"friend\"."
:type '(string)
:group 'wasp)
(define-derived-mode w/friend-mode special-mode "\"friend\"'s lair"
"Major mode for displaying \"friend\"'s lair."
:group 'wasp
(message "hi i'm \"friend\"")
(setq-local cursor-type nil))
(defun w/friend-get-buffer ()
"Return the \"friend\" buffer."
(unless (get-buffer w/friend-buffer)
(with-current-buffer (get-buffer-create w/friend-buffer)
(w/friend-mode)))
(get-buffer w/friend-buffer))
;;;; State
(defvar w/friend-state 'default)
(defvar w/friend-emotion "neutral")
(defvar w/friend-message-cache nil)
(defvar w/friend-state-timer 0)
(defvar w/friend-animation 1)
(defvar w/friend-speech "")
(defvar w/friend-speech-timer 0)
(defun w/friend-set-state (st &optional time)
"Set \"friend\"'s state to ST for TIME seconds."
(setf w/friend-state st)
(setf w/friend-state-timer (or time 5)))
(defun w/friend-set-speech (msg &optional time)
"Have \"friend\" say MSG for TIME."
(w/chat-write-event (s-concat "Friend says: " msg))
(setf w/friend-speech msg)
(setf w/friend-speech-timer (or time 5)))
(defun w/friend-say (msg)
"Have \"friend\" say MSG."
(w/daily-log (format "[FRIEND]: %s" msg))
(w/gizmo-ensure-shown (w/friend-get-buffer))
(w/friend-pronounce-phonemes (w/friend-replace-graphemes msg))
(w/friend-set-speech msg 30)
(w/friend-set-state 'chatting 30))
;;;; Core
(defun w/friend-personality (msg k &optional extra)
"Given MSG, pass a string with more personality to K.
Append EXTRA to the personality."
(let ((call (s-concat w/friend-emotion " | " msg)))
(w/ai
call
(lambda (new)
(let ((sp (s-split "|" (s-trim new))))
(if (= 2 (length sp))
(progn
(when (stringp (car sp))
(setf w/friend-emotion (s-trim (car sp))))
(when (stringp (cadr sp))
(let ((resp (s-trim (cadr sp))))
(push (cons call (s-trim new)) w/friend-message-cache)
(funcall k resp))))
(let ((resp (s-trim new)))
(push (cons call (s-trim new)) w/friend-message-cache)
(funcall k resp)))))
(s-concat
"You are the personality of a desktop buddy named \"friend\". \"friend\" is irreverant but kind, and only speaks in lowercase. You are kind of dumb in a cute way and silly like a virtual pet. You live in the corner of LCOLONQ's stream and provide commentary on events. You never use punctuation. You are foolish. You never use metaphors or similes or idioms. You never describe something by comparing it to another thing. You never use the words \"like\" or \"as\". Given an emotional state and a description of an event that happened to you, please respond with a new emotional state and a short message in response considering your emotional state. The message should only be one clause, but you can include an occasional emoji if it is cute only. You like people, video games, emojis, learning, and food. "
"The theme of LCOLONQ's stream today is " (s-trim (w/slurp "~/today.txt")) " "
"The title of LCOLONQ's stream today is " w/twitch-current-stream-title " "
(or extra ""))
(cons "neutral | Mimeyu fed you an apple." (reverse (-take 5 (-map #'car w/friend-message-cache))))
(cons "happy | yum apple so good" (reverse (-take 5 (-map #'cdr w/friend-message-cache)))))))
;;;; Interface
(defun w/friend-respond (ev &optional k)
"Call when an event EV happens to \"friend\".
If K is specified, call it after the response."
(w/friend-personality
ev
(lambda (msg)
(w/friend-say msg)
(when k
(funcall k)))))
;;;; Updating
(defun w/friend-random-event ()
"Activate a random \"friend\" event."
(cl-case (random 10)
(9 (w/friend-set-state 'jumping))))
(defun w/friend-update ()
"Update \"friend\"'s state per tick."
(setf w/friend-animation (% (+ w/friend-animation 1) 2))
(if (> w/friend-state-timer 0)
(cl-decf w/friend-state-timer)
(setf w/friend-state 'default))
(if (> w/friend-speech-timer 0)
(cl-decf w/friend-speech-timer))
(when (= (random 120) 0)
(w/friend-random-event))
(cl-case w/friend-state
(eating (setf w/friend-state 'eating0))
(eating0 (setf w/friend-state 'eating1))
(eating1 (setf w/friend-state 'eating2))
(eating2 (setf w/friend-state 'eating1))
(chatting (setf w/friend-state 'chatting0))
(chatting0 (setf w/friend-state 'chatting))))
;;;; Rendering
(defun w/friend-get-offset ()
"Return the number of newlines to print before \"friend\"."
(if (-contains? '(jumping) w/friend-state)
w/friend-animation
1))
(defun w/friend-get-face ()
"Return the eyes and mouth for \"friend\" as a list of strings."
(cl-case w/friend-state
(jumping (list "^" "^" "ww"))
(eating (list "v" "v" "<>"))
(eating0 (list "v" "v" "<>"))
(eating1 (list "-" "-" "mw"))
(eating2 (list "-" "-" "wm"))
(chatting (list ">" ">" "oo"))
(chatting0 (list ">" ">" "~~"))
(t (list "-" "-" "ww"))))
(defun w/friend-get-bubble ()
"Return the text bubble for \"friend\"."
(if (> w/friend-speech-timer 0)
w/friend-speech
nil))
(defun w/friend-render ()
"Render the \"friend\" buffer."
(save-excursion
(with-current-buffer (w/friend-get-buffer)
(setq-local cursor-type nil)
(let*
((inhibit-read-only t)
(face (w/friend-get-face))
(bubble (w/friend-get-bubble)))
(erase-buffer)
(w/write
(format-spec
"%a\
/\\ /\\
\\----/
/ %l %r \\
\\ %m /
+----+\
"
;; "%a\
;; /----\\
;; / %l %r \\
;; \\ %m /
;; +----+\
;; "
`((?a . ,(s-repeat (w/friend-get-offset) " \n"))
(?l . ,(car face))
(?r . ,(cadr face))
(?m . ,(caddr face)))))
(goto-char (point-min))
(end-of-line)
(w/write (or bubble ""))
(forward-line)
(end-of-line)
(w/write (if bubble "/" ""))
)))
(w/gizmo-upload (w/friend-get-buffer)))
(defvar w/friend-timer nil)
(defun w/friend-run-timer ()
"Run the \"friend\" timer."
(when w/friend-timer
(cancel-timer w/friend-timer))
(condition-case e
(progn
(w/friend-update)
(w/friend-render))
((debug error)
(message "friend error: %s" e)
(cancel-timer w/friend-timer)
(setq w/friend-timer nil)))
(setq
w/friend-timer
(run-with-timer 1 nil #'w/friend-run-timer)))
(defun w/friend-start ()
"Launch \"friend\"."
(interactive)
(w/friend-run-timer))
(defun w/friend-stop ()
"Stop \"friend\"."
(interactive)
(cancel-timer w/friend-timer)
(message "\"friend\" is going to sleep!"))
(provide 'wasp-friend)
;;; wasp-friend.el ends here
|