From c7b2c982004e350f5e3032321baadfc9021b6bad Mon Sep 17 00:00:00 2001
From: dozens
Date: Thu, 20 Jun 2024 09:17:06 -0600
Subject: 🗄️ big tidy up

- isolate core game logic and move it to src/game.fnl
- main.fnl should be just the ui now
- move all table funcs into lib/table
- move all (1) string funcs into lib/string
- move all game funcs into lib/game/
---
 lib/all-mills.fnl                   |  25 ------
 lib/all-mills.test.fnl              |  41 ----------
 lib/constants.fnl                   |  14 ++++
 lib/contains.fnl                    |   7 --
 lib/contains.test.fnl               |  17 ----
 lib/either.test.fnl                 |  72 ++++++++---------
 lib/equal.fnl                       |  22 ------
 lib/equal.test.fnl                  |  28 -------
 lib/game/README                     |   5 ++
 lib/game/all-mills.fnl              |  25 ++++++
 lib/game/all-mills.test.fnl         |  41 ++++++++++
 lib/game/index.fnl                  |  11 +++
 lib/game/mill.fnl                   |  41 ++++++++++
 lib/game/mill.test.fnl              | 123 +++++++++++++++++++++++++++++
 lib/game/no-moves.fnl               |  26 +++++++
 lib/game/no-moves.test.fnl          |  49 ++++++++++++
 lib/game/space-is-neighbor.fnl      |  19 +++++
 lib/game/space-is-neighbor.test.fnl |  20 +++++
 lib/head.fnl                        |   6 --
 lib/head.test.fnl                   |  12 ---
 lib/index.fnl                       |   8 --
 lib/keys.fnl                        |   7 --
 lib/keys.test.fnl                   |  13 ----
 lib/kvflip.fnl                      |   6 --
 lib/kvflip.test.fnl                 |  13 ----
 lib/mill.fnl                        |  41 ----------
 lib/mill.test.fnl                   | 150 ------------------------------------
 lib/no-moves.fnl                    |  26 -------
 lib/no-moves.test.fnl               |  51 ------------
 lib/slice.fnl                       |   5 --
 lib/slice.test.fnl                  |  19 -----
 lib/space-is-neighbor.fnl           |  18 -----
 lib/space-is-neighbor.test.fnl      |  22 ------
 lib/string.fnl                      |   2 +
 lib/string.test.fnl                 |  13 ++++
 lib/table.fnl                       |  50 ++++++++++--
 lib/table.test.fnl                  |  80 +++++++++++++++++++
 lib/tableprint.fnl                  |   7 --
 lib/tail.fnl                        |   7 --
 lib/tail.test.fnl                   |  19 -----
 lib/test.fnl                        |  42 +++++++---
 lib/test.test.fnl                   |  70 ++++++++++++-----
 42 files changed, 626 insertions(+), 647 deletions(-)
 delete mode 100644 lib/all-mills.fnl
 delete mode 100644 lib/all-mills.test.fnl
 delete mode 100644 lib/contains.fnl
 delete mode 100644 lib/contains.test.fnl
 delete mode 100644 lib/equal.fnl
 delete mode 100644 lib/equal.test.fnl
 create mode 100644 lib/game/README
 create mode 100644 lib/game/all-mills.fnl
 create mode 100644 lib/game/all-mills.test.fnl
 create mode 100644 lib/game/index.fnl
 create mode 100644 lib/game/mill.fnl
 create mode 100644 lib/game/mill.test.fnl
 create mode 100644 lib/game/no-moves.fnl
 create mode 100644 lib/game/no-moves.test.fnl
 create mode 100644 lib/game/space-is-neighbor.fnl
 create mode 100644 lib/game/space-is-neighbor.test.fnl
 delete mode 100644 lib/head.fnl
 delete mode 100644 lib/head.test.fnl
 delete mode 100644 lib/keys.fnl
 delete mode 100644 lib/keys.test.fnl
 delete mode 100644 lib/kvflip.fnl
 delete mode 100644 lib/kvflip.test.fnl
 delete mode 100644 lib/mill.fnl
 delete mode 100644 lib/mill.test.fnl
 delete mode 100644 lib/no-moves.fnl
 delete mode 100644 lib/no-moves.test.fnl
 delete mode 100644 lib/slice.fnl
 delete mode 100644 lib/slice.test.fnl
 delete mode 100644 lib/space-is-neighbor.fnl
 delete mode 100644 lib/space-is-neighbor.test.fnl
 create mode 100644 lib/string.test.fnl
 create mode 100644 lib/table.test.fnl
 delete mode 100644 lib/tableprint.fnl
 delete mode 100644 lib/tail.fnl
 delete mode 100644 lib/tail.test.fnl

(limited to 'lib')

diff --git a/lib/all-mills.fnl b/lib/all-mills.fnl
deleted file mode 100644
index 562bb97..0000000
--- a/lib/all-mills.fnl
+++ /dev/null
@@ -1,25 +0,0 @@
-(local {: mill-at? } (require :lib.mill))
-(local {: mills } (require :lib.constants))
-
-(fn toggle-player [p] (if (= p 1) 2 1))
-
-(fn only-player-moves [moves player]
-  (icollect [_ move (ipairs moves)] (if (= move player) player 0)))
-
-(fn all-moves-are-mills? [moves player]
-  (accumulate [result true
-               i m (ipairs moves) ]
-              (and result (if (= m 0) true (mill-at? mills moves i)))))
-
-(fn all-mills? [all-moves current-player]
-  (let [next-player (toggle-player current-player)
-        player-moves (only-player-moves all-moves next-player)
-        all-mills (all-moves-are-mills? player-moves current-player)]
-    all-mills))
-
-{: all-mills?
- ;; do not use; just for testing:
- : toggle-player
- : only-player-moves
- : all-moves-are-mills?
- }
diff --git a/lib/all-mills.test.fnl b/lib/all-mills.test.fnl
deleted file mode 100644
index 7f33ab1..0000000
--- a/lib/all-mills.test.fnl
+++ /dev/null
@@ -1,41 +0,0 @@
-(let [{: describe
-       :end test-end} (require :lib.test)
-      {: all-mills?
-       : toggle-player
-       : only-player-moves
-       : all-moves-are-mills?
-       } (require :lib.all-mills)]
-
-  (describe "all-mills" (fn []
-    (describe "#toggle-player()" (fn [t]
-      (t {:given "a player"
-          :should "return the next"
-          :expected 2
-          :actual (toggle-player 1)
-          })))
-    (describe "#only-player-moves()" (fn [t]
-      (let [moves    [ 0 2 0 2 2 2 0 0 0 0 0 0 0 2 0 0 0 2 0 2 0 1 1 1 ]
-            expected [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 ]
-            ]
-        (t {:given "a bunch of moves and a player"
-            :should "filter out all the moves not belonging to the player"
-            : expected
-            :actual (only-player-moves moves 1)
-            }))))
-    (describe "#all-moves-are-mills?()" (fn [t]
-      (let [moves [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 ]
-            ]
-        (t {:given "a bunch of moves and a player"
-            :should "return true if all the player moves are mills"
-            :expected true
-            :actual (all-moves-are-mills? moves 1)
-            }))
-      (let [moves [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 ]
-            ]
-        (t {:given "a bunch of moves and no mill and a player"
-            :should "return false"
-            :expected false
-            :actual (all-moves-are-mills? moves 1)
-            }))))
-    (test-end))))
-
diff --git a/lib/constants.fnl b/lib/constants.fnl
index c88b279..be9a6be 100644
--- a/lib/constants.fnl
+++ b/lib/constants.fnl
@@ -84,8 +84,22 @@
   "G x-----x-----x" ;; 22 23 24
   ])
 
