From f31a319dc17179baf945430e5abbbd8b421341d3 Mon Sep 17 00:00:00 2001 From: Max Wilson Date: Thu, 11 Jan 2024 09:22:45 -0800 Subject: [PATCH] (green) Don't let user exceed the number of available levels --- src/Core/Common.fs | 1 + src/Core/Menus.fs | 10 +++++----- src/UI/DFRPG/ChargenView.fs | 7 ++++--- test/Chargen.Accept.fs | 10 +++++----- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/Core/Common.fs b/src/Core/Common.fs index 2f1df1f..86181b6 100644 --- a/src/Core/Common.fs +++ b/src/Core/Common.fs @@ -8,6 +8,7 @@ let thunk v _ = v let thunk1 f arg _ = f arg let thunk2 f arg1 arg2 _ = f arg1 arg2 let thunk3 f arg1 arg2 arg3 _ = f arg1 arg2 arg3 +let thunk4 f arg1 arg2 arg3 arg4 _ = f arg1 arg2 arg3 arg4 let tuple2 x y = x,y let matchfail v = sprintf "No match found for %A. This is a bug." v |> invalidOp let ignoreM (_, monad) = (), monad diff --git a/src/Core/Menus.fs b/src/Core/Menus.fs index a87b704..4899b1d 100644 --- a/src/Core/Menus.fs +++ b/src/Core/Menus.fs @@ -8,7 +8,7 @@ type Key = KeySegment ReversedList type MenuOutput = | Either of label: string option * options: MenuSelection list | And of label: string option * grants: MenuOutput list - | Leveled of label: string * Key * level: int + | Leveled of label: string * Key * currentLevel: int * levelCount: int | Leaf of label: string with member this.DisplayText = @@ -18,7 +18,7 @@ type MenuOutput = | Either(Some label, children) -> $"Either({label}, {show children})" | And(None, grants) -> $"And({show grants})" | And(Some label, grants) -> $"And({label}, {show grants})" - | Leveled(label, key, level) -> $"Leveled({label}, {key}, {level})" + | Leveled(label, key, currentLevel, levelCount) -> $"Leveled({label}, {key}, {currentLevel}, {levelCount})" | Leaf(label) -> $"Leaf({label})" and MenuSelection = bool * Key * MenuOutput @@ -67,7 +67,7 @@ type 'reactElement RenderApi = { checked': string * Key * ('reactElement list) -> 'reactElement unchecked: string * Key -> 'reactElement unconditional: string * ('reactElement list) -> 'reactElement - leveledLeaf: string * Key * int -> 'reactElement + leveledLeaf: string * Key * int * int -> 'reactElement // label, key, currentValue, levelCount combine: 'reactElement list -> 'reactElement } @@ -94,7 +94,7 @@ let render (render: 'reactElement RenderApi) (menus: MenuOutput list) = | And(label, grants) -> let childReacts = grants |> List.map (recur true render.unconditional) renderMe(defaultArg label "And:", childReacts) - | Leveled(name, key, lvl) -> render.leveledLeaf(name, key, lvl) + | Leveled(name, key, lvl, levelCount) -> render.leveledLeaf(name, key, lvl, levelCount) | Leaf(name) -> renderMe(name, []) menus |> List.map (recur true render.unconditional) |> render.combine @@ -157,7 +157,7 @@ type Op = let level ix = let level = levels[ix] // e.g. if this is skill("Rapier", [+5..+8]) then ix 0 means level = +5 and value = Rapier +5 let value = spec.ctor level - Some value, Leveled(defaultArg config.label $"{spec.toString value}", fullKey, ix) + Some value, Leveled(defaultArg config.label $"{spec.toString value}", fullKey, ix, levels.Length) match input.lookup fullKey with | Some (Level lvl) when lvl < levels.Length -> level lvl | _ when levels.Length >= 1 -> // we are permissive in the input we accept, partly to make testing easier. Flag means "default to the lowest value", e.g. Rapier +5-+7 defaults to Rapier +5. diff --git a/src/UI/DFRPG/ChargenView.fs b/src/UI/DFRPG/ChargenView.fs index fc54baf..9c74660 100644 --- a/src/UI/DFRPG/ChargenView.fs +++ b/src/UI/DFRPG/ChargenView.fs @@ -9,8 +9,9 @@ module private Impl = let toggle dispatch (key: Key) (newValue: bool) = if newValue then dispatch (SetKey(key, Some Flag)) else dispatch (SetKey(key, None)) - let changeLevel dispatch key (newLevel: int) = - () + let changeLevel dispatch key (newLevel: int) (levelCount: int) = + if newLevel < 0 then dispatch (SetKey(key, None)) + elif newLevel < levelCount then dispatch (SetKey(key, Some (Level newLevel))) let checkbox (txt: string) checked' (onChange: bool -> unit) children = let id = System.Guid.NewGuid().ToString() Html.li [ @@ -27,7 +28,7 @@ module private Impl = { checked' = fun (label, key, children) -> checkbox label true (toggle key) children unchecked = fun (label, key) -> checkbox label false (toggle key) [] - leveledLeaf = fun (label, key, level) -> class' "" Html.li [ button "-" (thunk3 changeLevel dispatch key (level-1)); button "+" (thunk3 changeLevel dispatch key (level+1)); Html.text label ] + leveledLeaf = fun (label, key, level, levelCount) -> class' "" Html.li [ button "-" (thunk4 changeLevel dispatch key (level-1) levelCount); button "+" (thunk4 changeLevel dispatch key (level+1) levelCount); Html.text label ] unconditional = fun (label, children) -> class' "" Html.li [ Html.text label; combine children ] combine = combine } diff --git a/test/Chargen.Accept.fs b/test/Chargen.Accept.fs index a1a542f..a783bd6 100644 --- a/test/Chargen.Accept.fs +++ b/test/Chargen.Accept.fs @@ -54,7 +54,7 @@ type Pseudoreact = | Checked of string * Key * Pseudoreact list | Unchecked of string * Key | Unconditional of string * Pseudoreact list - | NumberInput of string * Key * int + | NumberInput of string * Key * int * int | Fragment of Pseudoreact list let pseudoReactApi = { @@ -128,7 +128,7 @@ let units = testList "Unit.Chargen" [ nestedEither |> testFor ["Sword!"; "Sword!-Rapier"] ( Either(None, [ true, key "Sword!", Either(Some "Sword!", [ - true, key "Sword!-Rapier", Leveled("Rapier +5", key "Sword!-Rapier", 0) // it's a Levelled, not a Leaf, because it's currently selected. Note that the level is 0, not +5, because it's the lowest level out of +5 to +5. + true, key "Sword!-Rapier", Leveled("Rapier +5", key "Sword!-Rapier", 0, 2) // it's a Levelled, not a Leaf, because it's currently selected. Note that the level is 0, not +5, because it's the lowest level out of +5 to +5. ]) ]) ) @@ -176,7 +176,7 @@ let tests = let offers = swash() let expectedMenus = [ Leaf "Climbing +1" // Leaf not Level because swash() template is only using trait', not level - Leveled("Stealth +1", key "Stealth", 0) // Leveled because it can go up to +3 + Leveled("Stealth +1", key "Stealth", 0, 3) // Leveled because it can go up to +3 Either(None, [ false, key "Combat Reflexes", Leaf "Combat Reflexes" false, key "Acrobatics", Leaf "Acrobatics +1" @@ -196,13 +196,13 @@ let tests = let (|Checked|) = function Checked(label, key, children) -> Checked(label, key, children) | v -> fail "Checked" v let (|Unchecked|) = function Unchecked(label, key) -> Unchecked(label, key) | v -> fail "Unchecked" v let (|Unconditional|) = function Unconditional(label, children) -> Unconditional(label, children) | v -> fail "Unconditional" v - let (|NumberInput|) = function NumberInput(label, key, value) -> NumberInput(label, key, value) | v -> fail "NumberInput" v + let (|NumberInput|) = function NumberInput(label, key, value, levelCount) -> NumberInput(label, key, value, levelCount) | v -> fail "NumberInput" v let (|Fragment|) = function Fragment(children) -> Fragment(children) | v -> fail "Fragment" v let (|Expect|_|) expect actual = if expect = actual then Some () else fail expect actual match pseudoActual with | Fragment([ Unconditional(Expect "Climbing +1", []) - NumberInput(Expect "Stealth +1", Expect ["Stealth"], Expect 0) + NumberInput(Expect "Stealth +1", Expect ["Stealth"], Expect 0, Expect 3) Unconditional(Expect "Choose one:", [ Unchecked(Expect "Combat Reflexes", Expect ["Combat Reflexes"]) Unchecked(Expect "Acrobatics +1", Expect ["Acrobatics"]) // note: Acrobatics is the key here, not Acrobatics +1, because it's leveled.