Graphical demos for the PicoCalc

Thank you very much for the detailed explanation. I will take a closer look at it during my vacation. For more than 15 years, I have only been working with Windows when there is no other option (with customers and stubborn family members). Otherwise, I use Linux almost exclusively.

I have written ‘Bricks’, a Breakout clone, for zeptoforth on the PicoCalc with the RP2350 (which it requires because it uses hardware single-precision floating point). The controls are simply left and right-arrow to move the player, and q to exit. The game continues ad infinitum until q is pressed ─ if the ball hits the bottom of the screen it and the player are simply reset as if it were a new game (but the bricks stay as-is). At the start of the game and each time the ball and player are reset the user needs to press a key to continue. Note that as it uses key? and key to detect keypresses it relies upon key repeat to function, which can be a bit wonky.

Here is a screenshot of the game right after the ball and player have been reset:

The source code of the game is at zeptoforth/test/rp2350/picocalc_bricks.fs at master · tabemann/zeptoforth · GitHub.

The full source is as follows:

\ Copyright (c) 2026 Travis Bemann
\
\ Permission is hereby granted, free of charge, to any person obtaining a copy
\ of this software and associated documentation files (the "Software"), to deal
\ in the Software without restriction, including without limitation the rights
\ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
\ copies of the Software, and to permit persons to whom the Software is
\ furnished to do so, subject to the following conditions:
\ 
\ The above copyright notice and this permission notice shall be included in
\ all copies or substantial portions of the Software.
\ 
\ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
\ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
\ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
\ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
\ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
\ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
\ SOFTWARE.

