Skip to content

Instantly share code, notes, and snippets.

@mmzsource
Last active June 27, 2021 10:16
Show Gist options
  • Save mmzsource/7c17b7336accb3bbf64bb7215e88b7fd to your computer and use it in GitHub Desktop.
Save mmzsource/7c17b7336accb3bbf64bb7215e88b7fd to your computer and use it in GitHub Desktop.
#!/usr/bin/env bb
;; This script prints generates a random sequence of 'cells' (on or off) and
;; calculates each next generation of cells based on a given rule.
;; See: https://en.wikipedia.org/wiki/Elementary_cellular_automaton
;;
;; This commandline script has 1 dependency:
;; Babashka installed: https://github.com/babashka/babashka#installation (and bb on PATH)
;;
;; Run like this: bb bb-elementary-cellular-automata.clj
;; arguments help: bb bb-elementary-cellular-automata.clj -h
;; 128 cells, rule 30: bb bb-elementary-cellular-automata.clj -c128 -r30
;;;;;;;;;;;;;;;;
;; USER INPUT ;;
;;;;;;;;;;;;;;;;
(defn to-int [string]
(try
(Integer. string)
(catch Exception)))
(defn valid-int? [int]
(and (integer? int) (>= int 0) (<= int 255)))
(def validate-msg "Must be a positive integer <= 255")
(def cli-options
[["-c" "--cells CELLS" "Number of cells in the one dimensional cellular automaton"
:default 128
:parse-fn to-int
:validate [valid-int? validate-msg] ]
["-r" "--rule RULE" "Rule number (see https://en.wikipedia.org/wiki/Elementary_cellular_automaton#The_numbering_system)"
:default 30
:parse-fn to-int
:validate [valid-int? validate-msg]]
["-h" "--help"]])
(def options (tools.cli/parse-opts *command-line-args* cli-options))
;;;;;;;;;;;;;;;;;;;;;;;;;
;; DATA TRANSFORMATION ;;
;;;;;;;;;;;;;;;;;;;;;;;;;
;; generates a 'rule map' with 3 bit sequences as keys
;; requires an int from 0..255
;; converts it to a rule according to
;; https://en.wikipedia.org/wiki/Elementary_cellular_automaton#The_numbering_system
(defn rule-map [i]
(let [ks ["111" "110" "101" "100" "011" "010" "001" "000"]
;; left pad the binary string to guarantee 8 bits
vs (str/replace (format "%8s" (Integer/toBinaryString i)) " " "0")]
(zipmap ks vs)))
;; generates a string of random bits with length n
;; requires an int
;; returns a 3 bit sequence at min, a 255 bit sequence at max
;; 3 bits min because of 'CA domain' (see wiki link)
;; 255 bits max is arbitrary, but seems reasonable for a cmdline program
(defn bits [n]
(cond
(< n 3) (bits 3)
(> n 255) (bits 255)
:else (reduce str (repeatedly n #(rand-nth ["0" "1"])))))
;; splits a one dimensional collection of 'cells'
;; (with its ends wrapped around in a circle)
;; into 'triplets' which represent a cell and its 2 neighbours
;;
;; requires a string representing at least 3 bits
;; will return a collection of 'triplets' like this (without the spaces between the bits):
;; "b1 b2 b3... bn" -> ["bn b1 b2" "b1 b2 b3" "b2 b3 .." "b3 .. .." ".. .. bn" ".. bn b1"]
(defn split [bits]
(let [first-element (str (last bits) (first bits) (second bits))
middle-elements (mapv #(reduce str %) (partition 3 1 bits))
last-element (str (last (butlast bits)) (last bits) (first bits))]
(concat [first-element] middle-elements [last-element])))
;; applies the CA rules to a string of bits leading to the next CA generation
(defn step [rules bits]
(reduce str (map #(get rules %) (split bits))))
;;;;;;;;;;;;;;;;
;; SUPER-GLUE ;;
;;;;;;;;;;;;;;;;
(defn print [state]
(-> state
(str/replace "1" "▒")
(str/replace "0" " ")
(prn)))
(when (:errors options)
(println (:errors options))
(System/exit 1))
(when ((comp :help :options) options)
(println (:summary options))
(System/exit 1))
(let [rules (rule-map ((comp :rule :options) options))
start-state (bits ((comp :cells :options) options))]
(print start-state)
(loop [state start-state]
(Thread/sleep 100)
(let [new-state (step rules state)]
(print new-state)
(recur new-state))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment