Move error handling to front-end

game.validate-move will now return an error code is the move is invalid.
default error messages are found in lib/constants.fnl. error handling is
now handled on the front end.
main
Dozens B. McCuzzins 2024-06-23 15:41:31 -06:00
parent c7b2c98200
commit 9bf31e86ce
4 changed files with 99 additions and 43 deletions

View File

@ -272,5 +272,44 @@ and will be spending a lot of time on my back.
So I'm either going to get a lot on 9mm, So I'm either going to get a lot on 9mm,
or nothing at all. or nothing at all.
We'll see! We'll see!
.
.
.IP "20 - 21"
I was having back surgery!
.
.
.IP "WEEK THREE REVIEW"
Finished the game, and then had a major surgery.
Doing some tidying up and housekeeping now.
Organzing tests,
breaking stuff out into modules.
Rewriting and refactoring a couple of functions.
Generally getting ready to start thinking
about Stretch Goal 1: Story Mode.
I have a vague inkling of an idea about
doing some kind of generative story / narration
based on each move of the game.
Assign symbolic meaning to the outer, middle, and inner rings of the board.
Add a little twist based on whether the move is a capture, a slide, a placement.
That kind of thing.
Not sure how I want to do it exactly yet.
I'd like to be able to show the story progress as you play.
But updating the story every move seems like kind of a lot.
Maybe just at every game phase.
So three installments.
One at the end of Placement,
the second at the end of Movement,
and the third at the end of the game?
Anyway.
Up next: big refactor of the validation logic.
I don't want it to just print to console
if I'm going to eventually support multiple frontends.
I need to return an error code or something
for the client to interpret.
I'm still interested in the Result / Either monad option
but I think I'm doing to first try the conventional lua way
and throw an error, and then 'pcall' the function and handle the error politely.
.pl \n[nl]u .pl \n[nl]u

View File

@ -97,9 +97,33 @@
:complete 5 ;; no more cows! jk the cows are fine. the game's just over okay :complete 5 ;; no more cows! jk the cows are fine. the game's just over okay
}) })
{: board
;; errror codes
(local errors {
:not-a-space "That space does not exist!\nHint: 1a 1A A1 a1 are all the same move."
:occupied "That space is occupied!"
:not-an-opponent "Choose an opponent's piece to remove."
:is-mill "Ma'am, it is ILLEGAL to break up a mill."
:bad-move-format "Try a move like B2B4 or A7 D7"
:not-yours "That's not yours, don't touch it."
:not-neighbor "That ain't your neighbor, Johnny"
})
;; if you like it then you shoulda put a ...
(local rings {
:outer [ 1 2 3 15 24 23 22 10 ]
:middle [ 4 5 6 14 21 20 19 11 ]
:inner [ 7 8 9 13 18 17 16 12 ]
})
{
: board
: errors
: mills : mills
: neighbors : neighbors
: rings
: spaces
: stages : stages
: spaces} }

View File

@ -34,17 +34,23 @@
;; game loop ;; game loop
(while (not (= game.stage const.stages.complete)) (while (not (= game.stage const.stages.complete))
(with-board game.moves) (with-board game.moves)
;; validation loop
(var is-valid false) (var is-valid false)
(var move "") (var move "")
;; validation loop
(while (not is-valid) (while (not is-valid)
(set move (get-move)) (set move (get-move))
(set is-valid (game.validate-move move)) (case (pcall game.validate-move move)
(if (not is-valid) (false msg)
(print "Try again.") (do
(do (let [(i j) (string.find msg ": ")
(print (string.format "Turn %d: You chose %s" game.turns move)) key (string.sub msg (+ 1 j))]
(game:update move))))) (print (. const.errors key)))
(print "Try again."))
ok
(do
(set is-valid true)
(print (string.format "Turn %d: You chose %s" game.turns move))
(game:update move)))))
;; game is complete ;; game is complete
(print "Congratulations!") (print "Congratulations!")
(print (string.format "Player %d is the winner!" game.player))) (print (string.format "Player %d is the winner!" game.player)))

View File

@ -4,15 +4,15 @@
} (require :lib.index)) } (require :lib.index))
(local { (local {
: all-mills? : all-mills?
:mill-at? mill-at-maker :mill-at? full-mill-at ;; stay with me...
:no-moves? no-moves-maker :no-moves? full-no-moves
:space-is-neighbor? space-is-neighbor-maker :space-is-neighbor? full-space-is-neighbor
} (require :lib.game.index)) } (require :lib.game.index))
(local const (require :lib.constants)) (local const (require :lib.constants))
;; front-loading with some partials ;; front-loading the "fulls" with some partials
(local mill-at? (partial mill-at-maker const.mills)) (local mill-at? (partial full-mill-at const.mills))
(local space-is-neighbor? (partial space-is-neighbor-maker const.neighbors)) (local space-is-neighbor? (partial full-space-is-neighbor const.neighbors))
(local no-moves? (partial no-moves-maker const.neighbors)) (local no-moves? (partial full-no-moves const.neighbors))
;; story mode: ;; story mode:
@ -124,8 +124,7 @@
; 2 = Player 2 ; 2 = Player 2
; NOTE: I think it might be a good idea to make moves ; NOTE: I think it might be a good idea to make moves
; a list of moves. so that there can be undo and history ; a list of moves. so that there can be undo and history
(set self.moves (fcollect [i 1 24] 0)) (set self.moves (fcollect [i 1 24] 0)))
)
}) })
@ -164,40 +163,28 @@
; is this a legal move? ; is this a legal move?
; note: string argument to assert funs are defined in /lib/constants
(fn valid-move? [move] (fn valid-move? [move]
(or (or
(and (and
(= const.stages.placing game.stage) (= const.stages.placing game.stage)
(or (space-exists? move) (assert (space-exists? move) :not-a-space)
(print "That space does not exist!\nHint: 1a 1A A1 a1 are all the same move.")) (assert (space-is-unoccupied? move) :occupied))
(or (space-is-unoccupied? move)
(print "That space is occupied!")))
(and (and
(= const.stages.capture game.stage) (= const.stages.capture game.stage)
(or (space-is-occupied-by-opponent? move) (assert (space-is-occupied-by-opponent? move) :not-an-opponent)
(print "Choose an opponent's piece to remove.")) (assert (or (all-mills? game.moves game.player) (not (mill-at? game.moves (index-of-move move)))) :is-mill))
(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 (and
(= const.stages.moving game.stage) (= const.stages.moving game.stage)
(or (moving-format? move) (assert (moving-format? move) :bad-move-format)
(print "Try a move like A1A2 or A7 D7")) (assert (not (space-is-occupied-by-opponent? (string.sub move 1 2))) :not-yours)
(or (not (space-is-occupied-by-opponent? (string.sub move 1 2))) (assert (space-is-unoccupied? (string.sub move -2 -1)) :occupied)
(print "That's not yours, don't touch it.")) (assert (space-is-neighbor? (index-of-move (string.sub move 1 2)) (index-of-move (string.sub move -2 -1))) :not-neighbor) )
(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 (and
(= const.stages.flying game.stage) (= const.stages.flying game.stage)
(or (moving-format? move) (assert (moving-format? move) :bad-move-format)
(print "Try a move like A1A2 or A7 D7")) (assert (not (space-is-occupied-by-opponent? (string.sub move 1 2))) :not-yours)
(or (not (space-is-occupied-by-opponent? (string.sub move 1 2))) (assert (space-is-unoccupied? (string.sub move -2 -1)) :occupied))))
(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?) (tset game :validate-move valid-move?)