test - blancas/eisen GitHub Wiki

We'll illustrate the share data model method by making some changes to the Snake game, from the book Programming Clojure. The full source code of the changed program, Worm, is in the directory src/main/resources/snake.

To play the game with in the Eisen project, launch the Clojure REPL and type:

(load "snake/worm")
(ns snake.worm)
(game)

The program worm.clj starts as a copy of snake.clj and then we make the following changes:

  • Enter the key-value pairs for the host data model.
  • Change references to data and functions to use fetch and call.
  • Define a function to read Eisen code between games.
  • Initialize the Eisen library and start the Timer thread.

The host model contains the data and functions to be available for Eisen user code, for reading and overrides, and is defined like so.

(host-model
  width       75
  height      50
  win-length   2
  point-size  10
  body        (list [1 1])
  dir         [1 0]
  add-points  (fn [& pts] (vec (apply map + pts))))

Any references to the above names must be change to use (fetch) or (call). For example, the function create-apple uses width and height. We make similar changes in other parts of the program.

(defn create-apple [] 
  {:location [(rand-int (fetch width)) (rand-int (fetch height))]
   :color (Color. 210 50 90)
   :type :apple}) 

Function move makes a call to add-points:

(defn move [{:keys [body dir] :as snake} & grow]
  (assoc snake :body (cons (call add-points (first body) dir)
			   (if grow body (butlast body)))))

The function that reads Eisen code between games is written as follows.

(defn run-eisen []
  (let [code (JOptionPane/showInputDialog
	        nil "Paste your code here:" "Change the game"
	        JOptionPane/PLAIN_MESSAGE)]
    (when (seq code)
      (let [result (eisen code)]
	(when-not (:ok result)
  	  (JOptionPane/showMessageDialog nil (:error result)))))))

Since the edit field is too small, it's easier to copy the text from an editor and just paste it there.

Playing the Game

We're now ready to play the game and change it. The Worm game starts with win-length set to 2 so that we quickly win by eating the first apple. Whether win or lose, we get a prompt to paste Eisen code; if nothing is entered the game continues.

Go ahead and play the first quick game. When prompted enter these lines, which will set the game to start with a longer snake, with a vertical direction, and needing two apples to win.

setq body = list #[50,20] #[50,21] #[50,22] 
setq dir = #[0, -1]
setq win-length = 5

Before the next game, this code will set a bigger snake, so we win with just one more apple.

setq body = list #[10,12] #[10,13] #[10,14] #[10,15]

You may notice that the snake can go over the edge in any direction. Since the original game doesn't wrap the snake within the game bounding box, we can add that feature to the program with this function. Called during every segment that the snake moves, this function detects when it's going over the edge and sets the x or y coordinate accordingly. The call to setv installs this function for add-points in the model.

fun add p1 p2 =
    let p = map `+` p1 p2;
        x = first p;
        y = second p
    in
        cond x > _width  => #[1, y];
             x < 0       => #[_width, y];
             y > _height => #[x, 1];
             y < 0       => #[x, _height];
             :else       => #[x, y]
        end
    end

setv add-points = add

Once this change is in place, the snake should come on the opposite side instead of getting away.

If the snake and apples seem too small, we can fix that by changing the size of their parts and the game's bounding box. The last two lines restore the snake to its original length and direction.

setq point-size = 20
setq width = 30
setq height = 35
setq dir = #[1, 0]
setq body = list #[1,1]

With this last change the game must seem to have been zoomed in.

⚠️ **GitHub.com Fallback** ⚠️