2021-04-20 Writing 2D Cellular Automata using uxn
Uxn is a portable 8-bit virtual computer inspired by forth-machines, capable of running simple tools and games programmable in its own esoteric assembly language. The distribution of Uxn projects is not unlike downloading a ROM for a console, as Uxn has its own emulator.
It comes with a few programs out of the box, a simple drawing program, a simple tracker, a simple editor.
It’s absolutely fascinating!
Sadly, I also don’t know how to write assembler, or how to think assembler, and I think I would need a very, very simple idea to get started. Something like a 2D game of life visualisation or something like that.
There's this super short introduction to assembly and to uxambly, the programming language for the Uxn stack-machine. That's all I had.
The idea was simple: have a line of cells. Every generation, a cell lives if it has at least one live neighbour and it dies if the neighbours are either both alive or both dead. Draw a dot for every live cell, draw a row for every generation.
Here's the code that I managed to write over the last few days.
%RTN { JMP2r }
%GOTO { JMP2 }
%GOSUB { JSR2 }
( devices )
|0100 ;System { vector 2 pad 6 r 2 g 2 b 2 }
|0110 ;Console { vector 2 pad 6 char 1 byte 1 short 2 string 2 }
|0120 ;Screen { vector 2 width 2 height 2 pad 2 x 2 y 2 addr 2 color 1 }
|0130 ;Audio { wave 2 envelope 2 pad 4 volume 1 pitch 1 play 1 value 2 delay 2 finish 1 }
|0140 ;Controller { vector 2 button 1 key 1 }
|0160 ;Mouse { vector 2 x 2 y 2 state 1 chord 1 }
|0170 ;File { vector 2 pad 6 name 2 length 2 load 2 save 2 }
|01a0 ;DateTime { year 2 month 1 day 1 hour 1 minute 1 second 1 dotw 1 doty 2 isdst 1 refresh 1 }
( program )
|0200
( theme ) #54ac =System.r #269b =System.g #378d =System.b
,main GOTO
BRK
@main ( -- )
( run for a few generations ) #00 #FF
$loop
OVR #00 SWP ,print-line GOSUB
,compute-next GOSUB
,copy-next GOSUB
( incr ) SWP #01 ADD SWP
( loop ) DUP2 LTH ^$loop JNZ
POP2
BRK
( 64 cells )
@cell [ 0001 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000 ]
@next [ 0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000 ]
@print-line ( y -- )
( set ) =Screen.y
( loop through 64 cells ) #00 #FF
$loop
( copy ) OVR #00 SWP DUP2
( pos ) =Screen.x
( addr ) ,cell ADD2
( draw ) PEK2 =Screen.color
( incr ) SWP #01 ADD SWP
( loop ) DUP2 LTH ^$loop JNZ
POP2
RTN
@compute-next ( -- )
( loop through 62 cells ) #01 #FE
$loop
OVR DUP DUP ( three copies of the counter )
#01 SUB #00 SWP ,cell ADD2 PEK2
SWP
#01 ADD #00 SWP ,cell ADD2 PEK2
( the cell dies if the neighbors are either both dead or both alive, i.e. Rule 90 )
NEQ
( one copy of the counter and the life value )
SWP #00 SWP ,next ADD2 POK2
( incr ) SWP #01 ADD SWP
( loop ) DUP2 LTH ^$loop JNZ
POP2
RTN
@copy-next ( -- )
( loop through 64 cells ) #00 #FF
$loop
OVR DUP ( two copies of the counter )
#00 SWP ,next ADD2 PEK2 ( one copy of the counter and the value )
SWP #00 SWP ,cell ADD2 POK2
( incr ) SWP #01 ADD SWP
( loop ) DUP2 LTH ^$loop JNZ
POP2
RTN
Looks like a Sierpiński triangle!
If you have suggestions for my assembler code, let me know!
Comments
Now with a small pseudo-random number generator!
%RTN { JMP2r }
%GOTO { JMP2 }
%GOSUB { JSR2 }
;seed { x 1 w 2 s 2 }
( devices )
|0100 ;System { vector 2 pad 6 r 2 g 2 b 2 }
|0110 ;Console { vector 2 pad 6 char 1 byte 1 short 2 string 2 }
|0120 ;Screen { vector 2 width 2 height 2 pad 2 x 2 y 2 addr 2 color 1 }
|0130 ;Audio { wave 2 envelope 2 pad 4 volume 1 pitch 1 play 1 value 2 delay 2 finish 1 }
|0140 ;Controller { vector 2 button 1 key 1 }
|0160 ;Mouse { vector 2 x 2 y 2 state 1 chord 1 }
|0170 ;File { vector 2 pad 6 name 2 length 2 load 2 save 2 }
|01a0 ;DateTime { year 2 month 1 day 1 hour 1 minute 1 second 1 dotw 1 doty 2 isdst 1 refresh 1 }
( program )
|0200
( theme ) #2aac =System.r #269b =System.g #378d =System.b
,main GOTO
BRK
@main ( -- )
,seed-line GOSUB
( run for a few generations ) #00 #FF
$loop
OVR #00 SWP ,print-line GOSUB
,compute-next GOSUB
,copy-next GOSUB
( incr ) SWP #01 ADD SWP
( loop ) DUP2 LTH ^$loop JNZ
POP2
BRK
( cells )
@cell [ 0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000 ]
@next [ 0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000 ]
@print-line ( y -- )
( set ) =Screen.y
( loop through cells ) #00 #FF
$loop
( copy ) OVR #00 SWP DUP2
( pos ) =Screen.x
( addr ) ,cell ADD2
( draw ) PEK2 =Screen.color
( incr ) SWP #01 ADD SWP
( loop ) DUP2 LTH ^$loop JNZ
POP2
RTN
@compute-next ( -- )
( loop through 62 cells ) #01 #FE
$loop
OVR DUP DUP ( three copies of the counter )
#01 SUB #00 SWP ,cell ADD2 PEK2
SWP
#01 ADD #00 SWP ,cell ADD2 PEK2
( the cell dies if the neighbors are either both dead or both alive, i.e. Rule 90 )
NEQ
( one copy of the counter and the life value )
SWP #00 SWP ,next ADD2 POK2
( incr ) SWP #01 ADD SWP
( loop ) DUP2 LTH ^$loop JNZ
POP2
RTN
@copy-next ( -- )
( loop through cells ) #00 #FF
$loop
OVR DUP ( two copies of the counter )
#00 SWP ,next ADD2 PEK2 ( one copy of the counter and the value )
SWP #00 SWP ,cell ADD2 POK2
( incr ) SWP #01 ADD SWP
( loop ) DUP2 LTH ^$loop JNZ
POP2
RTN
@seed-line ( -- )
#00 =DateTime.refresh
~DateTime.second =seed.x #0000 =seed.w #e2a9 =seed.s
( loop through cells ) #01 #FE
$loop
OVR ( one copy of the counter )
,rand GOSUB
#10 AND ( pick a bit )
SWP #00 SWP ,cell ADD2 POK2
( incr ) SWP #01 ADD SWP
( loop ) DUP2 LTH ^$loop JNZ
POP2
RTN
( https://en.wikipedia.org/wiki/Middle-square_method )
@rand ( -- 1 )
~seed.x #00 SWP DUP2 MUL2
~seed.w ~seed.s ADD2
DUP2 =seed.w
ADD2
#04 SFT SWP #40 SFT ADD
DUP =seed
RTN
-- Alex 2021-04-22 18:49 UTC
----
Related stuff by @eloquence@social.coop:
- http://eloquence.github.io/elixor/
- https://eloquence.github.io/xorworld/
-- Alex 2021-04-23 09:31 UTC
----
On the web:
- https://github.com/aduros/webuxn
- http://compudanzas.net/uxn_tutorial.html
-- Alex 2021-09-15 05:31 UTC
(The access token for short comments is “hello”.)