Nutshell

For general discussion about Conway's Game of Life.
Post Reply
M. I. Wright
Posts: 372
Joined: June 13th, 2015, 12:04 pm

Nutshell

Post by M. I. Wright » April 18th, 2018, 12:47 am

Hi! For the past month or so I've been working on my own derivative ruletable spec+language. I've written a Python 3.6+ utility to compile it down to a normal Golly ruletable.

https://github.com/eltrhn/nutshell

Its main draw, for anyone curious, is in not binding variables by name: this means that the same variable name can be used multiple times in a transition without needing to define uniquely-named variants as in a Golly table. There are many other features too, however!

The README serves as documentation and is as comprehensive as I could make it. Please be aware that a large amount of the info in this thread below is outdated, because I started posting very early on (when the project's name was still "rueltabel", among other larger differences) -- so don't rely on anything I say in my posts below up through May 19th, 2018, lol.

If you want to get started writing your own Nutshell rule, just follow the "setup" instructions in the README to get the transpiler script downloaded, then continue referring to it for info on syntax/semantics/etc. Please also feel free to give me feedback or ask questions on this post :D
Last edited by M. I. Wright on October 23rd, 2018, 4:47 pm, edited 20 times in total.

User avatar
77topaz
Posts: 1496
Joined: January 12th, 2018, 9:19 pm

Re: rueltabel

Post by 77topaz » April 18th, 2018, 5:44 am

After witnessing its development over the past few weeks on the Discord, it's nice to finally see this in its finished state on the forums. :D

M. I. Wright
Posts: 372
Joined: June 13th, 2015, 12:04 pm

Re: rueltable

Post by M. I. Wright » April 22nd, 2018, 2:57 am

Guess I'll log updates here too. Biggest change now is that I've renamed the thing from "rueltabel" to "rueltable" (for which a certain "Is it a typo?" may have been a motivating factor) -- but other than that, I've also added two new things!
  • The README used to make mention of a "tentative" syntax for the output specifiers -- this is tentative no more. You can now write an output map along the lines of "CD[CD: var]", CD being any compass direction. Take, for instance, the following transition:

    Code: Select all

    1, any, any, 0, SE..SW (3, 4), 0, any, 0 -> E[SE: (1, 2)]
    The "-> E[SE: (1, 2)]" says "Assuming I'm state 1: take the variable southeast of me, map it to the variable (1, 2), and spawn a cell with that value to my east." Or, in graphical terms:

    Code: Select all

    # T = 0 #
    0 0 0
    0 1 0
    3 3 3
    
    # T = 1 #
    0 0 0
    0 0 1
    3 3 3

    Code: Select all

    # T = 0 #
    0 0 0
    0 1 0
    3 3 4
    
    # T = 1 #
    0 0 0
    0 0 2
    3 3 4
    ...where "1" is the center cell at T=0. This feature, finally, allows me to actually make the rule I created rueltable for! (so expect that... at some point)
    ---
  • Variables now are able to be (a) subtracted from, and (b) negated, using the - (dash) and -- (double dash) operators. I'll copy/paste this one from the README:

    Code: Select all

    0, foo-bar, bar-2, bar-(2, 3), -1, --1, -bar, --(3, 4), (foo, bar), baz
    
    # foo-bar says "All states in foo that are not in bar"
    # foo-2 says "All states in foo that are not 2"
    # bar-(2, 3) says "All states in bar that are not in (2, 3)"
    # -1 says "All *live* states that are not 1" (expands to {2, 3, 4} assuming n_states==5)
    # --1 says "*All* states (including 0) that are not 1" (expands to {0, 2, 3, 4} assuming the same)
    # -bar and --(3, 4) say the same but with multiple states enclosed in a variable
    
    "Addition" of variables can be accomplished by writing them together in a variable literal, as with the last state above.
    Negation means that rules like DeficientSeeds can be written like so:

    Code: Select all

    @RUEL DeficientSeeds
    @TABLE
    states: 7
    neighborhood: Moore
    symmetries: rotate4 reflect
    
    0, N 0, NE -1, E..W 0, NW -1, 1
    0, N -2, NE..SW 0, W -2, NW 0, 2
    0, 0, 0, -3, SE..W 0, -3, 3
    0, -4, NE..W 0, -4, 4
    0, -5, NE..SE 0, -5, SW..NW 0, 5
    0, N..E 0, -6, S..W 0, -6, 6
    
    any, N..NW any, 0
    and this compiles to 30-40+ lines, depending on how you count. (Rueltabel is obsoleted for this purpose by my other script, but take this as a demo)
    It also allows "NewTons", Blinkerspawn's (basic) alpha-particles-y test rule, to be written like so:

    Code: Select all

    @RUEL NewTons
    @TABLE
    states: 7
    neighborhood: Moore
    symmetries: none
    
    # photon splitting
    0, (1, 5), NE..SE any, (1, 3), SW..NW any, 0 -> E[(4, _, ...)]  W[(6, _, ...)]
    0, any, any, (1, 6), SE..SW any, (1, 4), any, 0 -> N[(3, _, ...)]  S[(5, _, ...)]
    
    # photon moving
    0, --3, any, --4, any, 3, any, --6, any, 3
    0, --3, any, --4, any, --5, any, 4, any, 4
    0, 5, any, --4, any, --5, any, --6, any, 5
    0, --3, any, 6, any, --5, any, --6, any, 6
    
    # photon dying
    (3..6), N..NW any, 0
    (where it previously required a variable to be explicitly defined for each negation)
A future (non-bugfix) release will have support for two other things: a shorthand permute-symmetry syntax that looks something like this,

Code: Select all

0, 1:3, 0, 1  # gets filled in automatically
0, 1:3, 0:5, 1

# Both of which expand to:
0, 1, 1, 1, 0, 0, 0, 0, 0, 1
and, off a suggestion from SuperSuperMario24, the ability to change symmetries midway through a table. This would for example allow DeficientLife to be written like so:

Code: Select all

@RUEL DeficientLife
@TABLE
states: 12
neighborhood: Moore

# Birth
symmetries: rotate4 reflect
0, 0, -2, 0, 0, 0, -2, 0, -2, 2
0, -3, 0, -3, 0, 0, 0, -3, 0, 3
0, 0, 0, -4, 0, -4, 0, 0, -4, 4
0, N -5, NE..SW 0, W..NW -5, 5
0, N..NE -6, E..W 0, NW -6, 6
0, 0, -7, -7, SE..W 0, NW -7, 7
0, 0, -8, 0, 0, -8, 0, 0, -8, 8
0, -9, 0, 0, -9, 0, 0, 0, -9, 9
0, -10, 0, -10, 0, 0, 0, 0, -10, 10
0, -11, 0, 0, 0, -11, 0, 0, -11, 11

# Survival
symmetries: permute
live, live:2, 0, 1 # s2
live, live:3, 0, 1 # s3

any, N..NW any, 0
...but, being honest, I'm not quite sure how I should go about this. I know I'll have to go through the completed transitions and expand them all to the "least-abstracted" symmetry (e.g. "symmetries:permute" followed by "symmetries:none" would mean expanding the "symmetries:permute" segment), but I'll need to figure out how to make the compiler know to expand the different symmetry levels to each other. If anyone's reading this and has ideas, shoot :!:
Last edited by M. I. Wright on April 22nd, 2018, 12:00 pm, edited 1 time in total.

User avatar
77topaz
Posts: 1496
Joined: January 12th, 2018, 9:19 pm

Re: rueltable

Post by 77topaz » April 22nd, 2018, 5:56 am

But isn't changing from "rueltabel" (two differences from "ruletable") to "rueltable" (one difference from "ruletable") going to make people more likely to think the word is simply a typo? :?

User avatar
calcyman
Moderator
Posts: 2964
Joined: June 1st, 2009, 4:32 pm

Re: rueltable

Post by calcyman » April 22nd, 2018, 6:33 am

77topaz wrote:But isn't changing from "rueltabel" (two differences from "ruletable") to "rueltable" (one difference from "ruletable") going to make people more likely to think the word is simply a typo? :?
Indeed. I also preferred the original name of 'rueltabel' (and am totally in favour of this new high-level language!).
What do you do with ill crystallographers? Take them to the mono-clinic!

User avatar
rowett
Moderator
Posts: 4078
Joined: January 31st, 2013, 2:34 am
Location: UK
Contact:

Re: rueltable

Post by rowett » April 22nd, 2018, 9:20 am

Perhaps better to call it something completely different that isn't a typo.

How about either of the following:
  • "Dictator" - someone who makes rules.
  • "Scobberlotcher" - someone who avoids hard work.

M. I. Wright
Posts: 372
Joined: June 13th, 2015, 12:04 pm

Re: rueltable

Post by M. I. Wright » April 22nd, 2018, 11:52 am

All good points, thank you!
I'm going to change it back to "rueltabel" for now, then -- may as well -- but I do see the value in giving it a more-unique name too. Don't know about those two specifically (humorous as they are) but I'd be 100% open to further suggestions :)

User avatar
dvgrn
Moderator
Posts: 11212
Joined: May 17th, 2009, 11:00 pm
Location: Madison, WI
Contact:

Re: rueltable

Post by dvgrn » April 22nd, 2018, 12:24 pm

M. I. Wright wrote:All good points, thank you!
I'm going to change it back to "rueltabel" for now, then -- may as well -- but I do see the value in giving it a more-unique name too. Don't know about those two specifically (humorous as they are) but I'd be 100% open to further suggestions :)
How about just "ruletemplate"? Boring, I know, but maybe more likely to make sense to a casual reader than RULEZtable or SovereignTable or what have you.

M. I. Wright
Posts: 372
Joined: June 13th, 2015, 12:04 pm

Re: rueltabel

Post by M. I. Wright » April 29th, 2018, 5:31 pm

That's absolutely reasonable, but I have to admit I've grown somewhat attached to the idea of a weird name... eh :roll:

Anyway, here's another update. I finally implemented the symmetry-normalization thing as well as the permute shorthand, the former meaning that it's now acceptable to switch symmetries midway through a tabel. For instance, that DeficientLife.ruel file from above --

Code: Select all

@RUEL DeficientLife
@TABEL
states: 12
neighborhood: Moore

# Birth
symmetries: rotate4 reflect
0, 0, -2, 0, 0, 0, -2, 0, -2, 2
0, -3, 0, -3, 0, 0, 0, -3, 0, 3
0, 0, 0, -4, 0, -4, 0, 0, -4, 4
0, N -5, NE..SW 0, W..NW -5, 5
0, N..NE -6, E..W 0, NW -6, 6
0, 0, -7, -7, SE..W 0, NW -7, 7
0, 0, -8, 0, 0, -8, 0, 0, -8, 8
0, -9, 0, 0, -9, 0, 0, 0, -9, 9
0, -10, 0, -10, 0, 0, 0, 0, -10, 10
0, -11, 0, 0, 0, -11, 0, 0, -11, 11

# Survival
symmetries: permute
live, live:2, 0, 1 # s2
live, live:3, 0, 1 # s3

# Death otherwise
any, any:8, 0
now produces a working B3/S23-deficient rulefile.

EDIT: It now produces 'optimal' results (an update to the shrunken pre-edit text below)! The issue was that I was doing the normalizing after resolving variable name-bindings, so a transition napkin like "live_1, live_2, live_3, 0, 0, 0, 0, 0" where all the "live_N"s have the same value was producing multiple "distinct" outputs like:

Code: Select all

live_1, 0, 0, 0, 0, live_2, 0, live_3
live_3, 0, 0, 0, 0, live_2, 0, live_1
This was fixed by removing the _N suffixes of all variables that only appear once before normalizing and then restoring them afterward. You can see this in examples/compiled_ruletables/DeficientLife.rule.

However, it is suboptimal. Check the comparison here: rueltabel is producing 42 rotate4reflect transitions from permute-symmetric "live, live live, live, 0, 0, 0, 0, 0, 1" (which is the survival-on-3 line above) -- but, as shown by deficient_gen.py, it apparently only needs to be 10. I'm stumped here.
My approach to normalizing symmetries was to implement a class for each symmetry type with a hash function that produces identical hashes for each identical transformation of itself -- for instance, a "Rotate4((0, 1, 2, 3, 4, 5, 6, 7))" object has the same hash as "Rotate4((2, 3, 4, 5, 6, 7, 0, 1))". Each class also has an "expand()" method that returns a multiple-transition representation of itself under "symmetries:none".
So when expanding from, say, "symmetries:permute" to "symmetries:rotate4reflect", I create a Permute() object from the napkin* of every transition under "symmetries:permute" and expand each, then create a Rotate4Reflect() object from each item of this expansion, and finally toss these rotate4reflect objects into a set. (This is where the hash function comes in -- if an object has the same hash as another it won't create a duplicate set entry)
So I figure there's some symmetry stuff I'm not accounting for that would slim the rueltabel transition list from 42 to 10...? Not sure. Again, if anyone has ideas...
(...and note that my actual code is still rather dirty, so I don't know if looking through it will help much. Apologies)



But enough of that :P I've also added a "find this transition" flag:
rueltabel_find.png
rueltabel_find.png (72.45 KiB) Viewed 20368 times
which makes it so that, if a cell isn't behaving the way it's supposed to, you can "-f" the transition it's doing and it'll find the offending line for you (instead of you having to guess at what you typo'd). Has been quite handy.

*napkin: The neighborhood segment of a transition. (i.e. transition excluding its first and last items -- coined by 83bismuth38 a while ago? in reference to a triangular-neighborhood 'bowling napkin' specifically)

---

So I think this is pretty much a 'feature-complete' alpha now? Everything from my original spec's been implemented! -- no guarantees of being bug-free, though.

M. I. Wright
Posts: 372
Joined: June 13th, 2015, 12:04 pm

Re: rueltabel

Post by M. I. Wright » May 11th, 2018, 2:42 am

Alright, last update for now.

EDIT: The @COLORS syntax has changed, as have of course the @RUEL/@TABEL segment headers. Please view the repo's README (@COLORS described near the bottom) for the currently-correct syntax.
  • Changed permute-symmetry shorthand to use "*" instead of ":" to avoid confusion with the other uses of colons. Breaking change, but fortunately I don't have anything besides my own examples to break :P
  • Made all segments optional. The @TABEL and @RUEL segments, if omitted, will no longer throw a fit and can be made 'normal' by spelling the @header names conventionally, and segments whose headers aren't misspelt (@ICONS, @COLORS) can be ignored during parsing by writing "#golly" as the first word after. (That is: "@COLORS #golly" or "@COLORS<newline>#golly".)
  • Implemented a new @ICONS syntax that uses normal XRLE instead of the XPM format. An example follows:

    Code: Select all

    @NUTSHELL IconTest
    
    @TABLE
    n_states:14
    neighborhood:Moore
    symmetries:none
    
    @COLORS
    10: FFF
    
    @ICONS
    0: 303030
    1: D0D0D0
    2: 007FFF
    3: 535353
    4: AD331D
    5: 1A1A1A
    6: C0C0C0
    7: 6077A0
    8: 405780
    9: 0000FF
    
    #C 1
    x = 13, y = 13, rule = //10
    5.3A$5.3A$2.9A$2.3A3.3A$2.3A3.3A$3A7.3A$3A7.3A$3A7.3A$2.3A3.3A$2.3A3.
    3A$2.9A$5.3A$5.3A!
    
    #C 2
    x = 5, y = 11, rule = //10
    5B$5B$2B$2B$2B$2B$2B$2B$2B$5B$5B!
    
    #C 3
    x = 5, y = 11, rule = //10
    5B$5B$3.2B$3.2B$3.2B$3.2B$3.2B$3.2B$3.2B$5B$5B!
    
    #C 4
    x = 11, y = 11, rule = //10
    4.3C$4.3C$4.3C$4.3C$11C$11C$11C$4.3C$4.3C$4.3C$4.3C!
    
    #C 5
    x = 11, y = 3, rule = //10
    11C$11C$11C!
    
    #C 6
    x = 11, y = 11, rule = //10
    9.2D$7.4D$5.4D$3.4D$.4D$3D$.4D$3.4D$5.4D$7.4D$9.2D!
    
    #C 7
    x = 11, y = 11, rule = //10
    2D$4D$2.4D$4.4D$6.4D$8.3D$6.4D$4.4D$2.4D$4D$2D!
    
    #C 8
    x = 9, y = 11, rule = //10
    .7E$9E$9E$9E$9E$9E$.8E$5.4E$5.4E$4.4E$.6E!
     
    #C 9
    x = 9, y = 7, rule = //10
    .7E$9E$9E$9E$9E$9E$.7E!
    
    #C 11
    x = 9, y = 9, rule = //10
    2G5.2G$2G5.2G$.2G3.2G$.2G3.2G$2.2G.2G$2.2G.2G$3.3G$3.3G$4.G!
    
    #C 12
    x = 9, y = 9, rule = //10
    2H5.2H$2H5.2H$.2H3.2H$.2H3.2H$2.2H.2H$2.2H.2H$3.3H$3.3H$4.H!
    
    #C 13
    x = 9, y = 9, rule = //10
    4.I$3.3I$3.3I$2.2I.2I$2.2I.2I$.2I3.2I$.2I3.2I$2I5.2I$2I5.2I!
    
    The idea is that you're going to be in Golly anyway when you're fiddling with a rule, so it'll be easier to quickly copy/paste that RLE in and out of a blank Golly tab than to edit XPM images in your text editor. Icons are automatically centered & resized to the nearest Golly icon size (7, 15, 31).

    The first number on each line (in the bit before the RLEs) corresponds to a cell state used below. "0 303030", for instance, assigns the symbol "." (state 0) the color #303030.
    The parser also accepts the cellstate symbol to be written in place of the numbers, as in ". 303030 / A D0D0D0 / B 007FFF / ..." if it's easier to read. (The colon should not be used in this form.)

    The rule of each RLE is ignored -- I just chose a generic multistate //10 to get all desired colors in -- and the comment above each rule is required to start with '#' and contain numbers somewhere in it; more than one number means more than one cell state gets this icon. Multiple numbers here can be separated either by commas, whitespace, or both.
    Note that the icons don't have to be in sequence or even present (the pre-icon comment facilitates ordering). If an icon is skipped (like state 10 is above!) it will be made as a solid square with a color taken from the @COLORS declaration for that state. (An omitted icon with no fill color defined in @COLORS will raise an error, however.)
  • EDIT 5/14/18: You can now also declare a *gradient* to fill in missing states, even when they aren't addressed in @COLORS. The syntax is "? <hex color> <hex color>" or "? <hex color> <ignored separator> <hex color>", defined along with the other colors in @ICONS (before the RLEs).

    The gradient relies on the "states:" directive in @TABEL to compute its start and end -- but if you don't have any @TABEL section then this won't be available, so in this case you may append to this line a bracketed number indicating your desired "n_states" value e.g. "? <hex color> <hex color> [<n_states>]".

    A full example might look like any of "? 000 FFF", "? 000 - FFF", "? 000 FFF [13]", and "? 000 - FFF [13]" -- indicating a gradient from #000 (black) to #FFF (white), with the latter two implying the absence of the @TABEL section and explicitly defining n_states in its lieu.
    --
    (If you don't want a certain @COLORS color to take precedence over the gradient, put an *asterisk before its cellstate in the part before the colon; for instance, "*10: FFF" above. This is a touch particular/specific, though, so I only implemented it as a "just in case" and I fully expect it to go unused.)
Here's the output of that ruel file:

Code: Select all

@RULE IconTest
*********************************
**** COMPILED FROM RUELTABEL ****
*********************************


@TABLE
n_states:15
neighborhood:Moore
symmetries:none



@COLORS
10 255 15 240


@ICONS
XPM
"15 195 11 2"
".. c #303030"
"AA c #D0D0D0"
"BB c #007FFE"
"CC c #535353"
"DD c #AD331D"
"EE c #1A1A1A"
"FF c #C0C0C0"
"GG c #6077A0"
"HH c #405780"
"II c #0000FF"
"}b c #FFFFFF"
".............................."
"............AAAAAA............"
"............AAAAAA............"
"......AAAAAAAAAAAAAAAAAA......"
"......AAAAAA......AAAAAA......"
"......AAAAAA......AAAAAA......"
"..AAAAAA..............AAAAAA.."
"..AAAAAA..............AAAAAA.."
"..AAAAAA..............AAAAAA.."
"......AAAAAA......AAAAAA......"
"......AAAAAA......AAAAAA......"
"......AAAAAAAAAAAAAAAAAA......"
"............AAAAAA............"
"............AAAAAA............"
".............................."
".............................."
".............................."
"..........BBBBBBBBBB.........."
"..........BBBBBBBBBB.........."
"..........BBBB................"
"..........BBBB................"
"..........BBBB................"
"..........BBBB................"
"..........BBBB................"
"..........BBBB................"
"..........BBBB................"
"..........BBBBBBBBBB.........."
"..........BBBBBBBBBB.........."
".............................."
".............................."
".............................."
".............................."
"..........BBBBBBBBBB.........."
"..........BBBBBBBBBB.........."
"................BBBB.........."
"................BBBB.........."
"................BBBB.........."
"................BBBB.........."
"................BBBB.........."
"................BBBB.........."
"................BBBB.........."
"..........BBBBBBBBBB.........."
"..........BBBBBBBBBB.........."
".............................."
".............................."
".............................."
".............................."
"............CCCCCC............"
"............CCCCCC............"
"............CCCCCC............"
"............CCCCCC............"
"....CCCCCCCCCCCCCCCCCCCCCC...."
"....CCCCCCCCCCCCCCCCCCCCCC...."
"....CCCCCCCCCCCCCCCCCCCCCC...."
"............CCCCCC............"
"............CCCCCC............"
"............CCCCCC............"
"............CCCCCC............"
".............................."
".............................."
".............................."
".............................."
".............................."
".............................."
".............................."
".............................."
"....CCCCCCCCCCCCCCCCCCCCCC...."
"....CCCCCCCCCCCCCCCCCCCCCC...."
"....CCCCCCCCCCCCCCCCCCCCCC...."
".............................."
".............................."
".............................."
".............................."
".............................."
".............................."
".............................."
".............................."
"......................DDDD...."
"..................DDDDDDDD...."
"..............DDDDDDDD........"
"..........DDDDDDDD............"
"......DDDDDDDD................"
"....DDDDDD...................."
"......DDDDDDDD................"
"..........DDDDDDDD............"
"..............DDDDDDDD........"
"..................DDDDDDDD...."
"......................DDDD...."
".............................."
".............................."
".............................."
".............................."
"....DDDD......................"
"....DDDDDDDD.................."
"........DDDDDDDD.............."
"............DDDDDDDD.........."
"................DDDDDDDD......"
"....................DDDDDD...."
"................DDDDDDDD......"
"............DDDDDDDD.........."
"........DDDDDDDD.............."
"....DDDDDDDD.................."
"....DDDD......................"
".............................."
".............................."
".............................."
".............................."
"........EEEEEEEEEEEEEE........"
"......EEEEEEEEEEEEEEEEEE......"
"......EEEEEEEEEEEEEEEEEE......"
"......EEEEEEEEEEEEEEEEEE......"
"......EEEEEEEEEEEEEEEEEE......"
"......EEEEEEEEEEEEEEEEEE......"
"........EEEEEEEEEEEEEEEE......"
"................EEEEEEEE......"
"................EEEEEEEE......"
"..............EEEEEEEE........"
"........EEEEEEEEEEEE.........."
".............................."
".............................."
".............................."
".............................."
".............................."
".............................."
"........EEEEEEEEEEEEEE........"
"......EEEEEEEEEEEEEEEEEE......"
"......EEEEEEEEEEEEEEEEEE......"
"......EEEEEEEEEEEEEEEEEE......"
"......EEEEEEEEEEEEEEEEEE......"
"......EEEEEEEEEEEEEEEEEE......"
"........EEEEEEEEEEEEEE........"
".............................."
".............................."
".............................."
".............................."
"}b}b}b}b}b}b}b}b}b}b}b}b}b}b}b"
"}b}b}b}b}b}b}b}b}b}b}b}b}b}b}b"
"}b}b}b}b}b}b}b}b}b}b}b}b}b}b}b"
"}b}b}b}b}b}b}b}b}b}b}b}b}b}b}b"
"}b}b}b}b}b}b}b}b}b}b}b}b}b}b}b"
"}b}b}b}b}b}b}b}b}b}b}b}b}b}b}b"
"}b}b}b}b}b}b}b}b}b}b}b}b}b}b}b"
"}b}b}b}b}b}b}b}b}b}b}b}b}b}b}b"
"}b}b}b}b}b}b}b}b}b}b}b}b}b}b}b"
"}b}b}b}b}b}b}b}b}b}b}b}b}b}b}b"
"}b}b}b}b}b}b}b}b}b}b}b}b}b}b}b"
"}b}b}b}b}b}b}b}b}b}b}b}b}b}b}b"
"}b}b}b}b}b}b}b}b}b}b}b}b}b}b}b"
"}b}b}b}b}b}b}b}b}b}b}b}b}b}b}b"
"}b}b}b}b}b}b}b}b}b}b}b}b}b}b}b"
".............................."
".............................."
".............................."
"......GGGG..........GGGG......"
"......GGGG..........GGGG......"
"........GGGG......GGGG........"
"........GGGG......GGGG........"
"..........GGGG..GGGG.........."
"..........GGGG..GGGG.........."
"............GGGGGG............"
"............GGGGGG............"
"..............GG.............."
".............................."
".............................."
".............................."
".............................."
".............................."
".............................."
"......HHHH..........HHHH......"
"......HHHH..........HHHH......"
"........HHHH......HHHH........"
"........HHHH......HHHH........"
"..........HHHH..HHHH.........."
"..........HHHH..HHHH.........."
"............HHHHHH............"
"............HHHHHH............"
"..............HH.............."
".............................."
".............................."
".............................."
".............................."
".............................."
".............................."
"..............II.............."
"............IIIIII............"
"............IIIIII............"
"..........IIII..IIII.........."
"..........IIII..IIII.........."
"........IIII......IIII........"
"........IIII......IIII........"
"......IIII..........IIII......"
"......IIII..........IIII......"
".............................."
".............................."
".............................."
("}b" is a randomly-chosen name for the auto-filled-in color)
Last edited by M. I. Wright on July 24th, 2018, 8:07 pm, edited 2 times in total.

User avatar
Redstoneboi
Posts: 433
Joined: May 14th, 2018, 3:57 am

Re: rueltabel

Post by Redstoneboi » May 19th, 2018, 11:09 pm

does it have some way to "add" 1 to the state number of cells?
if yes, does it roll over when it reaches the state count?
it would be really nice to have an easy way to make a rock paper scissors rule, or emulate a generations rule.
c(>^w^<c)~*
This is 「Fluffy」
「Fluffy」is my sutando.
「Fluffy」has the ability to engineer r e p l i c a t o r s.
「Fluffy」likes to watch spaceship guns in Golly.
「Fluffy」knows Natsuki best girl.

M. I. Wright
Posts: 372
Joined: June 13th, 2015, 12:04 pm

Re: rueltabel

Post by M. I. Wright » May 19th, 2018, 11:41 pm

Sure! If I understand correctly, a mapping can handle this:

Code: Select all

@TABLE
states: 256
neighborhood: Moore
symmetries: none

(1..255), N..NW any, [0: (2..255, 1)]
That line expands to 255 Golly transitions: state 1 to state 2, 2 to 3, 3 to 4, ..., 254 to 255, and 255 to 1 (there's the roll-over!). The anonymous variables on either end make use of the double-dot 'range' syntax; 1..255 expands to every state from 1 to 255.

If it's easier to visualize, here's an example with fewer states:

Code: Select all

@TABLE
states: 5
neighborhood: von Neumann
symmetries: none

(1, 2, 3, 4), N..W any, [0: (2, 3, 4, 1)]
and this expands to

Code: Select all

@TABLE
n_states: 5
neighborhood: vonNeumann
symmetries: none

### variable declarations omitted ###

1, any_1, any_2, any_3, any_4, 2
2, any_1, any_2, any_3, any_4, 3
3, any_1, any_2, any_3, any_4, 4
4, any_1, any_2, any_3, any_4, 1
By the way, there's an example of "Rock-Paper-Scissors Life" in the examples/ directory of the repo, although it's not quite as elegant as I'd have hoped thanks to some annoying stuff with variables.

EDIT: Cellstate ranges now also accept a step; you can still write "first..last" to represent all states from first to last (inclusive), but "first-step..last" now indicates that only every "step-th" state should be included. For example, "(1, 3..9)" expands to (1, 3, 4, 5, 6, 7, 8, 9), but "(1, 3-3..9)" expands to (1, 3, 6, 9).

Cellstate ranges don't currently accept a step, though -- not sure how a not-ugly syntax for this might look? -- so if you wanted to do something fancier like only affect every other state you'd sadly have to type the variable out in full:

Code: Select all

@TABLE
states: 30
neighborhood: Moore
symmetries: none

from = (1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27)

# minor abuse of subtraction follows
# it represents all even states (i.e. all live states *except* those included in 'from')
to = live-from

from, N..NW any, [0: to]
...still fits the actual transition on one line, though![/size]

M. I. Wright
Posts: 372
Joined: June 13th, 2015, 12:04 pm

Re: Nutshell

Post by M. I. Wright » October 23rd, 2018, 9:51 pm

A small, belated update: Nutshell’s mostly stable + usable now. Some features worth highlighting:
  • Constants: The @NUTSHELL segment, Nutshell’s analogue to @RULE, provides a syntax for declaring “constants” (names that refer to a single cellstate) alongside a description of some particular state. These names can then be used in the rest of the rule (a) without clogging up @TABLE with single-cellstate variable declarations, and (b) without, in some cases, ever needing to refer to cellstates by number.
  • Optional directives: All of “neighborhood: Moore”, “symmetries: none”, and “states: ?” (where states is n_states) are preinitialized and need not be written outright (as long as those values are what’s desired). In particular, states: ? says to infer the states value from the maximum cellstate referred to in the table or declared in @NUTSHELL.
  • Custom range-1-Moore-subset neighborhoods: Passing a comma-delimited list of compass directions to the “neighborhood” directive, such as “neighborhood: NW, N, NE”, makes the rest of the table use that neighborhood (in that order) for its transitions. Nutshell then expands each transition into the smallest encompassing Golly neighborhood.
  • Inline-rulestring transitions: If a certain cellstate or group thereof needs to behave according to a Golly-compatible 2-state rulestring, Nutshell lets you declare so by replacing the middle states (the neighborhood) of a transition with a special syntax that indicates the rulestring, foreground state/s, and background state/s. Among other uses, this allows rules which extend some totalistic or isotropic-nontot rule to not waste precious space manually defining its transitions.
    For example, a History ruletable for any Hensel-expressible rule can be created by replacing the birth and survival conditions in the following template:

    Code: Select all

    @NUTSHELL XHistory
    Template for History rules.
    
    1: ON cell
    : {History} Indicates whether cell was ever ON
    : {MarkedON} Marked ON cell; may change to OFF, but remains marked
    : {MarkedOFF} Marked OFF cell; may change to ON, but remains marked
    : {InitialON} Becomes a normal marked OFF cell on dying, but is initially marked differently from a normal marked ON cell
    : {Boundary} Grey boundary cell
    
    @COLORS
    0F0: 1
    000080: History
    D8FFD8: MarkedON
    F00: MarkedOFF
    FF0: InitialON
    606060: Boundary
    
    @TABLE
    symmetries: permute
    
    off = (MarkedOFF, History, 0)
    on = (1, MarkedON, InitialON)
    
    # Birth; REPLACE THE 3 BELOW
    off, <3 / on / (off, Boundary)>; [0: (MarkedON, 1, ...)]
    
    # Death on touching a boundary cell
    on, Boundary ~ 1, (0, Boundary); [0: (History, MarkedOFF, ...)]
    
    # Survival; REPLACE THE 23 BELOW
    on, <23 / on / off>; [0]
    
    # Death
    on, any; [0: (History, MarkedOFF, ...)]
The README's also gotten a gigantic makeover since I last posted. As the edited first post says, I tried my best to make it comprehensive, and also to structure it as something of a quick-start guide for people familiar with Golly's ruletable format. Consider giving it a read!

User avatar
simsim314
Posts: 1824
Joined: February 10th, 2014, 1:27 pm

Re: Nutshell

Post by simsim314 » March 7th, 2019, 4:44 pm

I've discovered nutshell and used it in my script to generate the QCA rule.

In order to do that I've implemented a lua script. It allows to use golly to create the rule. But instead of stating the "states" your states in golly as input represent variables and functions to unroll some variable. For example I had majority gate - so it's a function that takes three arguments, they came at place 3, 5, 7. The script unrolls all the possibilities and generates a rule based on the options, while keeping other variables just as strings. You write the function in lua, which is relatively simple job usually. The maintenance and bug fixing of rule generated by the script reduced dramatically - I fix only the functions, or the input.

The compilation is done into Nutshell. I also hope to join forces and make a universal script for universal rule table generator, which is defined in golly visually only.

Code: Select all

--A script by michael simkin 2019 - auto generates rules using states in golly which represent variables. 
--Insert your code only between user definition start and end definitions. 
--Register your variables using the line: def:append("any", any)
--f1, f2, ..., fn - represent state 1,2,3...n in golly. 
--index_function is the part which selects what part of the rule you want to unroll in order to use the functions. 
--index_function[] = {2, 3} will unroll variables which placed in golly in the second and third place. 
--In golly you fill 8 states around some x = 0 y = 10*N. You also place the function index in x = 10 y = 10*N next to the rule. 
--Forum post here: http://conwaylife.com/forums/viewtopic.php?f=7&t=3361&p=59227&hilit=nutshell#p59227

------------------------------------Helper functions----------------------------------

-- class.lua
-- Compatible with Lua 5.1 (not 5.0).
function class(base, init)
   local c = {}    -- a new class instance
   if not init and type(base) == 'function' then
      init = base
      base = nil
   elseif type(base) == 'table' then
    -- our new class is a shallow copy of the base class!
      for i,v in pairs(base) do
         c[i] = v
      end
      c._base = base
   end
   -- the class will be the metatable for all its objects,
   -- and they will look up their methods in it.
   c.__index = c

   -- expose a constructor which can be called by <classname>(<args>)
   local mt = {}
   mt.__call = function(class_tbl, ...)
   local obj = {}
   setmetatable(obj,c)
   if init then
      init(obj,...)
   else 
      -- make sure that any stuff from the base class is initialized!
      if base and base.init then
      base.init(obj, ...)
      end
   end
   return obj
   end
   c.init = init
   c.is_a = function(self, klass)
      local m = getmetatable(self)
      while m do 
         if m == klass then return true end
         m = m._base
      end
      return false
   end
   setmetatable(c, mt)
   return c
end

Definitions = class(function(df)
  df.table_of_everything = {}
  df.table_of_keys = {}
  df.table_of_idx = {}
end)

function Definitions:append(name, value)
	
	self.table_of_everything[name] = value 
	self.table_of_keys[#self.table_of_keys + 1] = name
	self.table_of_idx[name] = #self.table_of_keys
end

function interpret(f, params)
	if #params == 0 then 
		return f()
	elseif #params == 1 then 
		return f(params[1])
	elseif #params == 2 then 
		return f(params[1], params[2])
	elseif #params == 3 then 
		return f(params[1], params[2], params[3])
	end 
end 

function index_to_function(idx)
	if idx == 1 then 
		return f1
	elseif idx == 2 then 
		return f2
	elseif idx == 3 then 
		return f3
	elseif idx == 4 then 
		return f4
	elseif idx == 5 then 
		return f5
	elseif idx == 6 then 
		return f6
	elseif idx == 7 then 
		return f7
	elseif idx == 8 then 
		return f8
	elseif idx == 9 then 
		return f9
	elseif idx == 10 then 
		return f10
	elseif idx == 11 then 
		return f11
	elseif idx == 12 then 
		return f12
	elseif idx == 13 then 
		return f13
	elseif idx == 14 then 
		return f14
	elseif idx == 15 then 
		return f15
	end 
end 

NdIterator = class(function(it, sizes)
  it.box = sizes
  it.cur = {}
  it.flags = {}
  for i = 1, #sizes do 
	it.cur[#it.cur + 1] = 1
	it.flags[#it.flags + 1] = false 
  end 
end)

function NdIterator:next()
	for i = 1, #self.box do 
		self.cur[i] = self.cur[i] + 1
		
		if self.cur[i] > self.box[i] then 
			self.cur[i] = 1
		else 
			break
		end 
	end 
end 

function NdIterator:total()
	total = 1 
	for i = 1, #self.box do 
		total = total * self.box[i]
	end 
	
	return total
end 

local def = Definitions()

--------------------------------------User definitions Start--------------------------------------

--visual compiler
local g =  golly()

--0 - empty space 
--switch - 0,1,unk,A0,A1 : 1, 2, 3, 4, 5
--hold - 0,1,unk,A0,A1 : 6, 7, 8, 9, 10
--relese  A0, A1, r,  : 11, 12, 13
--relax - A0, A1, r,  : 14, 15, 16

function from_idx(idx)
	
	if idx <= 10 then 
		idx = (idx - 1) % 5 + 1
		
		if idx == 1 then 
			return 0
		elseif idx == 2 then 
			return 1 
		elseif idx == 3 then 
			return 2 
		elseif idx == 4 then 
			return 0
		else
			return 1 
		end 
		
	else
		idx = idx - 10 
		idx = idx % 3 
		return idx 
	end 
end 

--state 1
any  = {"any"}

--state 2
live = {"live"}

--state 3
switch = {1, 2, 3, 4, 5}

--state 4
hold = {6, 7, 8, 9, 10}

--state 5
relese = {11, 12, 13}

--state 6
relax = {14, 15, 16}

--state 7
switch_hold = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

--state 8
not_switch_hold = {0, 11, 12, 13, 14, 15, 16}

--state 9
undefined_relax = {16}

--state 10
defined_relax = {14, 15}

--state 11 
not_switch = {0, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}

def:append("any", any)
def:append("live", live)
def:append("switch", switch)
def:append("hold", hold)
def:append("relese", relese)
def:append("relax", relax)
def:append("switch_hold", switch_hold)
def:append("not_switch_hold", not_switch_hold)
def:append("undefined_relax", undefined_relax)
def:append("defined_relax", defined_relax)
def:append("not_switch", not_switch)


--switch->hold 
function f1(sw)
	return sw + 5
end 

--hold->relese
function f2(hld)
	if hld < 9 then 
		return 13 
	else 
		return hld + 2
	end 
end 

--release->relax
function f3(ease)
	return ease + 3
end 

--(defined_relax)->(defined_switch)
function f4(defax)
	return defax - 10 
end 

--switch_hold->switch
function f5(sw)
	return (from_idx(sw) + 1)
end 

--(swr1, swr2)->switch (known or unknown depending)
function f6(sw1, sw2)
	if from_idx(sw1) == from_idx(sw2) then 
		return from_idx(sw1) + 1
	else 
		return 3
	end 
end 

function f7(sw1, sw2)
	if from_idx(sw1) == from_idx(sw2)then 
		if  from_idx(sw2) ~= 2 then 
			return 2 - from_idx(sw1) 
		else 
			return 3 
		end 
	else 
		return 3
	end 
end 

function f8(sw1, sw2, sw3)
	
	arr = {sw1, sw2, sw3}
	counter = {0,0,0}
	
	for i = 1, 3 do 
		idx = from_idx(arr[i]) + 1
		counter[idx] =  counter[idx] + 1
		
		if counter[idx] == 2 then 
			return idx
		end 
	end 
	
	return 3 
end 

function f9(sw)
	return 3
end 

index_function = {{0}, {0}, {0}, {0}, {7}, {3, 7}, {4, 6}, {3, 5, 7}, {0}}
colors = {
"0 255 0: 1, 4, 6, 9, 11, 14", 
"0 150 0: 6", 
"255 255 0: 2, 5, 10, 12, 15", 
"150 150 0: 7", 
"192 192 192: 3, 8", 
"64 64 64: 13, 16"
}
--------------------------------------User definitions End--------------------------------------
---------------------Compilation part don't touch--------------------------------

for i = 1, #index_function do 
	indeces = index_function[i]

	for j = 1, #indeces do 
		index_function[i][j] = index_function[i][j] + 1
	end 
end 

function toskip(x, y)
	
	if #(g.getcells({x - 10, y, 20, 1})) >= 60 or #(g.getcells({x - 10, y, 20, 1})) == 0 then 
		return true 
	else 
		return false 
	end 
end 

golly_order = {{0, 0},{0, -1},{1, -1},{1, 0},{1, 1},{0, 1},{-1, 1},{-1, 0},{-1, -1}}
rules = {}

rect = g.getrect()
y0 = math.floor(rect[2] / 10) * 10 - 20 
y1 = math.floor((rect[2] + rect[4]) / 10) * 10 + 20 

y = y0 
total = 0 
while y < y1 do 
	
	rule = {{}, 0}
	--Fill the variables 
	if toskip(0, y) == false then 
		for i = 1, 9 do 
			rule[1][#rule[1] + 1] = g.getcell(0 + golly_order[i][1], y + golly_order[i][2])
		end 
	
		--This is the function index 
		rule[2] = g.getcell(10, y) 		
		rules[#rules + 1] = rule 
	end 
	
	y = y + 10 
end 

name = g.getstring("Enter full path with file name")

local file = io.open(name, "w")

file:write("@NUTSHELL QdCA\n\n@TABLE\nstates: 17\nsymmetries: rotate4 reflect\nneighborhood: Moore\n")

for i = 1, #def.table_of_keys do 
	value = def.table_of_everything[def.table_of_keys[i]]
	
	if (value[1] == "any" or value[1] == "live") == false then 
		file:write(def.table_of_keys[i].." = (")
		for idx = 1, #value do 
			if idx == #value then 
				file:write(tostring(value[idx])..")\n")
			else 
				file:write(tostring(value[idx])..", ")
			end 
			
		end 
	end 
end 

for i = 1, #rules do 
	rule = rules[i]
	vars = rule[1] --9 variables around me 
	func_idx = rule[2] -- function index 

	input_vars = index_function[func_idx]
	local state_iterator = NdIterator({1, 1, 1, 1, 1, 1, 1, 1, 1})
		
	for j = 1, #input_vars do 
		values = def.table_of_everything[def.table_of_keys[vars[input_vars[j]]]]
		state_iterator.box[input_vars[j]] = #values
		state_iterator.flags[input_vars[j]] = true
	end 
	
	max_idx = state_iterator:total()

	for state_iterator_idx = 1, max_idx do 
		input_func = {}
		
		for j = 1, #state_iterator.box do 
			if state_iterator.flags[j] == false then 
				
				var_name = "0"
				
				if vars[j] ~= 0 then 
					var_name = def.table_of_keys[vars[j]]
				end 
				
				if j == #state_iterator.box then 
					file:write(var_name.."; ")
				else 
					file:write(var_name..", ")
				end 
							
			else
				values = def.table_of_everything[def.table_of_keys[vars[j]]]
				value = values[state_iterator.cur[j]]
				input_func[#input_func + 1] = value
				
				if j == #state_iterator.box then 
					file:write(tostring(value).."; ")
				else 
					file:write(tostring(value)..", ")
				end 
			end 
		end 
		
		value = interpret(index_to_function(func_idx), input_func)
		file:write(tostring(value).."\n")
		state_iterator:next()
	end 
end 

file:write("@COLORS\n")

for i = 1,#colors do 
	file:write(colors[i].."\n")
end 

file.close()
Here is an example of the input:

Code: Select all

x = 144, y = 246, rule = QdCA
63.80F7$60.F$60.F$60.F31.3A$60.F31.ACA8.A$60.F31.3A$61.F$4.5F52.F$3.F
2.F.2F51.F$2.F59.F$2.F59.F$.F35.4F21.F$.F23.F10.2F2.F3.F.6F.2F7.F$.F
5.4F5.4F5.3F7.F3.2F3.F.F5.3F6.F30.3A$.2F7.2F3.2F2.F5.2F.F5.F2.3F4.2F
5.2F.F6.F30.ADA8.B$2.F6.2F.F2.5F4.3F2.F3.2F2.2F5.2F5.F2.2F5.F30.3A$3.
2F4.F2.F2.F6.4F3.2F2.F2.F7.2F5.F.F.2F.F3.F$4.3F2.F2.F3.2F3.2F.2F5.F.
3F8.F7.2F2.F.F3.2F$7.2F3.F5.4F9.3F5.3F2.F11.3F4.F$31.9F23.F4$92.3A$
92.AEA8.C$92.3A8$92.3A$92.AJA8.D$92.3A9$64.80F9$92.3H$21.F70.GIH8.E$
20.F3.F67.A2H$7.F11.2F19.3F$7.F11.F7.F4.F5.3F.F$7.F11.F7.F3.3F4.F2.2F
$8.F5.F3.F4.2F2.F2.F6.F2.2F$8.F4.3F2.F5.F2.F.2F6.3F$9.F3.F.F.2F5.F2.F
.F5.4F$10.3F2.3F6.F2.2F8.F8.F$10.2F12.F2.2F8.F7.2F45.3H$38.F4.3F46.GI
G8.F$38.5F49.3H8$92.3H$92.HIH8.I$92.3H8$92.3K$92.CIK8.J$92.3K89$10.F
52.80F$9.2F23.F$.F7.F24.F$3F6.F24.F$4F4.F25.F2.F11.4F$2F.F4.F6.2F3.2F
4.2F3.7F8.4F3.3F$2F.F4.F4.2F.F2.F5.3F5.F8.2F5.4F$2F.2F3.F3.2F.2F2.F4.
2F2.F4.2F.F2.3F.F$2F2.F2.F3.4F4.F.F2.F3.F4.2F.F.F3.F$F3.F2.F3.2F5.F2.
F2.2F.2F4.F2.3F2.2F50.H$F3.F2.F2.F3.2F2.F.3F2.5F2.2F.6F52.I9.G$F4.2F
3.4F4.2F2.F6.F2.4F2.F3.2F48.G.G$5.2F3.2F10.F6.F.2F6.4F$5.2F15.F6.2F$
19.3F.F$18.F4.F$18.F4.F$18.F4.F$18.F4.F$18.F4.F$18.2F3.F39.80F$20.4F
8$93.H$92.GIG8.H$93.G$3.2F8.2F16.F21.2F$4.2F7.2F15.2F22.2F$4.3F5.2F.F
39.F$4.F.F5.F2.F39.F$4.F2.2F2.F3.F9.2F5.2F22.F$4.F4.3F3.F7.2F.F3.2F
17.F6.F$5.F3.2F4.F6.2F2.F3.2F3.3F11.F6.F$5.F10.F4.2F2.2F2.3F.6F17.F4.
F$5.F10.F3.2F3.2F2.2F2.2F2.F.F4.3F2.F6.F3.2F$5.2F9.F3.F3.F.F.3F2.F3.F
.F3.2F.F3.F6.F.2F.F8.F$5.2F9.F3.F2.F3.2F.F2.F2.2F.F3.F2.F3.F6.2F4.F7.
F$6.F10.F3.3F5.F3.F.2F2.F2.F7.F5.2F6.F5.2F$17.F11.F3.3F3.F2.F7.F7.F6.
2F3.F$17.F11.F3.2F5.2F8.2F6.F7.F3.F$18.F10.F10.2F9.F6.F4.F3.4F$18.F
10.F10.2F9.F7.F3.F6.F$18.2F8.2F29.F3.F5.F$19.F8.F30.F2.2F5.F$19.F8.F
31.2F7.F$28.F31.F8.F$27.F41.F$25.3F40.F$25.2F41.F$68.F$68.F$68.F$68.F
$67.F$66.2F$65.F$64.F$64.F$63.F$62.2F!
Input example (make sure all cells centers are at X = 0, Y = 10*N and the functions codes are at same Y, and X = 10) - better use the attached file.
Attachments
Golly_input_simkin_script.mc
(5.84 KiB) Downloaded 440 times

Post Reply