diff options
author | Grant Shangreaux <grant@unabridgedsoftware.com> | 2022-01-21 12:03:19 -0600 |
---|---|---|
committer | Grant Shangreaux <grant@unabridgedsoftware.com> | 2022-01-21 12:03:19 -0600 |
commit | 0678f718e61f23ef846a942449ab2e042526f16e (patch) | |
tree | 3bf33c8d5bc22d9abdf3362ccb8b3dd282d3c17d |
Feature: initial sketch of `mafia-play` command
Add basic class model for the game
Add simplest possible game loop
-rw-r--r-- | mafia.org | 191 |
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 |