summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGrant Shangreaux <grant@unabridgedsoftware.com>2022-01-21 12:03:19 -0600
committerGrant Shangreaux <grant@unabridgedsoftware.com>2022-01-21 12:03:19 -0600
commit0678f718e61f23ef846a942449ab2e042526f16e (patch)
tree3bf33c8d5bc22d9abdf3362ccb8b3dd282d3c17d
Feature: initial sketch of `mafia-play` command
Add basic class model for the game Add simplest possible game loop
-rw-r--r--mafia.org191
1 files changed, 191 insertions, 0 deletions
diff --git a/mafia.org b/mafia.org
new file mode 100644
index 0000000..79f7372
--- /dev/null
+++ b/mafia.org
@@ -0,0 +1,191 @@
+* serial killer game
+
+ideally a small city/village simulator where there are many independent actors
+all doing their routines. one of them gets triggered by something and commits
+a murder. they will continue to murder any time their pattern is triggered until
+the player figures out who they are.
+
+** Mafia - boiling it down
+
+the game [[https://en.wikipedia.org/wiki/Wink_murder]["Mafia"]] is a very simplified version of this, and serves as a starting
+point to work out a prototype. for fun, i'm just doing this in emacs-lisp.
+the following source block represents the whole program from a high level:
+
+#+name: mafia
+#+begin_src emacs-lisp :noweb yes :tangle yes :results silent
+ ;;; mafia.el --- Emacs Mafia game simulation -*- lexical-binding:t -*-
+
+ <<dependencies>>
+ <<mafia-class>>
+ <<actor-classes>>
+ <<initialization-helpers>>
+ <<top-level-mafia-methods>>
+ <<game-loop>>
+#+end_src
+
+If you put your cursor on the block above and press C-c C-c, then you should
+be able to press M-x mafia-play and it will prompt you for a number of players.
+Then look at the *Messages* buffer, you can either find it through the menu
+bar, ~M-x switch-to-buffer~ or C-c C-c the source block below:
+
+#+begin_src emacs-lisp
+(switch-to-buffer "*Messages*")
+#+end_src
+
+*** dependencies
+
+i'm going to bring in some dependencies that will make it more like Common
+Lisp. [[info:cl#Top][cl-lib]] brings in functions and macros. When you see things prefixed with
+~cl-~, you'll know its from Common Lisp. [[info:eieio#Top][EIEIO]] provies an OOP layer similar to
+CLOS, the powerful object oriented system from CL.
+
+#+name: dependencies
+#+begin_src emacs-lisp :results silent
+ (require 'cl-lib)
+ (require 'eieio)
+
+#+end_src
+
+*** game state
+
+a game consists of N >= 4 *actors*, one of which is the *killer*. each game will
+have a number of *rounds*, at the end of each an actor will /die/. each round
+must simulate time in some way. each *tick*, an actor /observes/ their
+fellows. the killer will /wink/ at another actor when they happen to be observing
+each other. after a *delay* of χ ticks, the winked-at actor dies and the round
+ends. once per round, each actor may /accuse/ another of being the killer.
+then the group attemps to reach /consensus/ on this accusation. if they do, the
+other actor reveals whether or not they are the killer. (Q: what makes the killer
+join the consensus against themself?) If they are the killer, the other actors
+win. If there's only 2 players left, the killer wins.
+
+using EIEIO, lets define a class ~mafia~ to represent the whole game state.
+
+#+name: mafia-class
+#+begin_src emacs-lisp
+ (defclass mafia () ; No superclasses
+ ((actors :initarg :actors
+ :initform (mafia-initialize-actors 4)
+ :type list
+ :documentation "The list of `actors' in the game.")
+ (round :initform 1
+ :type number
+ :documentation "The current round of the game.")
+ (tick :initform 0
+ :type number
+ :documentation "The current 'slice-of-time' the game is in."))
+ "A class representing the game state of Mafia, defaults to 4 players.")
+
+#+end_src
+
+Notice the ~:initform~ slot options (instance variables are called slots in
+CLOS/EIEIO). By default, ~(make-instance 'mafia)~ would initialize the object
+instance with these values. We will work on defining ~mafia-initialize-actors~ next.
+
+*** actors
+
+We may as well use an object to represent the actors as well. Each one will have
+an *id* and a *status* which will be one of ~'alive 'dead 'killer~. We can actually
+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))
+ "Base class for mafia actors.")
+
+ (defclass mafia-killer (mafia-actor)
+ ()
+ "Actor subclass to represent the killer.")
+
+ (defclass mafia-innocent (mafia-actor)
+ ((alive :initform t
+ :documentation "Either t or nil (for dead).")
+ (death-countdown :initform (1+ (random 10))
+ :documentation "The number of ticks until death.")))
+
+#+end_src
+
+Then the function ~mafia-initial-actors~ will handle initializing N actors
+into a list which is the ~:initform~ of the ~mafia~ game instance.
+
+#+name: initialization-helpers
+#+begin_src emacs-lisp :results silent
+ (defun mafia-initialize-actors (players)
+ "Returns a list of `players' mafia-actors, where one is the killer."
+ (cl-assert (>= players 4) () "Cannot play with fewer than 4 players")
+ (let ((killer (1+ (random players))))
+ (cl-loop for i from 1 to players
+ collect (if (eql i killer) (mafia-killer :id i) (mafia-innocent :id i)))))
+
+#+end_src
+
+*** visualizing initial game state
+
+Lets initialize a game with 6 actors and inspect it to see what to expect.
+
+#+begin_src emacs-lisp
+ (let* ((mafia-game (mafia :actors (mafia-initialize-actors 6)))
+ (actors (slot-value mafia-game 'actors)))
+ (cons '(class id alive?)
+ (mapcar (lambda (a)
+ (let ((class (eieio-object-class a)))
+ (list class (slot-value a 'id)
+ (when (eql class 'mafia-innocent) (slot-value a 'alive)))))
+ actors)))
+#+end_src
+
+#+RESULTS:
+| class | id | alive? |
+| mafia-innocent | 1 | t |
+| mafia-innocent | 2 | t |
+| mafia-innocent | 3 | t |
+| mafia-innocent | 4 | t |
+| mafia-innocent | 5 | t |
+| mafia-killer | 6 | nil |
+
+** Game loop
+
+The game loop will advance by a single tick where each actor /observes/ the others.
+So... we need to define a game loop function, and a method for the actors to observe.
+
+#+name: game-loop
+#+begin_src emacs-lisp :results silent
+ (defun mafia-play (players)
+ "Entry point to start a game of Mafia."
+ (interactive "nnumber of players: ")
+ (let* ((game (mafia :actors (mafia-initialize-actors players))))
+ (while (> (length (mafia-innocents-alive game)) 1)
+ (mafia-update game))
+ (message "Game over!")))
+
+#+end_src
+
+#+name: top-level-mafia-methods
+#+begin_src emacs-lisp :results silent
+ (cl-defmethod mafia-innocents-alive ((obj mafia))
+ "Returns the living innocents from a mafia game."
+ (cl-remove-if-not #'mafia-innocent-alive-p (mafia-innocents obj)))
+
+ (cl-defmethod mafia-innocents ((obj mafia))
+ "Returns the list of innocents from a mafia game."
+ (cl-remove-if #'mafia-killer-p (slot-value obj 'actors)))
+
+ (cl-defmethod mafia-innocent-alive-p ((obj mafia-innocent))
+ "Returns `t' if the actor is alive, otherwise `nil'"
+ (slot-value obj 'alive))
+
+ (cl-defmethod mafia-innocent-die ((obj mafia-innocent))
+ (with-slots (id alive) obj
+ (print (format "Innocent %s has been killed!" id))
+ (setf alive nil)))
+
+ (cl-defmethod mafia-update ((obj mafia))
+ "Performs the update logic for the mafia game instane."
+ (mafia-innocent-die (cl-first (mafia-innocents-alive obj))))
+
+#+end_src
+
+* english villiage simulator
+https://archaeology.co.uk/articles/specials/timeline/the-origins-of-the-english-village.htm