guide

Scripts read these with (input 'var1 default). Unset vars use the default.

Difficulty

Beat clock

Advanced

Dodge bot

How many paths it keeps each tick and how far ahead it looks for bullets. More finds more, slower.

Keys

Ctrl+Enter
Load the file live. main restarts; bullets in flight keep going.
Alt+Enter
Evaluate the form under the cursor, or the selection.
Ctrl+Shift+Enter
Start over with a fresh VM.
Ctrl+Shift+H
Hide or show the code.
Alt+P
Pause or play.
Alt+. and Alt+,
Step one tick forward or back.
F
Fullscreen, when you're not typing.
B, cancel
Cancel every cancellable bullet into items, like a bomb.
H, hitbox
Show each bullet's hit circle, lasers' hit width while they're active, and the ship's hitbox.
D, heat
A heatmap of where hittable bullets have been over the last second. Peak is the most bullets in one 8-unit cell.
dodge
A bot searches for a path through the pattern from a fresh start, then plays it back as a ghost ship with a trail; red rings mark ticks with no safe move. Escape stops it.
Ctrl+S, share
Copy a link that opens this script in this scene.
Ctrl+Shift+S
The same link in plain ASCII, for places that mangle Unicode.
download
Save the code as a .bs file.
Shift+Alt+F, format
Lay the code out the way the examples are: two-space bodies, arguments lined up, lines within 60 columns. Your line breaks stay.
Drop a .bs file
Open it from your computer.
bs serve
Run bs serve my-patterns/ and open ?serve (or ?serve=host:port): the folder's files show up under Local files, and saving one in your own editor reloads it here, as Ctrl+Enter does.

Forms

defn def fn
(defn ring [n :speed 2 :kind 'bullet] …), called as (ring 12 :speed 3). Keyword parameters come last, each with a default.
& opts
Last in the parameters, takes the other named arguments; last in a call, passes them on: (fire :dir d & opts).
[a b] patterns
Take lists apart in let, loop, doseq and parameters: (doseq [[n speed] rows] …), _ to skip one.
let if when cond do and or
The usual. Values never change.
dotimes forever loop recur
(dotimes [i n] …), (loop [a 0] (recur …))
doseq
(doseq [x xs] …) runs the body for each item, and may wait.
wait wait-until
Pause the script. (wait 0.5) is half a tick.
par race until
Run forms together. race and until cancel the rest.
fire
(fire :dir d :speed s :kind 'k :run f), all optional.
:at :tags :attach
:at [x y] spawn offset, :tags for cancel, :attach to the parent.
:motion :delay :cancellable
:motion 'polar or 'wave (:spin :amp :period), :delay ticks before it can hit.
mirror rotate offset speed-scale
Transform everything the body fires: (mirror (fan …))
cancel move-to
(cancel :tags [...]) turns bullets into items; (move-to x y :over n)
target-x target-y target-count
Where the targets are; (aim :target i), (aim :lead s).
by-diff
(by-diff easy normal hard lunatic), from the settings menu.
beat wait-beats
(wait-beats 1) waits for the next beat; set the clock in settings. Without one, (wait-beats 1 30) waits 30 ticks and (beat 0) is 0.
drift-x drift-y
The extra velocity from accel.
vanish emit
Remove this actor. (emit 'name) tells the host.
change-speed change-dir
(change-speed 0 :over 40) tweens in the background.
turn accel
(turn rate :for n), (accel ax ay :over n)
aim heading speed x y age
Ask about this actor and the target.
last-dir last-speed rand rank
The last shot (or (last-dir d) before one), (rand a b), difficulty.
tick
(tick) is the tick the code runs in.
laser
(laser :dir :length :width :warn :active :fade :turn :kind :attach)
curvy-laser
:curve 'head 'wave 'polar, :speed :turn :amplitude :period :spin :segments :window
list nth count first rest last
[1 2 3] is (list 1 2 3). Lists never change.
conj concat reverse empty? range
New lists from old: (range 0 360 30).
map filter reduce rand-nth
(map (fn [x] …) xs); the function may not wait.
ring fan spread
Built in: (ring n :dir 0), (fan n :dir (aim) :step 15), (spread n :dir (aim) :width 60)
polygon rose
(polygon sides :per-side 5 :dir 90), (rose n :petals 4 :dir 0)
stream burst
(stream n :dir (aim) :gap 4), (burst n :dir (aim) :width 60 :speed [1 3])
Their keys
All take :speed (2) and :kind, and pass any other key fire takes on to every shot: (ring 12 :tags 'soft).
+ - * / mod inc dec abs
Arithmetic.
min max floor ceil round trunc
Limits and rounding.
sqrt pow sin cos tan atan2
Degrees, with 0 along +x and counterclockwise.
lerp clamp < > = not= not
Blending and comparison.
input
Inputs: (input 'var1) to (input 'var8), set from the settings menu.
field-w field-h
(input 'field-w) and (input 'field-h) give the field size.

What a script can rely on

Field
W × 448 units, origin bottom-left, y up. W follows the window; stay relative to the emitter.
Angles
Degrees, 0 is +x (right), counterclockwise: 90 is up, 270 is down toward the player.
Speed
Units per tick, at 60 ticks a second.
main
Spawned at the enemy, as its kind. Ctrl+Enter respawns it; bullets in flight carry on.
(aim)
Points at the player ship, which follows the mouse.
rank
The rank slider, 0 to 1.
(input 'var1)
Up to var8, from the settings menu.
(input 'field-w)
(input 'field-w) and (input 'field-h) give the field size.
Settings
Difficulty (by-diff) and the beat clock (beat, wait-beats) are set there. The beat clock is off until you turn it on, so give beat and wait-beats a default.
Hits
The ship's hitbox is a circle of radius 2.5. Each kind has its own hit radius, a bit smaller than its sprite; the Kinds tab shows it on hover. A touch flashes the ship and counts in hits, once per bullet. Nothing ends the run.
Culling
Actors more than 32 units outside the field are removed.
emit
Names you emit show in the console at the bottom.
Local files
With bs serve running and ?serve in the address, the files on disk are the copies that count: saving one reloads it here, and replaces any edits made here.
More
The guide covers the whole language; lists every sprite; show each form at work.

Any symbol works as a kind. Unknown ones draw as 'bullet; 'emitter draws nothing. Click to insert.

paused beat 0 tick 0 actors 0 lasers 0 hits 0 peak 0.0 vm 0.00 ms 60 fps serving