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 Sirpinski triangle

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

Random noise

– Alex 2021-04-22 18:49 UTC


Related stuff by @eloquence:

– Alex 2021-04-23 09:31 UTC


Please make sure you contribute only your own work, or work licensed under the GNU Free Documentation License. Note: in order to facilitate peer review and fight vandalism, we will store your IP number for a number of days. See Privacy Policy for more information. See Info for text formatting rules. You can edit the comment page if you need to fix typos. You can subscribe to new comments by email without leaving a comment.

To save this page you must answer this question:

Just say HELLO