Graphical demos for the PicoCalc

I’ve made two enhanced versions of the Snakes and Rocks games which now use extra/rp_common/picocalc_keys.fs for better controls.

The enhanced Snakes game is at test/rp_common/picocalc_snake_keys_enhanced.fs, and a screenshot and source code is below:

\ Copyright (c) 2025-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.

\ The controls are:
\ 
\ Up: Go up
\ Down: Go down
\ Right: Go right
\ Left: Go left
\ S: Take a screenshot
\ Q: Give up in shame

begin-module snake
  
  oo import
  picocalc-term import
  picocalc-keys import
  picocalc-screenshot import
  pixmap8 import
  st7365p-8-common import
  tinymt32 import
  
  $B4 constant LEFT_ARROW
  $B5 constant UP_ARROW
  $B6 constant DOWN_ARROW
  $B7 constant RIGHT_ARROW
  
  tinymt32-size buffer: prng
  
  : init-prng ( -- )
    rng::random prng tinymt32-init
    prng tinymt32-prepare-example
  ;
  
  initializer init-prng
  
  : random { x -- u }
    x s>f prng tinymt32-generate-uint32 0 f* round-half-zero
    x min
  ;

  8 constant cell-width
  8 constant cell-height
  
  term-pixels-dim@
  cell-height / constant world-height
  cell-width / constant world-width
  
  world-width world-height * constant max-snake-len
  
  <object> begin-class <food>
  
    cell member food-count
    world-width world-height * 8 align 8 / cell align
    member food-bits
    
    method food-count@ ( food -- count )
    method create-food ( x y food -- )
    method eat-food ( x y food -- )
    method food-at? ( x y food -- at? )
    
  end-class
  
  <food> begin-implement
    
    :noname { self -- }
      self <object>->new
      self food-bits [ world-width world-height * 8 align 8 / ]
      literal 0 fill
      0 self food-count !
    ; define new
    
    :noname ( self -- count ) food-count @ ; define food-count@
    
    :noname { x y self -- }
      x y self food-at? not if
        1 self food-count +!
        y world-width * x + { index }
        index 7 and bit index 3 rshift self food-bits + cbis!
      then
    ; define create-food
    
    :noname { x y self -- }
      x y self food-at? if
        -1 self food-count +!
        y world-width * x + { index }
        index 7 and bit index 3 rshift self food-bits + cbic!
      then
    ; define eat-food
    
    :noname { x y self -- at? }
      y world-width * x + { index }
      index 7 and bit index 3 rshift self food-bits + cbit@
    ; define food-at?
    
  end-implement
  
  4 constant min-snake-len
  2 constant snake-len-incr
  
  0 constant none
  1 constant up
  2 constant down
  3 constant left
  4 constant right
  
  : opposite-dir ( dir -- dir' )
    case
      none of none endof
      up of down endof
      down of up endof
      left of right endof
      right of left endof
    endcase
  ;
  
  : adjust-coord ( x y dir -- x' y' )
    case
      up of 1- endof
      down of 1+ endof
      left of swap 1- swap endof
      right of swap 1+ swap endof
    endcase
  ;
  
  : in-bounds? { x y -- in-bounds? }
    x 0>= y 0>= and x world-width < and y world-height < and
  ;
  
  <object> begin-class <snake>
  
    cell member snake-dir
    cell member snake-head-index
    cell member snake-tail-index
    cell member snake-target-len
    max-snake-len cell align member snake-x
    max-snake-len cell align member snake-y
    
    method extend-snake ( dir snake -- alive? )
    method grow-snake ( snake -- )
    method shorten-snake ( snake -- )
    method push-snake-head ( x y snake -- )
    method drop-snake-tail ( snake -- )
    method snake-head@ ( snake -- x y )
    method snake-at? ( x y snake -- at? )
    method snake-len@ ( snake -- len )
    
  end-class
  
  <snake> begin-implement
    
    :noname { x y self -- }
      self <object>->new
      none self snake-dir !
      0 self snake-head-index !
      0 self snake-tail-index !
      min-snake-len self snake-target-len !
      x self snake-x c!
      y self snake-y c!
    ; define new
    
    :noname { dir self -- alive? }
      dir none = if self snake-dir @ to dir then
      dir none <> if
        dir opposite-dir self snake-dir @ = if
          self snake-dir @ to dir
        then
        dir self snake-dir !
        self snake-head@ dir adjust-coord { x y }
        x y in-bounds? if
          x y self snake-at? not if
            x y self push-snake-head true
          else
            false
          then
        else
          false
        then
      else
        true
      then
    ; define extend-snake
    
    :noname { self -- }
      snake-len-incr self snake-target-len +!
    ; define grow-snake
    
    :noname { self -- }
      self snake-len@ self snake-target-len @ > if
        self drop-snake-tail
      then
    ; define shorten-snake
    
    :noname { x y self -- }
      self snake-head-index @ 1+ max-snake-len umod
      dup { index } self snake-head-index !
      x self snake-x index + c!
      y self snake-y index + c!
    ; define push-snake-head
    
    :noname { self -- }
      self snake-tail-index @ { index }
      index self snake-head-index @ <> if
        index 1+ max-snake-len umod self snake-tail-index !
      then
    ; define drop-snake-tail
    
    :noname { self -- x y }
      self snake-head-index @ { index }
      self snake-x index + c@
      self snake-y index + c@
    ; define snake-head@
    
    :noname { x y self -- at? }
      self snake-tail-index @ { index }
      self snake-head-index @ { head-index }
      begin
        self snake-x index + c@ x =
        self snake-y index + c@ y = and if true exit then
        index head-index =
        index 1+ max-snake-len umod to index
      until
      false
    ; define snake-at?
    
    :noname { self -- len }
      self snake-head-index @ { head-index }
      self snake-tail-index @ { tail-index }
      head-index
      tail-index head-index > if max-snake-len + then
      tail-index - 1+
    ; define snake-len@
    
  end-implement
  
  4 constant init-food-count
  100 constant food-chance \ Actually the reciprocal
  0 255 0 rgb8 constant body-color
  255 255 0 rgb8 constant head-color
  255 0 0 rgb8 constant food-color
  
  <object> begin-class <world>
    
    <snake> class-size member the-snake
    <food> class-size member the-food
    
    method cycle-world ( dir world -- continue? )
    method draw-world ( world -- )
    method create-random-food ( world -- )
    
  end-class
  
  <world> begin-implement
  
    :noname { self -- }
      self <object>->new
      world-width 1- random world-height 1- random
      <snake> self the-snake init-object
      <food> self the-food init-object
      init-food-count 0 ?do self create-random-food loop
    ; define new
    
    :noname { dir self -- continue? }
      dir self the-snake extend-snake if
        self the-snake snake-head@ { x y }
        x y self the-food food-at? not if
          self the-snake shorten-snake
        else
          x y self the-food eat-food
          self the-snake grow-snake
        then
        food-chance random 0=
        self the-food food-count@ 0= or if
          self create-random-food
        then
        true
      else
        false
      then
    ; define cycle-world
    
    :noname ( self -- )
      [: { self display -- }
        display clear-pixmap
        world-height 0 ?do
          world-width 0 ?do
            0 { color }
            i j self the-snake snake-at? if
              self the-snake snake-head@ j = swap i = and if
                head-color
              else
                body-color
              then
              to color
            else
              i j self the-food food-at? if
                food-color to color
              then
            then
            color ?dup if
              i cell-width * j cell-height *
              cell-width cell-height
              display draw-rect-const
            then
          loop
        loop
        display update-display
      ;] with-term-display
    ; define draw-world
    
    :noname { self -- }
      begin
        world-width 1- random { x }
        world-height 1- random { y }
        x y self the-snake snake-at? not
        x y self the-food food-at? not and dup if
          x y self the-food create-food
        then
      until
    ; define create-random-food
    
  end-implement
  
  $1B constant escape
  
  : empty-keys ( -- ) begin key? while key drop repeat ;

  : handle-screenshot ( -- )
    [:
      screenshot-fs@ { fs }
      fs if
        screenshot-path@ fs ['] take-screenshot try-and-display-error 0<> if
          drop 2drop
        then
      then
    ;] console::with-serial-error-output
  ;
  
  : handle-key ( -- dir exit? )
    reset-keymap
    update-keymap
    [char] q keymap-pressed@ if none true exit then
    [char] s keymap-released@ if handle-screenshot then
    none 0
    UP_ARROW keymap-pressed@ if 1+ nip up swap then
    DOWN_ARROW keymap-pressed@ if 1+ nip down swap then
    RIGHT_ARROW keymap-pressed@ if 1+ nip right swap then
    LEFT_ARROW keymap-pressed@ if 1+ nip left swap then
    1 <> if drop none then
    false
  ;
  
  1875 constant snake-delay-ticks
  
  : play-snake ( -- )
    <world> [: { the-world }
      ansi-term::hide-cursor
      true raw-keys-enabled!
      clear-keymap
      page
      [: dup clear-pixmap update-display ;] with-term-display
      the-world draw-world
      begin
        handle-key not if
          systick::systick-counter { start-systick }
          the-world cycle-world { alive? }
          the-world draw-world
          start-systick snake-delay-ticks
          task::current-task task::delay
          alive? not
        else
          drop true
        then
      until
      ." *** GAME OVER ***" cr
      false raw-keys-enabled!
      clear-keymap
      1000 ms
      empty-keys
      ansi-term::show-cursor
    ;] with-object
  ;

end-module

The enhanced Rocks game is at test/rp2350/picocalc_rocks_keys_enhanced.fs, and a screenshot and source code is below:

\ Copyright (c) 2025-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.

\ The controls are:
\ 
\ Up: Thrust
\ Right: Turn right
\ Left: Turn left
\ Space: Fire
\ S: Take a screenshot
\ Q: Give up in shame

begin-module rocks
  
  oo import
  picocalc-term import
  picocalc-sound import
  picocalc-keys import
  picocalc-screenshot import
  pixmap8 import
  pixmap8-utils import
  font import
  st7365p-8-common import
  float32 import
  tinymt32 import
  
  $B4 constant LEFT_ARROW
  $B5 constant UP_ARROW
  $B6 constant DOWN_ARROW
  $B7 constant RIGHT_ARROW

  tinymt32-size buffer: prng
  
  : init-prng ( -- )
    rng::random prng tinymt32-init
    prng tinymt32-prepare-example
  ;
  
  initializer init-prng
  
  : urandom ( -- u ) prng tinymt32-generate-uint32 ;
  
  : vrandom ( -- v ) urandom 0 f64>v ;
  
  16e0 constant border-width
  16e0 constant border-height
  320e0 constant arena-width
  320e0 constant arena-height
  
  : convert-coord { x y -- x' y' }
    x v>n arena-height y v- v>n
  ;
  
  0 0 0 rgb8 constant bk-color
  255 255 0 rgb8 constant info-color
  
  16 constant info-x
  16 constant info-y
  
  180e0 constant base-shot-speed
  8e0 vpi v* constant ship-heading-decel
  8e0 constant ship-body-radius
  16e0 constant ship-dir-radius
  0 255 0 rgb8 constant ship-color
  40000 constant wave-start-ticks
  
  variable wave
  variable lives
  variable wave-start
  variable wave-start-systick
  
  : render-label ( color addr bytes n x y display -- )
    256 [: { color addr bytes n x y display buffer }
      addr buffer bytes move
      n s>d <# #s #> dup { bytes' } buffer bytes + swap move
      color buffer bytes bytes' + x y display term-font@
      draw-string-to-pixmap8
    ;] with-allot
  ;
  
  : render-info { color display -- }
    color s" Wave:  " wave @ info-x info-y display render-label
    color s" Lives: " lives @ 0 max
    info-x info-y term-font@ char-rows @ +
    display render-label
  ;
  
  : draw-info { display -- } info-color display render-info ;
  
  : erase-info { display -- } bk-color display render-info ;
   
  <object> begin-class <entity>
  
    cell member entity-active
    cell member entity-x
    cell member entity-y
    cell member entity-delta-x
    cell member entity-delta-y
    
    method entity-active? ( entity -- active? )
    method entity-coord@ ( entity -- x y )
    method entity-delta@ ( entity -- delta-x delta-y )
    method activate-entity ( x y delta-x delta-y entity -- )
    method deactivate-entity ( entity -- )
    method adjust-entity-delta ( adjust-x adjust-y entity -- )
    method update-entity ( interval entity -- )
    method draw-entity ( display entity -- )
    method erase-entity ( display entity -- )
    method do-update-entity ( interval entity -- )
    method do-draw-entity ( display entity -- )
    method do-erase-entity ( display entity -- )
    
  end-class
  
  <entity> begin-implement
  
    :noname { self -- }
      self <object>->new
      false self entity-active !
      0e0 self entity-x !
      0e0 self entity-y !
      0e0 self entity-delta-x !
      0e0 self entity-delta-y !
    ; define new
    
    :noname { self -- active? }
      self entity-active @
    ; define entity-active?
    
    :noname { self -- x y }
      self entity-x @ self entity-y @
    ; define entity-coord@
    
    :noname { self -- delta-x delta-y }
      self entity-delta-x @ self entity-delta-y @
    ; define entity-delta@
    
    :noname { x y delta-x delta-y self -- }
      true self entity-active !
      x self entity-x !
      y self entity-y !
      delta-x self entity-delta-x !
      delta-y self entity-delta-y !
    ; define activate-entity
    
    :noname { self -- }
      false self entity-active !
      0e0 self entity-x !
      0e0 self entity-y !
      0e0 self entity-delta-x !
      0e0 self entity-delta-y !
    ; define deactivate-entity
    
    :noname { adjust-x adjust-y self -- }
      self entity-delta-x @ adjust-x v+
      self entity-delta-x !
      self entity-delta-y @ adjust-y v+
      self entity-delta-y !
    ; define adjust-entity-delta
    
    :noname { interval self -- }
      self entity-active? if
        interval self do-update-entity
      then
    ; define update-entity
    
    :noname { display self -- }
      self entity-active? if display self do-draw-entity then
    ; define draw-entity
    
    :noname { display self -- }
      self entity-active? if display self do-erase-entity then
    ; define erase-entity
    
    :noname { interval self -- }
      self entity-x @
      self entity-delta-x @ interval v* v+ { x }
      self entity-y @
      self entity-delta-y @ interval v* v+ { y }
      x border-width vnegate v< if
        x arena-width v+ border-width v+ to x
      then
      x arena-width border-width v+ v> if
        x arena-width v- border-width v- to x
      then
      y border-height vnegate v< if
        y arena-height v+ border-height v+ to y
      then
      y arena-height border-height v+ v> if
        y arena-height v- border-height v- to y
      then
      x self entity-x !
      y self entity-y !
    ; define do-update-entity
    
    :noname { display self -- } ; define do-draw-entity
    
    :noname { display self -- } ; define do-erase-entity
    
  end-implement
  
  <entity> begin-class <shootable>
    
    method shootable-radius@ ( shootable -- radius )
    method try-shoot ( x y shootable -- hit? )
    method do-hit ( shootable -- )
  
  end-class
  
  <shootable> begin-implement
    
    :noname { self -- radius } 0e0 ; define shootable-radius@
    
    :noname { shot-x shot-y self -- hit? }
      self entity-active? if
        self entity-coord@ { x y }
        self shootable-radius@ { radius }
        shot-x x v- dup v* shot-y y v- dup v* v+
        radius dup v* v<= if self do-hit true else false then
      else
        false
      then
    ; define try-shoot
    
    :noname { self -- } ; define do-hit
    
  end-implement
  
  16e0 constant max-asteroid-radius
  3 constant max-divide-count
  max-asteroid-radius 4e0 v/ constant min-asteroid-radius
  20e0 constant max-asteroid-speed
  
  <shootable> begin-class <asteroid>
  
    cell member asteroid-radius
    
    method do-spawn-asteroid ( radius x y asteroid -- )
    method render-asteroid ( color display asteroid -- )
    method check-collide ( x y asteroid -- collide? )
    
  end-class
  
  120 constant max-asteroid-count
  max-asteroid-count <asteroid> class-size * buffer: asteroids
  
  : asteroid@ ( index -- asteroid )
    <asteroid> class-size * asteroids +
  ;
  
  : spawn-asteroid { radius x y -- }
    max-asteroid-count 0 ?do
      i asteroid@ { asteroid }
      asteroid entity-active? not if
        radius x y asteroid do-spawn-asteroid exit
      then
    loop
  ;
  
  : start-asteroid ( -- )
    max-asteroid-radius
    arena-width vrandom v* arena-height vrandom v*
    spawn-asteroid
  ;
  
  : init-asteroids ( -- )
    max-asteroid-count 0 ?do
      <asteroid> i asteroid@ init-object
    loop
  ;
  
  : deactivate-asteroids ( -- )
    max-asteroid-count 0 ?do
      i asteroid@ deactivate-entity
    loop
  ;
  
  : update-asteroids { interval -- }
    max-asteroid-count 0 ?do
      interval i asteroid@ update-entity
    loop
  ;
  
  : draw-asteroids { display -- }
    max-asteroid-count 0 ?do
      display i asteroid@ draw-entity
    loop
  ;
  
  : erase-asteroids { display -- }
    max-asteroid-count 0 ?do
      display i asteroid@ erase-entity
    loop
  ;
  
  : try-shoot-asteroids { x y -- hit? }
    false
    max-asteroid-count 0 ?do
      x y i asteroid@ try-shoot or
    loop
  ;
  
  : check-collide-asteroids { x y -- collide? }
    false
    max-asteroid-count 0 ?do
      x y i asteroid@ check-collide or
    loop
  ;
  
  : asteroid-count ( -- count )
    0
    max-asteroid-count 0 ?do
      i asteroid@ entity-active? if 1+ then
    loop
  ;
  
  255 255 255 rgb8 constant asteroid-color
  
  <asteroid> begin-implement
    
    :noname { self -- }
      self <shootable>->new
      0e0 self asteroid-radius !
    ; define new
    
    :noname { radius x y self -- }
      max-asteroid-speed vrandom v* { speed }
      2e0 vpi v* vrandom v* { angle }
      speed angle vcos v* { delta-x }
      speed angle vsin v* { delta-y }
      x y delta-x delta-y self activate-entity
      radius self asteroid-radius !
    ; define do-spawn-asteroid
    
    :noname { self -- radius }
      self asteroid-radius @
    ; define shootable-radius@
    
    :noname { self -- }
      max-divide-count 2 - u>v vrandom v* 2e0 v+
      vround-half-away-zero v>u { divide }
      self entity-coord@ { x y }
      self asteroid-radius @ divide u>v v/ { radius }
      self deactivate-entity
      radius min-asteroid-radius v>= if
        divide 0 ?do radius x y spawn-asteroid loop
      then
    ; define do-hit
    
    :noname { ship-x ship-y self -- hit? }
      self entity-active? if
        self entity-coord@ { x y }
        self shootable-radius@ { radius }
        ship-x x v- dup v* ship-y y v- dup v* v+
        radius ship-body-radius v+ dup v* v<= dup if
          self do-hit
        then
      else
        false
      then
    ; define check-collide
    
    :noname { color display self -- }
      self entity-coord@ { x y }
      color x y convert-coord
      self asteroid-radius @ v>n
      display draw-pixel-circle
    ; define render-asteroid
    
    :noname { display self -- }
      asteroid-color display self render-asteroid
    ; define do-draw-entity
    
    :noname { display self -- }
      bk-color display self render-asteroid
    ; define do-erase-entity
    
  end-implement
  
  <entity> begin-class <shot>
  
    cell member shot-energy
    
    method do-spawn-shot ( x y delta-x delta-y shot -- )
    method render-shot ( color display shot -- )
  
  end-class
  
  1e0 constant init-shot-energy
  120 constant max-shot-count
  max-shot-count <shot> class-size * buffer: shots
  
  : shot@ ( index -- shot )
    <shot> class-size * shots +
  ;
  
  : spawn-shot { x y delta-x delta-y -- }
    max-shot-count 0 ?do
      i shot@ { shot }
      shot entity-active? not if
        x y delta-x delta-y shot do-spawn-shot exit
      then
    loop
  ;
  
  : init-shots ( -- )
    max-shot-count 0 ?do <shot> i shot@ init-object loop
  ;
  
  : deactivate-shots ( -- )
    max-shot-count 0 ?do i shot@ deactivate-entity loop
  ;
  
  : update-shots { interval -- }
    max-shot-count 0 ?do interval i shot@ update-entity loop
  ;
  
  : draw-shots { display -- }
    max-shot-count 0 ?do display i shot@ draw-entity loop
  ;
  
  : erase-shots { display -- }
    max-shot-count 0 ?do display i shot@ erase-entity loop
  ;
  
  255 0 0 rgb8 constant shot-color
  4e0 constant shot-size
  
  <shot> begin-implement
    
    :noname { self -- }
      self <entity>->new
      0e0 self shot-energy !
    ; define new
    
    :noname { x y delta-x delta-y self -- }
      x y delta-x delta-y self activate-entity
      init-shot-energy self shot-energy !
    ; define do-spawn-shot
    
    :noname { interval self -- }
      interval self <entity>->do-update-entity
      self entity-coord@ try-shoot-asteroids if
        self deactivate-entity exit
      then
      self shot-energy @ interval v- dup { energy }
      self shot-energy !
      energy 0e0 v<= if self deactivate-entity then
    ; define do-update-entity
    
    :noname { color display self -- }
      self entity-coord@ { x y }
      self entity-delta@ { delta-x delta-y }
      delta-y vnegate delta-x vnegate vatan2 { angle }
      color x y convert-coord
      x angle vcos shot-size v* v+
      y angle vsin shot-size v* v+ convert-coord
      display draw-pixel-line
    ; define render-shot
    
    :noname { display self -- }
      shot-color display self render-shot
    ; define do-draw-entity
    
    :noname { display self -- }
      bk-color display self render-shot
    ; define do-erase-entity
    
  end-implement
  
  <entity> begin-class <ship>
    
    cell member ship-heading
    cell member ship-delta-heading
    
    method do-spawn-ship ( ship -- )
    method turn-ship ( angle ship -- )
    method thrust-ship ( speed ship -- )
    method ship-shoot ( ship -- )
    method render-ship ( color display ship -- )
    
  end-class
  
  <ship> class-size buffer: ship
  
  : init-ship ( -- ) <ship> ship init-object ;
  
  : spawn-ship ( -- )
    true wave-start !
    systick::systick-counter wave-start-systick !
    ship do-spawn-ship
  ;
  
  : deactivate-ship ( -- ) ship deactivate-entity ;
  
  : update-ship ( interval -- ) ship update-entity ;
  
  : draw-ship ( display -- ) ship draw-entity ;
  
  : erase-ship ( display -- ) ship erase-entity ;
  
  <ship> begin-implement
    
    :noname { self -- }
      self <entity>->new
      0e0 self ship-heading !
      0e0 self ship-delta-heading !
    ; define new
    
    :noname { self -- }
      arena-width 2e0 v/ arena-height 2e0 v/ 0e0 0e0
      self activate-entity
      vpi 2e0 v/ self ship-heading !
      0e0 self ship-delta-heading !
    ; define do-spawn-ship
    
    :noname { angle self -- }
      self ship-delta-heading @ angle v+
      self ship-delta-heading !
    ; define turn-ship
    
    :noname { thrust self -- }
      self ship-heading @ { heading }
      heading vcos thrust v* heading vsin thrust v*
      self adjust-entity-delta
    ; define thrust-ship
    
    :noname { self -- }
      self ship-heading @ { heading }
      self entity-delta@ { delta-x delta-y }
      self entity-coord@
      delta-x heading vcos base-shot-speed v* v+
      delta-y heading vsin base-shot-speed v* v+
      spawn-shot
    ; define ship-shoot
    
    :noname { interval self -- }
      interval self <entity>->do-update-entity
      self ship-heading @ { heading }
      self ship-delta-heading @ { delta-heading }
      delta-heading v0<> if
        heading delta-heading interval v* v+
        self ship-heading !
        delta-heading vabs ship-heading-decel interval v* v-
        0e0 vmax delta-heading dup vabs v/ v*
        self ship-delta-heading !
      then
      self entity-coord@ check-collide-asteroids if
        wave-start @ not if
          beep
          -1 lives +!
          lives @ 0>= if spawn-ship then
        then
      then
    ; define do-update-entity
    
    :noname { color display self -- }
      self entity-coord@ { x y }
      color x y convert-coord ship-body-radius v>n
      display draw-filled-circle
      self ship-heading @ { heading }
      x heading vcos ship-dir-radius v* v+ { x1 }
      y heading vsin ship-dir-radius v* v+ { y1 }
      color x y convert-coord x1 y1 convert-coord
      display draw-pixel-line
    ; define render-ship
    
    :noname { display self -- }
      ship-color display self render-ship
    ; define do-draw-entity
    
    :noname { display self -- }
      bk-color display self render-ship
    ; define do-erase-entity
    
  end-implement
  
  : init-world ( -- ) init-asteroids init-shots init-ship ;
  
  initializer init-world
  
  : deactivate-world ( -- )
    deactivate-asteroids deactivate-shots deactivate-ship
  ;
    
  : update-world { interval -- }
    interval update-asteroids
    interval update-shots
    interval update-ship
  ;
  
  : draw-world { display -- }
    display draw-asteroids
    display draw-shots
    display draw-ship
    display draw-info
  ;
  
  : erase-world { display -- }
    display erase-asteroids
    display erase-shots
    display erase-ship
    display erase-info
  ;
  
  3 constant init-asteroid-count
  
  : start-world ( -- )
    deactivate-world
    wave @ init-asteroid-count + 0 ?do start-asteroid loop
    spawn-ship
  ;
  
  vpi constant ship-turn-speed
  10e0 constant ship-thrust
  
  : handle-thrust ( -- ) ship-thrust ship thrust-ship ;
  
  : handle-turn-right ( -- )
    ship-turn-speed vnegate ship turn-ship
  ;
  
  : handle-turn-left ( -- )
    ship-turn-speed ship turn-ship
  ;
  
  : handle-shoot ( -- ) ship ship-shoot ;
  
  $1B constant escape
  
  : empty-keys ( -- ) begin key? while key drop repeat ;

  : handle-screenshot ( -- )
    [:
      screenshot-fs@ { fs }
      fs if
        screenshot-path@ fs ['] take-screenshot try-and-display-error 0<> if
          drop 2drop
        then
      then
    ;] console::with-serial-error-output
  ;
  
  : handle-key ( -- exit? )
    reset-keymap
    update-keymap
    [char] q keymap-pressed@ if true exit then
    UP_ARROW keymap-pressed@ if handle-thrust then
    RIGHT_ARROW keymap-pressed@ if handle-turn-right then
    LEFT_ARROW keymap-pressed@ if handle-turn-left then
    bl keymap-pressed@ if handle-shoot then
    false
  ;
  
  2 constant init-lives
  5 constant extra-life-wave
  
  : play-rocks ( -- )
    0 wave !
    init-lives lives !
    begin
      ansi-term::hide-cursor
      true raw-keys-enabled!
      clear-keymap
      [: dup clear-pixmap update-display ;] with-term-display
      start-world
      systick::systick-counter { last-systick }
      begin
        last-systick [: { last-systick display }
          display erase-world
          systick::systick-counter { current-systick }
          wave-start @ if
            current-systick wave-start-systick @ -
            wave-start-ticks < wave-start !
          then
          current-systick last-systick - u>v 10000e0 v/
          update-world
          handle-key { exit-key? }
          display draw-world
          display update-display
          current-systick exit-key?
        ;] with-term-display
        { exit-key? } to last-systick
        [char] s keymap-released@ if handle-screenshot then
        exit-key? lives @ 0< or if
          [: dup clear-pixmap update-display ;]
          with-term-display
          page
          ." *** GAME OVER ***" cr cr
          ." You survived " wave @ . ." waves" cr
          false raw-keys-enabled!
          clear-keymap
          1000 ms
          empty-keys
          ansi-term::show-cursor
          exit
        then
        asteroid-count 0=
      until
      1 wave +!
      wave @ extra-life-wave umod 0= if 1 lives +! then
    again
  ;
  
end-module
2 Likes

'Pico cube
CLS
Dim co(4)
co(1)=RGB(0,0,255)
co(2)=RGB(0,255,0)
co(3)=RGB(255,0,0)
Font 8
Color &H1f1f1f
Print @(0,0) " Pico"
For y=6 To 0 Step -1
For x=4 To 20
cc=Pixel(x,y)
If cc<>0 Then
Restore cube
For cy=0 To 31
Read cu$
For cx=1 To 32
cc=Val(Mid$(cu$,cx,1))
If cc<>0 Then
Pixel x*15+cx-31,10+y*14+x*8+cy,co(cc)
End If
Next cx
Next cy
End If
Next x
Next y
'Save image"cube.bmp"
cube:
 Data "00000000000000111100000000000000"
 Data "00000000000011111111000000000000"
 Data "00000000001111111111110000000000"
 Data "00000000111111111111111100000000"
 Data "00000011111111111111111111000000"
 Data "00001111111111111111111111110000"
 Data "00111111111111111111111111111100"
 Data "11111111111111111111111111111111"
 Data "22111111111111111111111111111133"
 Data "22221111111111111111111111113333"
 Data "22222211111111111111111111333333"
 Data "22222222111111111111111133333333"
 Data "22222222221111111111113333333333"
 Data "22222222222211111111333333333333"
 Data "22222222222222111133333333333333"
 Data "22222222222222223333333333333333"
 Data "22222222222222223333333333333333"
 Data "22222222222222223333333333333333"
 Data "22222222222222223333333333333333"
 Data "22222222222222223333333333333333"
 Data "22222222222222223333333333333333"
 Data "22222222222222223333333333333333"
 Data "00222222222222223333333333333300"
 Data "00002222222222223333333333330000"
 Data "00000022222222223333333333000000"
 Data "00000000222222223333333300000000"
 Data "00000000002222223333330000000000"
 Data "00000000000022223333000000000000"
 Data "00000000000000223300000000000000"
 Data "00000000000000000000000000000000"
 Data "00000000000000000000000000000000"
 Data "00000000000000000000000000000000"

1 Like

I have made a minor modification of the Rocks game to incorporate recoil, to make the game a bit harder (i.e. to avoid the obvious strategy of sitting in the center and merely spinning around forever).

The source code is at test/rp2350/picocalc_rocks_recoil.fs and it requires extra/rp_common/picocalc_keys.fs.

The updates source code is as follows:

\ Copyright (c) 2025-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.

\ The controls are:
\ 
\ Up: Thrust
\ Right: Turn right
\ Left: Turn left
\ Space: Fire
\ S: Take a screenshot
\ Q: Give up in shame

begin-module rocks
  
  oo import
  picocalc-term import
  picocalc-sound import
  picocalc-keys import
  picocalc-screenshot import
  pixmap8 import
  pixmap8-utils import
  font import
  st7365p-8-common import
  float32 import
  tinymt32 import
  
  $B4 constant LEFT_ARROW
  $B5 constant UP_ARROW
  $B6 constant DOWN_ARROW
  $B7 constant RIGHT_ARROW

  tinymt32-size buffer: prng
  
  : init-prng ( -- )
    rng::random prng tinymt32-init
    prng tinymt32-prepare-example
  ;
  
  initializer init-prng
  
  : urandom ( -- u ) prng tinymt32-generate-uint32 ;
  
  : vrandom ( -- v ) urandom 0 f64>v ;
  
  16e0 constant border-width
  16e0 constant border-height
  320e0 constant arena-width
  320e0 constant arena-height
  
  : convert-coord { x y -- x' y' }
    x v>n arena-height y v- v>n
  ;
  
  0 0 0 rgb8 constant bk-color
  255 255 0 rgb8 constant info-color
  
  16 constant info-x
  16 constant info-y
  
  180e0 constant base-shot-speed
  -2e0 constant recoil-speed
  8e0 vpi v* constant ship-heading-decel
  8e0 constant ship-body-radius
  16e0 constant ship-dir-radius
  0 255 0 rgb8 constant ship-color
  40000 constant wave-start-ticks
  
  variable wave
  variable lives
  variable wave-start
  variable wave-start-systick
  
  : render-label ( color addr bytes n x y display -- )
    256 [: { color addr bytes n x y display buffer }
      addr buffer bytes move
      n s>d <# #s #> dup { bytes' } buffer bytes + swap move
      color buffer bytes bytes' + x y display term-font@
      draw-string-to-pixmap8
    ;] with-allot
  ;
  
  : render-info { color display -- }
    color s" Wave:  " wave @ info-x info-y display render-label
    color s" Lives: " lives @ 0 max
    info-x info-y term-font@ char-rows @ +
    display render-label
  ;
  
  : draw-info { display -- } info-color display render-info ;
  
  : erase-info { display -- } bk-color display render-info ;
   
  <object> begin-class <entity>
  
    cell member entity-active
    cell member entity-x
    cell member entity-y
    cell member entity-delta-x
    cell member entity-delta-y
    
    method entity-active? ( entity -- active? )
    method entity-coord@ ( entity -- x y )
    method entity-delta@ ( entity -- delta-x delta-y )
    method activate-entity ( x y delta-x delta-y entity -- )
    method deactivate-entity ( entity -- )
    method adjust-entity-delta ( adjust-x adjust-y entity -- )
    method update-entity ( interval entity -- )
    method draw-entity ( display entity -- )
    method erase-entity ( display entity -- )
    method do-update-entity ( interval entity -- )
    method do-draw-entity ( display entity -- )
    method do-erase-entity ( display entity -- )
    
  end-class
  
  <entity> begin-implement
  
    :noname { self -- }
      self <object>->new
      false self entity-active !
      0e0 self entity-x !
      0e0 self entity-y !
      0e0 self entity-delta-x !
      0e0 self entity-delta-y !
    ; define new
    
    :noname { self -- active? }
      self entity-active @
    ; define entity-active?
    
    :noname { self -- x y }
      self entity-x @ self entity-y @
    ; define entity-coord@
    
    :noname { self -- delta-x delta-y }
      self entity-delta-x @ self entity-delta-y @
    ; define entity-delta@
    
    :noname { x y delta-x delta-y self -- }
      true self entity-active !
      x self entity-x !
      y self entity-y !
      delta-x self entity-delta-x !
      delta-y self entity-delta-y !
    ; define activate-entity
    
    :noname { self -- }
      false self entity-active !
      0e0 self entity-x !
      0e0 self entity-y !
      0e0 self entity-delta-x !
      0e0 self entity-delta-y !
    ; define deactivate-entity
    
    :noname { adjust-x adjust-y self -- }
      self entity-delta-x @ adjust-x v+
      self entity-delta-x !
      self entity-delta-y @ adjust-y v+
      self entity-delta-y !
    ; define adjust-entity-delta
    
    :noname { interval self -- }
      self entity-active? if
        interval self do-update-entity
      then
    ; define update-entity
    
    :noname { display self -- }
      self entity-active? if display self do-draw-entity then
    ; define draw-entity
    
    :noname { display self -- }
      self entity-active? if display self do-erase-entity then
    ; define erase-entity
    
    :noname { interval self -- }
      self entity-x @
      self entity-delta-x @ interval v* v+ { x }
      self entity-y @
      self entity-delta-y @ interval v* v+ { y }
      x border-width vnegate v< if
        x arena-width v+ border-width v+ to x
      then
      x arena-width border-width v+ v> if
        x arena-width v- border-width v- to x
      then
      y border-height vnegate v< if
        y arena-height v+ border-height v+ to y
      then
      y arena-height border-height v+ v> if
        y arena-height v- border-height v- to y
      then
      x self entity-x !
      y self entity-y !
    ; define do-update-entity
    
    :noname { display self -- } ; define do-draw-entity
    
    :noname { display self -- } ; define do-erase-entity
    
  end-implement
  
  <entity> begin-class <shootable>
    
    method shootable-radius@ ( shootable -- radius )
    method try-shoot ( x y shootable -- hit? )
    method do-hit ( shootable -- )
  
  end-class
  
  <shootable> begin-implement
    
    :noname { self -- radius } 0e0 ; define shootable-radius@
    
    :noname { shot-x shot-y self -- hit? }
      self entity-active? if
        self entity-coord@ { x y }
        self shootable-radius@ { radius }
        shot-x x v- dup v* shot-y y v- dup v* v+
        radius dup v* v<= if self do-hit true else false then
      else
        false
      then
    ; define try-shoot
    
    :noname { self -- } ; define do-hit
    
  end-implement
  
  16e0 constant max-asteroid-radius
  3 constant max-divide-count
  max-asteroid-radius 4e0 v/ constant min-asteroid-radius
  20e0 constant max-asteroid-speed
  
  <shootable> begin-class <asteroid>
  
    cell member asteroid-radius
    
    method do-spawn-asteroid ( radius x y asteroid -- )
    method render-asteroid ( color display asteroid -- )
    method check-collide ( x y asteroid -- collide? )
    
  end-class
  
  120 constant max-asteroid-count
  max-asteroid-count <asteroid> class-size * buffer: asteroids
  
  : asteroid@ ( index -- asteroid )
    <asteroid> class-size * asteroids +
  ;
  
  : spawn-asteroid { radius x y -- }
    max-asteroid-count 0 ?do
      i asteroid@ { asteroid }
      asteroid entity-active? not if
        radius x y asteroid do-spawn-asteroid exit
      then
    loop
  ;
  
  : start-asteroid ( -- )
    max-asteroid-radius
    arena-width vrandom v* arena-height vrandom v*
    spawn-asteroid
  ;
  
  : init-asteroids ( -- )
    max-asteroid-count 0 ?do
      <asteroid> i asteroid@ init-object
    loop
  ;
  
  : deactivate-asteroids ( -- )
    max-asteroid-count 0 ?do
      i asteroid@ deactivate-entity
    loop
  ;
  
  : update-asteroids { interval -- }
    max-asteroid-count 0 ?do
      interval i asteroid@ update-entity
    loop
  ;
  
  : draw-asteroids { display -- }
    max-asteroid-count 0 ?do
      display i asteroid@ draw-entity
    loop
  ;
  
  : erase-asteroids { display -- }
    max-asteroid-count 0 ?do
      display i asteroid@ erase-entity
    loop
  ;
  
  : try-shoot-asteroids { x y -- hit? }
    false
    max-asteroid-count 0 ?do
      x y i asteroid@ try-shoot or
    loop
  ;
  
  : check-collide-asteroids { x y -- collide? }
    false
    max-asteroid-count 0 ?do
      x y i asteroid@ check-collide or
    loop
  ;
  
  : asteroid-count ( -- count )
    0
    max-asteroid-count 0 ?do
      i asteroid@ entity-active? if 1+ then
    loop
  ;
  
  255 255 255 rgb8 constant asteroid-color
  
  <asteroid> begin-implement
    
    :noname { self -- }
      self <shootable>->new
      0e0 self asteroid-radius !
    ; define new
    
    :noname { radius x y self -- }
      max-asteroid-speed vrandom v* { speed }
      2e0 vpi v* vrandom v* { angle }
      speed angle vcos v* { delta-x }
      speed angle vsin v* { delta-y }
      x y delta-x delta-y self activate-entity
      radius self asteroid-radius !
    ; define do-spawn-asteroid
    
    :noname { self -- radius }
      self asteroid-radius @
    ; define shootable-radius@
    
    :noname { self -- }
      max-divide-count 2 - u>v vrandom v* 2e0 v+
      vround-half-away-zero v>u { divide }
      self entity-coord@ { x y }
      self asteroid-radius @ divide u>v v/ { radius }
      self deactivate-entity
      radius min-asteroid-radius v>= if
        divide 0 ?do radius x y spawn-asteroid loop
      then
    ; define do-hit
    
    :noname { ship-x ship-y self -- hit? }
      self entity-active? if
        self entity-coord@ { x y }
        self shootable-radius@ { radius }
        ship-x x v- dup v* ship-y y v- dup v* v+
        radius ship-body-radius v+ dup v* v<= dup if
          self do-hit
        then
      else
        false
      then
    ; define check-collide
    
    :noname { color display self -- }
      self entity-coord@ { x y }
      color x y convert-coord
      self asteroid-radius @ v>n
      display draw-pixel-circle
    ; define render-asteroid
    
    :noname { display self -- }
      asteroid-color display self render-asteroid
    ; define do-draw-entity
    
    :noname { display self -- }
      bk-color display self render-asteroid
    ; define do-erase-entity
    
  end-implement
  
  <entity> begin-class <shot>
  
    cell member shot-energy
    
    method do-spawn-shot ( x y delta-x delta-y shot -- )
    method render-shot ( color display shot -- )
  
  end-class
  
  1e0 constant init-shot-energy
  120 constant max-shot-count
  max-shot-count <shot> class-size * buffer: shots
  
  : shot@ ( index -- shot )
    <shot> class-size * shots +
  ;
  
  : spawn-shot { x y delta-x delta-y -- }
    max-shot-count 0 ?do
      i shot@ { shot }
      shot entity-active? not if
        x y delta-x delta-y shot do-spawn-shot exit
      then
    loop
  ;
  
  : init-shots ( -- )
    max-shot-count 0 ?do <shot> i shot@ init-object loop
  ;
  
  : deactivate-shots ( -- )
    max-shot-count 0 ?do i shot@ deactivate-entity loop
  ;
  
  : update-shots { interval -- }
    max-shot-count 0 ?do interval i shot@ update-entity loop
  ;
  
  : draw-shots { display -- }
    max-shot-count 0 ?do display i shot@ draw-entity loop
  ;
  
  : erase-shots { display -- }
    max-shot-count 0 ?do display i shot@ erase-entity loop
  ;
  
  255 0 0 rgb8 constant shot-color
  4e0 constant shot-size
  
  <shot> begin-implement
    
    :noname { self -- }
      self <entity>->new
      0e0 self shot-energy !
    ; define new
    
    :noname { x y delta-x delta-y self -- }
      x y delta-x delta-y self activate-entity
      init-shot-energy self shot-energy !
    ; define do-spawn-shot
    
    :noname { interval self -- }
      interval self <entity>->do-update-entity
      self entity-coord@ try-shoot-asteroids if
        self deactivate-entity exit
      then
      self shot-energy @ interval v- dup { energy }
      self shot-energy !
      energy 0e0 v<= if self deactivate-entity then
    ; define do-update-entity
    
    :noname { color display self -- }
      self entity-coord@ { x y }
      self entity-delta@ { delta-x delta-y }
      delta-y vnegate delta-x vnegate vatan2 { angle }
      color x y convert-coord
      x angle vcos shot-size v* v+
      y angle vsin shot-size v* v+ convert-coord
      display draw-pixel-line
    ; define render-shot
    
    :noname { display self -- }
      shot-color display self render-shot
    ; define do-draw-entity
    
    :noname { display self -- }
      bk-color display self render-shot
    ; define do-erase-entity
    
  end-implement
  
  <entity> begin-class <ship>
    
    cell member ship-heading
    cell member ship-delta-heading
    
    method do-spawn-ship ( ship -- )
    method turn-ship ( angle ship -- )
    method thrust-ship ( speed ship -- )
    method ship-shoot ( ship -- )
    method render-ship ( color display ship -- )
    
  end-class
  
  <ship> class-size buffer: ship
  
  : init-ship ( -- ) <ship> ship init-object ;
  
  : spawn-ship ( -- )
    true wave-start !
    systick::systick-counter wave-start-systick !
    ship do-spawn-ship
  ;
  
  : deactivate-ship ( -- ) ship deactivate-entity ;
  
  : update-ship ( interval -- ) ship update-entity ;
  
  : draw-ship ( display -- ) ship draw-entity ;
  
  : erase-ship ( display -- ) ship erase-entity ;
  
  <ship> begin-implement
    
    :noname { self -- }
      self <entity>->new
      0e0 self ship-heading !
      0e0 self ship-delta-heading !
    ; define new
    
    :noname { self -- }
      arena-width 2e0 v/ arena-height 2e0 v/ 0e0 0e0
      self activate-entity
      vpi 2e0 v/ self ship-heading !
      0e0 self ship-delta-heading !
    ; define do-spawn-ship
    
    :noname { angle self -- }
      self ship-delta-heading @ angle v+
      self ship-delta-heading !
    ; define turn-ship
    
    :noname { thrust self -- }
      self ship-heading @ { heading }
      heading vcos thrust v* heading vsin thrust v*
      self adjust-entity-delta
    ; define thrust-ship
    
    :noname { self -- }
      self ship-heading @ { heading }
      heading vcos { heading-cos }
      heading vsin { heading-sin }
      self entity-delta@ { delta-x delta-y }
      self entity-coord@
      delta-x heading-cos base-shot-speed v* v+
      delta-y heading-sin base-shot-speed v* v+
      spawn-shot
      heading-cos recoil-speed v* heading-sin recoil-speed v*
      self adjust-entity-delta
    ; define ship-shoot
    
    :noname { interval self -- }
      interval self <entity>->do-update-entity
      self ship-heading @ { heading }
      self ship-delta-heading @ { delta-heading }
      delta-heading v0<> if
        heading delta-heading interval v* v+
        self ship-heading !
        delta-heading vabs ship-heading-decel interval v* v-
        0e0 vmax delta-heading dup vabs v/ v*
        self ship-delta-heading !
      then
      self entity-coord@ check-collide-asteroids if
        wave-start @ not if
          beep
          -1 lives +!
          lives @ 0>= if spawn-ship then
        then
      then
    ; define do-update-entity
    
    :noname { color display self -- }
      self entity-coord@ { x y }
      color x y convert-coord ship-body-radius v>n
      display draw-filled-circle
      self ship-heading @ { heading }
      x heading vcos ship-dir-radius v* v+ { x1 }
      y heading vsin ship-dir-radius v* v+ { y1 }
      color x y convert-coord x1 y1 convert-coord
      display draw-pixel-line
    ; define render-ship
    
    :noname { display self -- }
      ship-color display self render-ship
    ; define do-draw-entity
    
    :noname { display self -- }
      bk-color display self render-ship
    ; define do-erase-entity
    
  end-implement
  
  : init-world ( -- ) init-asteroids init-shots init-ship ;
  
  initializer init-world
  
  : deactivate-world ( -- )
    deactivate-asteroids deactivate-shots deactivate-ship
  ;
    
  : update-world { interval -- }
    interval update-asteroids
    interval update-shots
    interval update-ship
  ;
  
  : draw-world { display -- }
    display draw-asteroids
    display draw-shots
    display draw-ship
    display draw-info
  ;
  
  : erase-world { display -- }
    display erase-asteroids
    display erase-shots
    display erase-ship
    display erase-info
  ;
  
  3 constant init-asteroid-count
  
  : start-world ( -- )
    deactivate-world
    wave @ init-asteroid-count + 0 ?do start-asteroid loop
    spawn-ship
  ;
  
  vpi constant ship-turn-speed
  10e0 constant ship-thrust
  
  : handle-thrust ( -- ) ship-thrust ship thrust-ship ;
  
  : handle-turn-right ( -- )
    ship-turn-speed vnegate ship turn-ship
  ;
  
  : handle-turn-left ( -- )
    ship-turn-speed ship turn-ship
  ;
  
  : handle-shoot ( -- ) ship ship-shoot ;
  
  $1B constant escape
  
  : empty-keys ( -- ) begin key? while key drop repeat ;

  : handle-screenshot ( -- )
    [:
      screenshot-fs@ { fs }
      fs if
        screenshot-path@ fs ['] take-screenshot try-and-display-error 0<> if
          drop 2drop
        then
      then
    ;] console::with-serial-error-output
  ;
  
  : handle-key ( -- exit? )
    reset-keymap
    update-keymap
    [char] q keymap-pressed@ if true exit then
    UP_ARROW keymap-pressed@ if handle-thrust then
    RIGHT_ARROW keymap-pressed@ if handle-turn-right then
    LEFT_ARROW keymap-pressed@ if handle-turn-left then
    bl keymap-pressed@ if handle-shoot then
    false
  ;
  
  2 constant init-lives
  5 constant extra-life-wave
  
  : play-rocks ( -- )
    0 wave !
    init-lives lives !
    begin
      ansi-term::hide-cursor
      true raw-keys-enabled!
      clear-keymap
      [: dup clear-pixmap update-display ;] with-term-display
      start-world
      systick::systick-counter { last-systick }
      begin
        last-systick [: { last-systick display }
          display erase-world
          systick::systick-counter { current-systick }
          wave-start @ if
            current-systick wave-start-systick @ -
            wave-start-ticks < wave-start !
          then
          current-systick last-systick - u>v 10000e0 v/
          update-world
          handle-key { exit-key? }
          display draw-world
          display update-display
          current-systick exit-key?
        ;] with-term-display
        { exit-key? } to last-systick
        [char] s keymap-released@ if handle-screenshot then
        exit-key? lives @ 0< or if
          [: dup clear-pixmap update-display ;]
          with-term-display
          page
          ." *** GAME OVER ***" cr cr
          ." You survived " wave @ . ." waves" cr
          false raw-keys-enabled!
          clear-keymap
          1000 ms
          empty-keys
          ansi-term::show-cursor
          exit
        then
        asteroid-count 0=
      until
      1 wave +!
      wave @ extra-life-wave umod 0= if 1 lives +! then
    again
  ;
  
end-module
1 Like

'Optical illusion 2
Color &Hffffff,0
CLS
cx=300
sz=30
'Line 0,0,319,0
For j=0 To 2*cx/sz
y=j*sz
d=Abs(((j+2) Mod 4)-2)
For x=0 To 2*cx+2*sz Step 2*sz
xx=x+sz/3*d-2*sz
For tx=1 To sz-1
For ty=1 To sz-1
px=xx+tx+2+sz-2
py=y+ty
If (px<320) And (py<320) And(px>=0) And (py>=0) Then
Pixel px,py
EndIf
Next ty
Next tx

Next x
Next j

2 Likes

'Clothoid
CLS
t=10
n=10000
scale=100
dt=t/n
xc=160
yc=160

For i=0 To 1
tm=0
x0=xc
y0=yc
prevx=0
prevy=0

For j=0 To n
dx=Cos(tm*tm)*dt
dy=Sin(tm*tm)*dt
tm=tm+dt
currx=prevx+dx
curry=prevy+dy
x1=xc+(1-2*i)*currx*scale
y1=yc-(1-2*i)*curry*scale
Line x0,y0,x1,y1
x0=x1
y0=y1
prevx=currx
prevy=curry
Next j
Next i

1 Like

'H-tree fractal
Color &Hffffff,0
CLS

drawhf(160,160,80,5)

Sub drawhf x1,y1,rz,min
Local x11,y11,x01,y01,x00,y00,x10,y10

x11=x1+rz
y11=y1+rz
x01=x1-rz
y01=y1+rz
x00=x1-rz
y00=y1-rz
x10=x1+rz
y10=y1-rz

drawh(x1,y1,rz)

If rz/2>=min Then
drawhf(x11,y11,rz/2,min)
drawhf(x01,y01,rz/2,min)
drawhf(x10,y10,rz/2,min)
drawhf(x00,y00,rz/2,min)
End If
End Sub

Sub drawh x,y,raz
Line x-raz,y-raz,x-raz,y+raz
Line x-raz,y,x+raz,y
Line x+raz,y-raz,x+raz,y+raz
End Sub

1 Like

'figure, Luis Alberto Migliorero
CLS
dr=Pi/180
xc=160
yc=160
h2=Pi/2
k2=2
k3=3
k5=0.25
k6=6
k8=8
ra=80
u=1
z=0
For rs=ra To 0 Step -1.5
For a=0 To 360 Step 0.1
t=a*dr
p1=Abs(Cos(k3*t))+k2*(k5-Abs(Cos(k3*+h2)))
p2=k2+k8*Abs(Cos(k6*t+h2))
r=rs*(u+p1/p2)
x2=xc+r*Cos(t)
y2=yc+r*Sin(t)
cc=1+Int((ra-rs)/10)
'EGA color
If cc And 8 Then
cv=255
Else
cv=127
End If
If cc And 1 Then
bb=cv
Else
bb=0
End If
If cc And 2 Then
gg=cv
Else
gg=0
End If
If cc And 4 Then
rr=cv
Else
rr=0
End If

Pixel x2,y2,RGB(rr,gg,bb)
Next a
Next rs

1 Like

'Orbits of a Dynamic System
'Lauerier,1991
Dim co(8)
co(0)=RGB(0,0,0)
co(1)=RGB(0,0,255)
co(2)=RGB(0,255,0)
co(3)=RGB(0,255,255)
co(4)=RGB(255,0,0)
co(5)=RGB(255,0,255)
co(6)=RGB(255,255,0)
co(7)=RGB(255,255,255)

CLS
Dim xa(8),ya(8),pa(8)
Restore cloud1
a=-0.5
b=2
For i=0 To 7
Read xa(i),ya(i),pa(i)
Next i

For k=0 To 7
x=xa(k)
y=ya(k)
p=pa(k)
GoSub l140
For n=0 To p
cc=n Mod 8
Color co(cc)
Pixel x*10+160,y*10+160
z=x
x=y+w
GoSub l140
y=w-z
Next n
Next k
'save image"orbit.bmp
End
l140:
w=x*(a+b/(1+Abs(x)))
Return

cloud1:
Data 2,0,200,4,0,400,6,0,600,8,0,800
Data 10,0,1000,12,0,1200,14,0,1400
Data 16,0,1600

2 Likes

'tree fractal IBM PC BASIC
CLS
da=Pi/4
shrink=0.67
a=Pi/2
l=80
level=0
maxlev=10
x=159
y=0
Dim xs(maxlev+1)
Dim ys(maxlev+1)
GoSub l1000
'save image"treef.bmp
End
l1000:
dx=l*Cos(a)
dy=l*Sin(a)
nx=x+dx
ny=y+dy
Line x,319-y,nx,319-ny
xs(level)=x
ys(level)=y
x=nx
y=ny
level=level+1
a=a+da
l=l*shrink
If level<maxlev Then GoSub l1000

a=a-da*2
If level<maxlev Then GoSub l1000

a=a+da
l=l/shrink
level=level-1
x=xs(level)
y=ys(level)
Return

2 Likes

'Orbits of a Dynamic System
'Lauerier,1991
Dim co(8)
co(0)=RGB(0,0,0)
co(1)=RGB(0,0,255)
co(2)=RGB(0,255,0)
co(3)=RGB(0,255,255)
co(4)=RGB(255,0,0)
co(5)=RGB(255,0,255)
co(6)=RGB(255,255,0)
co(7)=RGB(255,255,255)

CLS
zo=3'zoom
a=3.5
b=-3
x=3.21
y=6.54
GoSub 110
For n=0 To 10000
Color co((n Mod 64)/8)
Pixel x*zo+160,y*zo+160
z=x
x=y+w
GoSub 110
y=w-z
Next n
End
110
If x>1 Then w=a*x+b*(x-1):Return
If x<-1 Then w=a*x+b*(x+1):Return
w=a*x
Return
'alternatives for gosub
w=a*x+b*Sin(x):Return
w=a*x+b*Cos(x):Return
w=a+b*Sin(x):Return
w=a+b*Cos(x):Return

If Abs(x)<1 Then
w=a*x
Else
w=b*x+(a-b)/x
EndIf
Return

2 Likes

Recursive “fractal” trees - love them, so I did one on my system here using turtle graphics: Recorded it like the last demo I used, but for now here is a screenshot.

Oh, just thought - I’ll plug your numbers in:

not quite identical, but close enough.

Cheers,

-Gordon

1 Like

GOSUB, that’s a blast from the past! Reminds me of classic micro-era BASIC’s which lacked proper structured subroutines (aside from BBC BASIC, which indeed had them).

An original way to use recursion, don’t you think?

The older MS Basics did support recursion - of a fashion. You can GOSUB yourself. The issue then is that there is a very small limit to the depth you can GOSUB to and of-course no local variables - which you can emulate using arrays and a pointer which you can increment each time you enter and decrement each time you leave…

-G

Stuff like that is why I frankly never really liked BASIC, even as a kid when it was my only real option (I had access to Logo, but I did not have proper documentation to make full use of it, and I had access to the Apple //e’s ‘miniassembler’, but that was not very useful as it lacked labels and equates, and as an 8-year old I never thought of hand-assembling code). I’d get books from the local library on all kinds of languages, and be frustrated that what I could really use myself lacked things like proper procedures and local variables like those I saw in books.

Animating the tree… Here’s a little short:

And here is the code - One day it might be nice to think someone else will be able to run it (seriously, if interested then do get in-touch!) but the code is here for inspiration, if nothing else.


// Turtle tree with recursion

GET "libhdr"
GET "math"
GET "vdu"
GET "rubyKeys"

LET d2r (d) = d #* M_DEG2R

AND tree (len, ang, lim) BE
{
  IF lim < 1 THEN RETURN
  IF len < 2 THEN RETURN

  turtleMove (len)
  turtleTurn (ang)
  tree (len * 67/100, ang, lim - 1)
  turtleTurn (#- 2.0 #* ang)
  tree (len * 67/100, ang, lim - 1)
  turtleTurn (ang)
  turtleMove (-len)
}

AND start () = VALOF
{
  LET len = 80
  LET ang = 22
  LET lim = 10
  LET key = ?

  turtleInit ()
  turtleMoveTo (160,0)
  turtlePen (1)

  {
    vduCls ()
    tree (len, d2r (FLOAT ang), lim)

    vduXY (0,0)
    sawritef ("Len: %d, ang: %d, lim: %d  ",
      len, ang, lim)

    key := sardch ()
    SWITCHON key INTO
    {
      CASE KEY_ARROW_UP:    len := len + 5 ; ENDCASE
      CASE KEY_ARROW_DOWN:  len := len - 5 ; ENDCASE
      CASE KEY_ARROW_LEFT:  ang := ang - 2 ; ENDCASE
      CASE KEY_ARROW_RIGHT: ang := ang + 2 ; ENDCASE
      CASE '=': CASE '+':   lim := lim + 1 ; ENDCASE
      CASE '-': CASE '_':   lim := lim - 1 ; ENDCASE
      CASE 'q': BREAK
    }
    IF ang <  2 THEN ang :=  2
    IF lim > 15 THEN lim := 15
    IF len <  5 THEN len :=  5
  } REPEAT

  RESULTIS 0
}

-G

3 Likes

Converted to MMbasic

Option BASE 0
' Recursive tree drawing routine
Sub Tree(len, ang, lim)
If lim < 1 Then Exit Sub
If len < 2 Then Exit Sub
Turtle FORWARD len
Turtle RIGHT ang
Tree len * 0.67, ang, lim - 1
Turtle LEFT ang * 2
Tree len * 0.67, ang, lim - 1
Turtle RIGHT ang
Turtle BACK len
End Sub

' Main program
len = 80
ang = 22
lim = 10
CLS
Turtle reset
Turtle HOME
Turtle PEN DOWN
' Move near bottom centre of screen
Turtle SET XY MM.HRES / 2, MM.VRES - 20
Turtle SET HEADING 0
Do
CLS
Turtle SET XY MM.HRES / 2, MM.VRES - 20
Turtle SET HEADING 0
Tree len, ang, lim
Text 0,0,"Len: " + Str$(len) +"  Ang: " + Str$(ang) +"  Lim: " + Str$(lim)
Do
k$ = Inkey$
Loop Until k$<>""
Select Case k$
Case Chr$(128)        ' Up arrow (may vary by firmware)
len = len + 5
Case Chr$(129)        ' Down arrow
len = len - 5
Case Chr$(130)        ' Left arrow
ang = ang - 2
Case Chr$(131)        ' Right arrow
ang = ang + 2
Case "+"
lim = lim + 1
Case "="
lim = lim + 1
Case "-"
lim = lim - 1
Case "_"
lim = lim - 1
Case "q","Q"
Exit Do
End Select
If ang < 2 Then ang = 2
If lim > 15 Then lim = 15
If lim < 1 Then lim = 1
If len < 5 Then len = 5
Loop
4 Likes

'DYCPtro
t$="                                   "
t$=t$+"Welcome to DYCP, 1st demo at picocalc"
 Dim s(257) As integer
 For i=0 To 255
 s(i)=70+70*Sin(i*Pi/128)
 Next i
tp%=1'text posiion
dt%=0
a0%=0
b0%=0
FRAMEBUFFER create

Do
FRAMEBUFFER write f
CLS
tt%=tp%
aa%=a0%
bb%=b0%
For i=0 To 39
y%=s(aa%)+s(bb%)
Print @(dt%+i*8,y%) Mid$(t$,tt%,1)

tt%=tt%+1
If tt%>Len(t$) Then tt%=1

aa%=(aa%+3) And 255
bb%=(bb%+2) And 255

Next i
a0%=(a0%+7) And 255
b0%=(b0%+5) And 255

FRAMEBUFFER wait
FRAMEBUFFER write n
FRAMEBUFFER copy f,n

dt%=dt%-1
If dt%=-8 Then
dt%=0
tp%=tp%+1
If tp%>Len(t$) Then
tp%=1
a0%=(a0%+3) And 255
b0%=(b0%+5) And 255
EndIf
EndIf

Loop Until Inkey$=Chr$(27)

3 Likes

I implemented a similar interactive tree at zeptoforth/test/rp2350/picocalc_tree_interactive.fs at master · tabemann/zeptoforth · GitHub. Note that this requires an RP2350 as it uses floating point. Since the original posting of this comment, this demo has been updated to incorporate a textual display.

It is recommended one upgrade to the latest Turtle graphics implementation at zeptoforth/extra/rp_common/turtle_picocalc.fs at master · tabemann/zeptoforth · GitHub as this provides a critical optimization, specifically a sine lookup table.

Here is a screenshot:

The source code is as follows:

\ Copyright (c) 2025-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.

\ The controls are:
\ 
\ Up: Increase base branch length
\ Down: Decrease base branch length
\ Right: Increase branch angle
\ Left: Decrease branch angle
\ =: Increase recursion depth
\ -: Decrease recursion depth
\ +: Increase inherited branch length proportion
\ _: Decrease inherited branch length proportion
\ S: Take a screenshot
\ Q: Exit

begin-module tree

  turtle import
  pixmap8 import
  font import
  picocalc-term import
  picocalc-keys import
  picocalc-screenshot import
  float32 import
  
  $B4 constant LEFT_ARROW
  $B5 constant UP_ARROW
  $B6 constant DOWN_ARROW
  $B7 constant RIGHT_ARROW
  
  5 constant len-inc
  1e0 48e0 v/ constant len-fract-inc
  2.5e0 constant angle-inc
  1 constant level-inc
  
  10 value level
  100 value len
  2e0 3e0 v/ value len-fract
  45e0 value angle
  
  : string> { addr bytes buf-addr -- buf-addr' }
    addr buf-addr bytes move bytes buf-addr +
  ;
  
  : n> ( n buf-addr -- buf-addr' )
    base @ { saved-base }
    [: 10 base ! swap format-integer + ;] try
    saved-base base ! ?raise
  ;
  
  : v> ( v buf-addr -- buf-addr' )
    base @ { saved-base }
    [: 10 base ! swap v>f64 format-fixed 6 min + ;] try
    saved-base base ! ?raise
  ;
  
  255 255 255 rgb8 constant info-color
  16 constant info-x
  16 constant info-y

  : draw-info ( -- )
    256 [:
      [: { buf display }
        buf { buf' }
        s" Level: " buf' string> to buf'
        level buf' n> to buf'
        s"  Len: " buf' string> to buf'
        len buf' n> to buf'
        s"  Angle: " buf' string> to buf'
        angle v>n buf' n> to buf'
        s"  Len-fract: " buf' string> to buf'
        len-fract buf' v> to buf'
        info-color buf buf' buf - info-x info-y display
        term-font@ draw-string-to-pixmap8
      ;] with-term-display
    ;] with-allot
  ;
  
  defer draw-tree-branch
  :noname { len level -- }
    angle v>n { angle }
    len n>v len-fract v* v>n to len
    len forward
    level 0> len 1 > and if
      angle left
      len level 1- draw-tree-branch
      angle 2 * right
      len level 1- draw-tree-branch
      angle left
    then
    penup len backward pendown
  ; is draw-tree-branch
  
  : draw-tree ( -- )
    updateoff
    penup clear 0 -160 setxy 0 setheading pendown
    len level draw-tree-branch draw-info
    updateon
  ;
  
  : empty-keys ( -- ) begin key? while key drop repeat ;

  : handle-screenshot ( -- )
    [:
      screenshot-fs@ { fs }
      fs if
        screenshot-path@ fs ['] take-screenshot try-and-display-error 0<> if
          drop 2drop
        then
      then
    ;] console::with-serial-error-output
  ;
  
  : run-tree ( -- )
    page
    true raw-keys-enabled!
    true { update }
    clear-keymap
    hideturtle
    begin
      false { done }
      update if draw-tree false to update then
      update-keymap
      RIGHT_ARROW keymap-pressed@ if
        angle angle-inc v+ to angle true to update
      then
      LEFT_ARROW keymap-pressed@ if
        angle angle-inc v- to angle true to update
      then
      UP_ARROW keymap-pressed@ if
        len-inc +to len true to update
      then
      DOWN_ARROW keymap-pressed@ if
        len-inc negate +to len true to update
      then
      [char] = keymap-pressed@ if
        level level-inc + 15 min to level true to update
      then
      [char] - keymap-pressed@ if
        level level-inc - 0 max to level true to update
      then
      [char] + keymap-pressed@ if
        len-fract len-fract-inc v+ to len-fract true to update
      then
      [char] _ keymap-pressed@ if
        len-fract len-fract-inc v- to len-fract true to update
      then
      [char] s keymap-released@ if handle-screenshot then
      [char] q keymap-released@ if true to done then
      reset-keymap
    done until
    false raw-keys-enabled!
    1000 ms
    empty-keys
    showturtle
  ;
  
end-module
1 Like

Very nice!

FWIW: My stuff runs under the RISC-V CPUs and floating point is all done in single precision (32-bit) iEEE754) software.. Which one day I’ll improve & finish although right now it’s “fast enough” and I have sin/cos/tan as well as the usual arithmetic operations.

-Gordon