Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines
(1 edit) (+1)

Here's a more graphical and interactive program - this time, one that draws Lissajous curves based on the parameters given at runtime by the user.

Honestly, the resolution of the default mode's screen is a bit small for this, so some of the curves aren't very recognizable, but at least some of them work - and if you know what's happening, even some of the ones for which the end result isn't very nice can at least be followed while it's being drawn.


The inspiration for this program came from Coding Challenge #116 on The Coding Train, where he generates a grid (or table) of such curves.

Now, the screen resolution in Senbir's default mode is way too small to show a grid of the curves all at once, so instead, the idea here is a virtual grid that the user moves around in, showing one curve at a time.


The grid is 8 by 8, and in addition, this program allows you to set the (initial) angular offset between X and Y, so that you can start drawing at a different point in the grid. The default offset is 8, which corresponds to 90 degrees (the difference between sin and cos) - with offsets from 0 to 31, each step is 11.25 degrees. Thus, with the default offset, you can draw a circle.

Moving around on the grid is done using IJKL - J/L for X and I/K for Y. The current position on the grid is shown in the two center columns (X on left, Y on right), with position 1 at the top and 8 at the bottom.

Changing the offset is done using WASD, where W/S decreases/increases the offset by 1, and A/D decreases/increases the offset by 8. The current offset is shown in the leftmost area, each column being an offset of 8, each row an offset of 1 (essentially, start at 0 in top-left, moving down, wrapping at the bottom to the next column).

Drawing the curve for the currently selected settings is done using Enter. This action uses the top two pixels of the leftmost separator column as status indicators - if either (or both) of these are bright green, it's working on drawing a curve (or preparing for it).

Both position and offset wrap around if you move them past their edges.

Note that using the top half of either grid direction may cause pixels to be skipped while drawing, due to the limited angular resolution being used to draw the curve. I believe I could fix this fairly easily, but only at the cost of making the curve drawing even slower than it already is (probably by a factor of 2), even when not using that half, so (at least for now) I've left that out.


Implementation-wise, this is probably one of the trickier programs I've made, since it uses a combination of both overlays and a variant of the disk runner technique, including overlays that only partially overwrite the previous overlay - plus it works with the built-in bootloader instead of needing a custom ROM.

However, unlike some of my previous programs, this one doesn't make any assumptions about the size of the addresses (memory or disk) - so I believe it could be built to be stored further into a larger disk, simply by prepending an appropriate NILLIST before preprocessing it. I could probably have made it slightly smaller if I did make such assumptions, but I tend to prefer not to (even if only for the challenge).

In all, I suspect I wouldn't like to have to maintain it, especially not after leaving it for long enough I forgot most of it. ^_^'


Somewhat interestingly, more than a quarter of the disk space this program uses is taken up by lookup tables - 68 of the total 250 words. That's in addition to the various DATACs that are interspersed with the code itself. These tables also require a specific alignment on the disk, but I got a bit lucky there and ended up getting the alignment without having to spend any space on it, just by moving them and the overlays around a bit.

About half of that space (32 words) are the precomputed (and prescaled) cosine values, that I'm using not just because I believe it's much faster than computing them on the fly, but because I think it takes less registers and disk space (though this probably depends a bit on which of the algorithms was used).

Another 32 are for the fast keyboard handling. It's actually a constant-time algorithm regardless of which key was pressed (always takes 9 cycles to decide where to go and set up the disk runner to do so), unlike a simple loop or sequence of IFJMPs (which are faster for the first key checked, but slower for later ones, and easily take more total space when there's more than just a couple keys to handle).

In return it both takes some disk space and sets limits on which keys I can use - though I could add 7 more keys to the 9 I have without taking any more space (except for the code that actually handles the actions for those keys of course).

I originally had separate handler code for each key, again for speed, but this ended up taking too much space, so I had to deduplicate them as much as I could - and managed to get it down from 9 to 4, while only adding 2 instructions to each remaining block, which was enough saved to fit everything in.

All together, this means that changing a setting takes about a second per step, regardless of which setting it is, which isn't too bad in my opinion. Not ideal, certainly, but given the restrictions of the default mode, not too bad.


