luanette 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/api.json CHANGED
@@ -1 +1 @@
1
- [{"name":"abs","section":"Time, numbers, saving","help":[{"sig":"abs(x)","does":"the usual"}]},{"name":"act","section":"The host","help":[{"sig":"act(name, [arg])","does":"a command to the console: \"open\" opens the cart named arg; \"volume\" and \"brightness\" set that setting to the step arg (\"0\" to \"3\"). The launcher uses all three"}]},{"name":"btn","section":"Input","help":[{"sig":"btn(b)","does":"true while button b is held"}]},{"name":"btnp","section":"Input","help":[{"sig":"btnp(b)","does":"true on the frame b went down"}]},{"name":"buzz","section":"Feedback","help":[{"sig":"buzz(ms)","does":"the vibration motor, 10-500 ms (this board has none: caps().buzz is false)"}]},{"name":"camera","section":"Drawing","help":[{"sig":"camera(x, y)","does":"offset everything drawn by (-x, -y) / reset"},{"sig":"camera()","does":"offset everything drawn by (-x, -y) / reset"}]},{"name":"caps","section":"The host","help":[{"sig":"caps()","does":"what the cart runs on, as a table: the canvas size, where the picture sits, the board's name, what it has (touch, tilt, sound...). The fields are under \"Versions and what a cart runs on\""}]},{"name":"ceil","section":"Time, numbers, saving","help":[{"sig":"ceil(x)","does":"the usual"}]},{"name":"circ","section":"Drawing","help":[{"sig":"circ(x, y, r, [c])","does":"a circle outline / filled circle"}]},{"name":"circfill","section":"Drawing","help":[{"sig":"circfill(x, y, r, [c])","does":"a circle outline / filled circle"}]},{"name":"clip","section":"Drawing","help":[{"sig":"clip(x, y, w, h)","does":"draw only inside a rectangle / reset"},{"sig":"clip()","does":"draw only inside a rectangle / reset"}]},{"name":"cls","section":"Drawing","help":[{"sig":"cls([c])","does":"clear the screen (default 0)"}]},{"name":"data","section":"The host","help":[{"sig":"data(name)","does":"the latest value of a channel as a table, or nil: \"battery\" (mv, percent, charging, usb), \"carts\" (the carts on the board: a list of name, title, about), \"settings\" (volume and brightness, each a step from 0 to 3)"}]},{"name":"date","section":"Time, numbers, saving","help":[{"sig":"date([t])","does":"local time (the host's timezone, kept for offline use) as a table: year, month, day, hour, min, sec, wday (1 = Sunday), yday; for now() when t is left out. nil while the date is unknown"}]},{"name":"flr","section":"Time, numbers, saving","help":[{"sig":"flr(x)","does":"the usual"}]},{"name":"fps","section":"Time, numbers, saving","help":[{"sig":"fps(rate)","does":"the frame rate from the next frame (any rate that divides 60: 1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60) / lazy: active while there's input (a touch on the cart, a button) and half a second after, idle otherwise / the rate now. An idle cart wakes at once when a touch comes down or BOOT is pressed, so a slow idle rate costs no response time: give idle the slowest that still shows what moves on its own (a blink twice a second needs 4; a seconds counter 1 or 2). A cart doing a few frames a second instead of 30 or 60 spends that much less of the board's battery on its frames. Move things by time(), not by frames, when the rate changes"},{"sig":"fps(active, idle)","does":"the frame rate from the next frame (any rate that divides 60: 1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60) / lazy: active while there's input (a touch on the cart, a button) and half a second after, idle otherwise / the rate now. An idle cart wakes at once when a touch comes down or BOOT is pressed, so a slow idle rate costs no response time: give idle the slowest that still shows what moves on its own (a blink twice a second needs 4; a seconds counter 1 or 2). A cart doing a few frames a second instead of 30 or 60 spends that much less of the board's battery on its frames. Move things by time(), not by frames, when the rate changes"},{"sig":"fps()","does":"the frame rate from the next frame (any rate that divides 60: 1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60) / lazy: active while there's input (a touch on the cart, a button) and half a second after, idle otherwise / the rate now. An idle cart wakes at once when a touch comes down or BOOT is pressed, so a slow idle rate costs no response time: give idle the slowest that still shows what moves on its own (a blink twice a second needs 4; a seconds counter 1 or 2). A cart doing a few frames a second instead of 30 or 60 spends that much less of the board's battery on its frames. Move things by time(), not by frames, when the rate changes"}]},{"name":"ground","section":"3D","help":[{"sig":"ground(y, [color])","does":"shadows fall on the plane at height y, in color (the ground's color in shadow, 0xRRGGBB; default black)"}]},{"name":"hd.circle","section":"The full-color layer (hd)","help":[{"sig":"hd.circle(x, y, r, rgb, [a])","does":"a filled circle / a ring width px wide inside radius r"}]},{"name":"hd.clear","section":"The full-color layer (hd)","help":[{"sig":"hd.clear([rgb, a])","does":"wipe the canvas (inside the clip) to clear, or to a color at alpha a (255)"}]},{"name":"hd.clip","section":"The full-color layer (hd)","help":[{"sig":"hd.clip(x, y, w, h)","does":"draw only inside a box / anywhere (inside an hd.repaint: inside the box cut to the repaint's / the repaint's box)"},{"sig":"hd.clip()","does":"draw only inside a box / anywhere (inside an hd.repaint: inside the box cut to the repaint's / the repaint's box)"}]},{"name":"hd.frame","section":"The full-color layer (hd)","help":[{"sig":"hd.frame(x, y, w, h, rgb, [width, radius, a])","does":"a box's outline, width px (1)"}]},{"name":"hd.gradient","section":"The full-color layer (hd)","help":[{"sig":"hd.gradient(x, y, w, h, top, bottom, [a])","does":"a box shading from top to bottom, ordered-dithered (RGB565 has 32 steps of red and blue: a slow dark gradient would band)"}]},{"name":"hd.image","section":"The full-color layer (hd)","help":[{"sig":"hd.image(name, x, y, [w, h, a])","does":"draws an image (below) with its top left at x, y (to the pixel), at its own size or scaled to w x h (nearest pixel), at alpha a over its own; returns true, or false when there's no such image (yet)"}]},{"name":"hd.images","section":"The full-color layer (hd)","help":[{"sig":"hd.images()","does":"the names of the cart's own images"}]},{"name":"hd.inset","section":"The full-color layer (hd)","help":[{"sig":"hd.inset(y, [h])","does":"how far in from each side rows y to y + h show on this glass (its corners are rounded): keep a bar or a line of text that far in. 0 for the window, and for rows clear of the corners"}]},{"name":"hd.line","section":"The full-color layer (hd)","help":[{"sig":"hd.line(x0, y0, x1, y1, rgb, [width, a])","does":"a line width px wide (1), round ends"}]},{"name":"hd.measure","section":"The full-color layer (hd)","help":[{"sig":"hd.measure(text, [size, font])","does":"its width and line height"}]},{"name":"hd.order","section":"The full-color layer (hd)","help":[{"sig":"hd.order(\"over\" | \"under\")","does":"the layer over the picture (default), or under it: then the picture's color 0 shows the layer through, as over a 3D scene. Under a 3D scene's picture too: scene, layer, picture, in that order when \"under\""}]},{"name":"hd.rect","section":"The full-color layer (hd)","help":[{"sig":"hd.rect(x, y, w, h, rgb, [a, radius])","does":"a filled box, its corners rounded by radius"}]},{"name":"hd.repaint","section":"The full-color layer (hd)","help":[{"sig":"hd.repaint(x, y, w, h, fn, ...)","does":"calls fn(...) with all drawing kept inside the box, then puts the clip back: part of the screen drawn again by the code that draws it. fn paints the box's background too (the old pixels are there). Repaints nest (the inner box is cut to the outer); fn isn't called for an empty box"}]},{"name":"hd.ring","section":"The full-color layer (hd)","help":[{"sig":"hd.ring(x, y, r, rgb, [width, a])","does":"a filled circle / a ring width px wide inside radius r"}]},{"name":"hd.scanlines","section":"The full-color layer (hd)","help":[{"sig":"hd.scanlines(on)","does":"the scanlines on the layer (default on) / whether they're on"},{"sig":"hd.scanlines()","does":"the scanlines on the layer (default on) / whether they're on"}]},{"name":"hd.scroll","section":"The full-color layer (hd)","help":[{"sig":"hd.scroll(x, y, w, h, dy)","does":"moves what's in the box by dy rows (down when positive); the strip it uncovers keeps stale pixels, for the cart to draw again. The whole width (x 0, w the canvas's) moves nothing (the rows are re-mapped): scrolling a list costs only its new strip. A narrower box copies its pixels. ui.scroller does all this"}]},{"name":"hd.text","section":"The full-color layer (hd)","help":[{"sig":"hd.text(text, x, y, rgb, [size, font, align, a])","does":"text from (x, y) = the top of its line; returns its width. font: \"mono\" (JetBrains Mono, 12/14/16/20/24 px), \"bold\" (16/18/24/32) or \"vt\" (VT323, big terminal digits: 30/46/72, ASCII only); size snaps to the nearest the font has (16). align: \"left\", \"center\" or \"right\" of x. Latin-1, punctuation, arrows, box drawing, ★ ♥ ✓ and friends; anything else shows as a small box"}]},{"name":"hd.wrap","section":"The full-color layer (hd)","help":[{"sig":"hd.wrap(text, width, [size, font])","does":"a table of lines that fit width (at spaces; \\n breaks; a word too long is broken)"}]},{"name":"hide","section":"3D","help":[{"sig":"hide(o, [hidden])","does":"hide it (hide(o, false) shows it again)"}]},{"name":"light","section":"3D","help":[{"sig":"light(azimuth, elevation, [intensity, ambient])","does":"the sun's direction in degrees, its strength 0-255 (255), and the shadows' color (0x22283E)"}]},{"name":"line","section":"Drawing","help":[{"sig":"line(x0, y0, x1, y1, [c])","does":"a line"}]},{"name":"load","section":"Time, numbers, saving","help":[{"sig":"load()","does":"keep a small table (numbers, strings, booleans, nested tables; at most 2 KB) across runs / get it back (nil the first time). Whole numbers come back exactly (timestamps included)"}]},{"name":"log","section":"Feedback","help":[{"sig":"log(...)","does":"a line in the log: each value as tostring gives it, spaces between. The playground shows it in its Console, the board in its serial log, check.py in its result's log. At most 20 lines a frame and 200 characters a line"}]},{"name":"max","section":"Time, numbers, saving","help":[{"sig":"max(a, b)","does":"the usual"}]},{"name":"mid","section":"Time, numbers, saving","help":[{"sig":"mid(a, b, c)","does":"the usual"}]},{"name":"min","section":"Time, numbers, saving","help":[{"sig":"min(a, b)","does":"the usual"}]},{"name":"music","section":"Feedback","help":[{"sig":"music(n)","does":"your song from pattern n on / stop it (below)"},{"sig":"music(-1)","does":"your song from pattern n on / stop it (below)"}]},{"name":"now","section":"Time, numbers, saving","help":[{"sig":"now()","does":"the wall clock: seconds since 1970 (UTC), a whole number, or nil while the board doesn't know the date. It knows it from its RTC (kept through reboots and power-off while the battery lasts), which push.py sets; use it for anything that lives while the cart is closed (save(now()), then now() - saved on the next run)"}]},{"name":"obj","section":"3D","help":[{"sig":"obj(shape, color, [shine, unlit])","does":"an object: the shape placed in the scene, in color (0xRRGGBB); shine 0-255 adds a highlight; unlit shows the color as it is. Returns its number"}]},{"name":"orient","section":"3D","help":[{"sig":"orient(o, m1, ..., m9)","does":"turn it by a rotation matrix (rows), for carts that keep their own (physics)"},{"sig":"orient(o, {m1, ..., m9})","does":"turn it by a rotation matrix (rows), for carts that keep their own (physics)"}]},{"name":"paint","section":"3D","help":[{"sig":"paint(o, color, [shine, unlit])","does":"recolor it"}]},{"name":"pal","section":"Drawing","help":[{"sig":"pal(c0, c1)","does":"draw color c0 as c1 from now on / reset"},{"sig":"pal()","does":"draw color c0 as c1 from now on / reset"}]},{"name":"palette","section":"Drawing","help":[{"sig":"palette(i, 0xRRGGBB)","does":"change what color index i (0-15) looks like, for the whole picture (this frame included) / its color now / the starting 16 again. A cart still has 16 colors at a time; this picks which 16 (an exact red, softer pastels). pal remaps indices, palette the colors behind them"},{"sig":"palette(i)","does":"change what color index i (0-15) looks like, for the whole picture (this frame included) / its color now / the starting 16 again. A cart still has 16 colors at a time; this picks which 16 (an exact red, softer pastels). pal remaps indices, palette the colors behind them"},{"sig":"palette()","does":"change what color index i (0-15) looks like, for the whole picture (this frame included) / its color now / the starting 16 again. A cart still has 16 colors at a time; this picks which 16 (an exact red, softer pastels). pal remaps indices, palette the colors behind them"}]},{"name":"pget","section":"Drawing","help":[{"sig":"pget(x, y)","does":"set / read one pixel (pget returns the color index)"}]},{"name":"place","section":"3D","help":[{"sig":"place(o, x, y, z, [rx, ry, rz])","does":"move object o there, turned by rx, ry, rz degrees (about x, then y, then z)"}]},{"name":"print","section":"Drawing","help":[{"sig":"print(text, x, y, [c])","does":"text in the 4x6 font (3x5 glyphs, 4 px a character, so 32 a line); returns the x after the text. Letters show as capitals; \\n starts a new line at x; whole numbers print without .0"}]},{"name":"project","section":"3D","help":[{"sig":"project(x, y, z)","does":"where a point in the scene lands on the picture: x, y, depth (picture pixels, for labels), or nil behind the camera"}]},{"name":"pset","section":"Drawing","help":[{"sig":"pset(x, y, [c])","does":"set / read one pixel (pget returns the color index)"}]},{"name":"rect","section":"Drawing","help":[{"sig":"rect(x0, y0, x1, y1, [c])","does":"a box outline / filled box (corners inclusive)"}]},{"name":"rectfill","section":"Drawing","help":[{"sig":"rectfill(x0, y0, x1, y1, [c])","does":"a box outline / filled box (corners inclusive)"}]},{"name":"res","section":"3D","help":[{"sig":"res(mode)","does":"the scene drawn at \"full\" size (384 x 384, the default), \"half\" (192 x 192, each pixel shown 2x2: a quarter of the drawing, chunkier), or \"auto\" (the board draws at half while frames run over and goes back to full when there's room) / the size it's drawn at now"},{"sig":"res()","does":"the scene drawn at \"full\" size (384 x 384, the default), \"half\" (192 x 192, each pixel shown 2x2: a quarter of the drawing, chunkier), or \"auto\" (the board draws at half while frames run over and goes back to full when there's room) / the size it's drawn at now"}]},{"name":"rnd","section":"Time, numbers, saving","help":[{"sig":"rnd([x])","does":"a random number in [0, x) (x defaults to 1); rnd(t) with a table picks an element"}]},{"name":"save","section":"Time, numbers, saving","help":[{"sig":"save(t)","does":"keep a small table (numbers, strings, booleans, nested tables; at most 2 KB) across runs / get it back (nil the first time). Whole numbers come back exactly (timestamps included)"}]},{"name":"sfx","section":"Feedback","help":[{"sig":"sfx(n)","does":"a built-in sound: 0 blip, 1 coin, 2 hit, 3 jump, 4 explosion, 5 select, 6 error, 7 success; 8-63 your own (below); sfx(-1) stops yours"}]},{"name":"sgn","section":"Time, numbers, saving","help":[{"sig":"sgn(x)","does":"the usual"}]},{"name":"shadow","section":"3D","help":[{"sig":"shadow(o, [on])","does":"object o casts a shadow along the sun onto the ground (shadow(o, false) stops it). Costs its faces again"}]},{"name":"shape","section":"3D","help":[{"sig":"shape(\"box\", w, [h, d])","does":"a box (h, d default to w)"},{"sig":"shape(\"sphere\", r, [segs])","does":"a sphere (segs around, default 16)"},{"sig":"shape(\"torus\", R, r, [segs])","does":"a ring around the z axis (facing the camera at the start)"},{"sig":"shape(\"cylinder\", r, h, [segs])","does":"along y (the cone's point up)"},{"sig":"shape(\"cone\", r, h, [segs])","does":"along y (the cone's point up)"},{"sig":"shape(verts, faces, [smooth, uvs])","does":"your own: verts a flat list {x, y, z, x, y, z, ...}, faces a flat list of vertex numbers (from 1) in threes, each a triangle. Faces are turned outward from the shape's center, so wind them any way (meant for convex shapes: dice, gems). Flat-shaded unless smooth. uvs: texture coordinates {u, v, u, v, ...}, a pair a vertex, 0 to 1 across a texture (it repeats past them)"}]},{"name":"skin","section":"3D","help":[{"sig":"skin(o, tex, [ru, rv])","does":"object o in texture tex, repeated ru x rv times across its shape (default 1; rv defaults to ru), lit as its shine and unlit say (its color is set aside; skin(o) brings it back). A box's faces each show the whole texture, a sphere's, cylinder's and cone's wrap around their side (caps across), a torus's around and through its tube"}]},{"name":"sky","section":"3D","help":[{"sig":"sky(top, [bottom])","does":"the background, a gradient from top to bottom (0xRRGGBB each)"}]},{"name":"spr","section":"Drawing","help":[{"sig":"spr(n, x, y, [flip_x], [flip_y])","does":"sprite n from SPRITES (below), color 0 transparent"}]},{"name":"srand","section":"Time, numbers, saving","help":[{"sig":"srand(seed)","does":"seed rnd"}]},{"name":"stat","section":"Time, numbers, saving","help":[{"sig":"stat(n)","does":"stat(0) memory in KB, stat(1) the last frame's Lua time as a fraction of a frame, stat(7) frames a second"}]},{"name":"texture","section":"3D","help":[{"sig":"texture(w, h, texels)","does":"a texture: w x h texels (4, 8, 16, 32 or 64 each way), texels a string of color numbers as SPRITES are (hex digits row by row; spaces and line breaks ignored), in the palette as it is when made (palette() first for other colors). Returns its number (up to 16)"}]},{"name":"tilt","section":"Input","help":[{"sig":"tilt()","does":"ax, ay, each about -1..1: which way things would roll. ax > 0 when the right edge is lower, ay > 0 when the bottom edge is lower (screen directions: roll a ball by ax, ay and it goes downhill). Measured from how the board was held when the cart started (lying on a desk at a lean counts as level); level() takes the pose it's in now as level"}]},{"name":"time","section":"Time, numbers, saving","help":[{"sig":"time()","does":"seconds since the cart started (it keeps counting while the screen is dark)"}]},{"name":"touch","section":"Input","help":[{"sig":"touch()","does":"x, y in screen pixels while the cart window is touched, else nil / the same in canvas units (3x finer: for the full-color layer)"},{"sig":"touch(true)","does":"x, y in screen pixels while the cart window is touched, else nil / the same in canvas units (3x finer: for the full-color layer)"}]},{"name":"trifill","section":"Drawing","help":[{"sig":"trifill(x0, y0, x1, y1, x2, y2, [c])","does":"a filled triangle. Fractions count (to 1/16 px): triangles sharing an edge meet with no gap and no overlap, so meshes tile cleanly. For solid 3D: drop faces turned away, draw far to near"}]},{"name":"ui.changed","section":"The full-color layer (hd)","help":[{"sig":"ui.changed(key, ...)","does":"true the first time, and whenever the values differ from those given for key last time (one by one, with ==): ask each frame what a part of the screen shows, and repaint it when the answer is true: if ui.changed(\"score\", score, lives) then hd.repaint(0, 40, W, 60, draw_score) end. Ask about a part every frame it's on screen, also on a frame the whole screen is drawn anyway, so what's remembered stays what's shown"}]},{"name":"ui.scroller","section":"The full-color layer (hd)","help":[{"sig":"ui.scroller(x, y, w, h, draw)","does":"a box whose content (a list, a page) scrolls: draw(top, y0, y1) draws the content with its first row at screen row top, and only screen rows y0 to y1 are being painted (skip what's outside them). Returns s:"}]},{"name":"view","section":"3D","help":[{"sig":"view(x, y, z, [yaw, pitch, fov])","does":"the camera: at x, y, z, looking along +z (yaw 90 looks along +x, pitch 90 up); fov in degrees (60). Default view(0, 0, -420)"}]},{"name":"wake","section":"Feedback","help":[{"sig":"wake(secs)","does":"light the screen in secs seconds even if it went dark (a timer that's up) / cancel. The cart's frames resume then"},{"sig":"wake()","does":"light the screen in secs seconds even if it went dark (a timer that's up) / cancel. The cart's frames resume then"}]}]
1
+ [{"name":"abs","section":"Time, numbers, saving","help":[{"sig":"abs(x)","does":"the usual"}]},{"name":"act","section":"The host","help":[{"sig":"act(name, [arg])","does":"a command to the console: \"open\" opens the cart named arg; \"volume\" and \"brightness\" set that setting to the step arg (\"0\" to \"3\"). The launcher uses all three"}]},{"name":"btn","section":"Input","help":[{"sig":"btn(b)","does":"true while button b is held"}]},{"name":"btnp","section":"Input","help":[{"sig":"btnp(b)","does":"true on the frame b went down"}]},{"name":"buzz","section":"Feedback","help":[{"sig":"buzz(ms)","does":"the vibration motor, 10-500 ms (this board has none: caps().buzz is false)"}]},{"name":"camera","section":"Drawing","help":[{"sig":"camera(x, y)","does":"offset everything drawn by (-x, -y) / reset"},{"sig":"camera()","does":"offset everything drawn by (-x, -y) / reset"}]},{"name":"caps","section":"The host","help":[{"sig":"caps()","does":"what the cart runs on, as a table: the canvas size, where the picture sits, the board's name, what it has (touch, tilt, sound...). The fields are under \"Versions and what a cart runs on\""}]},{"name":"ceil","section":"Time, numbers, saving","help":[{"sig":"ceil(x)","does":"the usual"}]},{"name":"circ","section":"Drawing","help":[{"sig":"circ(x, y, r, [c])","does":"a circle outline / filled circle"}]},{"name":"circfill","section":"Drawing","help":[{"sig":"circfill(x, y, r, [c])","does":"a circle outline / filled circle"}]},{"name":"clip","section":"Drawing","help":[{"sig":"clip(x, y, w, h)","does":"draw only inside a rectangle / reset"},{"sig":"clip()","does":"draw only inside a rectangle / reset"}]},{"name":"cls","section":"Drawing","help":[{"sig":"cls([c])","does":"clear the screen (default 0)"}]},{"name":"data","section":"The host","help":[{"sig":"data(name)","does":"the latest value of a channel as a table, or nil: \"battery\" (mv, percent, charging, usb), \"carts\" (the carts on the board: a list of name, title, about), \"settings\" (volume and brightness, each a step from 0 to 3)"}]},{"name":"date","section":"Time, numbers, saving","help":[{"sig":"date([t])","does":"local time (the host's timezone, kept for offline use) as a table: year, month, day, hour, min, sec, wday (1 = Sunday), yday; for now() when t is left out. nil while the date is unknown"}]},{"name":"flr","section":"Time, numbers, saving","help":[{"sig":"flr(x)","does":"the usual"}]},{"name":"fps","section":"Time, numbers, saving","help":[{"sig":"fps(rate)","does":"the frame rate from the next frame (any rate that divides 60: 1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60) / lazy: active while there's input (a touch on the cart, a button) and half a second after, idle otherwise / the rate now. An idle cart wakes at once when a touch comes down or BOOT is pressed, so a slow idle rate costs no response time: give idle the slowest that still shows what moves on its own (a blink twice a second needs 4; a seconds counter 1 or 2). A cart doing a few frames a second instead of 30 or 60 spends that much less of the board's battery on its frames. Move things by time(), not by frames, when the rate changes"},{"sig":"fps(active, idle)","does":"the frame rate from the next frame (any rate that divides 60: 1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60) / lazy: active while there's input (a touch on the cart, a button) and half a second after, idle otherwise / the rate now. An idle cart wakes at once when a touch comes down or BOOT is pressed, so a slow idle rate costs no response time: give idle the slowest that still shows what moves on its own (a blink twice a second needs 4; a seconds counter 1 or 2). A cart doing a few frames a second instead of 30 or 60 spends that much less of the board's battery on its frames. Move things by time(), not by frames, when the rate changes"},{"sig":"fps()","does":"the frame rate from the next frame (any rate that divides 60: 1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60) / lazy: active while there's input (a touch on the cart, a button) and half a second after, idle otherwise / the rate now. An idle cart wakes at once when a touch comes down or BOOT is pressed, so a slow idle rate costs no response time: give idle the slowest that still shows what moves on its own (a blink twice a second needs 4; a seconds counter 1 or 2). A cart doing a few frames a second instead of 30 or 60 spends that much less of the board's battery on its frames. Move things by time(), not by frames, when the rate changes"}]},{"name":"ground","section":"3D","help":[{"sig":"ground(y, [color])","does":"shadows fall on the plane at height y, in color (the ground's color in shadow, 0xRRGGBB; default black)"}]},{"name":"hd.circle","section":"The full-color layer (hd)","help":[{"sig":"hd.circle(x, y, r, rgb, [a])","does":"a filled circle / a ring width px wide inside radius r"}]},{"name":"hd.clear","section":"The full-color layer (hd)","help":[{"sig":"hd.clear([rgb, a])","does":"wipe the canvas (inside the clip) to clear, or to a color at alpha a (255)"}]},{"name":"hd.clip","section":"The full-color layer (hd)","help":[{"sig":"hd.clip(x, y, w, h)","does":"draw only inside a box / anywhere (inside an hd.repaint: inside the box cut to the repaint's / the repaint's box)"},{"sig":"hd.clip()","does":"draw only inside a box / anywhere (inside an hd.repaint: inside the box cut to the repaint's / the repaint's box)"}]},{"name":"hd.frame","section":"The full-color layer (hd)","help":[{"sig":"hd.frame(x, y, w, h, rgb, [width, radius, a])","does":"a box's outline, width px (1)"}]},{"name":"hd.gradient","section":"The full-color layer (hd)","help":[{"sig":"hd.gradient(x, y, w, h, top, bottom, [a])","does":"a box shading from top to bottom, ordered-dithered (RGB565 has 32 steps of red and blue: a slow dark gradient would band)"}]},{"name":"hd.image","section":"The full-color layer (hd)","help":[{"sig":"hd.image(name, x, y, [w, h, a])","does":"draws an image (below) with its top left at x, y (to the pixel), at its own size or scaled to w x h (nearest pixel), at alpha a over its own; returns true, or false when there's no such image (yet)"}]},{"name":"hd.images","section":"The full-color layer (hd)","help":[{"sig":"hd.images()","does":"the names of the cart's own images"}]},{"name":"hd.inset","section":"The full-color layer (hd)","help":[{"sig":"hd.inset(y, [h])","does":"how far in from each side rows y to y + h show on this glass (its corners are rounded): keep a bar or a line of text that far in. 0 for the window, and for rows clear of the corners"}]},{"name":"hd.line","section":"The full-color layer (hd)","help":[{"sig":"hd.line(x0, y0, x1, y1, rgb, [width, a])","does":"a line width px wide (1), round ends"}]},{"name":"hd.measure","section":"The full-color layer (hd)","help":[{"sig":"hd.measure(text, [size, font])","does":"its width and line height"}]},{"name":"hd.order","section":"The full-color layer (hd)","help":[{"sig":"hd.order(\"over\" | \"under\")","does":"the layer over the picture (default), or under it: then the picture's color 0 shows the layer through, as over a 3D scene. Under a 3D scene's picture too: scene, layer, picture, in that order when \"under\""}]},{"name":"hd.rect","section":"The full-color layer (hd)","help":[{"sig":"hd.rect(x, y, w, h, rgb, [a, radius])","does":"a filled box, its corners rounded by radius"}]},{"name":"hd.repaint","section":"The full-color layer (hd)","help":[{"sig":"hd.repaint(x, y, w, h, fn, ...)","does":"calls fn(...) with all drawing kept inside the box, then puts the clip back: part of the screen drawn again by the code that draws it. fn paints the box's background too (the old pixels are there). Repaints nest (the inner box is cut to the outer); fn isn't called for an empty box"}]},{"name":"hd.ring","section":"The full-color layer (hd)","help":[{"sig":"hd.ring(x, y, r, rgb, [width, a])","does":"a filled circle / a ring width px wide inside radius r"}]},{"name":"hd.scanlines","section":"The full-color layer (hd)","help":[{"sig":"hd.scanlines(on)","does":"the scanlines on the layer (default on) / whether they're on"},{"sig":"hd.scanlines()","does":"the scanlines on the layer (default on) / whether they're on"}]},{"name":"hd.scroll","section":"The full-color layer (hd)","help":[{"sig":"hd.scroll(x, y, w, h, dy)","does":"moves what's in the box by dy rows (down when positive); the strip it uncovers keeps stale pixels, for the cart to draw again. The whole width (x 0, w the canvas's) moves nothing (the rows are re-mapped): scrolling a list costs only its new strip. A narrower box copies its pixels. ui.scroller does all this"}]},{"name":"hd.text","section":"The full-color layer (hd)","help":[{"sig":"hd.text(text, x, y, rgb, [size, font, align, a])","does":"text from (x, y) = the top of its line; returns its width. font: \"mono\" (JetBrains Mono, 12/14/16/20/24 px), \"bold\" (16/18/24/32) or \"vt\" (VT323, big terminal digits: 30/46/72, ASCII only); size snaps to the nearest the font has (16). align: \"left\", \"center\" or \"right\" of x. Latin-1, punctuation, arrows, box drawing, ★ ♥ ✓ and friends; anything else shows as a small box"}]},{"name":"hd.wrap","section":"The full-color layer (hd)","help":[{"sig":"hd.wrap(text, width, [size, font])","does":"a table of lines that fit width (at spaces; \\n breaks; a word too long is broken)"}]},{"name":"hide","section":"3D","help":[{"sig":"hide(o, [hidden])","does":"hide it (hide(o, false) shows it again)"}]},{"name":"light","section":"3D","help":[{"sig":"light(azimuth, elevation, [intensity, ambient])","does":"the sun's direction in degrees, its strength 0-255 (255), and the shadows' color (0x22283E)"}]},{"name":"line","section":"Drawing","help":[{"sig":"line(x0, y0, x1, y1, [c])","does":"a line"}]},{"name":"load","section":"Time, numbers, saving","help":[{"sig":"load()","does":"keep a small table (numbers, strings, booleans, nested tables; at most 2 KB) across runs / get it back (nil the first time). Whole numbers come back exactly (timestamps included)"}]},{"name":"log","section":"Feedback","help":[{"sig":"log(...)","does":"a line in the log: each value as tostring gives it, spaces between. The playground shows it in its Console, the board in its serial log, check.py in its result's log. At most 20 lines a frame and 200 characters a line"}]},{"name":"map","section":"Drawing","help":[{"sig":"map([cx, cy, sx, sy, cw, ch])","does":"the map (MAP, below): cw x ch cells from cell (cx, cy), drawn as sprites with that cell's top left at (sx, sy). With nothing, the whole map at (0, 0). Color 0 is transparent; the camera moves it, and only the cells on screen cost anything"}]},{"name":"max","section":"Time, numbers, saving","help":[{"sig":"max(a, b)","does":"the usual"}]},{"name":"mget","section":"Drawing","help":[{"sig":"mget(cx, cy)","does":"the sprite in a map cell, or nil for an empty cell or one off the map"}]},{"name":"mid","section":"Time, numbers, saving","help":[{"sig":"mid(a, b, c)","does":"the usual"}]},{"name":"min","section":"Time, numbers, saving","help":[{"sig":"min(a, b)","does":"the usual"}]},{"name":"mset","section":"Drawing","help":[{"sig":"mset(cx, cy, n)","does":"put sprite n (0-63) in a cell / empty it. It changes the map for this run, not MAP's text"},{"sig":"mset(cx, cy)","does":"put sprite n (0-63) in a cell / empty it. It changes the map for this run, not MAP's text"}]},{"name":"msize","section":"Drawing","help":[{"sig":"msize()","does":"the map's width and height in cells"}]},{"name":"music","section":"Feedback","help":[{"sig":"music(n)","does":"your song from pattern n on / stop it (below)"},{"sig":"music(-1)","does":"your song from pattern n on / stop it (below)"}]},{"name":"now","section":"Time, numbers, saving","help":[{"sig":"now()","does":"the wall clock: seconds since 1970 (UTC), a whole number, or nil while the board doesn't know the date. It knows it from its RTC (kept through reboots and power-off while the battery lasts), which push.py sets; use it for anything that lives while the cart is closed (save(now()), then now() - saved on the next run)"}]},{"name":"obj","section":"3D","help":[{"sig":"obj(shape, color, [shine, unlit])","does":"an object: the shape placed in the scene, in color (0xRRGGBB); shine 0-255 adds a highlight; unlit shows the color as it is. Returns its number"}]},{"name":"orient","section":"3D","help":[{"sig":"orient(o, m1, ..., m9)","does":"turn it by a rotation matrix (rows), for carts that keep their own (physics)"},{"sig":"orient(o, {m1, ..., m9})","does":"turn it by a rotation matrix (rows), for carts that keep their own (physics)"}]},{"name":"paint","section":"3D","help":[{"sig":"paint(o, color, [shine, unlit])","does":"recolor it"}]},{"name":"pal","section":"Drawing","help":[{"sig":"pal(c0, c1)","does":"draw color c0 as c1 from now on / reset"},{"sig":"pal()","does":"draw color c0 as c1 from now on / reset"}]},{"name":"palette","section":"Drawing","help":[{"sig":"palette(i, 0xRRGGBB)","does":"change what color index i (0-15) looks like, for the whole picture (this frame included) / its color now / the starting 16 again. A cart still has 16 colors at a time; this picks which 16 (an exact red, softer pastels). pal remaps indices, palette the colors behind them"},{"sig":"palette(i)","does":"change what color index i (0-15) looks like, for the whole picture (this frame included) / its color now / the starting 16 again. A cart still has 16 colors at a time; this picks which 16 (an exact red, softer pastels). pal remaps indices, palette the colors behind them"},{"sig":"palette()","does":"change what color index i (0-15) looks like, for the whole picture (this frame included) / its color now / the starting 16 again. A cart still has 16 colors at a time; this picks which 16 (an exact red, softer pastels). pal remaps indices, palette the colors behind them"}]},{"name":"pget","section":"Drawing","help":[{"sig":"pget(x, y)","does":"set / read one pixel (pget returns the color index)"}]},{"name":"place","section":"3D","help":[{"sig":"place(o, x, y, z, [rx, ry, rz])","does":"move object o there, turned by rx, ry, rz degrees (about x, then y, then z)"}]},{"name":"print","section":"Drawing","help":[{"sig":"print(text, x, y, [c])","does":"text in the 4x6 font (3x5 glyphs, 4 px a character, so 32 a line); returns the x after the text. Letters show as capitals; \\n starts a new line at x; whole numbers print without .0"}]},{"name":"project","section":"3D","help":[{"sig":"project(x, y, z)","does":"where a point in the scene lands on the picture: x, y, depth (picture pixels, for labels), or nil behind the camera"}]},{"name":"pset","section":"Drawing","help":[{"sig":"pset(x, y, [c])","does":"set / read one pixel (pget returns the color index)"}]},{"name":"rect","section":"Drawing","help":[{"sig":"rect(x0, y0, x1, y1, [c])","does":"a box outline / filled box (corners inclusive)"}]},{"name":"rectfill","section":"Drawing","help":[{"sig":"rectfill(x0, y0, x1, y1, [c])","does":"a box outline / filled box (corners inclusive)"}]},{"name":"res","section":"3D","help":[{"sig":"res(mode)","does":"the scene drawn at \"full\" size (384 x 384, the default), \"half\" (192 x 192, each pixel shown 2x2: a quarter of the drawing, chunkier), or \"auto\" (the board draws at half while frames run over and goes back to full when there's room) / the size it's drawn at now"},{"sig":"res()","does":"the scene drawn at \"full\" size (384 x 384, the default), \"half\" (192 x 192, each pixel shown 2x2: a quarter of the drawing, chunkier), or \"auto\" (the board draws at half while frames run over and goes back to full when there's room) / the size it's drawn at now"}]},{"name":"rnd","section":"Time, numbers, saving","help":[{"sig":"rnd([x])","does":"a random number in [0, x) (x defaults to 1); rnd(t) with a table picks an element"}]},{"name":"save","section":"Time, numbers, saving","help":[{"sig":"save(t)","does":"keep a small table (numbers, strings, booleans, nested tables; at most 2 KB) across runs / get it back (nil the first time). Whole numbers come back exactly (timestamps included)"}]},{"name":"sfx","section":"Feedback","help":[{"sig":"sfx(n)","does":"a built-in sound: 0 blip, 1 coin, 2 hit, 3 jump, 4 explosion, 5 select, 6 error, 7 success; 8-63 your own (below); sfx(-1) stops yours"}]},{"name":"sgn","section":"Time, numbers, saving","help":[{"sig":"sgn(x)","does":"the usual"}]},{"name":"shadow","section":"3D","help":[{"sig":"shadow(o, [on])","does":"object o casts a shadow along the sun onto the ground (shadow(o, false) stops it). Costs its faces again"}]},{"name":"shape","section":"3D","help":[{"sig":"shape(\"box\", w, [h, d])","does":"a box (h, d default to w)"},{"sig":"shape(\"sphere\", r, [segs])","does":"a sphere (segs around, default 16)"},{"sig":"shape(\"torus\", R, r, [segs])","does":"a ring around the z axis (facing the camera at the start)"},{"sig":"shape(\"cylinder\", r, h, [segs])","does":"along y (the cone's point up)"},{"sig":"shape(\"cone\", r, h, [segs])","does":"along y (the cone's point up)"},{"sig":"shape(verts, faces, [smooth, uvs])","does":"your own: verts a flat list {x, y, z, x, y, z, ...}, faces a flat list of vertex numbers (from 1) in threes, each a triangle. Faces are turned outward from the shape's center, so wind them any way (meant for convex shapes: dice, gems). Flat-shaded unless smooth. uvs: texture coordinates {u, v, u, v, ...}, a pair a vertex, 0 to 1 across a texture (it repeats past them)"}]},{"name":"skin","section":"3D","help":[{"sig":"skin(o, tex, [ru, rv])","does":"object o in texture tex, repeated ru x rv times across its shape (default 1; rv defaults to ru), lit as its shine and unlit say (its color is set aside; skin(o) brings it back). A box's faces each show the whole texture, a sphere's, cylinder's and cone's wrap around their side (caps across), a torus's around and through its tube"}]},{"name":"sky","section":"3D","help":[{"sig":"sky(top, [bottom])","does":"the background, a gradient from top to bottom (0xRRGGBB each)"}]},{"name":"spr","section":"Drawing","help":[{"sig":"spr(n, x, y, [flip_x], [flip_y])","does":"sprite n from SPRITES (below), color 0 transparent"}]},{"name":"srand","section":"Time, numbers, saving","help":[{"sig":"srand(seed)","does":"seed rnd"}]},{"name":"stat","section":"Time, numbers, saving","help":[{"sig":"stat(n)","does":"stat(0) memory in KB, stat(1) the last frame's Lua time as a fraction of a frame, stat(7) frames a second"}]},{"name":"texture","section":"3D","help":[{"sig":"texture(w, h, texels)","does":"a texture: w x h texels (4, 8, 16, 32 or 64 each way), texels a string of color numbers as SPRITES are (hex digits row by row; spaces and line breaks ignored), in the palette as it is when made (palette() first for other colors). Returns its number (up to 16)"}]},{"name":"tilt","section":"Input","help":[{"sig":"tilt()","does":"ax, ay, each about -1..1: which way things would roll. ax > 0 when the right edge is lower, ay > 0 when the bottom edge is lower (screen directions: roll a ball by ax, ay and it goes downhill). Measured from how the board was held when the cart started (lying on a desk at a lean counts as level); level() takes the pose it's in now as level"}]},{"name":"time","section":"Time, numbers, saving","help":[{"sig":"time()","does":"seconds since the cart started (it keeps counting while the screen is dark)"}]},{"name":"touch","section":"Input","help":[{"sig":"touch()","does":"x, y in screen pixels while the cart window is touched, else nil / the same in canvas units (3x finer: for the full-color layer)"},{"sig":"touch(true)","does":"x, y in screen pixels while the cart window is touched, else nil / the same in canvas units (3x finer: for the full-color layer)"}]},{"name":"trifill","section":"Drawing","help":[{"sig":"trifill(x0, y0, x1, y1, x2, y2, [c])","does":"a filled triangle. Fractions count (to 1/16 px): triangles sharing an edge meet with no gap and no overlap, so meshes tile cleanly. For solid 3D: drop faces turned away, draw far to near"}]},{"name":"ui.changed","section":"The full-color layer (hd)","help":[{"sig":"ui.changed(key, ...)","does":"true the first time, and whenever the values differ from those given for key last time (one by one, with ==): ask each frame what a part of the screen shows, and repaint it when the answer is true: if ui.changed(\"score\", score, lives) then hd.repaint(0, 40, W, 60, draw_score) end. Ask about a part every frame it's on screen, also on a frame the whole screen is drawn anyway, so what's remembered stays what's shown"}]},{"name":"ui.scroller","section":"The full-color layer (hd)","help":[{"sig":"ui.scroller(x, y, w, h, draw)","does":"a box whose content (a list, a page) scrolls: draw(top, y0, y1) draws the content with its first row at screen row top, and only screen rows y0 to y1 are being painted (skip what's outside them). Returns s:"}]},{"name":"view","section":"3D","help":[{"sig":"view(x, y, z, [yaw, pitch, fov])","does":"the camera: at x, y, z, looking along +z (yaw 90 looks along +x, pitch 90 up); fov in degrees (60). Default view(0, 0, -420)"}]},{"name":"wake","section":"Feedback","help":[{"sig":"wake(secs)","does":"light the screen in secs seconds even if it went dark (a timer that's up) / cancel. The cart's frames resume then"},{"sig":"wake()","does":"light the screen in secs seconds even if it went dark (a timer that's up) / cancel. The cart's frames resume then"}]}]
Binary file
package/luanette.mjs CHANGED
@@ -7,7 +7,7 @@
7
7
  // luanette sound "C4 pulse 5, E4 slide, -, G4 saw 7 fadeout" [--speed N] [--loop A-B]
