The trio the author decided on 2026-09-20 is now all built: def is CL's defparameter — its initialiser runs on every daemon re-run, unguarded, so an edited initialiser repaints the same storage on C-c C-c plus re-run — defonce (Clojure's name for CL's defvar, per the author) initialises once behind the .init~once. flag, and defconst stays the image. One parse arm reads both forms; the difference is Ast.reinit, carried to Tast.global's grerun. Emit.startup_plan gives a def no guard flag, and Check.check_global lifts every def initialiser — zero and literal included — into global/<n>, so the host's startup reaches it through the function cell and a re-evaluated def swaps it (Session's def_inits; Emit.redefinition declares the cell for a non-sibling target). The old defvar spelling is refused with the rename and both compiling spellings, and every program, test, doc and editor list is swept — except sand.flan, the author's live WIP, whose seven defvar lines are flagged in FIX.org and keep its three dependent tests red on this branch.
195 lines
8.7 KiB
Plaintext
195 lines
8.7 KiB
Plaintext
;;;; raylib [textures] example - fog of war
|
|
;;;;
|
|
;;;; examples/textures/textures_fog_of_war.c. Picked because it is the only
|
|
;;;; example in the whole upstream tree where a *texture filter* is the point
|
|
;;;; rather than a detail. The fog is drawn into a render texture one pixel
|
|
;;;; per tile — 25 by 15 — and then stretched to 800x450 on the way to the
|
|
;;;; screen, and the smooth edge between seen and unseen is entirely the
|
|
;;;; bilinear sampler doing the interpolation. With the default :point filter
|
|
;;;; the same program draws 32-pixel squares.
|
|
;;;;
|
|
;;;; That is what needed adding: `TextureFilter`, a new defenum in
|
|
;;;; vendor/raylib/raylib.flan, with set-texture-filter moved from the
|
|
;;;; generated half to the hand-written one and mapped in `bindings` so its
|
|
;;;; six members are checked against raylib.h. The header says `int filter`
|
|
;;;; and there is nothing in an `int` to say that 1 is the interesting value;
|
|
;;;; `:bilinear` says it.
|
|
;;;;
|
|
;;;; The three other things it exercises, none of which needed a line:
|
|
;;;;
|
|
;;;; - A render texture used as a *scratch surface* rather than as a
|
|
;;;; post-processing pass. core-scissor-test clipped the real framebuffer;
|
|
;;;; this draws a whole second frame into a 25x15 target and then samples
|
|
;;;; it. begin-texture-mode/end-texture-mode were already bound.
|
|
;;;; - draw-texture-pro with a NEGATIVE source height. A render texture's
|
|
;;;; rows come out of GL bottom-up, so every program that samples one has
|
|
;;;; to flip it, and raylib's convention for that is a source rectangle
|
|
;;;; with a negative height rather than a separate flag. Nothing in the
|
|
;;;; corpus had passed one. A Rectangle whose `height` field had been
|
|
;;;; permuted with `width` would draw the fog mirrored and the wrong way
|
|
;;;; up at once.
|
|
;;;; - clear-background with `blank` — alpha 0 — inside a texture mode, so
|
|
;;;; the untouched parts of the fog target are transparent and the map
|
|
;;;; shows through.
|
|
;;;;
|
|
;;;; The C's Map struct with its two calloc'd `unsigned char *` is two `[375
|
|
;;;; u8]` globals here. The dimensions are compile-time constants in the C
|
|
;;;; too (the #defines and the two assignments right after), so nothing is
|
|
;;;; lost by fixing them, and a global cannot hold a Vec in any case
|
|
;;;; (docs/PORTING.md §3). 375 is 25*15, written out because Flan's array length
|
|
;;;; must be a literal.
|
|
|
|
(import rl "vendor:raylib")
|
|
(import d "digits.flan")
|
|
|
|
(defconst screen-width 800)
|
|
(defconst screen-height 450)
|
|
|
|
(defconst map-tile-size 32)
|
|
(defconst player-size 16)
|
|
|
|
;; How far the player sees, in tiles. The C's loop runs from -this to +this
|
|
;; exclusive, so the lit patch is 2*this tiles across and not 2*this+1 — an
|
|
;; asymmetry in the original that is kept rather than tidied, because tidying
|
|
;; it would change the picture.
|
|
(defconst player-tile-visibility 2)
|
|
|
|
(defconst tiles-x 25)
|
|
(defconst tiles-y 15)
|
|
|
|
;; 0 or 1, picked once: which of the two blues a tile is drawn in.
|
|
(defonce tile-ids [375 u8])
|
|
|
|
;; 0 = never seen (solid black), 1 = visible now (no fog), 2 = seen before
|
|
;; (mostly black). The three-way state is why this is a byte per tile and not
|
|
;; a bit.
|
|
(defonce tile-fog [375 u8])
|
|
|
|
(defonce player-position rl/Vector2)
|
|
(defonce player-tile-x i32)
|
|
(defonce player-tile-y i32)
|
|
|
|
(defonce fog-of-war rl/RenderTexture2D)
|
|
|
|
(defn main [] ()
|
|
(rl/init-window screen-width screen-height
|
|
"raylib [textures] example - fog of war")
|
|
(defer (rl/close-window))
|
|
|
|
(set tile-ids (array 375 u8))
|
|
(set tile-fog (array 375 u8))
|
|
|
|
(dotimes [i (* tiles-x tiles-y)]
|
|
(set (at tile-ids i) (u8 (rl/get-random-value 0 1))))
|
|
|
|
(set player-position (rl/Vector2 {.x 180.0 .y 130.0}))
|
|
(set player-tile-x 0)
|
|
(set player-tile-y 0)
|
|
|
|
;; One texel per tile. The stretch to full size on draw is where the
|
|
;; smoothing happens, which is why the target is this small on purpose.
|
|
(set fog-of-war (rl/load-render-texture tiles-x tiles-y))
|
|
(defer (rl/unload-render-texture fog-of-war))
|
|
|
|
(rl/set-texture-filter (.texture fog-of-war) :bilinear)
|
|
|
|
(rl/set-target-fps 60)
|
|
|
|
(until (rl/window-should-close?)
|
|
;; Update
|
|
(when (rl/key-down? :key-right) (set (.x player-position) (+ (.x player-position) 5.0)))
|
|
(when (rl/key-down? :key-left) (set (.x player-position) (- (.x player-position) 5.0)))
|
|
(when (rl/key-down? :key-down) (set (.y player-position) (+ (.y player-position) 5.0)))
|
|
(when (rl/key-down? :key-up) (set (.y player-position) (- (.y player-position) 5.0)))
|
|
|
|
;; Keep the player inside the tilemap. The far edge is measured against
|
|
;; the player's far side, which is why player-size is subtracted.
|
|
(let [max-x (f32 (- (* tiles-x map-tile-size) player-size))
|
|
max-y (f32 (- (* tiles-y map-tile-size) player-size))]
|
|
(set (.x player-position) (clamp (.x player-position) 0.0 max-x))
|
|
(set (.y player-position) (clamp (.y player-position) 0.0 max-y)))
|
|
|
|
;; Everything lit on the previous frame drops to "seen before". The lit
|
|
;; tiles are then set back to 1 below, so a tile still in view never
|
|
;; spends a frame dimmed.
|
|
(dotimes [i (* tiles-x tiles-y)]
|
|
(when (= (at tile-fog i) 1) (set (at tile-fog i) 2)))
|
|
|
|
;; Which tile the player's centre is standing on.
|
|
(set player-tile-x
|
|
(i32 (/ (+ (.x player-position) (f32 (/ map-tile-size 2)))
|
|
(f32 map-tile-size))))
|
|
(set player-tile-y
|
|
(i32 (/ (+ (.y player-position) (f32 (/ map-tile-size 2)))
|
|
(f32 map-tile-size))))
|
|
|
|
;; Light the square around the player, skipping anything off the map.
|
|
;; Without that test this reads and writes outside the array, which in the
|
|
;; C is undefined and here is a bounds trap — the same bug, reported.
|
|
(let [y (- player-tile-y player-tile-visibility)]
|
|
(while (< y (+ player-tile-y player-tile-visibility))
|
|
(let [x (- player-tile-x player-tile-visibility)]
|
|
(while (< x (+ player-tile-x player-tile-visibility))
|
|
(when (and (>= x 0) (< x tiles-x) (>= y 0) (< y tiles-y))
|
|
(set (at tile-fog (+ (* y tiles-x) x)) 1))
|
|
(set x (+ x 1))))
|
|
(set y (+ y 1))))
|
|
|
|
;; Draw the fog into its own little target first, at one pixel per tile.
|
|
;; blank is alpha 0, so a tile that is neither unseen nor remembered
|
|
;; leaves nothing behind and the map below shows through unmodified.
|
|
(rl/with-texture-mode fog-of-war
|
|
(rl/clear-background rl/blank)
|
|
(dotimes [y tiles-y]
|
|
(dotimes [x tiles-x]
|
|
;; A tile in view (1) draws nothing at all and stays transparent.
|
|
(let [f (at tile-fog (+ (* y tiles-x) x))]
|
|
(when (!= f 1)
|
|
(rl/draw-rectangle x y 1 1
|
|
(if (= f 0) rl/black (rl/fade rl/black 0.8))))))))
|
|
|
|
(rl/with-drawing
|
|
(rl/clear-background rl/raywhite)
|
|
|
|
;; The map itself, in full size.
|
|
(dotimes [y tiles-y]
|
|
(dotimes [x tiles-x]
|
|
(rl/draw-rectangle (* x map-tile-size) (* y map-tile-size)
|
|
map-tile-size map-tile-size
|
|
(if (= (at tile-ids (+ (* y tiles-x) x)) 0)
|
|
rl/blue
|
|
(rl/fade rl/blue 0.9)))
|
|
(rl/draw-rectangle-lines (* x map-tile-size) (* y map-tile-size)
|
|
map-tile-size map-tile-size
|
|
(rl/fade rl/darkblue 0.5))))
|
|
|
|
(rl/draw-rectangle-v player-position
|
|
(rl/Vector2 {.x (f32 player-size) .y (f32 player-size)})
|
|
rl/red)
|
|
|
|
;; The fog, stretched over the whole map. The negative source height is
|
|
;; the flip — see the header comment.
|
|
(rl/draw-texture-pro
|
|
(.texture fog-of-war)
|
|
(rl/Rectangle {.x 0.0 .y 0.0
|
|
.width (f32 (.width (.texture fog-of-war)))
|
|
.height (- 0.0 (f32 (.height (.texture fog-of-war))))})
|
|
(rl/Rectangle {.x 0.0 .y 0.0
|
|
.width (f32 (* tiles-x map-tile-size))
|
|
.height (f32 (* tiles-y map-tile-size))})
|
|
(rl/Vector2 {.x 0.0 .y 0.0})
|
|
0.0
|
|
rl/white)
|
|
|
|
;; The C's TextFormat("Current tile: [%i,%i]", ...). Each number is drawn
|
|
;; before the next is formatted, which examples/digits.flan requires:
|
|
;; both share one static buffer in the runtime.
|
|
(let [x 10]
|
|
(set x (+ x (d/draw-piece "Current tile: [" x 10 20 rl/raywhite)))
|
|
(set x (+ x (d/draw-int player-tile-x x 10 20 rl/raywhite)))
|
|
(set x (+ x (d/draw-piece "," x 10 20 rl/raywhite)))
|
|
(set x (+ x (d/draw-int player-tile-y x 10 20 rl/raywhite)))
|
|
(d/draw-piece "]" x 10 20 rl/raywhite))
|
|
|
|
(rl/draw-text "ARROW KEYS to move" 10 (- screen-height 25) 20 rl/raywhite))))
|