diff options
author | Sébastien Dailly <sebastien@dailly.me> | 2024-02-24 11:31:28 +0100 |
---|---|---|
committer | Sébastien Dailly <sebastien@dailly.me> | 2024-02-24 11:31:28 +0100 |
commit | ef9beb0814c36cda979a4ed7e9175e72e69540ac (patch) | |
tree | c2852b1dd5df53f5d78e9e952f29360d50126e06 | |
parent | 9e7f27c60a425e2baa67cd459d8509a43b1d123d (diff) |
New application: build upgrade helper for aoo
-rw-r--r-- | calculette_aoo/bin/dune | 3 | ||||
-rw-r--r-- | calculette_aoo/bin/main.ml | 49 | ||||
-rw-r--r-- | calculette_aoo/index.html | 180 | ||||
-rw-r--r-- | calculette_aoo/js/dune | 19 | ||||
-rw-r--r-- | calculette_aoo/js/main.ml | 97 | ||||
-rw-r--r-- | calculette_aoo/lib/build.ml | 135 | ||||
-rw-r--r-- | calculette_aoo/lib/build.mli | 35 | ||||
-rw-r--r-- | calculette_aoo/lib/carac.ml | 40 | ||||
-rw-r--r-- | calculette_aoo/lib/carac.mli | 17 | ||||
-rw-r--r-- | calculette_aoo/lib/dune | 2 | ||||
-rw-r--r-- | calculette_aoo/test/dune | 2 | ||||
-rw-r--r-- | calculette_aoo/test/test_optim_aoo.ml | 0 |
12 files changed, 579 insertions, 0 deletions
diff --git a/calculette_aoo/bin/dune b/calculette_aoo/bin/dune new file mode 100644 index 0000000..1177b56 --- /dev/null +++ b/calculette_aoo/bin/dune @@ -0,0 +1,3 @@ +(executable + (name main) + (libraries aoo)) diff --git a/calculette_aoo/bin/main.ml b/calculette_aoo/bin/main.ml new file mode 100644 index 0000000..c738cd7 --- /dev/null +++ b/calculette_aoo/bin/main.ml @@ -0,0 +1,49 @@ +let cout_a = (800, 200, 100) +let cout_m = (110, 55, 35) +let cout_rm = (50, 40, 20) +let cout_pm = (5, 3, 1) +let cout_fm = (100, 50, 30) +let fm_oponent = 10 + +let env : Aoo.Build.env = + { + cost_max = 1800 + ; max_tours = 5.0 + ; cout_sort = 8 + ; degat_sort = 6 + ; fm_oponent + ; frequencies = Aoo.Build.buil_freq_table 9 fm_oponent + } + +(* +let () = Random.self_init () + +let roll_and_accumulate dices = + Seq.repeat () |> Seq.take dices + |> Seq.fold_left (fun res _ -> res + (1 + Random.int 6)) 0 + +let delta_carac level = + Seq.repeat () |> Seq.take 1000000 + |> Seq.fold_left + (fun acc _ -> + if roll_and_accumulate level > roll_and_accumulate (level - 2) then acc + else acc + 1) + 0 +*) + +(* Définition des caractéristiques *) +let a = Aoo.Carac.create 2 cout_a +let m = Aoo.Carac.create 5 cout_m +let rm = Aoo.Carac.create 7 cout_rm +let pm = Aoo.Carac.create 40 cout_pm +let fm = Aoo.Carac.create 9 cout_fm +let default = Aoo.Build.{ a; m; rm; pm; fm } + +let () = + let build = + Aoo.Build.traverse env + (Aoo.Build.cost default, Aoo.Build.score env default) + (Aoo.Build.upgrade env default) + in + + Aoo.Build.repr env Format.std_formatter build diff --git a/calculette_aoo/index.html b/calculette_aoo/index.html new file mode 100644 index 0000000..6dd2955 --- /dev/null +++ b/calculette_aoo/index.html @@ -0,0 +1,180 @@ +<!DOCTYPE html> +<html> +<head> +<title>AOO PC Builder</title> +<meta charset="UTF-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> + +<link rel="stylesheet" type="text/css" href="/theme/font-awesome/css/fontawesome.css"> +<link rel="stylesheet" type="text/css" href="/theme/font-awesome/css/brands.css"> +<link rel="stylesheet" type="text/css" href="/theme/font-awesome/css/solid.css"> + + +<style> +* { + box-sizing: border-box; +} + +/* Style the body */ +body { + font-family: Arial; + margin: 0; +} + +/* Header/logo Title */ +.header { + padding: 50px; + text-align: center; + color: white; + background-color: #333; +} + +/* Column container */ +.row { + display: grid; + grid-auto-flow: column; +} + +.side { + flex: 30%; + padding: 20px; +} + +/* Main column */ +.main { + flex: 70%; + background-color: white; + padding: 20px; +} + +input[type='text'] { font-size: 24px; } + +fieldset { + display: table; +} +fieldset div { + display: table-row; +} + +fieldset label { + display: table-cell; + padding-right: 20px; + vertical-align: middle; +} + +textarea { + width: 100%; + height: 300px; +} + +table tr td input { width: 2em; text-align: center} +table tr td input.wellplaced { background-color: lightgreen;} +table tr td input.misplaced { background-color: gold;} +tr:first-child { outline: thin solid; } + +/* Responsive layout - when the screen is less than 700px wide, make the two columns stack on top of each other instead of next to each other */ +@media screen and (max-width: 700px) { + .row, .navbar { + flex-direction: column; + } +} +</style> +</head> +<body> + +<!-- Header --> +<div class="header"> + <h1>AOO PC Builder</h1> +</div> + +<!-- The flexible grid (content) --> +<div class="main"> + <div> + <noscript>Sorry, you need to enable JavaScript to see this page.</noscript> + <script id="lib" type="text/javascript" defer="defer" src="js/aoo.js"></script> + <script> + var script = document.getElementById('lib'); + script.addEventListener('load', function() { + lib.run(); + }); + </script> + </div> + <form id="form" > + <div class="row"> + <fieldset> + <div> + <label for="a">A</label> + <input type="number" id="a" name="a" value="2"/> + </div> + <div> + <label for="m">M</label> + <input type="number" id="m" name="m" value="5"/> + </div> + <div> + <label for="fm">FM</label> + <input type="number" id="fm" name="fm" value="9"/> + </div> + <div> + <label for="rm">RM</label> + <input type="number" id="rm" name="rm" value="7"/> + </div> + <div> + <label for="pm">PM</label> + <input type="number" id="pm" name="pm" value="40"/> + </div> + </fieldset> + <fieldset> + <div> + <label for="a">Upgrade A</label> + <input type="number" id="a_bonus" name="a" value="0"/> + </div> + <div> + <label for="m">Upgrade M</label> + <input type="number" id="m_bonus" name="m" value="0"/> + </div> + <div> + <label for="fm">Upgrade FM</label> + <input type="number" id="fm_bonus" name="fm" value="0"/> + </div> + <div> + <label for="rm">Upgrade RM</label> + <input type="number" id="rm_bonus" name="rm" value="0"/> + </div> + <div> + <label for="pm">Upgrade PM</label> + <input type="number" id="pm_bonus" name="pm" value="0"/> + </div> + </fieldset> + </div> + <div class="row"> + <fieldset> + <div> + <label for="xp">XP à dépenser</label> + <input type="number" id="xp" name="xp" value="1000"/> + </div> + <div> + <label for="tours">Tour de combats</label> + <input type="number" id="tours" name="tours" value="5"/> + </div> + <div> + <label for="cost">Couts du sort</label> + <input type="number" id="cost" name="cost" value="8"/> + </div> + <div> + <label for="dammage">Dégats du sort</label> + <input type="number" id="dammage" name="dammage" value="6"/> + </div> + <div> + <label for="fm_oponent">FM de l’adversaire</label> + <input type="number" id="fm_oponent" name="fm_oponent" value="10"/> + </div> + </fieldset> + </div> + <input id="send" type="submit" value="Charger"/> + </form> + <h2>Résultats</h2> + <textarea id="result" > + </textarea> +</div> +</body> +</html> diff --git a/calculette_aoo/js/dune b/calculette_aoo/js/dune new file mode 100644 index 0000000..8732ae0 --- /dev/null +++ b/calculette_aoo/js/dune @@ -0,0 +1,19 @@ +(executable + (name main) + (libraries + brr + note.brr + application + aoo + ) + (modes js) + (preprocess (pps js_of_ocaml-ppx)) + (link_flags (:standard -no-check-prims)) + ) + +(rule + (targets aoo.js) + (deps main.bc.js) + (action (copy %{deps} %{targets}))) + + diff --git a/calculette_aoo/js/main.ml b/calculette_aoo/js/main.ml new file mode 100644 index 0000000..9a63d12 --- /dev/null +++ b/calculette_aoo/js/main.ml @@ -0,0 +1,97 @@ +open Brr + +let ( let=? ) : 'a option -> ('a -> unit) -> unit = + fun f opt -> Option.iter opt f + +let get_int_value element = + let value = El.prop El.Prop.value element in + match Jstr.to_int value with + | Some v -> v + | None -> 0 + +let cout_a = (800, 200, 100) +let cout_m = (110, 55, 35) +let cout_rm = (50, 40, 20) +let cout_pm = (5, 3, 1) +let cout_fm = (100, 50, 30) + +let get_element_by_id id = + Jstr.of_string id |> Brr.Document.find_el_by_id Brr.G.document + +let get_int_element id = + Jstr.of_string id + |> Brr.Document.find_el_by_id Brr.G.document + |> Option.map get_int_value |> Option.value ~default:0 + +let eval _ = + let a_element = get_int_element "a" in + let m_element = get_int_element "m" in + let rm_element = get_int_element "rm" in + let fm_element = get_int_element "fm" in + let pm_element = get_int_element "pm" in + + let a_bonus = get_int_element "a_bonus" in + let m_bonus = get_int_element "m_bonus" in + let rm_bonus = get_int_element "rm_bonus" in + let fm_bonus = get_int_element "fm_bonus" in + let pm_bonus = get_int_element "pm_bonus" in + + let a = Aoo.Carac.create ~bonus:a_bonus a_element cout_a in + let m = Aoo.Carac.create ~bonus:m_bonus m_element cout_m in + let rm = Aoo.Carac.create ~bonus:rm_bonus rm_element cout_rm in + let fm = Aoo.Carac.create ~bonus:fm_bonus fm_element cout_fm in + let pm = Aoo.Carac.create ~bonus:pm_bonus pm_element cout_pm in + + let build = Aoo.Build.{ a; m; rm; fm; pm } in + + (* Evaluate the cost for the given upgrades *) + let cost = Aoo.Build.cost build in + + let fm_oponent = get_int_element "fm_oponent" in + let env : Aoo.Build.env = + { + cost_max = get_int_element "xp" + cost + ; max_tours = float (get_int_element "tours") + ; cout_sort = get_int_element "cost" + ; degat_sort = get_int_element "dammage" + ; fm_oponent + ; frequencies = Aoo.Build.buil_freq_table fm_element fm_oponent + } + in + let upgrades = + match Aoo.Build.upgrade env build with + | [] -> + (* If there is no upgrade available, return the current build as a + solution instead of nothing. *) + [ build ] + | upgrades -> upgrades + in + let build = + Aoo.Build.traverse env + (Aoo.Build.cost build, Aoo.Build.score env build) + upgrades + in + + let () = Aoo.Build.repr env Format.str_formatter build in + let result = Format.flush_str_formatter () in + let=? result_element = get_element_by_id "result" in + + El.set_children result_element [ El.txt' result ]; + () + +let main () = + let=? btn = get_element_by_id "form" in + let _ = + Ev.listen Brr_io.Form.Ev.submit + (fun ev -> + Ev.prevent_default ev; + eval ev) + (El.as_target btn) + in + + eval () + +let () = + let open Jv in + let main = obj [| ("run", repr main) |] in + set global "lib" main diff --git a/calculette_aoo/lib/build.ml b/calculette_aoo/lib/build.ml new file mode 100644 index 0000000..ddefe67 --- /dev/null +++ b/calculette_aoo/lib/build.ml @@ -0,0 +1,135 @@ +type build = { + a : Carac.t + ; m : Carac.t + ; rm : Carac.t + ; pm : Carac.t + ; fm : Carac.t +} + +type env = { + cout_sort : int + ; degat_sort : int + ; max_tours : float + ; fm_oponent : int + ; cost_max : int + ; frequencies : (int * float) list +} + +let roll_and_accumulate dices = + Seq.repeat () |> Seq.take dices + |> Seq.fold_left (fun res _ -> res + (1 + Random.int 3)) 0 + +let delta_carac level thresold = + Seq.repeat () |> Seq.take 10000 + |> Seq.fold_left + (fun acc _ -> + if roll_and_accumulate thresold > roll_and_accumulate level then acc + else acc + 1) + 0 + +let get_chance : env -> int -> float = + fun env delta -> + match List.assoc_opt delta env.frequencies with + | None -> 1.0 + | Some v -> v + +(** Build a list with the differents percentages to hit *) +let buil_freq_table from thresold = + let () = Random.self_init () in + List.init 6 (fun i -> + (from - thresold + i, float (delta_carac (from + i) thresold) /. 10000.)) + +let eval : build -> env -> float * float = + fun { a; m; rm; pm; fm } env -> + let cout_tour = (Carac.value a * env.cout_sort) - Carac.value rm in + let nb_tour = + if cout_tour > 0 then + float (Carac.value pm) /. float cout_tour |> Float.min env.max_tours + else env.max_tours + in + + let degats = + float (Carac.value a * (env.degat_sort + Carac.value m - 5)) + *. get_chance env (Carac.value fm - env.fm_oponent) + in + (degats, nb_tour) + +let repr_degats : env -> Format.formatter -> build -> unit = + fun env out build -> + let delta = get_chance env (Carac.value build.fm - env.fm_oponent) in + Format.fprintf out "%d A × (%d du sorts + %d M - %d M) × %.2f %%" + (Carac.value build.a) env.degat_sort (Carac.value build.m) 5 (delta *. 100.); + let sum = env.degat_sort + Carac.value build.m - 5 in + let prod = Carac.value build.a * sum in + Format.fprintf out "@;%d A × %d = %d@;" (Carac.value build.a) sum prod; + Format.fprintf out "%d × %.2f = %.2f" prod delta (delta *. float prod); + () + +let cost : build -> int = + fun { a; m; rm; pm; fm } -> + Carac.(cout a + cout m + cout rm + cout pm + cout fm) + +let repr : env -> Format.formatter -> build -> unit = + fun env formatter build -> + let degats, nb_tour = eval build env in + + Format.fprintf formatter + {|Caractéristiques retenues : +- A : %a +- M : %a +- FM: %a +- RM: %a +- PM: %a +|} + Carac.repr build.a Carac.repr build.m Carac.repr build.fm Carac.repr + build.rm Carac.repr build.pm; + Format.fprintf formatter + "@[Le magicien fera %.2f degats par tour pour un total de %d sur %.2f \ + tours@;\ + (@[<v 2>%a@])@;" + degats + (int_of_float (nb_tour *. degats)) + nb_tour (repr_degats env) build; + Format.fprintf formatter "Le cout de ce build est de %d@]@." (cost build) + +let score : env -> build -> float = + fun env build -> + let d, v = eval build env in + d *. v + +(* Upgrade each caracteristic and keep only the values in range *) +let upgrade : env -> build -> build list = + fun env build -> + [ + { build with a = Carac.incr build.a } + ; { build with m = Carac.incr build.m } + ; { build with rm = Carac.incr build.rm } + ; { build with pm = Carac.incr build.pm } + ; { build with pm = Carac.incr ~step:3 build.pm } + ; { build with pm = Carac.incr ~step:10 build.pm } + ; { build with fm = Carac.incr build.fm } + ] + |> List.filter (fun f -> cost f <= env.cost_max) + +let rec traverse env (last_cost, last_score) = function + | [] -> failwith "Invalid data" + | hd :: [] -> hd + | hd :: tl -> + let score' = score env hd and cost' = cost hd in + if cost' > last_cost && score' < last_score then + traverse env (last_cost, last_score) tl + else + (* Get the new elements to add and filter them if they do not provide + anything better *) + let new_builds = + upgrade env hd + |> List.filter (fun build -> + List.for_all + (fun element -> + cost build > cost element + || score env build > score env element) + tl) + in + + (* For each new element, remove all the obsolete builds *) + traverse env (cost', score') (List.rev_append tl new_builds) diff --git a/calculette_aoo/lib/build.mli b/calculette_aoo/lib/build.mli new file mode 100644 index 0000000..3e2e690 --- /dev/null +++ b/calculette_aoo/lib/build.mli @@ -0,0 +1,35 @@ +type build = { + a : Carac.t + ; m : Carac.t + ; rm : Carac.t + ; pm : Carac.t + ; fm : Carac.t +} + +type env = { + cout_sort : int + ; degat_sort : int + ; max_tours : float + ; fm_oponent : int + ; cost_max : int + ; frequencies : (int * float) list +} + +val buil_freq_table : int -> int -> (int * float) list +(** Build a list with the differents percentages to hit *) + +val cost : build -> int +(** Get the cost for a build *) + +val score : env -> build -> float +(** Get the score for the build *) + +val traverse : env -> int * float -> build list -> build +(** Test differents upgrade configuration and present the best one with the + constraints given in [env]. + + The costt for the upgrade will not exceed the property [env.cost_max] + *) + +val upgrade : env -> build -> build list +val repr : env -> Format.formatter -> build -> unit diff --git a/calculette_aoo/lib/carac.ml b/calculette_aoo/lib/carac.ml new file mode 100644 index 0000000..a48c734 --- /dev/null +++ b/calculette_aoo/lib/carac.ml @@ -0,0 +1,40 @@ +type cout_carac = int * int * int + +type t = { + value : int + ; couts : cout_carac + ; bonus : int +} + +let create : ?bonus:int -> int -> cout_carac -> t = + fun ?(bonus = 0) value couts -> { value; couts; bonus } + +let incr ?(step = 1) t = { t with bonus = t.bonus + step } + +(* +Evaluate the cost for the successives upgrades. + +I’m pretty sure this can be transformed into a linear function, but I do not see how… + + c0 * t.bonus ++ max 0 ((((t.bonus - 1) * 2) - 1) * c1) ++ ? + + *) +let cout : t -> int = + fun t -> + let c0, c1, c2 = t.couts in + let rec c acc t = + match t with + | 0 -> acc + | 1 -> acc + c0 + | 2 -> c (acc + c0 + c1) (t - 1) + | 3 -> c (acc + c0 + (c1 * 2)) (t - 1) + | n -> c (acc + c0 + (c1 * 2) + ((n - 3) * c2)) (t - 1) + in + c 0 t.bonus + +let value t = t.value + t.bonus + +let repr : Format.formatter -> t -> unit = + fun out t -> Format.fprintf out "%d (+%d)" (value t) t.bonus diff --git a/calculette_aoo/lib/carac.mli b/calculette_aoo/lib/carac.mli new file mode 100644 index 0000000..f364c81 --- /dev/null +++ b/calculette_aoo/lib/carac.mli @@ -0,0 +1,17 @@ +type t +type cout_carac = int * int * int + +val create : ?bonus:int -> int -> cout_carac -> t + +val cout : t -> int +(** Get the cost for the upgrades for this property *) + +val value : t -> int +(** Get the value (including upgrades) for this property *) + +val incr : ?step:int -> t -> t +(** Increment this property. + step is default 1. + *) + +val repr : Format.formatter -> t -> unit diff --git a/calculette_aoo/lib/dune b/calculette_aoo/lib/dune new file mode 100644 index 0000000..54b5a85 --- /dev/null +++ b/calculette_aoo/lib/dune @@ -0,0 +1,2 @@ +(library + (name aoo)) diff --git a/calculette_aoo/test/dune b/calculette_aoo/test/dune new file mode 100644 index 0000000..859e67e --- /dev/null +++ b/calculette_aoo/test/dune @@ -0,0 +1,2 @@ +(test + (name test_optim_aoo)) diff --git a/calculette_aoo/test/test_optim_aoo.ml b/calculette_aoo/test/test_optim_aoo.ml new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/calculette_aoo/test/test_optim_aoo.ml |