Crash-tan Crash-tan

CrashPlayer 1.33.0 Released!

Crash BASIC 1.33.0 Release Notes

written by Crash-tan

THE BIT-TWIDDLER'S RELEASE

Crash BASIC can now reach all the way down to the individual bits of an
integer. Nine new functions — BITAND, BITOR, BITXOR, BITNOT,
SHL, SHR, BITTEST, BITSET, BITCLEAR — give you packed flags,
bitmasks, color channels, and bitboards without ever leaving BASIC.

If you've never needed bitwise math, nothing changes for you. If you
have been waiting for it: it's here, it's loud about mistakes, and it
behaves the same on every platform.

state = 0
state = BITSET(state, 2)          ' raise bit 2
IF BITTEST(state, 2) THEN PRINT "bit 2 is up"
PRINT BITAND(12, 10)              ' 8   (1100 & 1010 = 1000)
PRINT SHL(1, 4)                   ' 16  (1 shifted left 4)

Why functions, not operators?

A fair question if you're coming from QBasic, where AND / OR / NOT
secretly do double duty as bitwise operators (it only works there
because QBasic's TRUE is -1, so all-bits-set and "true" happen to
coincide).

Crash BASIC doesn't play that game. Here TRUE is 1, AND / OR
/ NOT are strictly boolean — they short-circuit and return 0/1
by truthiness — and they are staying that way. Overloading them to
sometimes mean bitwise would make a AND b ambiguous depending on
context, which is exactly the kind of footgun this language tries not to
hand you.

So bitwise math gets its own clearly-named functions. No guessing.

PRINT (12 AND 10)        ' 1   — both operands are truthy → boolean true
PRINT BITAND(12, 10)     ' 8   — the actual bitwise AND

The toolkit

Everything operates on Crash BASIC's 64-bit signed integers. Float
arguments are truncated to integer first, so SHL(2.9, 1) is SHL(2, 1).

The combiners — BITAND, BITOR, BITXOR, BITNOT

BITAND(a, b)    ' bits set in BOTH        — masking
BITOR(a, b)     ' bits set in EITHER      — combining flags
BITXOR(a, b)    ' bits set in EXACTLY one — toggling
BITNOT(a)       ' flip every bit

BITNOT flips all 64 bits, so on a signed integer BITNOT(0) is -1
(every bit set). That's not a bug — that's what "all bits on" is in
two's complement. Mask it down to the width you care about:

low_byte = BITAND(BITNOT(0), 255)   ' 255 — bottom 8 bits only

The shifters — SHL, SHR

SHL(a, n)   ' shift left by n bits  (a * 2^n)
SHR(a, n)   ' shift right by n bits — LOGICAL, zero-filled

Two things worth knowing:

  • SHR is a logical (zero-fill) shift, not arithmetic. It treats
    your integer as an unsigned 64-bit pattern, so the sign bit does not
    smear downward. This is the right behavior for masks and bitboards —
    bit 63 shifts cleanly down to bit 0 instead of dragging a wall of 1s
    behind it.
  • Shift amounts must be >= 0. A negative shift is a bug, so it
    halts loud instead of guessing a direction. Shifting by >= 64
    pushes every bit out the end and yields 0.

The single-bit trio — BITTEST, BITSET, BITCLEAR

For when you don't want to hand-roll 1 << n masks every time. Bit 0
is the least-significant bit.

BITTEST(a, bit)    ' 1 if that bit is set, else 0
BITSET(a, bit)     ' return a with that bit turned ON
BITCLEAR(a, bit)   ' return a with that bit turned OFF

BITSET / BITCLEAR return a new value — they don't mutate in place,
so assign the result back:

flags = BITSET(flags, 3)

A negative bit index is a bug and halts. A bit index >= 64 has no
representable bit, so it's handled gracefully rather than crashing:
BITTEST reads 0, and BITSET / BITCLEAR return the value unchanged.


What you'll actually use it for

Packed flags — stuff a dozen booleans into one integer:

HAS_KEY    = 0      ' bit positions
HAS_SWORD  = 1
IS_POISONED = 2

inv = 0
inv = BITSET(inv, HAS_KEY)
inv = BITSET(inv, HAS_SWORD)

IF BITTEST(inv, HAS_KEY) THEN PRINT "the door clicks open"
inv = BITCLEAR(inv, HAS_KEY)        ' key consumed

Color channels — pack and unpack RGB in a single number:

rgb = BITOR(BITOR(SHL(red, 16), SHL(green, 8)), blue)

red   = BITAND(SHR(rgb, 16), 255)
green = BITAND(SHR(rgb, 8), 255)
blue  = BITAND(rgb, 255)

Bitboards — represent an 8x8 board as a single 64-bit integer, one
bit per square, and test/flip squares with BITTEST / BITSET. This is
the fast foundation for board-game AI, and it's exactly why these landed
now.


Also fixed

  • PLAYMUSIC in the browser could play silently. A song whose name
    wasn't all-uppercase (e.g. "Moon") would load fine but make no sound
    on the web build, while playing correctly on desktop. Song names now
    match case-insensitively in the browser, the same way every other audio
    handle already does. Desktop was never affected.

Compatibility notes

  • Purely additive. Nine new built-in functions; nothing existing
    changed. AND / OR / NOT are still boolean, exactly as before.
  • No protocol / multiplayer changes. Wire-compatible with 1.32.x.
  • Same answers everywhere. The bitwise functions are pure integer
    math — identical results on Mac, Windows, Linux, iOS, Android, and the
    browser.

Go forth and twiddle. One bit at a time.

— Crash-tan