summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/index.fnl6
-rw-r--r--lib/kvflip.fnl (renamed from lib/flip.fnl)4
-rw-r--r--lib/kvflip.test.fnl (renamed from lib/flip.test.fnl)8
-rw-r--r--lib/space-is-neighbor.fnl18
-rw-r--r--lib/space-is-neighbor.test.fnl22
-rw-r--r--main.fnl127
6 files changed, 132 insertions, 53 deletions
diff --git a/lib/index.fnl b/lib/index.fnl
index 6323160..1fb686c 100644
--- a/lib/index.fnl
+++ b/lib/index.fnl
@@ -1,19 +1,21 @@
(local {: contains} (require :lib.contains))
-(local {: flip} (require :lib.flip))
(local {: head} (require :lib.head))
(local {: keys} (require :lib.keys))
+(local {: kvflip} (require :lib.kvflip))
(local {: mill-at?} (require :lib.mill))
(local {: pprint} (require :lib.tableprint))
(local {: slice} (require :lib.slice))
+(local {: space-is-neighbor?} (require :lib.space-is-neighbor))
(local {: tail} (require :lib.tail))
{
: contains
- : flip
: head
: keys
+ : kvflip
: mill-at?
: pprint
: slice
+ : space-is-neighbor?
: tail
}
diff --git a/lib/flip.fnl b/lib/kvflip.fnl
index 9d21b63..25fc222 100644
--- a/lib/flip.fnl
+++ b/lib/kvflip.fnl
@@ -1,6 +1,6 @@
-(fn flip [t]
+(fn kvflip [t]
"takes a table of {key value} and returns a table of {value key}"
(collect [k v (pairs t)] (values v k)))
-{: flip}
+{: kvflip}
diff --git a/lib/flip.test.fnl b/lib/kvflip.test.fnl
index 32fa005..162650d 100644
--- a/lib/flip.test.fnl
+++ b/lib/kvflip.test.fnl
@@ -1,13 +1,13 @@
-(let [{: flip} (require :lib.flip)
+(let [{: kvflip} (require :lib.kvflip)
{: describe :end test-end} (require :lib.test)]
- (describe "flip()" (fn [t]
+ (describe "kvflip()" (fn [t]
(let [input {:apple "red" :banana "yellow"}
expected {:red "apple" :yellow "banana"}
]
(t {:given "a table"
- :should "flip that table!"
+ :should "kvflip that table!"
: expected
- :actual (flip input)})
+ :actual (kvflip input)})
(test-end)))))
diff --git a/lib/space-is-neighbor.fnl b/lib/space-is-neighbor.fnl
new file mode 100644
index 0000000..380607c
--- /dev/null
+++ b/lib/space-is-neighbor.fnl
@@ -0,0 +1,18 @@
+(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
new file mode 100644
index 0000000..7b0c0af
--- /dev/null
+++ b/lib/space-is-neighbor.test.fnl
@@ -0,0 +1,22 @@
+(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/main.fnl b/main.fnl
index 9954984..af23d90 100644
--- a/main.fnl
+++ b/main.fnl
@@ -2,49 +2,50 @@
(local {
: contains
: head
- : flip
+ : kvflip
: pprint
: slice
:mill-at? mill-at-maker
+ :space-is-neighbor? space-is-neighbor-maker
} (require :lib.index))
;; constants...more like just strings
(local const (require :lib.constants))
-;; front-loading mill with a partial
+;; front-loading with some partials
(local mill-at? (partial mill-at-maker const.mills))
+(local space-is-neighbor? (partial space-is-neighbor-maker const.neighbors))
-; there are three phases of play:
-; placing, moving, and flying.
-; (plus one for capturing)
-; (plus one for complete)
+;; 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!
+ :complete 5 ;; no more cows! jk the cows are fine. the game's just over okay
})
-; there are two players
-; their names are LUIGI and MARIO
+;; there are two players
+;; their names are LUIGI and MARIO
(local player {
:one 1 ;; luigi has light cows
:two 2 ;; mario has DARK cows >:)
})
-; initialize moves[] to 0.
-; this is the game state.
-; shows which spaces are occupied by which players.
-; 0 = unoccupied
-; 1 = Player 1
-; 2 = Player 2
-;; TODO: move this to game.moves?
-(local moves (fcollect [i 1 24] 0))
+; return the numerical index (1-24) of a [A-Za-z0-9] formatted move
+(fn index-of-move [m]
+ (let [upper (string.upper m)
+ rev (string.reverse upper)
+ idx (head (icollect [i v (ipairs const.spaces)]
+ (if (or (= v upper) (= v rev)) i)))]
+ idx))
-; game state object
+;; game state object
(local game {
:player player.one
:stage stages.placing
@@ -52,19 +53,51 @@
(case self.stage
4 ;; capture
(do
- (tset moves move 0)
+ ;; TODO: capturing during moving is not working?
+ (tset self.moves (index-of-move move) 0)
(tset self :player (self:next-player))
- (tset self :stage stages.placing)
+ (tset self :stage (if (> self.pieces-placed 17) stages.moving stages.placing))
+ (tset self.moves (index-of-move move) self.player)
)
1 ;; placing
- (if (mill-at? moves move)
- (tset self :stage stages.capture)
- (tset self :player (self:next-player))
+ (do
+ (set self.pieces-placed (+ 1 self.pieces-placed))
+ (tset self :stage (if (> self.pieces-placed 17) stages.moving stages.placing))
+ (tset self.moves (index-of-move move) self.player)
+ (if (mill-at? self.moves (index-of-move move))
+ (tset self :stage stages.capture)
+ (tset self :player (self:next-player))
+ )
)
+ 2 ;; moving
+ (let [from (index-of-move (string.sub move 1 2))
+ to (index-of-move (string.sub move -2 -1))]
+ (print "From" from)
+ (print "To" to)
+ (tset self.moves from 0)
+ (tset self.moves to self.player)
+ (if (mill-at? self.moves to)
+ (tset self :stage stages.capture)
+ (tset self :player (self:next-player))
+ )
+ )
)
)
:next-player (fn [self] (if (= player.one self.player) player.two player.one))
+ :pieces-placed 0
+ :init (fn [self]
+ ; initialize moves[] to 0.
+ ; this is the game state.
+ ; shows which spaces are occupied by which players.
+ ; 0 = unoccupied
+ ; 1 = Player 1
+ ; 2 = Player 2
+ ; NOTE: I think it might be a good idea to make moves
+ ; a list of moves. so that there can be undo and history
+ (set self.moves (fcollect [i 1 24] 0))
+ )
})
+(game:init)
(fn string-upper [s]
@@ -83,7 +116,7 @@
(print (string.format row-template (table.unpack myslice)))
(set index offset)))
(print row))))
- (print (.. "Stage: " (string-upper (. (flip stages) game.stage))))
+ (print (.. "Stage: " (string-upper (. (kvflip stages) game.stage))))
(print (.. "Player " game.player "'s turn:")))
@@ -100,29 +133,25 @@
(fn space-exists? [m] (contains const.spaces (string.upper m)))
-; return the numerical index (1-24) of a [A-Za-z0-9] formatted move
-(fn index-of-move [m]
- (let [upper (string.upper m)
- rev (string.reverse upper)
- idx (head (icollect [i v (ipairs const.spaces)]
- (if (or (= v upper) (= v rev)) i)))]
- idx))
-
; is the space represented by a [A-Za-z0-9] move unoccupied?
(fn space-is-unoccupied? [m]
(let [unoccupied? 0] ; i.e. is move equal to 0
- (= unoccupied? (. moves (index-of-move m)))))
+ (= unoccupied? (. game.moves (index-of-move m)))))
(fn space-is-occupied-by-opponent? [m]
(let [opponent (if (= game.player 1) 2 1)
- result (= opponent (. moves (index-of-move m))) ]
+ result (= opponent (. game.moves (index-of-move m))) ]
result))
+(fn moving-format? [m]
+ (let [from (string.sub m 1 2)
+ to (string.sub m -2 -1)]
+ (and (space-exists? from) (space-exists? to))))
; is this a legal move?
-; TODO: maybe some functional error handling here?
+; maybe some functional error handling here?
; https://mostly-adequate.gitbook.io/mostly-adequate-guide/ch08#pure-error-handling
; https://mostly-adequate.gitbook.io/mostly-adequate-guide/appendix_b#either
; or maybe all i need is a case-try statement..
@@ -142,10 +171,21 @@
(= stages.capture game.stage)
(or (space-is-occupied-by-opponent? move)
(print "Choose an opponent's piece to remove."))
- (or (not (mill-at? moves (index-of-move move)))
+ (or (not (mill-at? game.moves (index-of-move move)))
(print "Ma'am, it is ILLEGAL to break up a mill."))
)
(and
+ (= stages.moving game.stage)
+ (or (moving-format? move)
+ (print "Try a move like A1A2 or A7 D7"))
+ (or (not (space-is-occupied-by-opponent? (string.sub move 1 2)))
+ (print "That's not yours, don't touch it."))
+ (or (space-is-unoccupied? (string.sub move -2 -1))
+ (print "That space is occupied!"))
+ (or (space-is-neighbor? (index-of-move (string.sub move 1 2)) (index-of-move (string.sub move -2 -1)))
+ (print "That ain't your neighbor, Johnny"))
+ )
+ (and
;; TODO: add flying phase
(= stages.flying game.stage)
)
@@ -161,7 +201,7 @@
(fn main []
;; game loop
(while (not (= game.stage stages.complete))
- (print-board const.board moves)
+ (print-board const.board game.moves)
;; validation loop
(var is-valid false)
@@ -169,14 +209,11 @@
(while (not is-valid)
(set move (get-move))
(set is-valid (valid-move? move))
- (let [idx (index-of-move move)]
- (if (not is-valid)
- (print "Try again.")
- (do
- (print (.. "You chose " move))
- (tset moves idx game.player)
- (game:update idx)
- )
+ (if (not is-valid)
+ (print "Try again.")
+ (do
+ (print (.. "You chose " move))
+ (game:update move)
)
)
)