begin-module bricks
  
  float32 import
  picocalc-term import
  tinymt32 import
  pixmap8 import
  pixmap8-utils import
  st7365p-8-common import
  
  begin-structure coord-size
    field: coord-x
    field: coord-y
  end-structure
  
  begin-structure delta-size
    field: delta-x
    field: delta-y
  end-structure
  
  begin-structure ball-size
    coord-size +field ball-coord
    delta-size +field ball-delta
    field: ball-radius
    field: ball-color
  end-structure
  
  begin-structure block-size
    coord-size +field block-coord
    field: block-width
    field: block-height
    field: block-color
    field: block-active
  end-structure
  
  begin-structure player-size
    coord-size +field player-coord
    delta-size +field player-delta
    field: player-width
    field: player-height
    field: player-color
  end-structure
  
  160e0 constant blocks-center-x
  80e0 constant blocks-center-y
  20e0 constant default-block-width
  7.5e0 constant default-block-height
  2e0 constant default-ball-radius
  160e0 constant init-ball-x
  160e0 constant init-ball-y
  120e0 constant init-ball-speed
  vpi 2e0 v/ constant init-ball-angle-range
  60e0 constant default-player-width
  7.5e0 constant default-player-height
  160e0 constant init-player-x
  300e0 constant init-player-y
  16 constant horiz-block-count
  8 constant vert-block-count
  horiz-block-count vert-block-count * constant block-count
  
  begin-structure world-size
    ball-size +field world-ball
    block-size block-count * +field world-blocks
    player-size +field world-player
    tinymt32-size +field world-prng
    field: world-last-tick
    field: world-tick
  end-structure
  
  : rnd ( world -- v )
    world-prng tinymt32-generate-uint32 0 f64>v
  ;
  
  : interval ( world -- v )
    dup world-tick @ swap world-last-tick @ - u>v 10000e0 v/
  ;
  
  : convert-angle ( n -- n' ) 255 * 60 / ;
  
  : angle-color { angle -- rgb8 }
    angle 60 < if
      255 angle convert-angle 0
    else
      angle 120 < if
        120 angle - convert-angle 255 0
      else
        angle 180 < if
          0 255 angle 120 - convert-angle
        else
          angle 240 < if
            0 240 angle - convert-angle 255
          else
            angle 300 < if
              angle 240 - convert-angle 0 255
            else
              255 0 360 angle - convert-angle
            then
          then
        then
      then
    then
    rgb8
  ;
  
  : rnd-color ( world -- rgb8 )
    rnd 360e0 v* v>n angle-color
  ;
  
  : init-block { x y world block -- }
    horiz-block-count 1- n>v default-block-width v* 2e0 v/
    blocks-center-x swap v-
    default-block-width x n>v v* v+
    block block-coord coord-x !
    vert-block-count 1- n>v default-block-height v* 2e0 v/
    blocks-center-y swap v-
    default-block-height y n>v v* v+
    block block-coord coord-y !
    default-block-width block block-width !
    default-block-height block block-height !
    world rnd-color block block-color !
    true block block-active !
  ;
  
  : init-blocks { world -- }
    vert-block-count 0 ?do
      horiz-block-count 0 ?do
        i j world j horiz-block-count * i + block-size *
        world world-blocks + init-block
      loop
    loop
  ;
  
  : init-ball { world ball -- }
    init-ball-x ball ball-coord coord-x !
    init-ball-y ball ball-coord coord-y !
    default-ball-radius ball ball-radius !
    vpi 2e0 v/ init-ball-angle-range 2e0 v/ v-
    init-ball-angle-range world rnd v* v+ { angle }
    angle vcos init-ball-speed v* ball ball-delta delta-x !
    angle vsin init-ball-speed v* ball ball-delta delta-y !
    world rnd-color ball ball-color !
  ;
  
  : init-player { world player -- }
    init-player-x player player-coord coord-x !
    init-player-y player player-coord coord-y !
    0e0 player player-delta delta-x !
    0e0 player player-delta delta-y !
    default-player-width player player-width !
    default-player-height player player-height !
    world rnd-color player player-color !
  ;
  
  : init-prng { prng -- }
    rng::random prng tinymt32-init
    prng tinymt32-prepare-example
  ;
  
  : init-world { world -- }
    systick::systick-counter dup world world-tick !
    world world-last-tick ! 
    world world-prng init-prng
    world init-blocks
    world dup world-ball init-ball
    world dup world-player init-player
  ;
  
  0 0 0 rgb8 constant erase-color
  
  : draw-rect { erase? color x y width height display -- }
    width 2e0 v/ { width2/ }
    height 2e0 v/ { height2/ }
    x width2/ v- vround-zero v>n { x0 }
    y height2/ v- vround-zero v>n { y0 }
    erase? if erase-color else color then
    x0 y0
    width 0.5e0 v+ vround-zero v>n
    height 0.5e0 v+ vround-zero v>n
    display draw-rect-const
  ;
  
  : draw-block { erase? display block -- }
    erase? block block-color @
    block block-coord coord-x @
    block block-coord coord-y @
    block block-width @
    block block-height @
    display draw-rect
  ;
  
  : draw-player { erase? display player -- }
    erase? player player-color @
    player player-coord coord-x @
    player player-coord coord-y @
    player player-width @
    player player-height @
    display draw-rect
  ;
  
  : draw-circle { erase? color x y radius display -- }
    erase? if erase-color else color then
    x v>n y v>n radius v>n display draw-filled-circle
  ;
  
  : draw-ball { erase? display ball -- }
    erase? ball ball-color @
    ball ball-coord coord-x @
    ball ball-coord coord-y @
    ball ball-radius @
    display draw-circle
  ;
  
  : draw-world { display world -- }
    display clear-pixmap
    horiz-block-count vert-block-count * 0 ?do
      false display i block-size * world world-blocks +
      draw-block
    loop
    false display world world-player draw-player
    false display world world-ball draw-ball
  ;
  
  : collide-rect { time x y width height ball -- collide? }
    ball ball-coord coord-x @ { bx }
    ball ball-coord coord-y @ { by }
    ball ball-delta delta-x @ { bdx }
    ball ball-delta delta-y @ { bdy }
    ball ball-radius @ { radius }
    bx bdx time v* v+ { bx' }
    by bdy time v* v+ { by' }
    width 2e0 v/ { width2/ }
    height 2e0 v/ { height2/ }
    x width2/ v- radius v- { x0 }
    y height2/ v- radius v- { y0 }
    x width2/ v+ radius v+ { x1 }
    y height2/ v+ radius v+ { y1 }
    bx x0 v< bx' x0 v>= and
    bx x1 v> bx' x1 v<= and or
    bx' x0 v>= bx' x1 v<= and or
    by y0 v< by' y0 v>= and
    by y1 v> by' y1 v<= and or
    by' y0 v>= by' y1 v<= and or and dup if
      bx x0 v<= bx' x0 v>= and if
        bdx vabs vnegate ball ball-delta delta-x !
        x0 ball ball-coord coord-x !
      then
      bx x1 v>= bx' x1 v<= and if
        bdx vabs ball ball-delta delta-x !
        x1 ball ball-coord coord-x !
      then
      by y0 v<= by' y0 v>= and if
        bdy vabs vnegate ball ball-delta delta-y !
        y0 ball ball-coord coord-y !
      then
      by y1 v>= by' y1 v<= and if
        bdy vabs ball ball-delta delta-y !
        y1 ball ball-coord coord-y !
      then
    then
  ;
  
  : collide-block { time display ball block -- }
    block block-active @ if
      time
      block block-coord coord-x @
      block block-coord coord-y @
      block block-width @
      block block-height @
      ball collide-rect if
        false block block-active !
        true display block draw-block
      then
    then
  ;
  
  : collide-blocks { time display ball world -- }
    horiz-block-count vert-block-count * 0 ?do
      time display ball
      i block-size * world world-blocks + collide-block
    loop
  ;
  
  0.5e0 constant collide-player-fract
  
  : collide-player { time ball player -- }
    time
    player player-coord coord-x @
    player player-coord coord-y @
    player player-width @
    player player-height @
    ball collide-rect if
      player player-delta delta-x @ collide-player-fract v*
      ball ball-delta delta-x @ v+
      ball ball-delta delta-x !
    then
  ;
  
  : collide-wall { time ball -- bottom? }
    ball ball-coord coord-x @ { bx }
    ball ball-coord coord-y @ { by }
    ball ball-delta delta-x @ { bdx }
    ball ball-delta delta-y @ { bdy }
    ball ball-radius @ { radius }
    bx bdx time v* v+ { bx' }
    by bdy time v* v+ { by' }
    term-pixels-dim@ n>v swap n>v { height width }
    bx' width radius v- v>= if
      bdx vabs vnegate ball ball-delta delta-x !
    then
    bx' radius v<= if
      bdx vabs ball ball-delta delta-x !
    then
    by' radius v<= if
      bdy vabs ball ball-delta delta-y !
    then
    by' height v>=
  ;
  
  : +delta! { time delta coord -- }
    delta delta-x @ time v* coord coord-x @ v+ coord coord-x !
    delta delta-y @ time v* coord coord-y @ v+ coord coord-y !
  ;
  
  : move-ball { time ball -- }
    time ball ball-delta ball ball-coord +delta!
  ;
  
  0.0625e0 constant friction0
  1e0 64e0 v/ constant friction1
  
  : apply-friction { time player -- }
    friction0 time v** friction1 time v** v* { friction' }
    player player-delta delta-x @ friction' v*
    player player-delta delta-x !
  ;
  
  : move-player { time player -- }
    time player player-delta player player-coord +delta!
    player player-width @ 2e0 v/ { width2/ }
    player player-coord coord-x @ { x }
    term-pixels-dim@ drop n>v { width' }
    x width2/ v<= dup if
      width2/ player player-coord coord-x !
    then
    x width' width2/ v- v>= dup if
      width' width2/ v- player player-coord coord-x !
    then
    or if
      0e0 player player-delta delta-x !
    then
  ;
   
  : update { display world -- died? }
    world interval { time }
    true display world world-ball draw-ball
    true display world world-player draw-player
    time display world world-ball world collide-blocks
    time world world-ball world world-player collide-player
    time world world-ball collide-wall dup if
      world dup world-ball init-ball
      world dup world-player init-player
    else
      time world world-ball move-ball
      time world world-player apply-friction
      time world world-player move-player
    then
    false display world world-ball draw-ball
    false display world world-player draw-player
  ;
  
  240e0 constant accel
  640e0 constant max-speed
  
  : accel-left { time player -- }
    player player-delta delta-x @ accel ( time v* ) v-
    max-speed vnegate vmax
    player player-delta delta-x !
  ;

  : accel-right { time player -- }
    player player-delta delta-x @ accel ( time v* ) v+
    max-speed vmin
    player player-delta delta-x !
  ;
  
  : reset-tick { world }
    systick::systick-counter dup world world-tick !
    world world-last-tick ! 
  ;
  
  : run-game ( -- )
    page
    world-size [: { world }
      world init-world
      world [: { world display }
        display world draw-world
        display update-display
      ;] with-term-display
      begin key? until
      key [char] q = if exit then
      world reset-tick
      begin
        world [: { world display }
          display world update
          display update-display
        ;] with-term-display
        if
          begin key? until
          key [char] q = if exit then
          world reset-tick
        else
          key? if
            key case
              $1B of
                key? if
                  key case
                    [char] [ of
                      key? if
                        key case
                          [char] D of
                            world interval
                            world world-player accel-left
                          endof
                          [char] C of
                            world interval
                            world world-player accel-right
                          endof
                        endcase
                      then
                    endof
                  endcase
                then
              endof
              [char] q of
                exit
              endof
            endcase
          then
          world world-tick @ world world-last-tick !
          systick::systick-counter world world-tick !
        then
      again
    ;] with-aligned-allot
  ;
  
end-module
2 Likes

A Mandelbrot set drawing app, written in Zeptoforth. It uses both of the cores in RP3250, and runs under less than 1 sec.

begin-module mand
\ Mandelbrot set

float32 import
picocalc-term-common import
picocalc-term import
oo import
pixmap8 import
task import
timer import
st7365p-8-common import


320 Constant W
300 Constant H
32 Constant MAX_ITER

variable pix W H * allot
<pixmap8> class-size buffer: mypix8

variable colors MAX_ITER allot
variable done0
variable done1

: initcolors
256 MAX_ITER / { n }
MAX_ITER 0 do
  i n * dup dup rgb8 colors i + c!
loop
;

: storepix { c x y }
 c 2 *  pix y W * x + + c! ;

: drawpix
  [: { c x y display }
    colors c + c@ x y display draw-pixel-const
  ;] with-term-display
;

: upd
  [: { display }
    display update-display
  ;] with-term-display
;

: upd1

[: { display }
  0 0 0 0 w h
  mypix8 display draw-rect
;] with-term-display
upd
;

: mandel { X_MIN X_MAX Y_MIN Y_MAX p }
{ fzx fzy fcx fcy }
{ fzx_next iter }
{ ftx fty }
us-counter-lsb { t1 }
X_MAX X_MIN v-  { XDELTA }
Y_MAX Y_MIN v-  { YDELTA }
YDELTA H n>v v/ { fystep }
XDELTA W n>v v/ { fxstep }
p 0= if
  Y_MIN to fcy
else
  Y_MIN fystep v+ to fcy
then
H p - p do

  fcy fystep 
  dup v+
   v+ to fcy
  X_MIN to fcx

  W 0 do
    fcx fxstep v+ to fcx
    0e0 dup to fzx to fzy
    0 to iter
    begin
    fzx dup v* to ftx
    fzy dup v* to fty
    ftx fty v+ 4e0 v<= iter MAX_ITER < and
    while
      ftx fty v- fcx v+ to fzx_next
      2e0 fzx v* fzy v* fcy v+ to fzy
      fzx_next to fzx 
      iter 1+ to iter
    repeat
  iter i j storepix
  loop
  upd
 2 +loop
p 0= if
  1 done0 !
else
  1 done1 !
then
done0 @ done1 @ and if
  us-counter-lsb t1 - .
  upd1
  us-counter-lsb t1 - .
  mypix8 destroy
then
;


 : mand1 -2e0 .5e0 -1.15e0 1.15e0 0 mandel ;
 : mand2 -2e0 .5e0 -1.15e0 1.15e0 1 mandel ;
\ : mand1 -0.75e0 .5e0 0e0 1.15e0 0 mandel ;
\ : mand2 -0.75e0 .5e0 0e0 1.15e0 1 mandel ;
\ : mand1 -0.375e0 .25e0 0.575e0 1.15e0 0 mandel ;
\ : mand2 -0.375e0 .25e0 0.575e0 1.15e0 1 mandel ;

: doit 
  0 done0 !
  0 done1 !
  initcolors
  pix W H <pixmap8> mypix8 init-object
  0 ['] mand1 1024 1024 1024 0 spawn-on-core
  0 ['] mand2 1024 1024 1024 1 spawn-on-core

 run
 run ;

end-module
: m mand::doit ;

3 Likes