Created
January 13, 2025 19:49
-
-
Save danstowell/661b5f8d871a6b5c4d2a27db9b6b53dd to your computer and use it in GitHub Desktop.
UXN code for "linf_land" demo by me 2024: https://www.youtube.com/watch?v=5dqfhiMWZdk
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
( linf_land_1.tal | |
~/dev/uxn/uxnasm linf_land_1.tal linf_land_1.rom && ~/dev/uxn/uxncli linf_land_1.rom | |
~/dev/uxn/uxnasm linf_land_1.tal linf_land_1.rom && ~/dev/uxn/uxnemu -3x linf_land_1.rom | |
) | |
( devices ) | |
|00 @System [ &vector $2 &pad $6 &r $2 &g $2 &b $2 ] | |
|20 @Screen [ &vector $2 &width $2 &height $2 &pad $2 &x $2 &y $2 &addr $2 &pixel $1 &sprite ] | |
|c0 @DateTime [ &year $2 &month $1 &day $1 &hour $1 &minute $1 &second $1 &dotw $1 &doty $2 &isdst $1 ] | |
( zero page ) | |
|0000 | |
@framecounter $2 | |
( ------------------------------------------------------------------------------------------------------ ) | |
( Some simple macros ) | |
%NL { #0a #18 DEO } ( -- ) | |
( these printdigit methods are BAD, DON'T USE THEM! They don't work for "abcdef" values. ) | |
%PRINTDIGIT { #30 ADD #18 DEO } ( number -- ) | |
%PRINTDIGITS { DUP #04 SFT PRINTDIGIT #0f AND PRINTDIGIT } ( number -- ) | |
( minimum of two values ) | |
%MIN { LTHk JMP SWP POP } ( b c -- retval ) | |
( maximum of two values ) | |
%MAX { GTHk JMP SWP POP } ( b c -- retval ) | |
( modular distance 1D ) | |
%moddist1d { | |
SUBk | |
SWP ROT | |
SUB | |
MIN | |
} ( b c -- dist ) | |
( ------------------------------------------------------------------------------------------------------ ) | |
( main program ) | |
|0100 | |
( set system colors ) | |
#29ec .System/r DEO2 | |
#01c0 .System/g DEO2 | |
#2ce5 .System/b DEO2 | |
( outputting some basics, and building up to outputting the results of our distance functions ) | |
#08 #0f MIN PRINTDIGITS NL ( 08 ) | |
#10 #23 moddist1d PRINTDIGITS NL ( 13 ) | |
#23 #10 moddist1d PRINTDIGITS NL ( 13 ) | |
#03 #fe moddist1d PRINTDIGITS NL ( 05 ) | |
;val1 ;val2 #02 ;distLinf JSR2 PRINTDIGITS NL ( 05 ) | |
;valtmp ;val4 #02 ;distLinf JSR2 PRINTDIGITS NL ( 7< ) | |
( ------------------------------------------------------------------------------------------------- ) | |
( now some work with "nnquerytable" -- initialising it and testing that it works ) | |
( First we load data INTO the querytable ) | |
;nnquerytable | |
;valtmp OVR2 STA2 | |
#0002 ADD2 ;val1 OVR2 STA2 | |
#0002 ADD2 ;val2 OVR2 STA2 | |
#0002 ADD2 ;val3 OVR2 STA2 | |
#0002 ADD2 ;val4 OVR2 STA2 | |
NL | |
( and at last -- invoke our nearest-neighbour subroutine, and print its outcomes ) | |
#02 #04 ;nnquerytable ;nearestneighbour JSR2 ( ndims ncentroids tbladdrHILO -- bestindex bestdistance ) | |
PRINTDIGITS NL PRINTDIGITS NL ( bestdistance is printed first, then bestindex: 44, 03 ) | |
( ------------------------------------------------------------------------------------------------- ) | |
( NOW LET'S DRAW FUN THINGS ) | |
#00 .framecounter STZ | |
;on-frame .Screen/vector DEO2 | |
BRK | |
( ------------------------------------------------------------------------------------------------------ ) | |
@on-frame ( -> ) | |
.framecounter LDZ INC #01 AND DUP .framecounter STZ ( increment and store ) | |
#00 EQU JMP BRK | |
;erasecentroids JSR2 | |
;erasegrid JSR2 | |
;makeamove JSR2 | |
;drawcentroids JSR2 | |
;drawvoronoigrid JSR2 | |
BRK | |
( ------------------------------------------------------------------------------------------------------ ) | |
( data ) | |
@val1 07 04 | |
@val2 09 09 | |
@val3 dc 76 | |
@val4 82 f2 | |
@valtmp 98 76 ( the current position to be used for queries will be loaded into this "variable" ) | |
( the "query table". the ADDRESS of valtmp will be loaded into the first entry, the others will be for the reference positions ) | |
@nnquerytable $10 ( reserve space: for each pointer one short, thus 2 * (1 + numcentroids) ) | |
( sprite ) | |
@square fe82 8282 8282 fe00 | |
@dotter 1010 10fe 1010 1000 | |
@eraser ffff ffff ffff ffff | |
( ------------------------------------------------------------------------------------------------------ ) | |
@drawcentroids | |
;square .Screen/addr DEO2 | |
#00 ;val1 LDA2 #00 SWP .Screen/y DEO2 .Screen/x DEO2 | |
#41 .Screen/sprite DEO | |
#00 ;val2 LDA2 #00 SWP .Screen/y DEO2 .Screen/x DEO2 | |
#42 .Screen/sprite DEO | |
#00 ;val3 LDA2 #00 SWP .Screen/y DEO2 .Screen/x DEO2 | |
#43 .Screen/sprite DEO | |
#00 ;val4 LDA2 #00 SWP .Screen/y DEO2 .Screen/x DEO2 | |
#44 .Screen/sprite DEO | |
JMP2r | |
@erasecentroids | |
;eraser .Screen/addr DEO2 | |
#00 ;val1 LDA2 #00 SWP .Screen/y DEO2 .Screen/x DEO2 | |
#40 .Screen/sprite DEO | |
#00 ;val2 LDA2 #00 SWP .Screen/y DEO2 .Screen/x DEO2 | |
#40 .Screen/sprite DEO | |
#00 ;val3 LDA2 #00 SWP .Screen/y DEO2 .Screen/x DEO2 | |
#40 .Screen/sprite DEO | |
#00 ;val4 LDA2 #00 SWP .Screen/y DEO2 .Screen/x DEO2 | |
#40 .Screen/sprite DEO | |
JMP2r | |
@drawvoronoigrid | |
;dotter .Screen/addr DEO2 | |
#f0 #f0 ;valtmp STA2 ( initialise our iteration counters ) | |
&whilegrid1 | |
&whilegrid2 | |
#00 ;valtmp LDA2 #00 SWP .Screen/y DEO2 .Screen/x DEO2 ( set draw location ) | |
( find the NN index, and use index+1 as the colour byte ) | |
( a neat trick! our "grid-point" value is already being pushed into "valtmp" and thus into the query table ) | |
#02 #04 ;nnquerytable ;nearestneighbour JSR2 ( ndims ncentroids tbladdrHILO -- bestindex bestdistance ) | |
POP .Screen/sprite DEO ( pop discards the distance value, and - another neat trick! - the index becomes the colour val consumed by sprite ) | |
( if the position has hit zero, we want to skip out ) | |
;valtmp LDA DUP #00 EQU ,&endwhilegrid2 JCN | |
( else let's decrement ) | |
#10 SUB ;valtmp STA | |
,&whilegrid2 JMP | |
&endwhilegrid2 | |
POP | |
( if the position has hit zero, we want to skip out ) | |
;valtmp #01 ADD LDA DUP #00 EQU ,&endwhilegrid1 JCN | |
( else let's decrement ) | |
#10 SUB ;valtmp #01 ADD STA | |
#f0 ;valtmp STA ( put the x counter back to its high starting point ) | |
,&whilegrid1 JMP | |
&endwhilegrid1 | |
POP | |
JMP2r | |
( I repeat the above code for the eraser. Not sure how best to do it with less duplication. ) | |
@erasegrid | |
;eraser .Screen/addr DEO2 | |
#f0 #f0 ;valtmp STA2 ( initialise our iteration counters ) | |
&whilegrid1 | |
&whilegrid2 | |
#00 ;valtmp LDA2 #00 SWP .Screen/y DEO2 .Screen/x DEO2 ( set draw location ) | |
#00 .Screen/sprite DEO ( pop discards the distance value, and - another neat trick! - the index becomes the colour val consumed by sprite ) | |
( if the position has hit zero, we want to skip out ) | |
;valtmp LDA DUP #00 EQU ,&endwhilegrid2 JCN | |
( else let's decrement ) | |
#10 SUB ;valtmp STA | |
,&whilegrid2 JMP | |
&endwhilegrid2 | |
POP | |
( if the position has hit zero, we want to skip out ) | |
;valtmp #01 ADD LDA DUP #00 EQU ,&endwhilegrid1 JCN | |
( else let's decrement ) | |
#10 SUB ;valtmp #01 ADD STA | |
#f0 ;valtmp STA ( put the x counter back to its high starting point ) | |
,&whilegrid1 JMP | |
&endwhilegrid1 | |
POP | |
JMP2r | |
( ------------------------------------------------------------------------------------------------------ ) | |
@makeamove | |
.DateTime/second DEI ( we use the clockseconds as a source of simple arbitrariness and change ) | |
DUP #03 AND #01 ADD ( which centroid to move (seconds & 03 + 1) ) | |
OVR #01 AND ( which axis (seconds & 1) ) | |
ROT #03 AND #00 EQU #10 SFT #01 SUB #10 SFT ( and whether it's +1 or -1 (something consistent, unchanging in each onesecond bout, maybe (seconds_mod_4==0)*2-1) ) | |
( now we should have [ whichc whichaxis adder ] ... ) | |
ROT #10 SFT ;nnquerytable ROT #00 SWP ADD2 LDA2 ( now whichaxis adder vectoraddress2 ) | |
OVR2 POP #00 SWP ADD2 ( now adder addhere2 ) | |
ROT #00 OVR2 LDA NIP ADD ( now addhere2 newval ) | |
ROT ROT STA | |
JMP2r | |
( ------------------------------------------------------------------------------------------------------ ) | |
( L-inf distance calculated modulo, APPLIED TO VECTORLISTS IN MAIN MEMORY, written as a subroutine which we then put after the main ) | |
@distLinf ( point1addr point2addr dim -- thedist ) | |
( HOW THIS WORKS: we progressively increment the pointers and decrement "dim" until it hits zero, accumulating the distance as we go. ) | |
( push a zero at stackpos2 - this is the distance value, already finished if dim equals zero! ) | |
#00 SWP ( Stack: point1HILO point2HILO thedist dim ) | |
&loopsbacktohere | |
DUP #00 EQU ,&exithere JCN ( if the dim (at top of stack) is zero, jump to ,&exithere, we've done it all ) | |
( decrement dim, and push the dim DOWN BELOW THE DIST because we'll not need it for the rest of this iteration ) | |
#01 SUB SWP | |
( here we use short-rotting to get the pointers to the top of the stack, | |
retrieve their values onto the stack, and increment the pointers. ) | |
( there are some dummy "00" values used for padding in here. I wonder if it can be achieved without them. ) | |
ROT2 ROT2 | |
LDAk #00 | |
ROT2 | |
LDAk #00 SWP2 | |
INC2 ( point1 has been incremented ) | |
ROT2 ROT2 POP SWP POP | |
ROT2 INC2 ( point2 has been incremented ) | |
SWP2 ( Stack now: dim thedist point1HILO point2HILO val1 val2 ) | |
( here we calculate the next 1Ddistance, then rearrange so we can combine it (max-it) with the numbers so far ) | |
moddist1d | |
( the tricky part: we want to rearrange to get thedist and newdist together, which requires some shortsmashing............... ) | |
ROT2 ROT ROT | |
MAX ( max is how we combine dimensions, because it's L-inf distance ) | |
ROT2 ROT ROT ( Stack: point2HILO point1HILO thedist dim ) | |
( We're now BACK YAYYYY in the arrangement we entered the loop with! ) | |
,&loopsbacktohere JMP | |
&exithere | |
( We now want to REDUCE the stack leaving only "thedist" but we've got point2HILO point1HILO thedist dim ) | |
ROT2 ROT2 POP2 POP2 POP | |
JMP2r | |
( ------------------------------------------------------------------------------------------------------ ) | |
( Find which of the given centroids is the NN. ) | |
( IMPORTANT NOTES about the arguments and returns: | |
* It uses 1-based indexing: the returned value is in the range [1, ncentroids] | |
* The "tbladdr" should be a pointer to a table, which itself contains ncentroids+1 addrs. The first is the addr of the query point, the rest are the centroids. | |
) | |
@nearestneighbour ( ndims ncentroids tbladdrHILO -- bestindex bestdistance ) | |
( | |
Implementation note: instead of keeping "ncentroids" living in here, I first create a pointer | |
to where the LASTcentroid is stored i.e. currentcentroidaddr = tbladdr + 2 * ncentroids | |
and then to handle the iteration I simply do currentcentroidaddr -= 2 and terminate if it MATCHES tbladdr. | |
) | |
( create a pointer initialised to where the LASTcentroid is stored i.e. currentcentroidaddr = tbladdr + 2 * ncentroids ) | |
ROT #00 SWP | |
#10 SFT2 ADD2k NIP2 ( stack: ndims tbladdrHILO currentcentroidaddr ) | |
( instantiate a "bestindex" value at zero and a "bestdist" value at ff -- these will live on the RETURNstack ) | |
LIT2r 00ff ( Stack: ndims tbladdrHILO currentaddrHILO | besti bestd ) | |
&while | |
( rotate and duplicate, to get the "dist" routine-call arguments ready. | |
duplication because the routine will consume them, but we also need to dereference the pointers. ) | |
ROT2k ROT2 LDA2 ROT2 LDA2 ROT2 NIP ( stack: ndims tbladdrHILO currentaddrHILO tbladdrHILOgot currentaddrHILOgot ndims ) | |
;distLinf JSR2 ( point1addr point2addr dim -> newdist ) | |
( test whether the new distance is better. if not then we simply forget it and skip onwards. if so then we update our besties. ) | |
DUP STHkr GTH ?&afterupdatebesties ( i believe this leaves the stack unchanged ) | |
( now update besties ) | |
POP2r | |
STH ( the dist goes to return stack, note it should end up on top though ... ) | |
SWP2k SUB2 #01 SFT2 STH POP | |
#00 SWPr | |
&afterupdatebesties | |
POP ( stack: ndims tbladdrHILO currentaddrHILO | besti bestd ) | |
( decrement currentcentroidaddr -= 2, and if it's reached tbladdr then we boing out of the loop, ready to finish up. ) | |
#0002 SUB2 NEQ2k ?&while | |
( set the return values nicely. clear the stack values and then put those 2 items from the return stack in their place. ) | |
POP2 POP2 POP STH2r | |
JMP2r |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment