                     :search-type search-type
                     :collection (into (search-type collections-init-by-search-type) [starting-node-key])}})) ; use the appropriate collection type for the search type
 
-(defn setup 
+(defn setup
   "quil setup step"
-  [] 
-  ;; don't change :dfs/:bfs color names as they are equivalent to search-states's search-type keywords
-  (def colors {:dfs(q/color 220 255 255)
-               :bfs(q/color 225 150 225)
-               :visiting (q/color 0)
-               :edge (q/color 240 150)
-               :untouched (q/color 240 150)})
-
+  []
   (q/frame-rate frame-rate)
 
   (q/color-mode :rgb)
 
-  ;; create initial nodes and edges ~randomly 
-  (let [nodes (loop [nodes {} amt-to-add node-count]
-                (let [label (keyword (str "n" amt-to-add))
-                      node (node label
-                                 {:x (rand-int (q/width))
-                                  :y (rand-int (q/height))}
-                                 :untouched
-                                 (+ size-min (rand-int (- size-max size-min)))
-                                 {:x (rand-bi-dir directional-speed-max)
-                                  :y (rand-bi-dir directional-speed-max)
-                                  :s (rand-bi-dir size-acceleration)}
-                                 '())
-                      nodes (assoc nodes label node)]
-                  (if (> amt-to-add 0) 
-                    (recur nodes (dec amt-to-add))
-                    nodes)))
-        nodes (loop [nodes nodes amt-to-add edge-count]
-                (let [ks (keys nodes)
-                      k1 (rand-nth ks)
-                      ks (into [] (disj (into #{} ks) k1))
-                      k2 (rand-nth ks)
-                      nodes (add-edge nodes #{k1 k2})] ; edge might already exist, but whatever
-                  (if (> amt-to-add 0)
-                    (recur nodes (dec amt-to-add))
-                    nodes)))]
-    (infer-new-search-state :dfs nodes)))
+  ;; create initial nodes and edges ~randomly
+  (let [labels (map #(keyword (str "n" %))
+                    (range node-count))
+        nodes (into {} (for [label labels]
+                         [label (create-random-node label)]))
+        create-random-edge #(take 2 (shuffle labels))
+        edges (repeatedly edge-count create-random-edge)
+        nodes (reduce add-edge nodes edges)
+        state (infer-new-search-state :dfs nodes)]
+    (assoc state
+           ;; don't change :dfs/:bfs color names as they are equivalent
+           ;; to search-states's search-type keywords
+           :colors {:dfs (q/color 220 255 255)
+                    :bfs (q/color 225 150 225)
+                    :visiting (q/color 0)
+                    :edge (q/color 240 150)
+                    :untouched (q/color 240 150)})))
 
 (defn update-movement
   "apply changes to velocity, position, and size"
   [state]
-  (assoc state
-         :nodes (loop [moved-nodes {} nodes (:nodes state)]
-                  (let [k (first (keys nodes))
-                        n (k nodes)
-                        nodes (dissoc nodes k)
-                        n (assoc n
-                                 :vel {:x (adjust-directional-vel-to-bounds
-                                            (+ (:x (:vel n)) (rand-bi-dir directional-axis-acceleration))
-                                            (:x (:pos n))
-                                            directional-speed-max
-                                            0
-                                            (q/width)
-                                            directional-bounce-velocity
-                                            (:size n))
-
-                                       :y (adjust-directional-vel-to-bounds
-                                            (+ (:y (:vel n)) (rand-bi-dir directional-axis-acceleration))
-                                            (:y (:pos n))
-                                            directional-speed-max
-                                            0
-                                            (q/height)
-                                            directional-bounce-velocity
-                                            (:size n))
-
-                                       :s (let
-                                            [new-vel (+ (:s (:vel n)) (rand-bi-dir size-acceleration))]
-                                            (if (> (+ (:size n) new-vel) size-max)
-                                              (- 0 size-bounce-velocity)
-                                              (if (< (+ (:size n) new-vel) size-min)
-                                                size-bounce-velocity
-                                                new-vel)))}
-
-                                 :size (+ (:size n) (:s (:vel n)))
-
-                                 :pos {:x (+ (:x (:pos n)) (:x (:vel n)))
-                                       :y (+ (:y (:pos n)) (:y (:vel n)))})
-                        moved-nodes (assoc moved-nodes k n)]
-                    (if (empty? nodes)
-                      moved-nodes
-                      (recur moved-nodes nodes))))))
+  (letfn [(new-velocity-coord [node coordinate max]
+            (adjust-directional-vel-to-bounds
+             (+ (get-in node [:vel coordinate]) (rand-bi-dir directional-axis-acceleration))
+             (get-in node [:pos coordinate])
+             directional-speed-max
+             0
+             max
+             directional-bounce-velocity
+             (:size node)))
+
+          (new-velocity [node]
+            {:x (new-velocity-coord node :x (q/width))
+             :y (new-velocity-coord node :y (q/height))
+             :s (let [new-vel (+ (:s (:vel node)) (rand-bi-dir size-acceleration))]
+                  (cond (> (+ (:size node) new-vel) size-max) (- 0 size-bounce-velocity)
+                        (< (+ (:size node) new-vel) size-min) size-bounce-velocity
+                        :else new-vel))})
+
+          (update-node [node]
+            (assoc node
+                   :vel (new-velocity node)
+                   :size (+ (:size node) (:s (:vel node)))
+                   :pos {:x (+ (:x (:pos node)) (:x (:vel node)))
+                         :y (+ (:y (:pos node)) (:y (:vel node)))}))]
+    (update-in state [:nodes] #(map-value update-node %))) )
 
 (defn x-first-search
   "Colors nodes based on the progression of a search algorithm, determined by the search-type and collection type.
   Generic for breadth first search and depth first search because they just use different collection types (queue vs stack)."
   [state]
-  (let [search-type (get-in state [:search-state :search-type]) ; search-type key for coloring / checking if visited
-        nodes (:nodes state)
-        collection (get-in state [:search-state :collection]) ; is a stack if dfs, queue if bfs
-        n ((peek collection) nodes) ; for both list and queue, the first element
-        next-n-key (first 
-                     ;; filter is lazy so we aren't necessarily searching through entire list.
-                     (filter (fn [node-key]  
-                               (not= search-type (:color-key (node-key nodes)))) 
-                             (:siblings n)))
-        collection (if (nil? next-n-key)
+  (let [{:keys [nodes search-state]} state
+        ; search-type key for coloring / checking if visited
+        ; collection is  a stack if dfs, queue if bfs. Contains node labels.
+        {:keys [search-type collection]} search-state
+        n (nodes (peek collection)) ; for both list and queue, the first element
+        next-n-label (first
+                      ;; filter is lazy so we aren't necessarily searching through entire list.
+                      (filter (fn [node-label]
+                                (not= search-type (:color-key (nodes node-label))))
+                              (:siblings n)))
+        collection (if (nil? next-n-label)
                      (pop collection) ; for both list and queue, remove the first element
-                     (conj collection next-n-key)) ; for list, add to front, for queue add to end - the defining difference between nfs and bfs
-        nodes (if (nil? next-n-key)
+                     (conj collection next-n-label)) ; for list, add to front, for queue add to end - the defining difference between nfs and bfs
+        nodes (if (nil? next-n-label)
                 nodes
-                (set-color nodes next-n-key search-type))]
+                (set-color nodes next-n-label search-type))]
     (-> state
         (assoc :nodes nodes)
         (assoc-in [:search-state :collection] collection))))

   :dfs '()})

;;;
;;; graph elements and operations
;;;

(defn node 
  "Construct a node record"
  [label ; used for referencing the node in the graph's map
   pos ; {:x __ :y __}
   color-key ; color key used by the x-first-search and as the key for physically coloring the node
   size ; float
   vel ; velocity {:x __ :y __ :s __} where s is size-change velocity
   siblings] ; set of labels of directly connected nodes

  {:label label :pos pos :color-key color-key :size size :vel vel :siblings siblings})

(defn add-edge
  "add an edge (set with two elements - the label of each node) to a node-map (map node-label->node)."
  [nodes edge]
  (let [edge (into '() edge) ; cannot index/peek set, so use list
        n1-key (peek edge)
        n2-key (peek (pop edge))
        ;; each node has the other node as a sibling
        nodes (update-in nodes [n1-key :siblings] (fn [s] (conj s n2-key)))
        nodes (update-in nodes [n2-key :siblings] (fn [s] (conj s n1-key)))]
    nodes))

(defn extract-edges
  "get a set of edges given a node-map"
  [nodes]
  ;; reduce edge sets from each node into a single set
  (reduce 
    (fn [edges n]
      (sets/union edges 
                  ;; create set of edges from a given node
                  (reduce 
                    (fn [edges k]
                      (conj edges #{(:label n) k}))
                    #{}
                    (:siblings n))))
    #{}
    nodes))

;;;
;;; util
;;;

(defn rand-bi-dir
  "get a random value between -n and n"
  [n]
  (- (rand (* 2 n)) n))

;;;
;;; quil state manipulation and drawing

        state (if (= (:frames-to-wait search-state) 1)
                (x-first-search (assoc-in state [:search-state :frames-to-wait] frames-between-visits))
                (update-in state [:search-state :frames-to-wait] dec))
        collection (get-in state [:search-state :collection])
        state (if (empty? collection)
                ;; when x-first-search returns empty collection, search finished, so start fresh with the other search alg
                (merge state
                       (infer-new-search-state (if (= :bfs (get-in state [:search-state :search-type])) :dfs :bfs) (:nodes state)))
                state)]
    state))

(defn update-state
  "quil update-state step"
  [state]
  (-> state update-movement update-colors))

(defn draw-edges [state]
  (let [{:keys [nodes colors]} state
        edges (extract-edges (vals nodes))]
    (q/fill (:edge colors)) ; set fill as edge color
    ;; draw each edge as a quad. Determine points using some mathematical math.
    (doseq [edge edges]
      (let [[n1 n2] (map nodes edge)
            n1-pos (:pos n1)
            n2-pos (:pos n2)
            x1 (:x n1-pos)
