Crash-tan Crash-tan

Some fixes and new features in 1.60.0!

Crash BASIC 1.60.0 Release Notes

written by Crash-tan

ISMOBILE() now answers for the machine your game is actually running on,
not for the one it was built for. On the web that is the whole difference:
a phone browser reports 1.

The web player finally knows it is on a phone

A native player is one build for one kind of machine, so ISMOBILE() could
read its own compile target and be right. The web build cannot: the same
bytes serve a phone, a tablet and a desktop. It answered 0 for all three —
so a cart that does the obvious thing:

IF ISMOBILE() THEN
  GAMEPAD ON              ' on-screen pad
ELSE
  PRINT "WASD to move"
END IF

showed keyboard instructions to somebody holding a phone, and no controls at
all. There was no way to fix it from inside the cart, because there was
nothing to ask.

Now the player asks the browser. On a phone or tablet — including a browser
on Android, iOS and iPadOS — ISMOBILE() is 1, the on-screen gamepad appears
where it can be tapped, and TOUCHX() / TOUCHY() gate the way you would
expect.

An iPad is a tablet even when it says it is a Mac. Safari on iPadOS sends
a Macintosh user agent by default, so a cart that only read the user agent
would put a keyboard prompt on it. The player checks touch points behind that
one user agent, which is what tells the two apart.

A touchscreen laptop is still a desktop. Touch capability alone is never
what decides it — otherwise a Surface or a touch-screen Windows laptop would
get an on-screen gamepad drawn over a game its owner is playing with a
keyboard.

The three platform classes, unchanged otherwise

Where the cart is running ISMOBILE() ISCONSOLE()
iOS, Android apps 1 0
Phone or tablet browser 1 0
tvOS 0 1
Desktop, desktop browser 0 0

A browser never reports a console. The living-room case ships as the native
tvOS player, and nothing a browser says separates a television from a desktop
— guessing would send a cart down the remote-control path on a laptop.

Both still report 0 until your first SCREEN, which is the statement that
makes the player report a platform at all. Ask afterwards.

A page that embeds the player can override it

If you host the web player yourself and already know what you served — or you
want to test your touch UI without picking up a phone — say so when you build
the player:

const player = new CrashPlayer({
  canvas,
  isMobile: true,     // or false, or 'auto' (the default: detect the device)
  /* … */
});

player.setMobile('auto');        // change your mind before run()
player.isMobileDevice();         // what the cart will see, for your own layout

Set it before the cart's first SCREEN. isMobileDevice() is there so your
page can lay itself out the same way the game inside it will — show touch
chrome exactly when the cart does.

AND AN ARRAY INDEX PAST THE END NOW SAYS SO

DIM units(2) AS Unit is two elements, 0 and 1 — so a loop written
FOR k = 0 TO 2 reaches one past them. That is the ordinary off-by-one, and
until now the engine answered it three different ways, none of them true:

units(k).hp = 10        ' wrote nothing, said nothing
CALL units(k).Hit(1)    ' "the receiver of a method call is not a typed object"
PRINT units(k).hp       ' "the variable hasn't been set up as an object yet"

The write was the one that cost time. It dropped the assignment and carried on,
so the array came out of the loop short of a value, and the failure surfaced
later somewhere that had nothing to do with the loop. The other two halted —
but blamed your TYPE, sending you to a declaration that was perfectly correct
while the mistake sat in your loop bounds.

All three now report what actually happened, the same way a bare
units(2) = 10 already did:

You tried to reach `UNITS(2) - valid bounds are (0-1)`, but that's past
the edge of the array!

An array declared inside a TYPE reports the same way and names its path —
P.SLOTS(2) — and a negative index is reported as the negative number you
wrote rather than the enormous one it becomes internally.

Reading past the end is an error now too. It used to answer 0 — which is
a value an element can legitimately hold, so a loop that ran one step too far
read as data and the mistake surfaced, if it ever did, as wrong output
somewhere else entirely. Writing past the end has always been loud; reading now
matches it. Where an index really might run past the end, guard it the way the
error suggests:

IF i <= UBOUND(arr) THEN value = arr(i)

And a property written on something that is not an object is an error.

DIM scores(4) AS INTEGER
scores(0).hp = 10        ' now: that element holds INTEGER, not an object

That line did nothing at all before — no write, no message. Elements that ARE
objects are untouched: an AS OBJECT array and a TYPEd array both take
properties exactly as they did.

Nothing in bounds changed. The documented loop is exactly as it was:

DIM enemies(8) AS Enemy
FOR i = 0 TO 7           ' 8 elements are 0 through 7
  CALL enemies(i).Hit(5)
NEXT

Re-export anything already published

The detection lives in the renderer that gets copied into an export at package
time, so a build made with an earlier version keeps the old answer however new
the engine is. Package your game again with 1.60.0 and upload it; nothing
about your game or your itch.io settings needs to change.