aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSébastien Dailly <sebastien@dailly.me>2024-02-24 11:31:28 +0100
committerSébastien Dailly <sebastien@dailly.me>2024-02-24 11:31:28 +0100
commitef9beb0814c36cda979a4ed7e9175e72e69540ac (patch)
treec2852b1dd5df53f5d78e9e952f29360d50126e06
parent9e7f27c60a425e2baa67cd459d8509a43b1d123d (diff)
New application: build upgrade helper for aoo
-rw-r--r--calculette_aoo/bin/dune3
-rw-r--r--calculette_aoo/bin/main.ml49
-rw-r--r--calculette_aoo/index.html180
-rw-r--r--calculette_aoo/js/dune19
-rw-r--r--calculette_aoo/js/main.ml97
-rw-r--r--calculette_aoo/lib/build.ml135
-rw-r--r--calculette_aoo/lib/build.mli35
-rw-r--r--calculette_aoo/lib/carac.ml40
-rw-r--r--calculette_aoo/lib/carac.mli17
-rw-r--r--calculette_aoo/lib/dune2
-rw-r--r--calculette_aoo/test/dune2
-rw-r--r--calculette_aoo/test/test_optim_aoo.ml0
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