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_crush/Crush.ml.html
Source file Crush.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(* 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. *) (* See Crush.mli *) open Playground open Basics (* float arithmetics *) (*****************************************************************************) (* The levels: slices, front to back *) (*****************************************************************************) (* '#' a block, 'E' the exit, 'P' where Danny starts (in the front * slice, facing the camera's first way) *) type level = { name : string; hint : string; slices : string list list } (* the gap is four blocks wide, too far to jump; a bridge three slices * back, and a wall behind the far side -- crush to cross, uncrush to * get past the wall *) let gap = { name = "the gap"; hint = "crush (c): the bridge behind comes to you. Then uncrush, or the wall behind will too."; slices = [ [ ".............."; ".............."; ".............."; ".............."; ".P...........E"; "####....######"; "####....######" ]; [ ".............."; ".............."; ".............."; ".............."; ".............."; ".............."; ".............." ]; [ ".............."; ".............."; ".............."; ".............."; ".............."; "....####......"; ".............." ]; [ ".............."; ".............."; "..........#..."; "..........#..."; "..........#..."; "..........#..."; ".............." ] ] } (* the exit is deep in the level, across a hole in depth: turn the * camera (tab) to crush along the other axis, where a floor far to the * right fills the hole. Crushed from the front, a block in slice 3 * hides the exit (a block wins over what is behind it), and where * Danny starts there is no room to crush at all. *) let deep = { name = "the deep"; hint = "turn the camera (tab), then crush: the floor far to the right fills the hole."; slices = [ [ "........"; "........"; "........"; ".P......"; "####...." ]; [ "........"; "........"; "........"; "........"; "......##" ]; [ "........"; "........"; "........"; "........"; "......##" ]; [ "........"; "........"; "........"; ".#......"; "......##" ]; [ "........"; "........"; "........"; "........"; "####...." ]; [ "........"; "........"; "........"; ".E......"; "####...." ] ] } let levels = [| gap; deep |] (*****************************************************************************) (* The grid, and the planes cut from it *) (*****************************************************************************) let tile = 48. let nx (l : level) : int = String.length (List.hd (List.hd l.slices)) let ny (l : level) : int = List.length (List.hd l.slices) let nz (l : level) : int = List.length l.slices (* the cell at (x, y, z): y the row from the top, z the slice from the * front; outside the grid, nothing *) let cell (l : level) (x : int) (y : int) (z : int) : char = if x < 0 || y < 0 || z < 0 || x >= nx l || y >= ny l || z >= nz l then '.' else (List.nth (List.nth l.slices z) y).[x] (* The camera: 0 looks along z (the plane is x across), 1 along x (the * plane is z across). [across] and [deep] are the grid's sizes that * way; [at] turns (across, depth) back into the grid's (x, z). *) let across (l : level) (view : int) : int = if view = 0 then nx l else nz l let deep_n (l : level) (view : int) : int = if view = 0 then nz l else nx l let at (view : int) (u : int) (d : int) : int * int = if view = 0 then (u, d) else (d, u) (* a cell of the crushed plane: a block if any depth has one, else the * exit if one does *) let project (l : level) (view : int) (u : int) (y : int) : char = let cells = List.init (deep_n l view) (fun d -> let x, z = at view u d in cell l x y z) in if List.mem '#' cells then '#' else if List.mem 'E' cells then 'E' else '.' (* the plane Danny plays on: one slice, or all of them crushed *) let plane (l : level) (view : int) (crushed : bool) (depth : int) : Tilemap.t = Tilemap.of_strings tile (List.init (ny l) (fun y -> String.init (across l view) (fun u -> if crushed then project l view u y else let x, z = at view u depth in match cell l x y z with 'P' -> '.' | c -> c))) (*****************************************************************************) (* The model *) (*****************************************************************************) type play = { level : int; view : int; crushed : bool; squash : number; (* 1 uncrushed, 0 crushed; in between, the crush drawn *) (* Danny: across the plane and up, in the plane's coordinates, and the * depth of the slice he is in (the plane's, uncrushed) *) u : number; y : number; depth : int; vx : number; vy : number; ground : bool; message : string; } let size = (28., 40.) let enter (i : int) : play = let l = levels.(i) in let map = plane l 0 false 0 in let u, y = match Tilemap.find (Tilemap.of_strings tile (List.hd l.slices)) 'P' with (c, r) :: _ -> Tilemap.center map c r | [] -> (0., 0.) in { level = i; view = 0; crushed = false; squash = 1.; u; y; depth = 0; vx = 0.; vy = 0.; ground = false; message = l.hint } (*****************************************************************************) (* The rules: crushing, uncrushing, turning *) (*****************************************************************************) let solid (c : char) : bool = c = '#' let current (p : play) : Tilemap.t = plane levels.(p.level) p.view p.crushed p.depth (* Crush: refused if Danny would be inside a block of the crushed * plane. *) let crush (p : play) : play = let crushed = plane levels.(p.level) p.view true p.depth in if Tile_move.hits solid crushed size p.u p.y then { p with message = "no room to crush here" } else { p with crushed = true; message = "" } (* Uncrush: back into a slice -- the one of the block under Danny's * feet nearest where he was, or, in the air, the one he was in (every * depth is free where he is: the crushed plane was). *) let uncrush (p : play) : play = let l = levels.(p.level) in let map = current p in let u, y = Tilemap.cell map p.u (p.y - (snd size / 2.) - 2.) in let supports = List.filter (fun d -> let x, z = at p.view u d in solid (cell l x y z)) (List.init (deep_n l p.view) Fun.id) in let depth = match List.sort (fun a b -> compare (abs (a -.. p.depth)) (abs (b -.. p.depth))) supports with | d :: _ when p.ground -> d | _ -> p.depth in (* the slice must have room for him, if he stood on a block's edge *) let depth = if Tile_move.hits solid (plane l p.view false depth) size p.u p.y then p.depth else depth in { p with crushed = false; depth; message = "" } (* Turn the camera a quarter: what was across becomes depth, what was * depth across. Only on the ground, uncrushed. *) let turn (p : play) : play = let l = levels.(p.level) in let map = current p in let u, _ = Tilemap.cell map p.u p.y in let view = 1 -.. p.view in (* the depth becomes the position across, and the other way round *) let new_map = plane l view false u in let nu, _ = Tilemap.center new_map p.depth 0 in { p with view; depth = u; u = nu; vx = 0.; message = "" } (*****************************************************************************) (* Update *) (*****************************************************************************) let gravity = 0.8 let jump_speed = 11. let run_speed = 4. (* what the player does this frame, which is all [step] needs: the game * reads it off the keyboard, the tests make it up *) type input = { dx : number; jump : bool; step_deep : int; crush_key : bool; turn_key : bool } let nothing = { dx = 0.; jump = false; step_deep = 0; crush_key = false; turn_key = false } let step (i : input) (p : play) : play = let target = if p.crushed then 0. else 1. in if p.squash <> target then (* the crush drawn: the depths slide together, or apart; the game waits *) { p with squash = (if target < p.squash then Float.max 0. (p.squash - 0.06) else Float.min 1. (p.squash + 0.06)) } else if i.crush_key && p.ground then if p.crushed then uncrush p else crush p else if i.turn_key && p.ground && not p.crushed then turn p else let l = levels.(p.level) in (* a step in depth, uncrushed, if the next slice has room *) let p = if i.step_deep <> 0 && not p.crushed then let d = p.depth +.. i.step_deep in if d >= 0 && d < deep_n l p.view && not (Tile_move.hits solid (plane l p.view false d) size p.u p.y) then { p with depth = d } else p else p in let map = current p in let vy = if i.jump && p.ground then jump_speed else Float.max (-14.) (p.vy - gravity) in let (u, y), hit_x = Tile_move.move_by solid map size (p.u, p.y) (i.dx * run_speed, 0.) in let (u, y), hit_y = Tile_move.move_by solid map size (u, y) (0., vy) in { p with u; y; vx = (if hit_x then 0. else i.dx * run_speed); vy = (if hit_y then 0. else vy); ground = Tile_move.on_ground solid map size u y } let fell (p : play) : bool = p.y < -.(float_of_int (ny levels.(p.level)) * tile / 2.) - 100. let at_exit (p : play) : bool = p.squash = (if p.crushed then 0. else 1.) && Tile_move.hits (fun c -> c = 'E') (current p) size p.u p.y (* the keys, read into an [input]: [pressed] says whether a key went * down this frame (Scene2d.pressed) *) let read_input (pressed : (keyboard -> bool) -> bool) (k : keyboard) : input = { dx = to_x k; jump = pressed (fun k -> k.kspace); step_deep = (if pressed (fun k -> k.kup) then 1 else if pressed (fun k -> k.kdown) then -1 else 0); crush_key = pressed (fun k -> Set_.mem "c" k.keys); turn_key = pressed (fun k -> Set_.mem "Tab" k.keys) } type outcome = Going of play | Next_level of play | Finished let frame (i : input) (p : play) : outcome = let p = step i p in if fell p then Going { (enter p.level) with message = "you fell -- again" } else if at_exit p then if p.level +.. 1 < Array.length levels then Next_level (enter (p.level +.. 1)) else Finished else Going p
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>