flan/examples/shapes-collision-area.flan
Joseph Ferano 25f56c24a8 A begin/end pair that cannot come apart, and what it still cannot promise
vendor/raylib/modes.flan: five macros over the five pairs the package binds
-- with-drawing, with-mode-2d, with-mode-3d, with-texture-mode,
with-scissor-mode. A second file with no declare-c in it, split out on
vector.flan's reasoning: raylib.flan is the package's statement about C and
nothing here names C, so nothing here can be made wrong by raylib changing.

Each expands to (do (begin-... args) body... (end-...)) -- the calls the
author used to type, in the order they typed them. No let, no gensym: nothing
binds a name, so there is nothing for a caller's name to collide with.

What it removes is the End* that is missing, wrong, or no longer beside its
Begin*. What it cannot remove is a body leaving through the unwind path: a
return or an invoke-restart skips the rest of the do and the End* with it.
defer is the obvious fix and is refused inside a loop body, which is where a
pair always lives -- checked, not assumed. So sand.flan's discipline stays:
keep the restart boundary outside the pair.

35 call sites converted across examples/ and sand.flan. The one left is
core-scissor-test.flan, whose Begin and End sit in two separate `when`s with
the drawing between them -- a conditional pair is a shape a bracketing macro
cannot express.

test/programs/rl-with.flan covers with-scissor-mode, which no example can,
with a frame function unreachable from main so it needs no libraylib;
rl-with-reject.flan is the arity half.
2026-09-13 18:00:35 +07:00

128 lines
5.3 KiB
Plaintext

;;;; raylib [shapes] example - collision area
;;;;
;;;; examples/shapes/shapes_collision_area.c. Picked for one call:
;;;; get-collision-rec, which is the only binding in the package that takes
;;;; two Rectangles and answers a third. Everything else in the corpus that
;;;; crosses a struct either passes one in (Camera2D, Color) or gets one back
;;;; (get-mouse-position, fade) — this is the first per-frame call doing both
;;;; at once, and the returned struct comes back through the shim's `out`
;;;; pointer rather than in registers, which is a different lowering again.
;;;;
;;;; It is also the only headless-adjacent thing in the file: both
;;;; collision-recs? and get-collision-rec are pure arithmetic over their
;;;; arguments and need no window at all. The acceptance table already asserts
;;;; the pair; what this adds is the same two calls on a frame path with one
;;;; rectangle driven by the mouse, where a permuted Rectangle would put the
;;;; green overlap patch somewhere the two boxes are not.
;;;;
;;;; Both TextFormats go through examples/digits.flan the way the core ports
;;;; do it: draw the literal part, then the number at x plus its width.
(import rl "vendor:raylib")
(import d "digits.flan")
(defconst screen-width 800)
(defconst screen-height 450)
;; The top strip the collision message is drawn into, and the floor box B is
;; not allowed above.
(defconst screen-upper-limit 40)
(defvar box-a rl/Rectangle) ; moves by itself, bounces off the sides
(defvar box-b rl/Rectangle) ; follows the mouse
(defvar box-collision rl/Rectangle) ; their overlap, valid only while touching
(defvar box-a-speed-x i32)
(defvar paused bool)
(defvar collision bool)
(defn main [] ()
(rl/init-window screen-width screen-height
"raylib [shapes] example - collision area")
(defer (rl/close-window))
(set box-a (rl/Rectangle {.x 10.0
.y (- (/ (f32 screen-height) 2.0) 50.0)
.width 200.0 .height 100.0}))
(set box-a-speed-x 4)
(set box-b (rl/Rectangle {.x (- (/ (f32 screen-width) 2.0) 30.0)
.y (- (/ (f32 screen-height) 2.0) 30.0)
.width 60.0 .height 60.0}))
;; A fresh Rectangle is four zeroes, which is the C's `= { 0 }`. It is never
;; drawn before the first collision sets it.
(set box-collision (rl/Rectangle {.x 0.0 .y 0.0 .width 0.0 .height 0.0}))
(set paused false)
(set collision false)
(rl/set-target-fps 60)
(until (rl/window-should-close?)
;; Update
(when (not paused)
(set (.x box-a) (+ (.x box-a) (f32 box-a-speed-x))))
;; Bounce at either edge. The test is on the far edge going right and on
;; the near one going left, so the box reverses on whichever it reaches.
(when (or (>= (+ (.x box-a) (.width box-a)) (f32 (rl/get-screen-width)))
(<= (.x box-a) 0.0))
(set box-a-speed-x (* box-a-speed-x -1)))
;; Box B is centred on the mouse and then clamped back inside the play
;; area — which is the window minus the top strip.
(set (.x box-b) (- (f32 (rl/get-mouse-x)) (/ (.width box-b) 2.0)))
(set (.y box-b) (- (f32 (rl/get-mouse-y)) (/ (.height box-b) 2.0)))
(if (>= (+ (.x box-b) (.width box-b)) (f32 (rl/get-screen-width)))
(set (.x box-b) (- (f32 (rl/get-screen-width)) (.width box-b)))
(when (<= (.x box-b) 0.0) (set (.x box-b) 0.0)))
(if (>= (+ (.y box-b) (.height box-b)) (f32 (rl/get-screen-height)))
(set (.y box-b) (- (f32 (rl/get-screen-height)) (.height box-b)))
(when (<= (.y box-b) (f32 screen-upper-limit))
(set (.y box-b) (f32 screen-upper-limit))))
(set collision (rl/collision-recs? box-a box-b))
;; Only meaningful while they touch: raylib answers a zero rectangle for
;; two boxes that do not, and the C leaves the previous one in place
;; rather than reading that back. This follows it.
(when collision (set box-collision (rl/get-collision-rec box-a box-b)))
(when (rl/key-pressed? :space) (set paused (not paused)))
;; Draw
(rl/with-drawing
(rl/clear-background rl/raywhite)
(rl/draw-rectangle 0 0 screen-width screen-upper-limit
(if collision rl/red rl/black))
(rl/draw-rectangle-rec box-a rl/gold)
(rl/draw-rectangle-rec box-b rl/blue)
(when collision
;; The overlap itself, drawn over both boxes. This patch landing
;; anywhere other than where the gold and the blue cross is what a
;; permuted Rectangle would look like.
(rl/draw-rectangle-rec box-collision rl/lime)
(rl/draw-text "COLLISION!"
(- (/ (rl/get-screen-width) 2)
(/ (rl/measure-text "COLLISION!" 20) 2))
(- (/ screen-upper-limit 2) 10) 20 rl/black)
;; The C's TextFormat("Collision Area: %i", ...). Both dimensions are
;; truncated to int before multiplying, as the C's two casts do.
(let [x (- (/ (rl/get-screen-width) 2) 100)
y (+ screen-upper-limit 10)]
(set x (+ x (d/draw-piece "Collision Area: " x y 20 rl/black)))
(d/draw-int (* (i32 (.width box-collision)) (i32 (.height box-collision)))
x y 20 rl/black)))
(rl/draw-text "Press SPACE to PAUSE/RESUME" 20 (- screen-height 35) 20
rl/lightgray)
(rl/draw-fps 10 10))))