summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authordozens2024-06-20 09:17:06 -0600
committerdozens2024-06-20 09:17:06 -0600
commitc7b2c982004e350f5e3032321baadfc9021b6bad (patch)
tree3f8883c94bed5a1f847cf60cb11207ca53946c67 /src
parentce09973e7cacccdc779f91b8e6e48a520b9f9f4d (diff)
🗄️ 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/
Diffstat (limited to 'src')
-rw-r--r--src/game.fnl204
1 files changed, 204 insertions, 0 deletions
diff --git a/src/game.fnl b/src/game.fnl
new file mode 100644
index 0000000..b48a6ac
--- /dev/null
+++ b/src/game.fnl
@@ -0,0 +1,204 @@
+;; helper and utility functions
+(local {
+  : tbl
+  } (require :lib.index))
+(local {
+  : all-mills?
+  :mill-at? mill-at-maker
+  :no-moves? no-moves-maker
+  :space-is-neighbor? space-is-neighbor-maker
+  } (require :lib.game.index))
+(local const (require :lib.constants))
+;; 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))
+(local no-moves? (partial no-moves-maker const.neighbors))
+
+
+;; story mode:
+;; there are two players
+;; their names are WIGI and MALO
+(local player {
+  :one 1 ;; wigi has light cows
+  :two 2 ;; malo has DARK cows >:)
+})
+
+
+; return the numerical index (1-24) of a [A-Za-z0-9] formatted move
+(fn index-of-move [m]
+  (assert (= "string" (type m)) "index-of-move needs a string argument")
+  (let [upper (string.upper m)
+        rev   (string.reverse upper)
+        idx   (tbl.head (icollect [i v (ipairs const.spaces)]
+                      (if (or (= v upper) (= v rev)) i)))]
+       idx))
+
+
+(fn player-count [moves player]
+  (accumulate [count 0
+               _ x (ipairs moves)]
+              (if (= x player) (+ count 1) count)))
+
+
+;; game state object
+(local game {
+  :player player.one
+  :stage const.stages.placing
+  :update (fn [self move]
+    (case self.stage
+      4 ;; CAPTURE
+        (do
+          (tset self.moves (index-of-move move) 0)
+          (let [flytime (and (self:phase-two?) (= 3 (player-count self.moves (self:next-player))))
+                movetime (and (self:phase-two?) (> (player-count self.moves (self:next-player)) 3))
+                endtime (and (self:phase-two?)
+                             (or (< (length (icollect [_ m (ipairs self.moves)] (if (= m 1) 1))) 3)
+                                 (< (length (icollect [_ m (ipairs self.moves)] (if (= m 2) 2))) 3)))]
+            (tset self :stage (if endtime const.stages.complete
+                                flytime const.stages.flying
+                                movetime const.stages.moving
+                                const.stages.placing))
+            (if (not endtime) (tset self :player (self:next-player)))
+            ))
+      1 ;; PLACING
+        (do
+          (set self.pieces-placed (+ 1 self.pieces-placed))
+          (tset self :stage (if (self:phase-two?) const.stages.moving const.stages.placing))
+          (tset self.moves (index-of-move move) self.player)
+          (let [flytime (and (self:phase-two?) (= 3 (player-count self.moves self.player)))
+                movetime (and (self:phase-two?) (> (player-count self.moves self.player) 3))
+                capturetime (mill-at? self.moves (index-of-move move))]
+            (tset self :stage (if
+                                capturetime const.stages.capture
+                                flytime const.stages.flying
+                                movetime const.stages.moving
+                                const.stages.placing))
+            (if (not capturetime) (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))]
+          (tset self.moves from 0)
+          (tset self.moves to self.player)
+          (let [flytime (and (self:phase-two?) (= 3 (player-count self.moves (self:next-player))))
+                movetime (and (self:phase-two?) (> (player-count self.moves (self:next-player)) 3))
+                capturetime (mill-at? self.moves (index-of-move (string.sub move -2 -1)))
+                endtime (no-moves? self.moves (self:next-player))]
+            (tset self :stage (if
+                                capturetime const.stages.capture
+                                flytime const.stages.flying
+                                movetime const.stages.moving
+                                endtime const.stages.complete
+                                const.stages.placing))
+            (if (not capturetime) (tset self :player (self:next-player)))))
+        3 ;; FLYING
+          (let [from (index-of-move (string.sub move 1 2))
+                to  (index-of-move (string.sub move -2 -1))]
+            (tset self.moves from 0)
+            (tset self.moves to self.player)
+            (let [flytime (and (self:phase-two?) (= 3 (player-count self.moves (self:next-player))))
+                  movetime (and (self:phase-two?) (> (player-count self.moves (self:next-player)) 3))
+                  capturetime (mill-at? self.moves (index-of-move (string.sub move -2 -1)))]
+              (tset self :stage (if
+                                  capturetime const.stages.capture
+                                  flytime const.stages.flying
+                                  movetime const.stages.moving
+                                  const.stages.placing))
+              (if (not capturetime) (tset self :player (self:next-player)))))
+        5 ;; COMPLETE
+          (print "Unreachable!")
+    )
+    (tset self :turns (+ self.turns 1))
+  )
+  :next-player (fn [self] (if (= player.one self.player) player.two player.one))
+  :pieces-placed 0
+  :turns 1
+  ; so basically there's phase 1 where you place your checkers
+  ; and then phase 2 when you move and fly around trying to capture pieces
+  :phase-two? (fn [self] (> self.pieces-placed 17))
+  :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))
+  )
+})
+
+
+; add the inverse of each valid move
+; e.g. 1A = A1
+(fn add-reverse-moves []
+  (let [reversed (icollect [_ v (ipairs const.spaces)] (string.reverse v))]
+    (each [_ v (ipairs reversed)]
+      (table.insert const.spaces v)))) ;; oh nooooo i'm mutating a const????
+(add-reverse-moves)
+
+
+; does the move exist within the domain of valid spaces
+(fn space-exists? [m] (tbl.contains const.spaces (string.upper m)))
+
+
+; 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? (. game.moves (index-of-move m)))))
+
+; is the space m occupied by the player's opponent?
+(fn space-is-occupied-by-opponent? [m]
+  "is the space m occupied by the player's opponent?"
+  (let [opponent (if (= game.player 1) 2 1)
+        result (= opponent (. game.moves (index-of-move m))) ]
+    result))
+
+; checks that the first 2 charcters and the last 2 characters
+; of a string are legal spaces
+; moving-format is the same as flying-format
+(fn moving-format? [m]
+  (let [from (string.sub m 1 2)
+        to (string.sub m -2 -1)]
+    (and (>= (length m) 4) (space-exists? from) (space-exists? to))))
+
+
+; is this a legal move?
+(fn valid-move? [move]
+  (or
+    (and
+      (= const.stages.placing game.stage)
+      (or (space-exists? move)
+          (print "That space does not exist!\nHint: 1a 1A A1 a1 are all the same move."))
+      (or (space-is-unoccupied? move)
+          (print "That space is occupied!")))
+    (and
+      (= const.stages.capture game.stage)
+      (or (space-is-occupied-by-opponent? move)
+          (print "Choose an opponent's piece to remove."))
+      (or (or (all-mills? game.moves game.player)
+              (not (mill-at? game.moves (index-of-move move))))
+          (print "Ma'am, it is ILLEGAL to break up a mill.")
+          ))
+    (and
+      (= const.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
+      (= const.stages.flying 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!")))))
+
+(tset game :validate-move valid-move?)
+
+{: game}