The source code is pretty long, but this program is pretty self-contained, so here's a minified version ready to be put into Senbir:

DATAC 00000000000000000000000000010001
JMP 1 4
DATAC 00000000000000000000000000000001
DATAC 00000000000000000000000000010010
DATAC 00000000000000000000000000011100
MOVI 15 1
MOVI 14 10
MOVI 12 16
MOVI 2 2
MOVI 3 3
GETDATA 1 3 2
MOVO 1 22
MATH 15 2 0
MATH 15 14 0
MOVO 14 10
IFJMP 2 5 3
JMP 3 9
DATAC 00000000000000000000000010011000
GETDATA 1 3 2
MATH 1 3 5
PMOV 15 14 7 30 1 1
MOVO 14 29
MATH 15 14 0
MATH 15 2 0
GETDATA 1 3 2
MOVO 1 0
IFJMP 2 5 3
JMP 1 0
DATAC 00000000000000000000000000110010
JMP 1 17
MATH 3 3 1
PMOV 15 4 0 31 9 1
PMOV 2 3 9 17 9 0
PMOV 15 2 8 30 1 1
SETDATA 0 3 2
MATH 4 2 0
IFJMP 2 2 1
JMP 1 17
NILLIST 2
SETDATA 0 3 3
SETDATA 0 3 2
JMP 1 17
GETDATA 1 3 12
MATH 15 12 0
MATH 1 2 5
GETDATA 1 3 12
MATH 15 12 0
MOVO 1 20
NIL
JMP 1 17
DATAC 00000000000000000000000000111111
GETDATA 2 0 0000000000000000000000
MATH 1 2 5
PMOV 1 6 28 31 0 0
GETDATA 1 3 6
MATH 1 3 5
IFJMP 1 0 1
PMOV 6 5 28 31 0 0
GETDATA 1 3 5
MATH 1 12 5
GETDATA 1 3 11
JMP 1 16
SETDATA 0 3 3
DATAC 00000000000000000000000000000001
DATAC 00000000000000000000000001100001
DATAC 00000000000000000000000000000000
DATAC 00000000000000000000000001110011
DATAC 00000000000000000000000001100100
DATAC 00000000000000000000000000000000
DATAC 00000000000000000000000000000000
DATAC 00000000000000000000000001110111
DATAC 00000000000000000000000000000000
DATAC 00000000000000000000000001101001
DATAC 00000000000000000000000001101010
DATAC 00000000000000000000000001101011
DATAC 00000000000000000000000001101100
DATAC 00000000000000000000000000001101
DATAC 00000000000000000000000000000000
DATAC 00000000000000000000000000000000
NIL
DATAC 00000000000000000000000011000011
NIL
DATAC 00000000000000000000000011000011
DATAC 00000000000000000000000011000011
NIL
NIL
DATAC 00000000000000000000000011000011
NIL
DATAC 00000000000000000000000011010111
DATAC 00000000000000000000000011001100
DATAC 00000000000000000000000011010111
DATAC 00000000000000000000000011001100
DATAC 00000000000000000000000011100010
NIL
NIL
DATAC 00000000000000000000000000000000
DATAC 00000000000000000000000000000000
DATAC 00000000000000000000000000000000
DATAC 00000000000000000000000000000001
DATAC 00000000000000000000000000000001
DATAC 00000000000000000000000000000010
DATAC 00000000000000000000000000000010
DATAC 00000000000000000000000000000011
DATAC 00000000000000000000000000000100
DATAC 00000000000000000000000000000100
DATAC 00000000000000000000000000000101
DATAC 00000000000000000000000000000101
DATAC 00000000000000000000000000000110
DATAC 00000000000000000000000000000110
DATAC 00000000000000000000000000000111
DATAC 00000000000000000000000000000111
DATAC 00000000000000000000000000000111
DATAC 00000000000000000000000000000111
DATAC 00000000000000000000000000000111
DATAC 00000000000000000000000000000110
DATAC 00000000000000000000000000000110
DATAC 00000000000000000000000000000101
DATAC 00000000000000000000000000000101
DATAC 00000000000000000000000000000100
DATAC 00000000000000000000000000000100
DATAC 00000000000000000000000000000011
DATAC 00000000000000000000000000000010
DATAC 00000000000000000000000000000010
DATAC 00000000000000000000000000000001
DATAC 00000000000000000000000000000001
DATAC 00000000000000000000000000000000
DATAC 00000000000000000000000000000000
DATAC 11111111111111111111111111111000
DATAC 00000000000000000000000000000001
DATAC 00000000000000000000000000001000
DATAC 11111111111111111111111111111111
DATAC 00000000000000000000000010001010
PMOV 15 2 0 31 3 1
PMOV 15 3 0 31 2 1
SETDATA 0 3 2
MATH 4 2 0
IFJMP 1 2 1
JMP 1 17
DATAC 00000000000000000000000010010111
PMOV 7 2 0 31 18 0
MATH 0 3 5
PMOV 2 10 9 13 18 1
GETDATA 1 3 10
PMOV 1 13 29 31 6 1
PMOV 2 10 25 29 2 1
GETDATA 1 3 10
PMOV 1 13 29 31 9 1
SETDATA 0 3 13
MATH 9 2 0
IFJMP 1 2 1
JMP 1 17
JMP 1 14
DATAC 00000000000010000000000000000000
JMP 1 1
JMP 1 14
DATAC 10010000010010100000000000000000
JMP 1 3
JMP 1 14
DATAC 00010100000011100000000000000000
JMP 1 3
JMP 1 14
DATAC 10011100010100000000000000000000
JMP 1 3
MATH 7 7 1
SET 7 3 00001000
MATH 8 8 1
SET 8 3 00000010
MATH 9 9 1
JMP 1 14
DATAC 00000000000000000000000001000000
MATH 2 6 5
JMP 1 14
DATAC 00000000000000000000000001010000
MATH 2 5 5
JMP 1 14
DATAC 00000000000000000000000001100000
MATH 2 10 5
MATH 13 13 1
SET 13 0 01100000
PMOV 15 2 0 31 2 1
PMOV 7 2 27 31 9 1
SETDATA 0 3 2
SET 2 0 01010100
PMOV 8 2 29 31 9 1
SETDATA 0 3 2
SET 2 0 01011000
PMOV 9 2 29 31 9 1
SETDATA 0 3 2
JMP 1 14
DATAC 00000000000000000000000010000000
MATH 2 11 5
JMP 1 14
DATAC 00000000000000000000000000110011
JMP 3 9
PMOV 3 11 29 30 1 1
JMP 1 9
MATH 3 3 1
PMOV 7 3 27 31 9 1
MATH 2 7 0
PMOV 15 2 0 31 2 1
PMOV 7 2 27 31 9 1
JMP 1 11
JMP 1 0
PMOV 15 3 0 29 0 0
MATH 15 3 1
MATH 2 2 1
SET 2 0 00010100
PMOV 8 2 29 31 9 1
MATH 3 8 1
MATH 3 3 1
SET 3 0 01010100
PMOV 8 3 29 31 9 1
JMP 1 11
JMP 1 0
PMOV 3 11 30 31 0 0
JMP 1 9
MATH 3 3 1
SET 3 0 00011000
PMOV 9 3 29 31 9 1
MATH 2 9 1
MATH 2 2 1
SET 2 0 01011000
PMOV 9 2 29 31 9 1
JMP 1 11
JMP 1 0
SETDATA 0 0 1101000000000000000000
JMP 1 14
DATAC 00000000000000000000000010000100
JMP 3 9
SETDATA 0 0 1101000010000000000000
PMOV 15 9 0 28 0 0
PMOV 15 8 0 28 0 0
MATH 15 9 0
MATH 15 8 0
PMOV 8 9 28 31 16 0
PMOV 15 7 0 26 0 0
PMOV 7 3 0 31 18 0
PMOV 15 0 0 31 7 0
MATH 9 0 2
MATH 3 0 0
JMP 1 14
DATAC 00000000000000000000000010001011
JMP 3 9
SETDATA 0 0 1001000000000000000000
MATH 15 9 1
MATH 15 8 1
JMP 1 14
DATAC 00000000000000000000000000110011
SETDATA 0 0 1001000010000000000000
JMP 3 9