+
+;; there are three phases of play:
+;; placing, moving, and flying.
+;; (plus one for capturing)
+;; (plus one for game-over)
+(local stages {
+  :placing  1 ;; placing the cows
+  :moving   2 ;; moving the cows
+  :flying   3 ;; flying the cows
+  :capture  4 ;; capture a cow (we do not shoot cows)
+  :complete 5 ;; no more cows! jk the cows are fine. the game's just over okay
+})
+
 {: board
  : mills
  : neighbors
+ : stages
  : spaces}
 
diff --git a/lib/contains.fnl b/lib/contains.fnl
deleted file mode 100644
index 75275af..0000000
--- a/lib/contains.fnl
+++ /dev/null
@@ -1,7 +0,0 @@
-(fn contains [t x]
-  (accumulate [found false
-               _ v (ipairs t)
-               &until found] ; escape early
-    (or found (= x v))))
-
-{: contains}
diff --git a/lib/contains.test.fnl b/lib/contains.test.fnl
deleted file mode 100644
index 45a00af..0000000
--- a/lib/contains.test.fnl
+++ /dev/null
@@ -1,17 +0,0 @@
-(let [{: contains } (require :lib.contains)
-      {: describe } (require :lib.test)
-      {: describe :end test-end} (require :lib.test)
-      ]
-
-       (describe "contains()" (fn [t]
-                                (t {:given "a list and an element it contains"
-                                    :should "returns true"
-                                    :expected true
-                                    :actual (contains [:apple :orange :pear] :apple)}
-                                   )
-                                (t {:given "a list and an element it does not contain"
-                                    :should "returns false"
-                                    :expected false
-                                    :actual (contains [:apple :orange :pear] :gorilla)
-                                    })
-                                (test-end))))
diff --git a/lib/either.test.fnl b/lib/either.test.fnl
index 8ae0c08..5a29ea7 100644
--- a/lib/either.test.fnl
+++ b/lib/either.test.fnl
@@ -1,41 +1,33 @@
-(let [{: pprint} (require :lib.tableprint)
-      {: describe :end test-end} (require :lib.test)
+(let [{:print pprint} (require :lib.table)
+      {: describe : test-end} (require :lib.test)
       {: Either : Left : Right } (require :lib.either)]
-  (describe "Either" (fn [t]
-                       (t {:given "a new either"
-                           :should "set its value correctly"
-                           :expected :poop
-                           :actual (. (Either:new :poop) :value)
-                           })
-                       (t
-                        (let [r (Right:new "rain")
-                              map (r:map #(.. "b" $1))
-                              expected :brain
-                              actual (. map :value)]
-                          {:given "a Right of some value"
-                           :should "map"
-                           expected
-                           actual
-                           }))
-                       (t
-                        (let [ l (Left:new "rain")
-                              map (l:map #(.. "b" $1))
-                              expected :rain
-                              actual (. map :value)
-                              ]
-                          {:given "a Left of some value"
-                           :should "not map"
-                           expected
-                           actual
-                           }))
-                       (t
-                        (let [ e (Either.of "rank")
-                              map (e:map #(.. "f" $1))
-                              expected :frank
-                              actual (. map :value) ]
-                          {:given "Either.of"
-                           :should "map"
-                           expected
-                           actual
-                           }))
-                       (test-end))))
+  (describe "# EITHER" (fn [t]
+    (t {:given "a new either"
+        :should "set its value correctly"
+        :expected :poop
+        :actual (. (Either:new :poop) :value) })
+    (t (let [r (Right:new "rain")
+             map (r:map #(.. "b" $1))
+             expected :brain
+             actual (. map :value)]
+       {:given "a Right of some value"
+        :should "map"
+        expected
+        actual }))
+    (t (let [ l (Left:new "rain")
+              map (l:map #(.. "b" $1))
+              expected :rain
+              actual (. map :value) ]
+       {:given "a Left of some value"
+        :should "not map"
+        expected
+        actual }))
+    (t (let [ e (Either.of "rank")
+              map (e:map #(.. "f" $1))
+              expected :frank
+              actual (. map :value) ]
+       {:given "Either.of"
+        :should "map"
+        expected
+        actual }))
+    (test-end))))
diff --git a/lib/equal.fnl b/lib/equal.fnl
deleted file mode 100644
index cc34ada..0000000
--- a/lib/equal.fnl
+++ /dev/null
@@ -1,22 +0,0 @@
-;; thanks:
-;; https://gist.github.com/sapphyrus/fd9aeb871e3ce966cc4b0b969f62f539
-;; and antifennel
-(fn deep-equals [o1 o2 ignore-mt]
-  (when (= o1 o2) (lua "return true"))
-  (local o1-type (type o1))
-  (local o2-type (type o2))
-  (when (not= o1-type o2-type) (lua "return false"))
-  (when (not= o1-type :table) (lua "return false"))
-  (when (not ignore-mt)
-    (local mt1 (getmetatable o1))
-    (when (and mt1 mt1.__eq)
-      (let [___antifnl_rtn_1___ (= o1 o2)] (lua "return ___antifnl_rtn_1___"))))
-  (each [key1 value1 (pairs o1)]
-    (local value2 (. o2 key1))
-    (when (or (= value2 nil) (= (deep-equals value1 value2 ignore-mt) false))
-      (lua "return false")))
-  (each [key2 _ (pairs o2)]
-    (when (= (. o1 key2) nil) (lua "return false")))
-  true)	
-
-{:equal deep-equals}
diff --git a/lib/equal.test.fnl b/lib/equal.test.fnl
deleted file mode 100644
index 0ee8da7..0000000
--- a/lib/equal.test.fnl
+++ /dev/null
@@ -1,28 +0,0 @@
-(let [{: equal} (require :lib.equal)
-      {: describe :end test-end} (require :lib.test)]
-  (describe "equal()" (fn [t]
-                       (t {:given "two equal tables"
-                           :should "return true"
-                           :expected true
-                           :actual (equal [:orange :apple :pear] [:orange :apple :pear]) })
-                       (t {:given "two different tables"
-                           :should "return false"
-                           :expected false
-                           :actual (equal [:apple :pear] [:orange :apple :pear]) })
-                       (t {:given "equal strings"
-                           :should "be true"
-                           :expected true
-                           :actual (equal :apple :apple) })
-                       (t {:given "different strings"
-                           :should "be false"
-                           :expected false
-                           :actual (equal :apple :pear) })
-                       (t {:given "equal bools"
-                           :should "be true"
-                           :expected true
-                           :actual (equal true true) })
-                       (t {:given "different strings"
-                           :should "be false"
-                           :expected false
-                           :actual (equal true false) })
-                       (test-end))))
diff --git a/lib/game/README b/lib/game/README
new file mode 100644
index 0000000..6dcd00f
--- /dev/null
+++ b/lib/game/README
@@ -0,0 +1,5 @@
+These are all game specific functions that are big and or complex enough that i
+wanted to break them out into their own modules so i could test them.
+
+when you add a function here, add a test file, and be sure to import/export it
+to/from index.fnl
diff --git a/lib/game/all-mills.fnl b/lib/game/all-mills.fnl
new file mode 100644
index 0000000..e5b3d2b
--- /dev/null
+++ b/lib/game/all-mills.fnl
@@ -0,0 +1,25 @@
+(local {: mill-at? } (require :lib.game.mill))
+(local {: mills } (require :lib.constants))
+
+(fn toggle-player [p] (if (= p 1) 2 1))
+
+(fn only-player-moves [moves player]
+  (icollect [_ move (ipairs moves)] (if (= move player) player 0)))
+
+(fn all-moves-are-mills? [player moves]
+  (accumulate [result true
+               i m (ipairs moves) ]
+              (and result (if (= m 0) true (mill-at? mills moves i)))))
+
+(fn all-mills? [all-moves current-player]
+  (->> current-player
+       (toggle-player)
+       (only-player-moves all-moves)
+       (all-moves-are-mills? current-player)))
+
+{: all-mills?
+ ;; do not use; just for testing:
+ : toggle-player
+ : only-player-moves
+ : all-moves-are-mills?
+ }
diff --git a/lib/game/all-mills.test.fnl b/lib/game/all-mills.test.fnl
new file mode 100644
index 0000000..055f6a5
--- /dev/null
+++ b/lib/game/all-mills.test.fnl
@@ -0,0 +1,41 @@
+(let [{: describe
+       : test-end} (require :lib.test)
+      {: all-mills?
+       : toggle-player
+       : only-player-moves
+       : all-moves-are-mills?
+       } (require :lib.game.all-mills)]
+
+  (describe "# ALL-MILLS" (fn []
+    (describe "toggle-player()" (fn [t]
+      (t {:given "a player"
+          :should "return the next"
+          :expected 2
+          :actual (toggle-player 1)
+          })))
+    (describe "only-player-moves()" (fn [t]
+      (let [moves    [ 0 2 0 2 2 2 0 0 0 0 0 0 0 2 0 0 0 2 0 2 0 1 1 1 ]
+            expected [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 ]
+            ]
+        (t {:given "a bunch of moves and a player"
+            :should "filter out all the moves not belonging to the player"
+            : expected
+            :actual (only-player-moves moves 1)
+            }))))
+    (describe "all-moves-are-mills?()" (fn [t]
+      (let [moves [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 ]
+            ]
+        (t {:given "a bunch of moves and a player"
+            :should "return true if all the player moves are mills"
+            :expected true
+            :actual (all-moves-are-mills? 1 moves)
+            }))
+      (let [moves [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 ]
+            ]
+        (t {:given "a bunch of moves and no mill and a player"
+            :should "return false"
+            :expected false
+            :actual (all-moves-are-mills? 1 moves)
+            }))))
+    (test-end))))
+
diff --git a/lib/game/index.fnl b/lib/game/index.fnl
new file mode 100644
index 0000000..f542f76
--- /dev/null
+++ b/lib/game/index.fnl
@@ -0,0 +1,11 @@
+(local {: all-mills?} (require :lib.game.all-mills))
+(local {: mill-at?} (require :lib.game.mill))
+(local {: space-is-neighbor?} (require :lib.game.space-is-neighbor))
+(local {: no-moves?} (require :lib.game.no-moves))
+
+{
+ : all-mills?
+ : mill-at?
+ : no-moves?
+ : space-is-neighbor?
+ }
diff --git a/lib/game/mill.fnl b/lib/game/mill.fnl
new file mode 100644
index 0000000..d15b53e
--- /dev/null
+++ b/lib/game/mill.fnl
@@ -0,0 +1,41 @@
+(local {: contains} (require :lib.table))
+
+(fn get-candidates [all-mills next-move]
+  "a list of mills that contain next-move"
+  (icollect [_ mill (ipairs all-mills)]
+    (if (contains mill next-move) mill)))
+
+(fn any [t]
+  "take a list of booleans, returns true if any of them are true"
+  (accumulate [acc false
+               i x (ipairs t)]
+               (or acc x)))
+
+(fn move-mills [moves-list]
+  (icollect [_ moves (ipairs moves-list)]
+    (let [player (. moves 1)]
+      (accumulate [acc true
+                   _ m (ipairs moves)]
+                  (and acc (not= m 0) (= player m))))))
+
+(fn candidate-moves [moves candidates]
+  "Just turning board spaces into player moves"
+  (icollect [_ spaces (ipairs candidates)]
+            (icollect [_ space (ipairs spaces)]
+                      (. moves space))))
+
+(fn mill-at? [all-mills current-moves move]
+  "Is there a mill at this move?"
+  (->> (get-candidates all-mills move)
+       (candidate-moves current-moves) 
+       (move-mills)
+       (any)))
+
+{: mill-at?
+ ;; not for consumption,
+ ;; just for testing:
+ : get-candidates
+ : candidate-moves
+ : move-mills
+ : any
+ }
diff --git a/lib/game/mill.test.fnl b/lib/game/mill.test.fnl
new file mode 100644
index 0000000..604c759
--- /dev/null
+++ b/lib/game/mill.test.fnl
@@ -0,0 +1,123 @@
+(let [{: describe
+       : test-end} (require :lib.test)
+      {: mill-at?
+       : get-candidates
+       : move-mills
+       : candidate-moves
+       : any
+       } (require :lib.game.mill)
+      {: mills } (require :lib.constants)
+      with-mills (partial mill-at? mills)]
+
+  (describe "# MILL" (fn []
+    (describe "get-candidates()" (fn [t]
+        (t
+          (let [move 3
+                expected [[1 2 3] [3 15 24]]
+                moves [ 1 1 0 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] ]
+            {:given (string.format "a move of %d" move)
+            :should "return [[1 2 3] [3 15 24]]" 
+            : expected 
+            :actual (get-candidates mills move)
+            }))
+        (t
+          (let [move 1
+                expected [[1 2 3] [1 10 22]]
+                moves [ 0 0 0 ] ]
+            {:given (string.format "a move of %d" move)
+            :should "return [[1 2 3] [1 10 22]]" 
+            : expected 
+            :actual (get-candidates mills move) }))
+        (t
+          (let [move 1
+                moves [2 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
+                expected [[1 2 3] [1 10 22]]
+               ]
+            {:given (string.format "a move of %d" move)
+            :should "still return [[1 2 3] [1 10 22]]" 
+            : expected 
+            :actual (get-candidates mills move) }))))
+        
+    (describe "any()" (fn [t]
+        (t {:given "a table of false false true"
+            :should "return true"
+            :expected true
+            :actual (any [false false true]) })
+        (t {:given "a table of true false"
+            :should "return true"
+            :expected true
+            :actual (any [true false]) })
+        (t {:given "a single false"
+            :should "return false"
+            :expected false
+            :actual (any [false]) })
+        (t {:given "a single true"
+            :should "return true"
+            :expected true
+            :actual (any [true]) })))
+
+    (describe "move-mills()" (fn [t]
+      (t
+        (let [moves [[1 1 1] [0 2 2]] ]
+          {:given "a list of moves"
+           :should "turn them into true/false if they are mills"
+           :expected [true false]
+           :actual (move-mills moves) }))
+      (t
+        (let [moves [[0 1 1] [0 2 2]] ]
+          {:given "no mills"
+           :should "should return false"
+           :expected [false false]
+           :actual (move-mills moves) }))
+      (t
+        (let [moves [[2 2 2] [2 0 0]] ]
+          {:given "mill, no mill"
+           :should "should return true false"
+           :expected [true false]
+           :actual (move-mills moves) }))))
+
+    (describe "candidate-moves()" (fn [t]
+      (t (let [spaces [[1 2 3] [1 10 22]]
+               moves [2 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] ]
+         {:given "spaces [[1 2 3] [1 10 22]]"
+          :should "map to moves"
+          :expected [[2 2 2] [2 0 0]]
+          :actual (candidate-moves moves spaces)}))))
+
+    (describe "mill-at?()" (fn [t]
+        (t 
+          (let [move 1
+                moves [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] ]
+            {:given "no mills"
+             :should "return false"
+             :expected false
+             :actual (mill-at? mills moves move)}))
+        (t 
+          (let [move 4
+                moves [1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
+                with-mills (partial mill-at? mills) 
+                with-moves (partial with-mills moves)]
+            {:given "a mill but not at Move"
+             :should "return false"
+             :expected false
+             :actual (with-moves move)}))
+        (t 
+          (let [move 1
+                moves [2 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
+                with-mills (partial mill-at? mills) 
+                with-moves (partial with-mills moves)]
+            {:given "a mill"
+             :should "return true"
+             :expected true
+             :actual (with-moves move) }))
+        (t 
+          (let [move 1
+                moves [2 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
+                with-mills (partial mill-at? mills) 
+                with-moves (partial with-mills moves)]
+            {:given "a mill"
+             :should "return the opposite of false"
+             :expected false
+             :actual (not (with-moves move)) }))))
+
+    (test-end))))
diff --git a/lib/game/no-moves.fnl b/lib/game/no-moves.fnl
new file mode 100644
index 0000000..02482cc
--- /dev/null
+++ b/lib/game/no-moves.fnl
@@ -0,0 +1,26 @@
+(local {: tail} (require :lib.table))
+
+(fn get-player-idxs [player moves]
+  (icollect [i p (ipairs moves)] (when (= p player) i)))
+
+(fn idx-to-neighbors [idxs all-neighbors]
+  (icollect [_ i (ipairs idxs)] (tail (. all-neighbors i))))
+
+(fn neighbor-is-occupied? [neighbors moves]
+  (icollect [_ move (ipairs neighbors)]
+    (icollect [_ neighbor (ipairs move)]
+      (not= (. moves neighbor) 0))))
+
+(fn reduce-to-bool [xs]
+  (accumulate [acc true
+               _ x (ipairs xs)]
+               (and x)))
+
+(fn no-moves? [neighbors all-moves player]
+  (-> (get-player-idxs player all-moves)
+    (idx-to-neighbors neighbors)
+    (neighbor-is-occupied? all-moves)
+    (reduce-to-bool)
+    (reduce-to-bool)))
+
+{: no-moves? }
diff --git a/lib/game/no-moves.test.fnl b/lib/game/no-moves.test.fnl
new file mode 100644
index 0000000..a94d60a
--- /dev/null
+++ b/lib/game/no-moves.test.fnl
@@ -0,0 +1,49 @@
+(let [{: no-moves?} (require :lib.game.no-moves)
+      {: neighbors} (require :lib.constants)
+      {: describe : test-end} (require :lib.test)
+      with-neighbors (partial no-moves? neighbors)
+      ]
+
+  (describe "# NOMOVES" (fn [t]
+    (let [moves [ 1 2 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 ]
+          player 1
+          ]
+        (t {:given "one move with no moves"
+            :should "return true"
+            :expected true
+            :actual (with-neighbors moves player)
+            }))
+    (let [moves [ 1 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ]
+          player 1
+          ]
+        (t {:given "one move with one move"
+            :should "return false"
+            :expected false
+            :actual (with-neighbors moves player)
+            }))
+    (let [moves [ 1 1 1 0 2 0 0 0 0 2 0 0 0 0 2 0 0 0 0 0 0 0 ]
+          player 1
+          ]
+        (t {:given "several moves with no moves"
+            :should "return true"
+            :expected true
+            :actual (with-neighbors moves player)
+            }))
+    (let [moves [ 0 2 0 2 1 2 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ]
+          player 1
+          ]
+        (t {:given "four occupied neighbors"
+            :should "return true"
+            :expected true
+            :actual (with-neighbors moves player)
+            }))
+    (let [moves [ 1 2 1 2 0 2 1 2 1 2 1 0 1 2 1 2 2 2 0 1 0 0 0 0 0 ]
+          player 2
+          ]
+        (t {:given "this turn that is giving me trouble"
+            :should "return true"
+            :expected true
+            :actual (with-neighbors moves player)
+            }))
+
+    (test-end))))
diff --git a/lib/game/space-is-neighbor.fnl b/lib/game/space-is-neighbor.fnl
new file mode 100644
index 0000000..373feaf
--- /dev/null
+++ b/lib/game/space-is-neighbor.fnl
@@ -0,0 +1,19 @@
+(local {: contains
+        : head
+        : tail
+        } (require :lib.table))
+
+(lambda space-is-neighbor? [all-neighbors from to]
+  ;; i have learned to check that i'm passing the correct type of move
+  ;; i.e. a number and not a string
+  (assert (= "number" (type from)) "from must be a number")
+  (assert (= "number" (type to)) "to must be a number")
+  (assert (= "table" (type all-neighbors)) "all-neighbors must be a table")
+
+  (let [neighborhood-list (icollect [_ n (ipairs all-neighbors)] (if (= from (head n)) n))
+        neighborhood (head neighborhood-list)
+        neighbors (tail neighborhood)
+        is-neighbor (contains neighbors to)]
+    is-neighbor))
+
+{: space-is-neighbor?}
diff --git a/lib/game/space-is-neighbor.test.fnl b/lib/game/space-is-neighbor.test.fnl
new file mode 100644
index 0000000..0ae7d4e
--- /dev/null
+++ b/lib/game/space-is-neighbor.test.fnl
@@ -0,0 +1,20 @@
+(let [{: space-is-neighbor?} (require :lib.game.space-is-neighbor)
+      {: neighbors} (require :lib.constants)
+      {: describe : test-end} (require :lib.test)
+      with-neighbors (partial space-is-neighbor? neighbors) ]
+
+  (describe "# SPACE-IS-NEIGHBOR" (fn [t]
+    (t {:given "space of 3"
+        :should "know 2 is a neighbor"
+        :expected true
+        :actual  (with-neighbors 3 2)})
+    (t {:given "space of 3"
+        :should "know 15 is a neighbor"
+        :expected true
+        :actual  (with-neighbors 3 15)})
+    (t {:given "space of 3"
+        :should "know 1 is not a neighbor"
+        :expected false
+        :actual  (with-neighbors 3 1)})
+
+    (test-end))))
diff --git a/lib/head.fnl b/lib/head.fnl
deleted file mode 100644
index ddee698..0000000
--- a/lib/head.fnl
+++ /dev/null
@@ -1,6 +0,0 @@
-; return the first item in a table
-(fn head [t] (if (> (length t) 0)
-               (?. t 1)
-               []))
-
-{: head}
diff --git a/lib/head.test.fnl b/lib/head.test.fnl
deleted file mode 100644
index 1209599..0000000
--- a/lib/head.test.fnl
+++ /dev/null
@@ -1,12 +0,0 @@
-(let [{: head} (require :lib.head)
-      {: describe :end test-end} (require :lib.test)]
-  (describe "head()" (fn [t]
-                       (t {:given "a list of elements"
-                           :should "returns the first element of a list"
-                           :expected :apple
-                           :actual  (head [:apple :orange :pear])})
-                       (t {:given "an empty list"
-                           :should "returns an empty list"
-                           :expected 0
-                           :actual (length (head []))})
-                       (test-end))))
diff --git a/lib/index.fnl b/lib/index.fnl
index 2eff31e..1b4b728 100644
--- a/lib/index.fnl
+++ b/lib/index.fnl
@@ -1,15 +1,7 @@
 (local str (require :lib.string))
 (local tbl (require :lib.table))
-(local {: all-mills?} (require :lib.all-mills))
-(local {: mill-at?} (require :lib.mill))
-(local {: space-is-neighbor?} (require :lib.space-is-neighbor))
-(local {: no-moves?} (require :lib.no-moves))
 
 {
  : str
  : tbl
- : all-mills?
- : mill-at?
- : no-moves?
- : space-is-neighbor?
  }
diff --git a/lib/keys.fnl b/lib/keys.fnl
deleted file mode 100644
index 0f3364a..0000000
--- a/lib/keys.fnl
+++ /dev/null
@@ -1,7 +0,0 @@
-(fn keys [t]
-  "takes a table returns a sequential list of its keys"
-  (local out [])
-  (each [k v (pairs t)] (table.insert out k))
-  out)
-
-{: keys}
diff --git a/lib/keys.test.fnl b/lib/keys.test.fnl
deleted file mode 100644
index 413a773..0000000
--- a/lib/keys.test.fnl
+++ /dev/null
@@ -1,13 +0,0 @@
-(let [{: keys} (require :lib.keys)
-      {: describe :end test-end} (require :lib.test)]
-  (describe "keys()" (fn [t]
-                       (let [input {:apple :red :banana :yellow}
-                             actual (keys input)
-                             sorted (table.sort actual) ;; SIDE EFFECT!!
-                             ]
-                         (t {:given "a table"
-                             :should "returns a list of keys"
-                             :expected [:apple :banana]
-                             : actual})
-                         (test-end)))))
-
diff --git a/lib/kvflip.fnl b/lib/kvflip.fnl
deleted file mode 100644
index 25fc222..0000000
--- a/lib/kvflip.fnl
+++ /dev/null
@@ -1,6 +0,0 @@
-(fn kvflip [t]
-  "takes a table of {key value} and returns a table of {value key}"
-  (collect [k v (pairs t)] (values v k)))
-
-{: kvflip}
-
diff --git a/lib/kvflip.test.fnl b/lib/kvflip.test.fnl
deleted file mode 100644
index 162650d..0000000
--- a/lib/kvflip.test.fnl
+++ /dev/null
@@ -1,13 +0,0 @@
-(let [{: kvflip} (require :lib.kvflip)
-      {: describe :end test-end} (require :lib.test)]
-  (describe "kvflip()" (fn [t]
-                       (let [input {:apple "red" :banana "yellow"}
-                             expected {:red "apple" :yellow "banana"}
-                             ]
-                         (t {:given "a table"
-                             :should "kvflip that table!"
-                             : expected
-                             :actual (kvflip input)})
-                         (test-end)))))
-
-
diff --git a/lib/mill.fnl b/lib/mill.fnl
deleted file mode 100644
index f9c8673..0000000
--- a/lib/mill.fnl
+++ /dev/null
@@ -1,41 +0,0 @@
-(local {: contains} (require :lib.contains))
-
-(fn get-candidates [all-mills next-move]
-  "a list of mills that contain next-move"
-  (icollect [_ mill (ipairs all-mills)] (if (contains mill next-move) mill)))
-
-(fn any [t]
-  "take a list of booleans, returns true if any of them are true"
-  (accumulate [acc false
-               i x (ipairs t)]
-               (or acc x)))
-
-(fn move-mills [moves-list]
-  (icollect [_ moves (ipairs moves-list)]
-    (let [player (. moves 1)]
-      (accumulate [acc true
-                   _ m (ipairs moves)]
-                  (and acc (not= m 0) (= player m))))))
-
-(fn candidate-moves [candidates moves]
-  "Just turning board spaces into player moves"
-  (icollect [_ spaces (ipairs candidates)]
-            (icollect [_ space (ipairs spaces)]
-                      (. moves space))))
-
-(fn mill-at? [all-mills current-moves move]
-  "Is there a mill at this move?"
-  (let [candidates (get-candidates all-mills move)
-        my-moves (candidate-moves candidates current-moves)
-        my-mills (move-mills my-moves)
-        result (any my-mills)]
-    result))
-
-{: mill-at?
- ;; not for consumption,
- ;; just for testing:
- : get-candidates
- : candidate-moves
- : move-mills
- : any
- }
diff --git a/lib/mill.test.fnl b/lib/mill.test.fnl
deleted file mode 100644
index 04f7e97..0000000
--- a/lib/mill.test.fnl
+++ /dev/null
@@ -1,150 +0,0 @@
-(let [{: describe
-       :end test-end} (require :lib.test)
-      {: mill-at?
-       : get-candidates
-       : move-mills
-       : candidate-moves
-       : any
-       } (require :lib.mill)
-      {: mills } (require :lib.constants)
-      with-mills (partial mill-at? mills)]
-
-  (describe "Mill" (fn []
-    (describe "#get-candidates()" (fn [t]
-        (t
-          (let [move 3
-                expected [[1 2 3] [3 15 24]]
-                moves [ 1 1 0 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ]
-               ]
-            {:given (string.format "a move of %d" move)
-            :should "return [[1 2 3] [3 15 24]]" 
-            : expected 
-            :actual (get-candidates mills move)
-            }))
-        (t
-          (let [move 1
-                expected [[1 2 3] [1 10 22]]
-                moves [ 0 0 0 ]
-               ]
-            {:given (string.format "a move of %d" move)
-            :should "return [[1 2 3] [1 10 22]]" 
-            : expected 
-            :actual (get-candidates mills move)
-            }))
-        (t
-          (let [move 1
-                moves [2 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
-                expected [[1 2 3] [1 10 22]]
-               ]
-            {:given (string.format "a move of %d" move)
-            :should "still return [[1 2 3] [1 10 22]]" 
-            : expected 
-            :actual (get-candidates mills move)
-            }))
-        ))
-        
-    (describe "#any()" (fn [t]
-        (t {:given "a table of false false true"
-            :should "return true"
-            :expected true
-            :actual (any [false false true])
-           })
-        (t {:given "a table of true false"
-            :should "return true"
-            :expected true
-            :actual (any [true false])
-           })
-        (t {:given "a single false"
-            :should "return false"
-            :expected false
-            :actual (any [false])
-           })
-        (t {:given "a single true"
-            :should "return true"
-            :expected true
-            :actual (any [true])
-           })))
-
-    (describe "#move-mills()" (fn [t]
-      (t
-        (let [moves [[1 1 1] [0 2 2]]
-              ]
-          {:given "a list of moves"
-           :should "turn them into true/false if they are mills"
-           :expected [true false]
-           :actual (move-mills moves)
-           }))
-      (t
-        (let [moves [[0 1 1] [0 2 2]]
-              ]
-          {:given "no mills"
-           :should "should return false"
-           :expected [false false]
-           :actual (move-mills moves)
-           }))
-      (t
-        (let [moves [[2 2 2] [2 0 0]]
-              ]
-          {:given "mill, no mill"
-           :should "should return true false"
-           :expected [true false]
-           :actual (move-mills moves)
-           }))
-      ))
-
-    (describe "#candidate-moves()" (fn [t]
-      (t (let [spaces [[1 2 3] [1 10 22]]
-               moves [2 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
-               ]
-         {:given "spaces [[1 2 3] [1 10 22]]"
-          :should "map to moves"
-          :expected [[2 2 2] [2 0 0]]
-          :actual (candidate-moves spaces moves) 
-          }
-         )
-       )  
-    ))
-
-    (describe "#mill-at?()" (fn [t]
-        (t 
-          (let [move 1
-                moves [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
-                ]
-            {:given "no mills"
-             :should "return false"
-             :expected false
-             :actual (mill-at? mills moves move)
-             }))
-        (t 
-          (let [move 4
-                moves [1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
-                with-mills (partial mill-at? mills) 
-                with-moves (partial with-mills moves)]
-            {:given "a mill but not at Move"
-             :should "return false"
-             :expected false
-             :actual (with-moves move)
-             }))
-        (t 
-          (let [move 1
-                moves [2 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
-                with-mills (partial mill-at? mills) 
-                with-moves (partial with-mills moves)]
-            {:given "a mill"
-             :should "return true"
-             :expected true
-             :actual (with-moves move)
-             }))
-        (t 
-          (let [move 1
-                moves [2 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
-                with-mills (partial mill-at? mills) 
-                with-moves (partial with-mills moves)]
-            {:given "a mill"
-             :should "return the opposite of false"
-             :expected false
-             :actual (not (with-moves move))
-             }))
-        ))
-
-    (test-end))))
diff --git a/lib/no-moves.fnl b/lib/no-moves.fnl
deleted file mode 100644
index 591cb7c..0000000
--- a/lib/no-moves.fnl
+++ /dev/null
@@ -1,26 +0,0 @@
-(local {: tail} (require :lib.tail))
-
-(fn get-player-idxs [player moves]
-  (icollect [i p (ipairs moves)] (when (= p player) i)))
-
-(fn idx-to-neighbors [idxs all-neighbors]
-  (icollect [_ i (ipairs idxs)] (tail (. all-neighbors i))))
-
-(fn neighbor-is-occupied? [neighbors moves]
-  (icollect [_ move (ipairs neighbors)]
-    (icollect [_ neighbor (ipairs move)]
-      (not= (. moves neighbor) 0))))
-
-(fn reduce-to-bool [xs]
-  (accumulate [acc true
-               _ x (ipairs xs)]
-               (and x)))
-
-(fn no-moves? [neighbors all-moves player]
-  (-> (get-player-idxs player all-moves)
-    (idx-to-neighbors neighbors)
-    (neighbor-is-occupied? all-moves)
-    (reduce-to-bool)
-    (reduce-to-bool)))
-
-{: no-moves? }
diff --git a/lib/no-moves.test.fnl b/lib/no-moves.test.fnl
deleted file mode 100644
index db0613c..0000000
--- a/lib/no-moves.test.fnl
+++ /dev/null
@@ -1,51 +0,0 @@
-(let [{: no-moves?} (require :lib.no-moves)
-      {: neighbors} (require :lib.constants)
-      {: describe :end test-end} (require :lib.test)
-      with-neighbors (partial no-moves? neighbors)
-      ]
-
-  (describe "no-moves()" (fn [t]
-    (let [moves [ 1 2 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 ]
-          player 1
-          ]
-        (t {:given "one move with no moves"
-            :should "return true"
-            :expected true
-            :actual (with-neighbors moves player)
-            }))
-    (let [moves [ 1 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ]
-          player 1
-          ]
-        (t {:given "one move with one move"
-            :should "return false"
-            :expected false
-            :actual (with-neighbors moves player)
-            }))
-    (let [moves [ 1 1 1 0 2 0 0 0 0 2 0 0 0 0 2 0 0 0 0 0 0 0 ]
-          player 1
-          ]
-        (t {:given "several moves with no moves"
-            :should "return true"
-            :expected true
-            :actual (with-neighbors moves player)
-            }))
-    (let [moves [ 0 2 0 2 1 2 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ]
-          player 1
-          ]
-        (t {:given "four occupied neighbors"
-            :should "return true"
-            :expected true
-            :actual (with-neighbors moves player)
-            }))
-    (let [moves [ 1 2 1 2 0 2 1 2 1 2 1 0 1 2 1 2 2 2 0 1 0 0 0 0 0 ]
-          player 2
-          ]
-        (t {:given "this turn that is giving me trouble"
-            :should "return true"
-            :expected true
-            :actual (with-neighbors moves player)
-            }))
-
-    (test-end))))
-
-
diff --git a/lib/slice.fnl b/lib/slice.fnl
deleted file mode 100644
index 4f0de0f..0000000
--- a/lib/slice.fnl
+++ /dev/null
@@ -1,5 +0,0 @@
-(fn slice [t start stop]
-  (fcollect [i start (or stop (length t))]
-    (. t i)))
-
-{: slice}
diff --git a/lib/slice.test.fnl b/lib/slice.test.fnl
deleted file mode 100644
index 9293f93..0000000
--- a/lib/slice.test.fnl
+++ /dev/null
@@ -1,19 +0,0 @@
-(let [{: slice} (require :lib.slice)
-      {: describe :end test-end} (require :lib.test)]
-  (describe "slice()" (fn [t]
-                       (t 
-                         (let [t [:apple :orange :pear :banana :strawberry]
-                              ]
-                           {:given "a list of elements and a start"
-                            :should "return the list starting at start"
-                            :expected [:orange :pear :banana :strawberry]
-                            :actual  (slice t 2)}))
-                       (t 
-                         (let [t [:apple :orange :pear :banana :strawberry]
-                              ]
-                           {:given "a list of elements and a start and a stop"
-                            :should "return the items between the two"
-                            :expected [:orange :pear]
-                            :actual  (slice t 2 3)}))
-                       (test-end))))
-
diff --git a/lib/space-is-neighbor.fnl b/lib/space-is-neighbor.fnl
deleted file mode 100644
index 380607c..0000000
--- a/lib/space-is-neighbor.fnl
+++ /dev/null
@@ -1,18 +0,0 @@
-(local {: contains} (require :lib.contains))
-(local {: head} (require :lib.head))
-(local {: tail} (require :lib.tail))
-
-(lambda space-is-neighbor? [all-neighbors from to]
-  ;; i have learned to check that i'm passing the correct type of move
-  ;; i.e. a number and not a string
-  (assert (= "number" (type from)) "from must be a number")
-  (assert (= "number" (type to)) "to must be a number")
-  (assert (= "table" (type all-neighbors)) "all-neighbors must be a table")
-
-  (let [neighborhood-list (icollect [_ n (ipairs all-neighbors)] (if (= from (head n)) n))
-        neighborhood (head neighborhood-list)
-        neighbors (tail neighborhood)
-        is-neighbor (contains neighbors to)]
-    is-neighbor))
-
-{: space-is-neighbor?}
diff --git a/lib/space-is-neighbor.test.fnl b/lib/space-is-neighbor.test.fnl
deleted file mode 100644
index 7b0c0af..0000000
--- a/lib/space-is-neighbor.test.fnl
+++ /dev/null
@@ -1,22 +0,0 @@
-(let [{: space-is-neighbor?} (require :lib.space-is-neighbor)
-      {: neighbors} (require :lib.constants)
-      {: describe :end test-end} (require :lib.test)
-      with-neighbors (partial space-is-neighbor? neighbors)
-      ]
-
-  (describe "space-is-neighbor()" (fn [t]
-    (t {:given "space of 3"
-        :should "know 2 is a neighbor"
-        :expected true
-        :actual  (with-neighbors 3 2)})
-    (t {:given "space of 3"
-        :should "know 15 is a neighbor"
-        :expected true
-        :actual  (with-neighbors 3 15)})
-    (t {:given "space of 3"
-        :should "know 1 is not a neighbor"
-        :expected false
-        :actual  (with-neighbors 3 1)})
-
-    (test-end))))
-
diff --git a/lib/string.fnl b/lib/string.fnl
index 510b0ed..28d1866 100644
--- a/lib/string.fnl
+++ b/lib/string.fnl
@@ -1,3 +1,5 @@
+;; string funs
+
 (fn capitalize [s]
   (.. (string.upper (string.sub s 1 1)) (string.sub s 2)))
 
diff --git a/lib/string.test.fnl b/lib/string.test.fnl
new file mode 100644
index 0000000..1f9bdbd
--- /dev/null
+++ b/lib/string.test.fnl
@@ -0,0 +1,13 @@
+(let [{: capitalize
+       } (require :lib.string)
+      {: describe
+       : test-end} (require :lib.test)]
+
+(describe "# STRING" (fn []
+  (describe "capitalize()" (fn [t]
+    (t {:given "a string"
+        :should "capitalize it"
+        :expected :Giraffe
+        :actual (capitalize :giraffe)})))
+ (test-end))))
+
diff --git a/lib/table.fnl b/lib/table.fnl
index f40c299..276e12d 100644
--- a/lib/table.fnl
+++ b/lib/table.fnl
@@ -1,16 +1,50 @@
-(local {: contains} (require :lib.contains))
-(local {: head} (require :lib.head))
-(local {: keys} (require :lib.keys))
-(local {:kvflip invert} (require :lib.kvflip))
-(local {:pprint print} (require :lib.tableprint))
-(local {: slice} (require :lib.slice))
-(local {: tail} (require :lib.tail))
+;; table funs
+
+(fn contains [t x]
+  "does table t contain element x?"
+  (accumulate [found false
+               _ v (ipairs t)
+               &until found] ; escape early
+    (or found (= x v))))
+
+(fn head [t] 
+  "return the first item in a table"
+  (if (> (length t) 0)
+   (?. t 1)
+   []))
+
+(fn tail [t]
+  "return the table minus the head"
+  (icollect [i v (ipairs t)]
+            (if (> i 1)
+              v)))
+
+(fn keys [t]
+  "takes a table returns a sequential list of its keys"
+  (local out [])
+  (each [k v (pairs t)] (table.insert out k))
+  out)
+
+(fn flip [t]
+  "takes a table of {key value} and returns a table of {value key}"
+  (collect [k v (pairs t)] (values v k)))
+
+(fn print [tbl]
+  "print a table"
+  (each [k v (pairs tbl)]
+    (let [table? (= (type v) :table)]
+      (print k v))))
+
+(fn slice [t start stop]
+  "return a slice of a table"
+  (fcollect [i start (or stop (length t))]
+    (. t i)))
 
 {
  : contains
+ : flip
  : head
  : keys
- : invert
  : print
  : slice
  : tail
diff --git a/lib/table.test.fnl b/lib/table.test.fnl
new file mode 100644
index 0000000..c004d3f
--- /dev/null
+++ b/lib/table.test.fnl
@@ -0,0 +1,80 @@
+(let [{: contains
+       : flip
+       : head
+       : keys
+       : slice
+       : tail
+       } (require :lib.table)
+      {: describe
+       : test-end} (require :lib.test)]
+
+(describe "# TABLE" (fn []
+  (describe "contains()" (fn [t]
+    (t {:given "a list and an element it contains"
+        :should "returns true"
+        :expected true
+        :actual (contains [:apple :orange :pear] :apple)})
+    (t {:given "a list and an element it does not contain"
+        :should "returns false"
+        :expected false
+        :actual (contains [:apple :orange :pear] :gorilla)})))
+
+  (describe "flip()" (fn [t]
+    (let [input {:apple "red" :banana "yellow"}
+          expected {:red "apple" :yellow "banana"} ]
+      (t {:given "a table"
+          :should "flip that table!"
+          : expected
+          :actual (flip input)}))))
+
+  (describe "head()" (fn [t]
+    (t {:given "a list of elements"
+        :should "returns the first element of a list"
+        :expected :apple
+        :actual  (head [:apple :orange :pear])})
+    (t {:given "an empty list"
+        :should "returns an empty list"
+        :expected 0
+        :actual (length (head []))})))
+
+  (describe "keys()" (fn [t]
+    (let [input {:apple :red :banana :yellow}
+          actual (keys input)
+          sorted (table.sort actual) ;; SIDE EFFECT!!
+          ]
+      (t {:given "a table"
+          :should "returns a list of keys"
+          :expected [:apple :banana]
+          : actual}))))
+
+
+  (describe "slice()" (fn [t]
+    (t (let [t [:apple :orange :pear :banana :strawberry] ]
+        {:given "a list of elements and a start"
+         :should "return the list starting at start"
+         :expected [:orange :pear :banana :strawberry]
+         :actual  (slice t 2)}))
+    (t (let [t [:apple :orange :pear :banana :strawberry] ]
+        {:given "a list of elements and a start and a stop"
+         :should "return the items between the two"
+         :expected [:orange :pear]
+         :actual  (slice t 2 3)}))))
+
+
+  (describe "tail()" (fn [t]
+    (t {:given "a list"
+        :should "return it minus the head"
+        :expected [:apple :pear]
+        :actual (tail [:orange :apple :pear])
+        })
+    (t {:given "a single item list"
+        :should "return empty list"
+        :expected []
+        :actual (tail [:orange])
+        })
+    (t {:given "an empty list"
+        :should "return empty list"
+        :expected []
+        :actual (tail [])
+        })))
+ (test-end))))
diff --git a/lib/tableprint.fnl b/lib/tableprint.fnl
deleted file mode 100644
index 4d9bfbe..0000000
--- a/lib/tableprint.fnl
+++ /dev/null
@@ -1,7 +0,0 @@
-; print a table
-(fn pprint [tbl]
-  (each [k v (pairs tbl)]
-    (let [table? (= (type v) :table)]
-      (print k v))))
-
-{: pprint}
diff --git a/lib/tail.fnl b/lib/tail.fnl
deleted file mode 100644
index 24de254..0000000
--- a/lib/tail.fnl
+++ /dev/null
@@ -1,7 +0,0 @@
-; return the table minus the head
-(fn tail [t]
-  (icollect [i v (ipairs t)]
-            (if (> i 1)
-              v)))
-
-{: tail}
diff --git a/lib/tail.test.fnl b/lib/tail.test.fnl
deleted file mode 100644
index e507a0b..0000000
--- a/lib/tail.test.fnl
+++ /dev/null
@@ -1,19 +0,0 @@
-(let [{: tail} (require :lib.tail)
-      {: describe :end test-end} (require :lib.test)]
-  (describe "tail()" (fn [t]
-                       (t {:given "a list"
-                           :should "return it minus the head"
-                           :expected [:apple :pear]
-                           :actual (tail [:orange :apple :pear])
-                           })
-                       (t {:given "a single item list"
-                           :should "return empty list"
-                           :expected []
-                           :actual (tail [:orange])
-                           })
-                       (t {:given "an empty list"
-                           :should "return empty list"
-                           :expected []
-                           :actual (tail [])
-                           })
-                       (test-end))))
diff --git a/lib/test.fnl b/lib/test.fnl
index fbaaf8d..737275f 100644
--- a/lib/test.fnl
+++ b/lib/test.fnl
@@ -1,5 +1,25 @@
-(local {: pprint} (require :lib.tableprint))
-(local {: equal} (require :lib.equal))
+(local {:print pprint} (require :lib.table))
+
+;; thanks:
+;; https://gist.github.com/sapphyrus/fd9aeb871e3ce966cc4b0b969f62f539
+;; and antifennel
+(fn deep-equals [o1 o2 ignore-mt]
+  (when (= o1 o2) (lua "return true"))
+  (local o1-type (type o1))
+  (local o2-type (type o2))
+  (when (not= o1-type o2-type) (lua "return false"))
+  (when (not= o1-type :table) (lua "return false"))
+  (when (not ignore-mt)
+    (local mt1 (getmetatable o1))
+    (when (and mt1 mt1.__eq)
+      (let [___antifnl_rtn_1___ (= o1 o2)] (lua "return ___antifnl_rtn_1___"))))
+  (each [key1 value1 (pairs o1)]
+    (local value2 (. o2 key1))
+    (when (or (= value2 nil) (= (deep-equals value1 value2 ignore-mt) false))
+      (lua "return false")))
+  (each [key2 _ (pairs o2)]
+    (when (= (. o1 key2) nil) (lua "return false")))
+  true)	
 
 (var plan 0)
 
@@ -13,7 +33,7 @@
 
 (fn test [obj]
   (let [{: given : should : actual : expected} obj
-        ok (if (equal actual expected) :ok "not ok")
+        ok (if (deep-equals actual expected) :ok "not ok")
         description (.. "Given " given " should " should)
         ]
     (set plan (+ 1 plan))
@@ -38,15 +58,15 @@
 
 (local print-header (once (fn [] (print "TAP version 14"))))
 
-(fn desc [str cb]
+(fn describe [str cb]
   (print-header)
   (print (.. "#" str))
-  (cb test)
-  )
-(fn end []
-  (print (.. 1 ".." plan))
-  )
+  (cb test))
+
+(fn test-end []
+  (print (.. 1 ".." plan)))
 
 
-{:describe desc
- : end}
+{: describe
+ : deep-equals
+ : test-end}
diff --git a/lib/test.test.fnl b/lib/test.test.fnl
index 7958141..81ddedd 100644
--- a/lib/test.test.fnl
+++ b/lib/test.test.fnl
@@ -1,19 +1,53 @@
-(let [{: describe :end test-end} (require :lib.test)]
+(let [{: describe 
+       : test-end
+       : deep-equals
+       } (require :lib.test)]
+
+  ;; just a little something to test with
   (fn add [x y] (let [x (or x 0)
-                      y (or y 0)]
-                  (+ x y)))
-  (describe "add()" (fn [test]
-                  (let [should "return the right number"]
-                    (test {:given "two numbers"
-                           : should
-                           :actual (add 2 3)
-                           :expected 5})
-                    (test {:given "no arguments"
-                           :should "return 0"
-                           :actual (add)
-                           :expected 0})
-                    (test {:given "zero"
-                           : should
-                           :actual (add 0 4)
-                           :expected 4}))
-                  (test-end))))
+        y (or y 0)]
+    (+ x y)))
+
+  (describe "# TEST" (fn []
+    (describe "add()" (fn [test]
+      (let [should "return the right number"]
+        (test {:given "two numbers"
+               : should
+               :actual (add 2 3)
+               :expected 5})
+        (test {:given "no arguments"
+               :should "return 0"
+               :actual (add)
+               :expected 0})
+        (test {:given "zero"
+               : should
+               :actual (add 0 4)
+               :expected 4}))))
+
+    (describe "equal()" (fn [t]
+      (t {:given "two equal tables"
+          :should "return true"
+          :expected true
+          :actual (deep-equals [:orange :apple :pear] [:orange :apple :pear]) })
+      (t {:given "two different tables"
+          :should "return false"
+          :expected false
+          :actual (deep-equals [:apple :pear] [:orange :apple :pear]) })
+      (t {:given "equal strings"
+          :should "be true"
+          :expected true
+          :actual (deep-equals :apple :apple) })
+      (t {:given "different strings"
+          :should "be false"
+          :expected false
+          :actual (deep-equals :apple :pear) })
+      (t {:given "equal bools"
+          :should "be true"
+          :expected true
+          :actual (deep-equals true true) })
+      (t {:given "different strings"
+          :should "be false"
+          :expected false
+          :actual (deep-equals true false) })))
+                       
+  (test-end))))
-- 
cgit 1.4.1-2-gfad0