actors: Remove define-simple-actor, add define-actor.
[8sync.git] / demos / actors / robotscanner.scm
1 ;;; 8sync --- Asynchronous programming for Guile
2 ;;; Copyright (C) 2016 Christopher Allan Webber <cwebber@dustycloud.org>
3 ;;;
4 ;;; This file is part of 8sync.
5 ;;;
6 ;;; 8sync is free software: you can redistribute it and/or modify it
7 ;;; under the terms of the GNU Lesser General Public License as
8 ;;; published by the Free Software Foundation, either version 3 of the
9 ;;; License, or (at your option) any later version.
10 ;;;
11 ;;; 8sync is distributed in the hope that it will be useful,
12 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 ;;; GNU Lesser General Public License for more details.
15 ;;;
16 ;;; You should have received a copy of the GNU Lesser General Public
17 ;;; License along with 8sync.  If not, see <http://www.gnu.org/licenses/>.
18
19 ;;; =====================================================================
20 ;;; Robot Scanner test demo (from XUDD, originally).
21 ;;; 
22 ;;; Here's the premise.  There's a warehouse full of droids, some
23 ;;; infected, and some not.  The SecurityRobot is being sent in to clean
24 ;;; up the mess.  It's capable of sending a message that infected droids
25 ;;; are susceptible to responding in a predictable way.  Once it has
26 ;;; identified that a droid is infected, it shoots it full of holes till
27 ;;; the droid is terminated.  The SecurityRobot goes from room to room
28 ;;; till things are cleared out.
29 ;;; 
30 ;;; Overseeing the operation is the "overseer".  The security robot keeps
31 ;;; the overseer up to date on its progress as it goes.  (For this demo,
32 ;;; the overseer is also responsible for initializing the world and
33 ;;; reporting info back to the user.)
34 ;;; =====================================================================
35
36 (use-modules (8sync actors)
37              (oop goops)
38              (ice-9 match))
39
40 (set! *random-state* (random-state-from-platform))
41
42 (define room-structure
43   ;; A list of (clean-droids infected-droids)
44   '((3 1)
45     (0 2)
46     (8 5)
47     (5 0)
48     (2 1)))
49
50 (define-actor <overseer> (<actor>)
51   ((init-world
52     (lambda (actor message)
53       ;; Porting mostly straight up from super-imperative XUDD code.
54       (define previous-room #f)
55       (define first-room #f)
56
57       ;; Set up all rooms
58       (for-each
59        (match-lambda
60          ((clean-droids infected-droids)
61           ;; Create this room
62           (define room (create-actor* actor <warehouse-room> "room"))
63           (define* (init-droid #:key infected)
64             (define droid (create-actor* actor <droid> "droid"
65                                          #:infected infected
66                                          #:room room))
67             (<-wait droid 'register-with-room))
68
69           ;; Link rooms.
70           ;; Couldn't this just be folded into the warehouse room init?
71           ;; I guess it stress tests more the message sending process
72           (when previous-room
73             (<- previous-room 'set-next-room
74                 #:id room)
75             (<- room 'set-previous-room
76                 #:id previous-room))
77
78           ;; Set up clean droids in the room
79           (for-each
80            (lambda _
81              (init-droid #:infected #f))
82            (iota clean-droids))
83
84           ;; Set up infected droids in the room
85           (for-each
86            (lambda _
87              (init-droid #:infected #t))
88            (iota clean-droids))
89
90           (set! previous-room room)
91           (if (not first-room)
92               (set! first-room room))))
93        room-structure)
94
95       ;; Add security robot
96       (let ((security-robot
97              (create-actor actor <security-robot>)))
98         (<- security-robot 'begin-mission
99             #:starting-room first-room
100             #:overseer (actor-id actor)))))
101
102    (transmission
103     (lambda* (actor message #:key text)
104       (display text)
105       (newline)))))
106
107
108 ;;; A room full of robots.
109 (define-class <warehouse-room> (<actor>)
110   (droids #:init-value '())
111   (next-room #:init-value #f)
112   (previous-room #:init-value #f)
113
114   (actions
115    #:allocation #:each-subclass
116    #:init-value
117    (build-actions
118     (set-next-room
119      (lambda* (actor message #:key id)
120        "Set the room following this"
121        (slot-set! actor 'next-room id)))
122
123     (set-previous-room
124      (lambda* (actor message #:key id)
125        "Set the room previous to this"
126        (slot-set! actor 'previous-room id)))
127
128     (get-next-room
129      (lambda (actor message)
130        "Return a reference to the link following this"
131        (<-reply message (slot-ref actor 'next-room))))
132
133     (get-previous-room
134      (lambda (actor message)
135        "Return a reference to the link preceding this"
136        (<-reply message (slot-ref actor 'previous-room))))
137
138     (list-droids
139      (lambda (actor message)
140        "Return a list of all the droid ids we know of in this room"
141        (<-reply message
142                 #:droid-ids (slot-ref actor 'droids))))
143
144     (register-droid
145      (lambda* (actor message #:key droid-id)
146        "Register a droid as being in this room"
147        (slot-set! actor 'droids
148                   (cons droid-id
149                         (slot-ref actor 'droids))))))))
150
151
152 ;;; A droid that may or may not be infected!
153 ;;; What will happen?  Stay tuned!
154 (define-class <droid> (<actor>)
155   (infected #:init-keyword #:infected)
156   (room #:init-keyword #:room)
157   (hp #:init-value 50)
158
159   (actions
160    #:allocation #:each-subclass
161    #:init-value
162    (build-actions
163     (register-with-room
164      (lambda (actor message)
165        "Register ourselves as being in a room"
166        (let ((room-id (slot-ref actor 'room)))
167          (<-wait room-id 'register-droid
168                  #:droid-id (actor-id actor))
169          (format #t "Droid ~a registered with room ~a\n"
170                  (actor-id-actor actor)
171                  (address-actor-id room-id)))))
172
173     (infection-expose
174      (lambda (actor message)
175        "Leak whether or not we're infected to a security droid"
176        (<-reply message (slot-ref actor 'infected))))
177
178     (get-shot
179      (lambda (actor message)
180        "Get shot by bullets"
181        (let* ((damage (random 60))
182               (new-hp (- (slot-ref actor 'hp) damage))
183               (alive (> new-hp 0)))
184          ;; Set our health to the new value
185          (slot-set! actor 'hp new-hp)
186          (<-reply message
187                   #:hp-left new-hp
188                   #:damage-taken damage
189                   #:alive alive)
190          (when (not alive)
191            (format #t "~a: *Kaboom!*\n" (actor-id-actor actor))
192            (self-destruct actor))))))))
193
194
195 (define (droid-status-format shot-response)
196   (call-with-message
197    shot-response
198    (lambda* (_ #:key alive damage-taken hp-left)
199      (if alive
200          (format #f "Droid ~a shot; taken ~a damage. Still alive... ~a hp left."
201                  (address-actor-id (message-from shot-response))
202                  damage-taken hp-left)
203          (format #f "Droid ~a shot; taken ~a damage. Terminated."
204                  (address-actor-id (message-from shot-response))
205                  damage-taken)))))
206
207
208 ;;; Security robot... designed to seek out and destroy infected droids.
209 (define-actor <security-robot> (<actor>)
210   ((begin-mission security-robot-begin-mission)))
211
212 (define* (security-robot-begin-mission actor message
213                                        #:key starting-room overseer)
214   ;; used to track the current room / if any rooms are remaining
215   (define room starting-room)
216
217   ;; Walk through all rooms, clearing out infected droids
218   ;; Continue this whil there's still another room to investigate.
219   (define response)
220   (while room
221     (<- overseer 'transmission
222         #:text (format #f "Entering room ~a..."
223                        (address-actor-id room)))
224
225     ;; Find all droids in this room and exterminate the infected ones.
226     (mbody-receive (_ #:key list-droids droid-ids #:allow-other-keys)
227         (<-wait room 'list-droids)
228       (for-each
229        (lambda (droid-id)
230          (cond
231           ;; Looks like it's infected
232           ((mbody-val (<-wait droid-id 'infection-expose))
233            ;; Inform that it's infected
234            (<- overseer 'transmission
235                #:text (format #f "~a found to be infected... taking out"
236                               (address-actor-id droid-id)))
237
238            ;; Keep firing till it's dead.
239            (let ((still-alive #t))
240              (while still-alive
241                (mbody-receive (response #:key alive #:allow-other-keys)
242                    (<-wait droid-id 'get-shot)
243                  (<- overseer 'transmission
244                      #:text (droid-status-format response))
245                  (set! still-alive alive)))))
246
247           ;; Not infected... inform and go to the next one
248           (else
249            (<- overseer 'transmission
250                #:text
251                (format #f "~a is clean... moving on."
252                        (address-actor-id droid-id))))))
253        droid-ids))
254
255     ;; Switch to next room, if there is one.
256     (set! room (mbody-val (<-wait room 'get-next-room))))
257
258   ;; Good job everyone!  Shut down the operation.
259   (<- overseer 'transmission
260       #:text "Mission accomplished."))
261
262 (define (main . args)
263   (define hive (make-hive))
264   (define overseer (bootstrap-actor hive <overseer>))
265   (define initial-messages
266     (list (bootstrap-message hive overseer 'init-world)))
267   (run-hive hive initial-messages))