package elm_playground_gamekits
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
Genre kits for Playground games: platformers, racing, shoot 'em ups, fighting and more
Install
dune-project
Dependency
Authors
Maintainers
Sources
0.3.6.tar.gz
md5=7c636383d146d30ac6f2fa234a6253c8
sha512=c79f3823c5f8f57e5038eb640d487c61168b84aa07c61999d6622ef9fd0c890e2b03b4c6a7cdbbe9352a49e25dda00ac7bb14693cee8e3d7beeed251351a2af0
doc/src/elm_playground_gamekits.kit_segments/Segments.ml.html
Source file Segments.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242(* Claude Code * * Copyright (C) 2026 Yoann Padioleau * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License * (LGPL) as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. *) type vec = float * float * float type box = { x0 : float; y0 : float; z0 : float; x1 : float; y1 : float; z1 : float } type segment = { box : box; rgb : int * int * int; light : float } type side = { axis : int; positive : bool } type quad = { corners : vec list; normal : vec; side : side } type opening = { into : int; quad : quad } type level = { segments : segment array; walls : quad list array; openings : opening list array; start : vec; exit : int; } (*****************************************************************************) (* Boxes, axis by axis *) (*****************************************************************************) (* the two other axes, in the order that makes (u, v, axis) turn the * same way as (x, y, z): u = axis + 1, v = axis + 2 *) let others (axis : int) : int * int = ((axis + 1) mod 3, (axis + 2) mod 3) let low (b : box) (axis : int) : float = match axis with 0 -> b.x0 | 1 -> b.y0 | _ -> b.z0 let high (b : box) (axis : int) : float = match axis with 0 -> b.x1 | 1 -> b.y1 | _ -> b.z1 let coord ((x, y, z) : vec) (axis : int) : float = match axis with 0 -> x | 1 -> y | _ -> z (* the point whose [axis] coordinate is [c], [u] and [v] the other two * (in [others]' order) *) let point (axis : int) (c : float) (u : float) (v : float) : vec = let a, _ = others axis in let get i = if i = axis then c else if i = a then u else v in (get 0, get 1, get 2) let face (b : box) (axis : int) (positive : bool) : float = if positive then high b axis else low b axis (* the side's rectangle, in the two other axes *) let rect (b : box) (axis : int) : float * float * float * float = let u, v = others axis in (low b u, high b u, low b v, high b v) let contains (b : box) (p : vec) : bool = let (x, y, z) = p in x >= b.x0 && x <= b.x1 && y >= b.y0 && y <= b.y1 && z >= b.z0 && z <= b.z1 (* A side's rectangle as a quad, its corners counterclockwise seen from * inside the box: (u0, v0), (u1, v0), (u1, v1), (u0, v1) turns that way * seen from the axis' positive end, so the positive side (whose inside * is the other way) takes it backwards. *) let quad_of (side : side) (c : float) ((u0, u1, v0, v1) : float * float * float * float) : quad = let p u v = point side.axis c u v in let corners = [ p u0 v0; p u1 v0; p u1 v1; p u0 v1 ] in let one = if side.positive then -1. else 1. in { corners = (if side.positive then List.rev corners else corners); normal = point side.axis one 0. 0.; side } (*****************************************************************************) (* Building *) (*****************************************************************************) (* the part of two rectangles that overlaps, if it has an area *) let overlap (a0, a1, b0, b1) (c0, c1, d0, d1) : (float * float * float * float) option = let u0 = Float.max a0 c0 and u1 = Float.min a1 c1 and v0 = Float.max b0 d0 and v1 = Float.min b1 d1 in if u0 < u1 && v0 < v1 then Some (u0, u1, v0, v1) else None (* The rock of a side: its rectangle with the openings cut out. The * openings' edges cut it into a grid, and a cell of that grid is rock * unless it falls inside an opening: * * +----+-------+---+ the side, with one opening (##); the * | | | | lines through its edges make 9 cells, * +----+-------+---+ 8 of them rock, drawn as they are (a * | |#######| | rectangle each, never cut further) * +----+-------+---+ * | | | | * +----+-------+---+ *) let rock (side : side) (c : float) (r : float * float * float * float) (holes : (float * float * float * float) list) : quad list = let u0, u1, v0, v1 = r in let cuts lo hi ends = List.sort_uniq compare ((lo :: hi :: ends) |> List.filter (fun x -> x >= lo && x <= hi)) in let us = cuts u0 u1 (List.concat_map (fun (a0, a1, _, _) -> [ a0; a1 ]) holes) in let vs = cuts v0 v1 (List.concat_map (fun (_, _, b0, b1) -> [ b0; b1 ]) holes) in let rec pairs = function a :: (b :: _ as rest) -> (a, b) :: pairs rest | _ -> [] in List.concat_map (fun (ua, ub) -> List.filter_map (fun (va, vb) -> let mu = (ua +. ub) /. 2. and mv = (va +. vb) /. 2. in if List.exists (fun (a0, a1, b0, b1) -> mu > a0 && mu < a1 && mv > b0 && mv < b1) holes then None else Some (quad_of side c (ua, ub, va, vb))) (pairs vs)) (pairs us) let make (segments : segment list) ~(start : vec) ~(exit : int) : level = let segments = Array.of_list segments in let n = Array.length segments in let openings = Array.make n [] in (* the holes of each (segment, side), to cut the rock around *) let holes = Hashtbl.create 64 in let add_hole i side r = Hashtbl.replace holes (i, side) (r :: Option.value (Hashtbl.find_opt holes (i, side)) ~default:[]) in (* i's positive side against j's negative one: the same rectangle * becomes an opening on both sides of it *) for i = 0 to n - 1 do for j = 0 to n - 1 do if i <> j then for axis = 0 to 2 do let a = segments.(i).box and b = segments.(j).box in if high a axis = low b axis then match overlap (rect a axis) (rect b axis) with | None -> () | Some r -> let at = high a axis in let mine_ = { axis; positive = true } and theirs = { axis; positive = false } in add_hole i mine_ r; add_hole j theirs r; openings.(i) <- { into = j; quad = quad_of mine_ at r } :: openings.(i); openings.(j) <- { into = i; quad = quad_of theirs at r } :: openings.(j) done done done; let walls = Array.init n (fun i -> List.concat_map (fun axis -> List.concat_map (fun positive -> let side = { axis; positive } in let b = segments.(i).box in rock side (face b axis positive) (rect b axis) (Option.value (Hashtbl.find_opt holes (i, side)) ~default:[])) [ false; true ]) [ 0; 1; 2 ]) in { segments; walls; openings; start; exit } (*****************************************************************************) (* Asking *) (*****************************************************************************) let segment_at (lv : level) (p : vec) : int option = let rec go i = if i >= Array.length lv.segments then None else if contains lv.segments.(i).box p then Some i else go (i + 1) in go 0 let inside (lv : level) ~(radius : float) (p : vec) : bool = match segment_at lv p with | None -> false | Some i -> let b = lv.segments.(i).box in (* every side the ball reaches must be an opening, with room * around the ball *) List.for_all (fun (axis, positive) -> let c = face b axis positive in if Float.abs (coord p axis -. c) >= radius then true else let u, v = others axis in let pu = coord p u and pv = coord p v in List.exists (fun (o : opening) -> o.quad.side.axis = axis && o.quad.side.positive = positive && let corners = o.quad.corners in let us = List.map (fun q -> coord q u) corners and vs = List.map (fun q -> coord q v) corners in let mn l = List.fold_left Float.min infinity l and mx l = List.fold_left Float.max neg_infinity l in pu -. radius >= mn us && pu +. radius <= mx us && pv -. radius >= mn vs && pv +. radius <= mx vs) lv.openings.(i)) [ (0, false); (0, true); (1, false); (1, true); (2, false); (2, true) ] let move (lv : level) ~(radius : float) (p : vec) (delta : vec) : vec = let step p axis = let d = coord delta axis in let (x, y, z) = p in let q = match axis with 0 -> (x +. d, y, z) | 1 -> (x, y +. d, z) | _ -> (x, y, z +. d) in if inside lv ~radius q then q else p in List.fold_left step p [ 0; 1; 2 ] let clear (lv : level) ((x1, y1, z1) : vec) ((x2, y2, z2) : vec) : bool = let n = Float.to_int (sqrt (((x2 -. x1) ** 2.) +. ((y2 -. y1) ** 2.) +. ((z2 -. z1) ** 2.))) + 1 in List.for_all (fun k -> let f = float_of_int k /. float_of_int n in segment_at lv (x1 +. ((x2 -. x1) *. f), y1 +. ((y2 -. y1) *. f), z1 +. ((z2 -. z1) *. f)) <> None) (List.init (n + 1) Fun.id) (*****************************************************************************) (* The mine *) (*****************************************************************************) let box x0 y0 z0 x1 y1 z1 = { x0; y0; z0; x1; y1; z1 } let seg ?(rgb = (96, 88, 80)) ?(light = 0.7) b : segment = { box = b; rgb; light } (* Twelve boxes: the start room, then two ways round to the reactor * room -- east through a corridor, a shaft up, the upper room and a * corridor north; or north, another room, a shaft up and the west room * -- and the exit beyond the reactor: * * seen from above (x to the right, z up the page) * * +------+ 10 +----------+ * | |----------| | * | 8/9 | ^ | 6 |--+ 11 (exit) * +------+ | +----------+ * | 5 | * 7 | * +------+ 1 +------+ | * | 0 |-----| 2/3 |--+ 4 above 2 * +------+ +------+ *) let mine : level = make ~start:(20., 12., 20.) ~exit:11 [ seg ~rgb:(104, 96, 84) (box 0. 0. 0. 40. 24. 40.); (* the corridor east *) seg ~light:0.5 (box 40. 4. 12. 80. 20. 28.); seg ~rgb:(96, 92, 100) (box 80. 0. 0. 120. 24. 40.); (* the shaft up, out of the junction room's ceiling *) seg ~light:0.45 (box 92. 24. 12. 108. 64. 28.); seg ~rgb:(96, 92, 100) (box 80. 64. 0. 120. 88. 40.); (* the corridor north, to the reactor room *) seg ~light:0.5 (box 92. 68. 40. 108. 84. 80.); (* the reactor room *) seg ~rgb:(120, 80, 72) ~light:0.85 (box 60. 56. 80. 140. 96. 140.); (* the corridor north, out of the start room *) seg ~light:0.5 (box 12. 4. 40. 28. 20. 80.); seg ~rgb:(104, 96, 84) (box 0. 0. 80. 40. 24. 120.); (* its shaft up *) seg ~light:0.45 (box 12. 24. 92. 28. 60. 108.); seg ~rgb:(96, 92, 100) (box 0. 60. 80. 60. 96. 120.); (* the exit, east of the reactor room *) seg ~rgb:(72, 120, 80) ~light:1. (box 140. 64. 100. 160. 80. 120.) ]
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>