8
8
  // luanette sound CART.lua N --wav OUT.wav a cart's sound, as a WAV
9
9
  // luanette song CART.lua [--from N] --wav OUT.wav its song
10
- // luanette link CART.lua [--name NAME] a playground share link
10
+ // luanette link CART.lua [--name NAME] [--play] a playground share link (--play: opens in play mode)
11
11
  // luanette new NAME [--template blank|platformer|shooter|puzzle]
12
12
  // luanette mcp an MCP server over stdio (for agents)
13
13
  //
@@ -30,7 +30,7 @@ export const PLAYGROUND = "https://geofresh.github.io/luanette/";
30
30
  const Gif = require(path.join(LIB, "gif.js"));
31
31
  const Tables = require(path.join(LIB, "tables.js"));
32
32
 
33
- export const BUDGET = 30000, MAX_SOURCE = 32 * 1024, API_VERSION = 4;
33
+ export const BUDGET = 30000, MAX_SOURCE = 32 * 1024, API_VERSION = 5;
34
34
  const W = 410, H = 502; // the glass
35
35
  const BUTTONS = { left: 1, right: 2, up: 4, down: 8, a: 16, b: 32 };
36
36
 
@@ -219,6 +219,7 @@ export async function check(src, name, opts = {}) {
219
219
  if (api < 2 && /\bui\s*\./.test(code)) warn("it uses the runtime's ui helpers (API 2): add '-- api: 2' to its header, so a board with older firmware says it needs an update instead of failing at its first ui call");
220
220
  else if (api < 2 && /\bhd\s*\./.test(code)) warn("it uses the full-color layer (API 2): add '-- api: 2' to its header, so a board with older firmware says it needs an update instead of failing at its first hd call");
221
221
  if (api < 4 && /^\s*\[?\d*\]?\s*=?\s*"[0-9a-fA-F]+\s+[0-9a-fA-F]/m.test(code.split(/^SPRITES\s*=/m)[1] || "")) warn("its sprites have spaces between rows (API 4): add '-- api: 4' to its header, so a board with older firmware says it needs an update instead of failing to draw them");
222
+ if (api < 5 && /^\s*MAP\s*=/m.test(code)) warn("it has a map (API 5): add '-- api: 5' to its header, so a board with older firmware says it needs an update instead of failing at its first map call");
222
223
  if (api < 4 && run.log.length) warn("it calls log() (API 4): add '-- api: 4' to its header, so a board with older firmware says it needs an update instead of failing at its first log call");
223
224
  if (api < 3 && /\bmusic\s*\(|^\s*(SOUNDS|MUSIC)\s*=/m.test(code)) warn("it has its own sounds or music (API 3): add '-- api: 3' to its header, so a board with older firmware says it needs an update instead of failing at its first sfx or music call");
224
225
  if (!full) warn("it shows in the 384 x 384 window, about 30% of the glass left dark: add '-- canvas: full' (and '-- api: 2') and use the whole glass, laid out from caps(): the picture keeps its window, and the layer has the bands above and below it for a title, the score, what to do (docs/DESIGN.md, 1)");
@@ -375,9 +376,10 @@ export async function renderAudio(src, n, from = null, rate = 44100) {
375
376
  }
376
377
 
377
378
  // ---- links and new carts ---------------------------------------------------------------------------
378
- export function shareLink(src, name, base = PLAYGROUND) {
379
+ // "&play" after it opens the cart in play mode: the screen alone, with buttons on a phone.
380
+ export function shareLink(src, name, base = PLAYGROUND, play = false) {
379
381
  const data = zlib.deflateRawSync(Buffer.from(JSON.stringify({ name, src })), { level: 9 });
380
- return base + "#cart=d" + data.toString("base64url");
382
+ return base + "#cart=d" + data.toString("base64url") + (play ? "&play" : "");
381
383
  }
382
384
  export function newCart(name, template = "blank") {
383
385
  if (!/^[a-z0-9_-]{1,32}$/.test(name)) throw new Error("a cart's name is 1 to 32 of a-z, 0-9, _ and -");
@@ -436,7 +438,7 @@ async function main() {
436
438
  print({ ok: true, wav: opt.wav || "song.wav", seconds: Number(r.seconds.toFixed(2)) });
437
439
  } else if (cmd === "link") {
438
440
  const { src, name } = readCart(pos[0]);
439
- process.stdout.write(shareLink(src, opt.name || name, opt.base || PLAYGROUND) + "\n");
441
+ process.stdout.write(shareLink(src, opt.name || name, opt.base || PLAYGROUND, opt.play === true) + "\n");
440
442
  } else if (cmd === "new") {
441
443
  const name = pos[0], file = opt.out || `${name}.lua`;
442
444
  if (fs.existsSync(file)) throw new Error(`${file} is there already`);
package/mcp.mjs CHANGED
@@ -45,8 +45,8 @@ const TOOLS = [
45
45
  inputSchema: { type: "object", required: ["notes"], properties: { notes: { type: "string" },
46
46
  speed: { type: "integer", description: "Ticks of 1/120 s a note, 1-255 (8)" },
47
47
  loop: { type: "array", items: { type: "integer" }, description: "[first, end]: loop those notes for good" } } } },
48
- { name: "share_link", description: "A link that opens the cart in the playground (https://geofresh.github.io/luanette/), to play it, edit it or send it to a board.",
49
- inputSchema: { type: "object", properties: { ...CART, name: { type: "string" } } } },
48
+ { name: "share_link", description: "A link that opens the cart in the playground (https://geofresh.github.io/luanette/), to play it, edit it or send it to a board. With play, it opens in play mode: the screen alone, with buttons on a phone.",
49
+ inputSchema: { type: "object", properties: { ...CART, name: { type: "string" }, play: { type: "boolean", description: "Open in play mode" } } } },
50
50
  { name: "api_help", description: "The cart API from docs/api.md: a call's signatures and what it does, or every call whose name or description has a word in it, or (with nothing) the list of names.",
51
51
  inputSchema: { type: "object", properties: { name: { type: "string" }, search: { type: "string" } } } },
52
52
  { name: "new_cart", description: "A new cart's source from a template (blank, platformer, shooter, puzzle): full-screen, with sprites, sounds and log() to build on.",
@@ -112,7 +112,7 @@ const handlers = {
112
112
  },
113
113
  async share_link(a) {
114
114
  const { src, name } = cartOf(a);
115
- return { content: [text(L.shareLink(src, a.name || name))] };
115
+ return { content: [text(L.shareLink(src, a.name || name, undefined, a.play === true))] };
116
116
  },
117
117
  async api_help(a) {
118
118
  if (a.name) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "luanette",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Check, run, hear and share carts for Luanette, a Lua game console for ESP32-S3 boards: the console's own engine in Node, nothing to build",
5
5
  "bin": { "luanette": "luanette.mjs" },
6
6
  "files": ["luanette.mjs", "pyrandom.mjs", "mcp.mjs", "engine/", "lib/", "templates/", "api.json", "README.md", "LICENSE"],
@@ -1,13 +1,15 @@
1
1
  -- title: Platformer
2
2
  -- by: you
3
- -- about: jump between platforms and pick up the coins
4
- -- api: 4
3
+ -- about: run and jump across a level three screens wide, and pick up every coin
4
+ -- api: 5
5
5
  -- canvas: full
6
6
 
7
7
  -- sprites (the playground's sprite editor writes this table)
8
8
  SPRITES = {
9
9
  [1] = "00888800 08888880 08f1f180 08ffff80 00888800 08888880 08800880 08800880", -- player
10
10
  [2] = "00aaaa00 0aa99aa0 aa9aa9aa aa9aa9aa aa9aa9aa aa9aa9aa 0aa99aa0 00aaaa00", -- coin
11
+ [3] = "bbbbbbbb b3bbb3bb 33b3b333 43334334 44444444 44544444 44444454 44444444", -- grass
12
+ [4] = "44444444 44544444 44444454 44444444 45444444 44444544 44444444 44454444", -- dirt
11
13
  }
12
14
  -- sounds (the playground's sound editor writes this table)
13
15
  SOUNDS = {
@@ -15,6 +17,25 @@ SOUNDS = {
15
17
  [9] = "03 0000 2f160 34165", -- coin
16
18
  [10] = "06 0000 1f050 18053", -- fall
17
19
  }
20
+ -- the level: a character a cell, "." empty, else a sprite (the playground's map editor writes this)
21
+ LEVEL = {
22
+ "................................................",
23
+ "................................................",
24
+ "................................................",
25
+ "................................................",
26
+ "..........................................2.2...",
27
+ "...........................2.............33333..",
28
+ "..................2.2.....333...................",
29
+ ".................33333..........2...............",
30
+ ".........2..............................2.......",
31
+ "........333..........2.........333.....333......",
32
+ "..............2.....333.............2...........",
33
+ ".............333...........2.......333..........",
34
+ ".....2...................33333..............2...",
35
+ "....333.........................................",
36
+ "333333333333333333333...33333333333333...3333333",
37
+ "444444444444444444444...44444444444444...4444444",
38
+ }
18
39
 
19
40
  -- The whole screen: the 128x128 picture in the middle, and bands above and below it in the
20
41
  -- full-color layer (hd) for the title and what to do. Drawn once; they stay.
@@ -31,24 +52,29 @@ local function bands(title, hint)
31
52
  end
32
53
 
33
54
  local GRAVITY, JUMP, SPEED = 0.25, -3.6, 1.4
34
- local floors = { -- platforms: x, y (their top), w
35
- {x = 0, y = 120, w = 128}, {x = 12, y = 96, w = 36}, {x = 72, y = 80, w = 40},
36
- {x = 24, y = 58, w = 32}, {x = 80, y = 40, w = 36},
37
- }
38
- local me = {x = 8, y = 100, vx = 0, vy = 0, ground = false}
39
- local coins, score, level = {}, 0, 1
55
+ local me, score, left, round, cam = {}, 0, 0, 0, 0
56
+
57
+ -- A wall or floor at pixel (x, y): grass or dirt in that map cell.
58
+ local function solid(x, y)
59
+ local t = mget(flr(x / 8), flr(y / 8))
60
+ return t == 3 or t == 4
61
+ end
40
62
 
41
- local function new_coins()
42
- coins = {}
43
- for i = 2, #floors do
44
- local f = floors[i]
45
- coins[#coins + 1] = {x = f.x + flr(rnd(f.w - 8)), y = f.y - 12}
63
+ local function start()
64
+ MAP = {table.unpack(LEVEL)} -- a fresh copy: picking up coins (mset) changes the map
65
+ local w, h = msize()
66
+ left = 0
67
+ for y = 0, h - 1 do
68
+ for x = 0, w - 1 do
69
+ if mget(x, y) == 2 then left = left + 1 end
70
+ end
46
71
  end
72
+ me = {x = 8, y = 96, vx = 0, vy = 0, ground = false}
47
73
  end
48
74
 
49
75
  function _init()
50
76
  bands("PLATFORMER", "drag to run · tap to jump")
51
- new_coins()
77
+ start()
52
78
  end
53
79
 
54
80
  function _update()
@@ -57,39 +83,45 @@ function _update()
57
83
  me.vy = JUMP
58
84
  sfx(8)
59
85
  end
86
+ -- across, unless a wall is in the way
87
+ local nx = mid(0, me.x + me.vx, msize() * 8 - 8)
88
+ if not (solid(nx + 1, me.y + 1) or solid(nx + 6, me.y + 1) or solid(nx + 1, me.y + 7) or solid(nx + 6, me.y + 7)) then
89
+ me.x = nx
90
+ end
91
+ -- up or down: land on a floor, bump a ceiling
60
92
  me.vy = me.vy + GRAVITY
61
- local was = me.y
62
- me.x = mid(0, me.x + me.vx, 120)
63
- me.y = me.y + me.vy
93
+ local ny = me.y + me.vy
64
94
  me.ground = false
65
- for _, f in ipairs(floors) do -- land on a platform's top on the way down
66
- if me.vy >= 0 and was + 8 <= f.y and me.y + 8 >= f.y and me.x + 6 > f.x and me.x + 2 < f.x + f.w then
67
- me.y, me.vy, me.ground = f.y - 8, 0, true
68
- end
95
+ if me.vy > 0 and (solid(me.x + 1, ny + 8) or solid(me.x + 6, ny + 8)) then
96
+ ny, me.vy, me.ground = flr((ny + 8) / 8) * 8 - 8, 0, true
97
+ elseif me.vy < 0 and (solid(me.x + 1, ny) or solid(me.x + 6, ny)) then
98
+ ny, me.vy = flr(ny / 8) * 8 + 8, 0
69
99
  end
70
- for i = #coins, 1, -1 do
71
- local k = coins[i]
72
- if abs(me.x - k.x) < 7 and abs(me.y - k.y) < 7 then
73
- table.remove(coins, i)
74
- score = score + 1
75
- sfx(9)
76
- end
100
+ me.y = ny
101
+ -- a coin in the cell at the player's middle
102
+ local cx, cy = flr((me.x + 4) / 8), flr((me.y + 4) / 8)
103
+ if mget(cx, cy) == 2 then
104
+ mset(cx, cy)
105
+ score, left = score + 1, left - 1
106
+ sfx(9)
77
107
  end
78
- if #coins == 0 then
79
- level = level + 1
80
- log("level", level, "score", score)
81
- new_coins()
108
+ if left == 0 then
109
+ round = round + 1
110
+ log("round", round, "score", score)
111
+ start()
82
112
  end
83
- if me.y > 128 then -- fell off: back to the start
84
- me.x, me.y, me.vy = 8, 100, 0
113
+ if me.y > 128 then -- fell in a gap: back to the start
114
+ me.x, me.y, me.vy = 8, 96, 0
85
115
  sfx(10)
86
116
  end
117
+ cam = mid(0, me.x - 60, msize() * 8 - 128) -- the camera follows, inside the level
87
118
  end
88
119
 
89
120
  function _draw()
90
121
  cls(12)
91
- for _, f in ipairs(floors) do rectfill(f.x, f.y, f.x + f.w - 1, f.y + 3, 3) end
92
- for _, k in ipairs(coins) do spr(2, k.x, k.y) end
122
+ camera(cam, 0)
123
+ map()
93
124
  spr(1, me.x, me.y, me.vx < 0)
125
+ camera()
94
126
  print("score " .. score, 2, 2, 7)
95
127
  end