summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGrant Shangreaux <grant@unabridgedsoftware.com>2022-01-25 23:19:18 -0600
committerGrant Shangreaux <grant@unabridgedsoftware.com>2022-01-25 23:19:18 -0600
commit3d6a49a6c249befba41a53dbd7ccb9156654a13e (patch)
treef4f406a47d5366a02aa6d6106b4cbe78fda0bfba
parent089946168e646271e840b5e20d3b8ef9c97d0580 (diff)
Add: more WIP observation methods
- build out being-watched? logic as a method (unfinished) - add a neighbors function for "fuzzy" observation notes? - split out some methods into separate source blocks and add org notes
-rw-r--r--mafia.org180
1 files changed, 134 insertions, 46 deletions
diff --git a/mafia.org b/mafia.org
index f3ab461..4086c39 100644
--- a/mafia.org
+++ b/mafia.org
@@ -95,22 +95,21 @@ use inheritance here to set apart the killer with its own ~:initform~.
#+name: actor-classes
#+begin_src emacs-lisp :results silent
- (defclass mafia-actor ()
- ((id :initarg :id :type number)
- (being-watched? :documentation "Returns true if this actor believes they're being watched.")
- (current-target :documentation "Id of another actor being observed.")
- (notes :initform '() :documentation "An alist containing pairs of (id . target-id)"))
- "Base class for mafia actors.")
-
- (defclass mafia-killer (mafia-actor)
- ()
- "Actor subclass to represent the killer.")
-
- (defclass mafia-innocent (mafia-actor)
- ((status :initform 'alive
- :documentation "'alive, 'dying, or 'dead")
- (death-countdown :initform (1+ (random 10))
- :documentation "The number of ticks to go from dying to dead")))
+ (defclass mafia-actor ()
+ ((id :initarg :id :type number)
+ (target :initarg :target :documentation "Actor currently being observed.")
+ (notes :initform '() :documentation "An alist containing pairs of (actor-id . target-id)"))
+ "Base class for mafia actors.")
+
+ (defclass mafia-killer (mafia-actor)
+ ()
+ "Actor subclass to represent the killer.")
+
+ (defclass mafia-innocent (mafia-actor)
+ ((status :initform 'alive
+ :documentation "'alive, 'dying, or 'dead")
+ (death-countdown :initform (1+ (random 10))
+ :documentation "The number of ticks to go from dying to dead")))
#+end_src
Then the function ~mafia-initial-actors~ will handle initializing N actors
@@ -146,9 +145,9 @@ Lets initialize a game with 6 actors and inspect it to see what to expect.
| mafia-innocent | 1 | alive |
| mafia-innocent | 2 | alive |
| mafia-innocent | 3 | alive |
-| mafia-killer | 4 | nil |
+| mafia-innocent | 4 | alive |
| mafia-innocent | 5 | alive |
-| mafia-innocent | 6 | alive |
+| mafia-killer | 6 | nil |
** Game Loop
@@ -181,48 +180,92 @@ So... we need to define a game loop function, and a method for the actors to
#+end_src
** Actor Behavior
+*** Observation
:PROPERTIES:
-:header-args: :noweb-ref actor-behavior-methods :noweb-sep "\n\n"
+:header-args: :noweb-ref actor-behavior-methods :noweb-sep "\n\n" :results silent
:END:
-*** Observation
-
-Each actor will have a ~current-target~, another actor they are observing.
+Each actor will have a ~target~, another actor they are observing.
While they are observing, they'll notice who their target is observing.
If two actors are observing each other, they have *eye-contact*. The killer
will wink if they believe they aren't ~being-watched?~ when eye contact
is being made.
+We'll implement a base method for all actors that can be called after more
+specialized methods. This base method will be responsible for the actors'
+"memory", adding a note about who they're observing is observing. Perhaps
+later on it will be "fuzzy."
+
+#+name: observe-base
+#+begin_src emacs-lisp :results silent
+ (cl-defmethod mafia-observe ((actor mafia-actor))
+ "Base behavior for an actor. Note the ids of the observed target and who
+ they are perceived to be targeting."
+ (with-slots (notes (my-target target)) actor
+ (when my-target
+ (with-slots (id (their-target target)) my-target
+ (when their-target
+ (setf notes (cons `(,id . ,(slot-value their-target 'id)) notes)))))))
+#+end_src
+
+We may not have to specialize the other actors, but while the killer is
+observing, they will decide whether or not to wink. But first we'll need
+a method to determine eye-contact and one for the killer to determine if
+they're being watched.
+
+#+name: eye-contact?
+#+begin_src emacs-lisp
+ (cl-defmethod mafia-eye-contact? (a b)
+ "Given two `mafia-actor's, returns t if they are eachother's current target."
+ (and (equal (slot-value a 'target) b)
+ (equal (slot-value b 'target) a)))
+#+end_src
+
+Eye contact is pretty straight forward, but ~being-watched?~ needs to utilize
+the *notes* "memory" from above. For now, we'll look at the first element in
+the list and see if the target is the killer.
+
+#+name: neighbors
+#+begin_src emacs-lisp
+ (defun neighbors (e lst)
+ (let* ((idx (seq-position lst e))
+ (last-idx (1- (length lst)))
+ (left (if (zerop idx) last-idx (1- idx)))
+ (right (if (= idx last-idx) 0 (1+ idx))))
+ (list (elt lst left) (elt lst right))))
+#+end_src
+
+#+name: being-watched?
#+begin_src emacs-lisp
- (cl-defgeneric mafia-observe (a b)
- "Behavior for actor a observing actor b.")
+ (cl-defmethod mafia-being-watched? ((killer mafia-killer))
+ "Specilized on the killer, returns true when there is a most recent memory,
+and the target is the killer themselves."
+ (with-slots (id notes) killer
+ (let* ((most-recent-memory (car notes))
+ (their-target (cdr most-recent-memory)))
+ (and their-target (= id their-target)))))
+#+end_src
- (cl-defmethod mafia-observe ((actor mafia-actor) other)
- "Base behavior for an actor which add a note about the current target
- of `other' to the `actor's notes alist."
- (with-slots (notes) actor
- (with-slots (id current-target) other
- (setf notes (cons `(,id . ,(slot-value current-target 'id)) notes)))))
+If they're being watched, simply have them target a random other actor (?)
- (cl-defmethod mafia-observe ((killer mafia-killer) other)
+#+name: mafia-observe-killer
+#+begin_src emacs-lisp :results silent
+ (cl-defmethod mafia-observe ((killer mafia-killer))
"Specialized behavior for the `mafia-killer'."
- (with-slots ((being-watched?)) killer
- ;; (if being-watched?
- ;; (mafia-maybe-refocus killer)
- (if (mafia-eye-contact? killer other)
+ (with-slots (target) killer
+ (if (mafia-being-watched? killer)
+ (setf target (mafia-random-other killer))
+ (if (mafia-eye-contact? killer target)
(print "the killer winks")
(print "the killer abides"))
- ;; (mafia-wink other)
- (cl-call-next-method killer other))))
-
- (cl-defmethod mafia-eye-contact? (a b)
- "Given two `mafia-actor's, returns t if they are eachother's current target."
- (and (equal (slot-value a 'current-target) b)
- (equal (slot-value b 'current-target) a)))
+ ;; (mafia-wink other)
+ (cl-call-next-method killer))))
#+end_src
-#+RESULTS:
-: mafia-eye-contact\?
+*** Example Three Actor Play
+:PROPERTIES:
+:header-args: :noweb-ref example-three-way-setup
+:END:
Imagining "optimal" play if there are only 3 actors. The game begins and
each actor chooses a target. If the killer makes eye contact with anyone,
@@ -231,12 +274,57 @@ the game. If the other two make eye contact, they will never want to
observe the other player, because then they'll be killed. One of the two
would /accuse/ and the other would /second/ and they win.
+Let's set this up. We'll need a killer with no target, and two innocents,
+~marple~ the killer and ~poirot~ targets the her.
+
+~let*~ sets some local variables for a block. latter definitions can refer
+to variables created previously.
+
+#+begin_src emacs-lisp
+ (let* ((killer (mafia-killer :id 1))
+ (marple (mafia-innocent :id 2 :target killer))
+ (poirot (mafia-innocent :id 3 :target marple)))
+#+end_src
+
+Then, set the killer's target to ~poirot~ :
+
+#+begin_src emacs-lisp
+ (setf (slot-value killer 'target) poirot)
+#+end_src
+
+The killer observes:
+
+#+begin_src emacs-lisp
+ (mafia-observe killer)
+#+end_src
+
+Then change target and observe again:
+
+#+begin_src emacs-lisp
+ (setf (slot-value killer 'target) marple)
+ (mafia-observe killer)) ;; end let*
+#+end_src
+
+~C-c C-c~ on the following block will run this code:
+
+#+begin_src emacs-lisp :noweb yes :noweb-ref none :tangle no :results output
+ <<example-three-way-setup>>
+#+end_src
+
+#+RESULTS:
+:
+: "the killer abides"
+:
+: "the killer winks"
+
Add in a 4th actor, and then its trickier. The killer would like to wink
when they are sure they aren't being watched and then immediately try for
eye contact with another actor. The other actors may want to maintain
eye contact as long as they feel the actor they are observing is being
watched by someone else. ???
+*** Unfinished Targeting behavior
+
#+begin_src emacs-lisp
(cl-defgeneric mafia-maybe-refocus (actor)
@@ -250,8 +338,8 @@ watched by someone else. ???
(slot-value actor 'being-watched?))
(cl-defmethod mafia-refocus ((actor) other-actors)
- (with-slots ((current-target) actor)
- (setf current-target (seq-random-elt other-actors))))
+ (with-slots ((target) actor)
+ (setf target (seq-random-elt other-actors))))
#+end_src
*** Helper method to tell if an actor is alive