mniemiec wrote:The problem with interpreting B2a2e as B2ae is that while it works fine for totally positive rules (i.e. B2x2y = B2(x|y)) and negative rules (i.e. B2-x2-y = B2-(x|y)), what consistent meaning could you apply when mixing both positive and negative rules, e.g. B2ae2-ca?
Hmm. It might be nice if the parser rejected that as an invalid rule string, just as it rejects
x = 3, y = 3, rule = B3/S2S3
obo$obo$3o!
But in fact it doesn't. From a quick experiment, it looks like a minus sign does in fact cancel any preceding definition for that neighbor count:
x = 3, y = 3, rule = B3-a/S23-a
obo$obo$3o!
x = 3, y = 3, rule = B3-r/S23-r
obo$obo$3o!
x = 3, y = 3, rule = B3-ar/S23-ar
obo$obo$3o!
x = 3, y = 3, rule = B3-a3-r/S23-a3-r
obo$obo$3o!
#C -- illegal because a minus sign can only follow a digit
x = 3, y = 3, rule = B3-a-r/S23-a-r
obo$obo$3o!
The above is not the most obvious example, maybe, but it seems to show that
B3-a3-r/S23-a3-r != B3-ar/S23-ar B3-a3-r/S23-a3-r = B3-r/S23-rThis is in fact the behavior that y'all prefer, where a later definition cancels an earlier definition -- but only in the case where a minus sign is present in a later definition!
This seems pretty reasonable to me, since the minus sign is just syntactic sugar to minimize the rulestring length. If your priority is minimizing the rulestring length then it doesn't really make any sense to have more than one minus sign, or to put the minus sign later after adding extra characters. Garbage in, garbage out -- or at least you might as well take whatever you get, given a reasonable arbitrary choice of parsing algorithm.
Here's the
relevant code that's in Golly at the moment:
// all valid rule letters
valid_rule_letters = "012345678ceaiknjqrytwz-" ;
// rule letters per neighbor count
rule_letters[0] = "ce" ;
rule_letters[1] = "ceaikn" ;
rule_letters[2] = "ceaiknjqry" ;
rule_letters[3] = "ceaiknjqrytwz" ;
// isotropic neighborhoods per neighbor count
static int entry0[2] = { 1, 2 } ;
static int entry1[6] = { 5, 10, 3, 40, 33, 68 } ;
static int entry2[10] = { 69, 42, 11, 7, 98, 13, 14, 70, 41, 97 } ;
static int entry3[13] = { 325, 170, 15, 45, 99, 71, 106, 102, 43, 101, 105, 78, 108 } ;
rule_neighborhoods[0] = entry0 ;
rule_neighborhoods[1] = entry1 ;
rule_neighborhoods[2] = entry2 ;
rule_neighborhoods[3] = entry3 ;
// bit offset for survival part of rule
survival_offset = 9 ;
// bit in letter bit mask indicating negative
negative_bit = 13 ;
// maximum number of letters per neighbor count
max_letters[0] = 0 ;
max_letters[1] = (int) strlen(rule_letters[0]) ;
max_letters[2] = (int) strlen(rule_letters[1]) ;
max_letters[3] = (int) strlen(rule_letters[2]) ;
max_letters[4] = (int) strlen(rule_letters[3]) ;
max_letters[5] = max_letters[3] ;
max_letters[6] = max_letters[2] ;
max_letters[7] = max_letters[1] ;
max_letters[8] = max_letters[0] ;
for (i = 0 ; i < survival_offset ; i++) {
max_letters[i + survival_offset] = max_letters[i] ;
}
// canonical letter order per neighbor count
static int order0[1] = { 0 } ;
static int order1[2] = { 0, 1 } ;
static int order2[6] = { 2, 0, 1, 3, 4, 5 } ;
static int order3[10] = { 2, 0, 1, 3, 6, 4, 5, 7, 8, 9 } ;
static int order4[13] = { 2, 0, 1, 3, 6, 4, 5, 7, 8, 10, 11, 9, 12 } ;
order_letters[0] = order0 ;
order_letters[1] = order1 ;
order_letters[2] = order2 ;
order_letters[3] = order3 ;
order_letters[4] = order4 ;
order_letters[5] = order3 ;
order_letters[6] = order2 ;
order_letters[7] = order1 ;
order_letters[8] = order0 ;
for (i = 0 ; i < survival_offset ; i++) {
order_letters[i + survival_offset] = order_letters[i] ;
}
// ...
// set rule from birth or survival string
void liferules::setRuleFromString(const char *rule, bool survival) {
// current and next character
char current ;
char next ;
// whether character normal or inverted
int normal = 1 ;
// letter index
char *letterindex = 0 ;
int lindex = 0 ;
int nindex = 0 ;
// process each character
while ( *rule ) {
current = *rule ;
rule++ ;
// find the index in the valid character list
letterindex = strchr((char*) valid_rule_letters, current) ;
lindex = letterindex ? int(letterindex - valid_rule_letters) : -1 ;
// check if it is a digit
if (lindex >= 0 && lindex <= 8) {
// determine what follows the digit
next = *rule ;
nindex = -1 ;
if (next) {
letterindex = strchr((char*) rule_letters[3], next) ;
if (letterindex) {
nindex = int(letterindex - rule_letters[3]) ;
}
}
// is the next character a digit or minus?
if (nindex == -1) {
setTotalistic(lindex, survival) ;
}
// check for inversion
normal = 1 ;
if (next == '-') {
rule++ ;
next = *rule ;
// invert following character meanings
normal = 0 ;
}
// process non-totalistic characters
if (next) {
letterindex = strchr((char*) rule_letters[3], next) ;
nindex = -1 ;
if (letterindex) {
nindex = int(letterindex - rule_letters[3]) ;
}
while (nindex >= 0) {
// set symmetrical
setSymmetrical(lindex, survival, nindex, normal) ;
// get next character
rule++ ;
next = *rule ;
nindex = -1 ;
if (next) {
letterindex = strchr((char*) rule_letters[3], next) ;
if (letterindex) {
nindex = int(letterindex - rule_letters[3]) ;
}
}
}
}
}
}
}
// ...
// set symmetrical neighborhood into 3x3 map
void liferules::setSymmetrical512(int x, int b) {
int y = x ;
int i = 0 ;
// process each of the 4 rotations
for (i = 0 ; i < 4 ; i++) {
rule3x3[y] = (char) b ;
y = rotateBits90Clockwise(y) ;
}
// flip
y = flipBits(y) ;
// process each of the 4 rotations
for (i = 0 ; i < 4 ; i++) {
rule3x3[y] = (char) b ;
y = rotateBits90Clockwise(y) ;
}
}
// set symmetrical neighborhood
void liferules::setSymmetrical(int value, bool survival, int lindex, int normal) {
int xorbit = 0 ;
int nindex = value - 1 ;
int x = 0 ;
int offset = 0 ;
// check for homogeneous bits
if (value == 0 || value == 8) {
setTotalistic(value, survival) ;
}
else {
// update the rulebits
if (survival) {
offset = survival_offset ;
}
rulebits |= 1 << (value + offset) ;
// reflect the index if in second half
if (nindex > 3) {
nindex = 6 - nindex ;
xorbit = 0x1ef ;
}
// update the letterbits
letter_bits[value + offset] |= 1 << lindex ;
if (!normal) {
// set the negative bit
letter_bits[value + offset] |= 1 << negative_bit ;
}
// lookup the neighborhood
x = rule_neighborhoods[nindex][lindex] ^ xorbit ;
if (survival) {
x |= 0x10 ;
}
setSymmetrical512(x, normal) ;
}
}
I haven't quoted quite all of the relevant parts, unfortunately -- the link above is a better source if you really want to dig into what it's doing. Seems as if when it hits the second "3-" in each pair in "B3-a3-r/S23-a3-r", that resets the isotropic bits to all-ON... whereas it doesn't reset the bits to all-OFF when it hits the second "2" in "B2a2e".
I'm perfectly happy with the way things work currently in Golly and in LifeViewer, because it allows useful non-canonical strings (with a more complete specification for each isotropic bit) to be interpreted correctly, and it either throws an error or gives a reasonable best guess for non-useful garbage rulestrings.
Here's a case that y'all probably won't like, unfortunately. It seems to me that it's okay that it works exactly this way. I wouldn't mind too much if it threw an error instead, but somewhere in here is a slippery slope to complaining too much, say by allowing only rulestrings that are completely canonical:
x = 3, y = 3, rule = B3-a3a/S2-e2e3-a3a
obo$obo$3o!
This is just plain-vanilla Life, because only a digit followed by a minus sign performs a reset on the relevant isotropic bits for that digit. I read it as "all 3-neighbor births except for 'a' -- and also 'a' after all", and so on.
Yes, this is the same kind of unnecessary obfuscation as muzik's b3s2-aceikn2aceikn3. But if Calcyman wants to allow non-canonical rulestrings rather than rejecting them or canonicalizing them somehow, then it seems like that's a reasonable choice. That level of sanitation doesn't have to be Catagolue's problem... though it does seem a bit like giving people enough rope to hang themselves.
LifeViewer and Golly have a consistent (in the sense of predictable and reproducible) interpretation of isotropic rulestrings, and I don't think there are any rulestring parsers currently in use that do anything else. Is there really a problem here, that won't be made significantly worse by any possible change at this point?