Starter code for Sharks
Fall 2010, David Matuszek

About this code

The Clojure Shark assignment is not intended to be terribly difficult, and I don't think it is. However, there are a lot of pieces to put together, and I had to constantly refer back to my slides while I was doing even this much. Since I just wrote those slides, it's probably even harder for other people. I therefore thought it might be helpful to supply some "starter" code for you to build on.

Use of this code is totally optional. If it helps you get started, use it. If it gets in your way, ignore it. I am reasonably confident that the code I'm supplying is correct, as far as it goes--I do not know how much it will need to be modified in order to complete the assignment.

About Clojure/Lisp

Comments on starter code

A defstruct is something like a class definition, something like a map.

You can create a struct with the struct-map function.

You can use (assoc struct key value ... key value) to get a new struct based on the given struct, but with different values for the keys.

You can make a reference to a struct (or anything else); this lets you replace the value of the reference with a different value.

You can only change a reference within a dosync.

dosync takes any number of expressions as arguments, so you can make multiple changes at once.

Possible changes

I said "Use an agent, whose value is a struct, to represent a shark." That seems to make sense--an agent, like an actor in Erlang, is a concurrent process, and each shark should be able to move concurrently with other sharks. I do want concurrency in this program, so that STM actually has some meaning, but this may not be the best way to introduce the concurrency. If you find some way to introduce concurrency that seems to work better, feel free to use it.

Starter code

(defstruct shark :id :direction :weight :hunger :status)

(defn make-shark
  "Defines a shark with :id = n and various attributes."
  [n]
  (struct-map shark
    :id n
    :direction (if (< (rand) 0.5)
      :left
      :right )
    :weight  (+ 90 (* 20 (rand)))
    :hunger 0
    :status :alive) )

(defn str-shark
  "Returns a string representation of a shark (or () for an empty list)."
  [shark]
  (cond
    (= shark ()) "()"
    (= (shark :direction) :left) (concat "<" (str (shark :id)))
    :else  (concat (str (shark :id)) ">") ) )

(defn print-sharks
  "Prints out a list containing sharks and empty locations."
  [lst]
  (println (map str-shark lst)) )
  
(defn make-shark-list 
  "Makes a list of how-many actual sharks, and some number of empty locations."
  ([how-many] (make-shark-list [] 1 how-many))
  ([lst n how-many]
    (cond
      (> n how-many) lst
      (> (rand) 0.75) (cons '() (make-shark-list lst n how-many))
      :else (cons (make-shark n) (make-shark-list lst (inc n) how-many)) ) ) )

(defn run-me []
  (do
    (print-sharks (make-shark-list 3))
    (print-sharks (make-shark-list 3))
    (print-sharks (make-shark-list 3))
    (print-sharks (make-shark-list 3)) ) )

(defn run-me-too []
  (do
    (let [
      ; create a shark called Sherman
      sherman (make-shark 1)
      ; create a reference to Sherman, giving it a silly
      ; name to emphasize it's a ref, not the "real thing"
      look!-a-shark! (ref sherman)]
      ; print Sherman in two different ways
      (println sherman)
      (println (str-shark sherman))
      ; put Sherman on a diet
      (dosync
        (ref-set look!-a-shark! (assoc @look!-a-shark! :hunger 4))
        (ref-set look!-a-shark!
          (assoc  @look!-a-shark! :weight (* 0.75 (@look!-a-shark! :weight))) )
        (ref-set look!-a-shark! (assoc @look!-a-shark! :status :ill-tempered))
      ; show off Sherman's new look
      (println @look!-a-shark!)
      (println (str-shark @look!-a-shark!))
      ; sorry, Sherman, it didn't take--you're immutable
      (println sherman)
      (println (str-shark sherman)) ) ) ) )