9mm/main.fnl

261 lines
9.6 KiB
Plaintext
Raw Normal View History

2024-05-28 21:04:00 +00:00
;; helper and utility functions
(local {
2024-06-16 03:15:59 +00:00
: str
: tbl
2024-06-09 02:58:40 +00:00
: all-mills?
:mill-at? mill-at-maker
:space-is-neighbor? space-is-neighbor-maker
2024-06-16 03:15:59 +00:00
:no-moves? no-moves-maker
2024-05-28 21:04:00 +00:00
} (require :lib.index))
2024-05-30 01:26:41 +00:00
;; constants...more like just strings
(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))
2024-06-16 03:15:59 +00:00
(local no-moves? (partial no-moves-maker const.neighbors))
2024-05-28 21:04:00 +00:00
;; there are three phases of play:
;; placing, moving, and flying.
;; (plus one for capturing)
;; (plus one for game-over)
2024-05-28 21:04:00 +00:00
(local stages {
2024-05-30 01:26:41 +00:00
: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
2024-05-28 21:04:00 +00:00
})
2024-06-09 02:58:40 +00:00
;; story mode:
;; there are two players
2024-06-09 02:58:40 +00:00
;; their names are WIGI and MALO
2024-05-28 21:04:00 +00:00
(local player {
2024-06-09 02:58:40 +00:00
:one 1 ;; wigi has light cows
:two 2 ;; malo has DARK cows >:)
2024-05-28 21:04:00 +00:00
})
; return the numerical index (1-24) of a [A-Za-z0-9] formatted move
(fn index-of-move [m]
2024-06-09 02:58:40 +00:00
(assert (= "string" (type m)) "index-of-move needs a string argument")
(let [upper (string.upper m)
rev (string.reverse upper)
2024-06-16 03:15:59 +00:00
idx (tbl.head (icollect [i v (ipairs const.spaces)]
2024-06-09 02:58:40 +00:00
(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)))
2024-05-28 21:04:00 +00:00
;; game state object
2024-05-28 21:04:00 +00:00
(local game {
:player player.one
:stage stages.placing
:update (fn [self move]
(case self.stage
2024-06-09 02:58:40 +00:00
4 ;; CAPTURE
(do
(tset self.moves (index-of-move move) 0)
2024-06-16 03:15:59 +00:00
(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 stages.complete
flytime stages.flying
2024-06-09 02:58:40 +00:00
movetime stages.moving
2024-06-16 03:15:59 +00:00
stages.placing))
(if (not endtime) (tset self :player (self:next-player)))
))
2024-06-09 02:58:40 +00:00
1 ;; PLACING
(do
(set self.pieces-placed (+ 1 self.pieces-placed))
2024-06-16 03:15:59 +00:00
(tset self :stage (if (self:phase-two?) stages.moving stages.placing))
(tset self.moves (index-of-move move) self.player)
2024-06-16 03:15:59 +00:00
(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))
2024-06-09 02:58:40 +00:00
capturetime (mill-at? self.moves (index-of-move move))]
(tset self :stage (if
capturetime stages.capture
flytime stages.flying
movetime stages.moving
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))]
2024-06-09 02:58:40 +00:00
(tset self.moves from 0)
(tset self.moves to self.player)
2024-06-16 03:15:59 +00:00
(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))]
2024-06-09 02:58:40 +00:00
(tset self :stage (if
capturetime stages.capture
flytime stages.flying
movetime stages.moving
2024-06-16 03:15:59 +00:00
endtime stages.complete
2024-06-09 02:58:40 +00:00
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)
2024-06-16 03:15:59 +00:00
(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))
2024-06-09 02:58:40 +00:00
capturetime (mill-at? self.moves (index-of-move (string.sub move -2 -1)))]
(tset self :stage (if
capturetime stages.capture
flytime stages.flying
movetime stages.moving
stages.placing))
(if (not capturetime) (tset self :player (self:next-player)))))
2024-06-16 03:15:59 +00:00
5 ;; COMPLETE
(print "Unreachable!")
)
2024-06-16 03:15:59 +00:00
(tset self :turns (+ self.turns 1))
)
:next-player (fn [self] (if (= player.one self.player) player.two player.one))
:pieces-placed 0
2024-06-16 03:15:59 +00:00
:turns 0
; 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))
)
2024-05-28 21:04:00 +00:00
})
(game:init)
2024-05-28 21:04:00 +00:00
; Print! That! Board!
(fn print-board [board moves]
2024-05-30 01:26:41 +00:00
(var index 1)
2024-05-28 21:04:00 +00:00
(each [_ row (ipairs board)]
2024-05-30 01:26:41 +00:00
(let [(row-template slots) (string.gsub row "x" "%%d")]
(if (> slots 0)
2024-05-28 21:04:00 +00:00
(do
2024-05-30 01:26:41 +00:00
(let [offset (+ index slots)
2024-06-16 03:15:59 +00:00
myslice (tbl.slice moves index offset)]
(print (string.format row-template (table.unpack myslice)))
(set index offset)))
2024-05-30 01:26:41 +00:00
(print row))))
2024-06-16 03:15:59 +00:00
(print (.. "Stage: " (str.capitalize (. (tbl.invert stages) game.stage))))
2024-05-30 01:26:41 +00:00
(print (.. "Player " game.player "'s turn:")))
2024-06-16 03:15:59 +00:00
(local with-board (partial print-board const.board))
2024-05-28 21:04:00 +00:00
; add the inverse of each valid move
; e.g. 1A = A1
(fn add-reverse-moves []
2024-05-30 01:26:41 +00:00
(let [reversed (icollect [_ v (ipairs const.spaces)] (string.reverse v))]
2024-05-28 21:04:00 +00:00
(each [_ v (ipairs reversed)]
2024-05-30 01:26:41 +00:00
(table.insert const.spaces v)))) ;; oh nooooo i'm mutating a const????
2024-05-28 21:04:00 +00:00
(add-reverse-moves)
; does the move exist within the domain of valid spaces
2024-06-16 03:15:59 +00:00
(fn space-exists? [m] (tbl.contains const.spaces (string.upper m)))
2024-05-30 01:26:41 +00:00
2024-05-28 21:04:00 +00:00
2024-05-30 01:26:41 +00:00
; is the space represented by a [A-Za-z0-9] move unoccupied?
2024-05-28 21:04:00 +00:00
(fn space-is-unoccupied? [m]
2024-05-30 01:26:41 +00:00
(let [unoccupied? 0] ; i.e. is move equal to 0
(= unoccupied? (. game.moves (index-of-move m)))))
2024-05-28 21:04:00 +00:00
2024-06-09 02:58:40 +00:00
; is the space m occupied by the player's opponent?
2024-05-30 01:26:41 +00:00
(fn space-is-occupied-by-opponent? [m]
2024-06-09 02:58:40 +00:00
"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))
2024-05-30 01:26:41 +00:00
2024-06-09 02:58:40 +00:00
; 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)]
2024-06-09 02:58:40 +00:00
(and (>= (length m) 4) (space-exists? from) (space-exists? to))))
2024-05-30 01:26:41 +00:00
2024-05-28 21:04:00 +00:00
; is this a legal move?
(fn valid-move? [move]
(or
(and
(= stages.placing game.stage)
2024-05-30 01:26:41 +00:00
(or (space-exists? move)
(print "That space does not exist!\nHint: 1a 1A A1 a1 are all the same move."))
2024-05-30 01:26:41 +00:00
(or (space-is-unoccupied? move)
(print "That space is occupied!")))
2024-05-28 21:04:00 +00:00
(and
(= stages.capture game.stage)
(or (space-is-occupied-by-opponent? move)
2024-05-30 01:26:41 +00:00
(print "Choose an opponent's piece to remove."))
2024-06-09 02:58:40 +00:00
(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.")
2024-06-16 03:15:59 +00:00
))
(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)))
2024-06-16 03:15:59 +00:00
(print "That ain't your neighbor, Johnny")) )
2024-05-28 21:04:00 +00:00
(and
(= stages.flying game.stage)
2024-06-09 02:58:40 +00:00
(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))
2024-06-16 03:15:59 +00:00
(print "That space is occupied!")))
2024-05-30 01:26:41 +00:00
)
2024-05-28 21:04:00 +00:00
)
; get player input
(fn get-move []
(io.read))
(fn main []
;; game loop
(while (not (= game.stage stages.complete))
2024-06-16 03:15:59 +00:00
(with-board game.moves)
2024-05-28 21:04:00 +00:00
;; validation loop
(var is-valid false)
(var move "")
(while (not is-valid)
(set move (get-move))
(set is-valid (valid-move? move))
(if (not is-valid)
(print "Try again.")
(do
2024-06-16 03:15:59 +00:00
(print (string.format "Turn %d: You chose %s" game.turns move))
(game:update move)))))
;; game is complete
(print "Congratulations!")
(print (string.format "Player %d is the winner!" game.player))
2024-05-28 21:04:00 +00:00
)
(main)