Re: Life tag testing
Posted: July 15th, 2016, 10:32 am
Brilliant, thanks!Nathaniel wrote:Done!rowett wrote:Nathaniel - sorry I found a bug with LifeViewer on the Wiki! Please will you upload build 207.
Brilliant, thanks!Nathaniel wrote:Done!rowett wrote:Nathaniel - sorry I found a bug with LifeViewer on the Wiki! Please will you upload build 207.
I don't know of any pattern collections of any size that use either of those syntax conventions. However, as a way of defending my limited remaining sanity, I don't pay much attention to hex-rule pattern collections. There might well be something like that out there.rowett wrote:I took a quick look at the resurrected Life32 project. He uses some unique notation for neighbourhoods. Dave, do you know if it's commonly used or used in any pattern collections?
Basically he prefixes a B/S rule with the neighbourhood letter and a colon: e.g. H:B23/S24 for Hex etc. Also he supports an octal number e.g. 757 to specify an arbitrary neighbourhood.
The first test build of these changes (in Molly) is here. I had to rewrite most of the liferules module so it certainly needs some testing!dvgrn wrote:I'd certainly like to see isotropic rule support find its way into Golly 2.8, if that's possible -- just because it's hard to know when the next Golly release will end up being, and it would be really nice to get Golly to match LifeViewer on that point. Let me know if there's anything I can do to help there.
Code: Select all
/*** /
This file is part of Golly, a Game of Life Simulator.
Copyright (C) 2013 Andrew Trevorrow and Tomas Rokicki.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Web site: http://sourceforge.net/projects/golly
Authors: rokicki@gmail.com andrew@trevorrow.com
/ ***/
#include "liferules.h"
#include "util.h"
#include <cstdlib>
#include <string.h>
#include <stdlib.h>
liferules::liferules() {
valid_rule_letters = "012345678ceaiknjqrytwz-" ;
rule_letters[0] = "ce" ;
rule_letters[1] = "ceaikn" ;
rule_letters[2] = "ceaiknjqry" ;
rule_letters[3] = "ceaiknjqrytwz" ;
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 ;
survival_offset = 9 ;
// initialize
initRule() ;
}
liferules::~liferules() {
}
// initialise
void liferules::initRule() {
alternate_rules = 0 ;
neighbormask = MOORE ;
neighbors = 8 ;
wolfram = -1 ;
totalistic = true ;
rulebits = 0 ;
memset(letter_bits, 0, sizeof(letter_bits));
memset(neg_letter_bits, 0, sizeof(letter_bits));
alternate_rules = false ;
memset(rule0, 0, sizeof(rule0)) ;
memset(rule1, 0, sizeof(rule1)) ;
memset(rule3x3, 0, sizeof(rule3x3)) ;
memset(canonrule, 0, sizeof(canonrule)) ;
}
// set 3x3 grid based on totalistic value
void liferules::setTotalistic(int value, bool survival) {
int mask = 0 ;
int nbrs = 0 ;
int nhood = 0 ;
int i = 0 ;
int j = 0 ;
int offset = 0 ;
// update the rulebits
if (survival) {
offset = survival_offset ;
}
rulebits |= 1 << (value + offset) ;
// update the mask if survival
if (survival) {
mask = 0x10 ;
}
// fill the array based on totalistic value
for (i = 0; i < ALL3X3; i += 32) {
for (j = 0; j < 16; j++) {
nbrs = 0 ;
nhood = (i+j) & neighbormask ;
while (nhood > 0) {
nbrs += (nhood & 1) ;
nhood >>= 1 ;
}
if (value == nbrs) {
rule3x3[i+j+mask] = 1 ;
}
}
}
}
// flip bits
int liferules::flipBits(int x) {
return ((x & 0x07) << 6) | ((x & 0x1c0) >> 6) | (x & 0x38) ;
}
// rotate 90
int liferules::rotateBits90Clockwise(int x) {
return ((x & 0x4) << 6) | ((x & 0x20) << 2) | ((x & 0x100) >> 2)
| ((x & 0x2) << 4) | (x & 0x10) | ((x & 0x80) >> 4)
| ((x & 0x1) << 2) | ((x & 0x8) >> 2) | ((x & 0x40) >> 6) ;
}
// 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] = b ;
y = rotateBits90Clockwise(y) ;
}
// flip
y = flipBits(y) ;
// process each of the 4 rotations
for (i=0; i<4; i++) {
rule3x3[y] = 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
if (normal) {
letter_bits[value + offset] |= 1 << lindex ;
}
else {
neg_letter_bits[value + offset] |= 1 << lindex ;
}
// lookup the neighborhood
x = rule_neighborhoods[nindex][lindex] ^ xorbit ;
if (survival) {
x |= 0x10 ;
}
setSymmetrical512(x, normal) ;
}
}
// set totalistic birth or survival rule from a string
void liferules::setTotalisticRuleFromString(const char *rule, bool survival) {
char current ;
// process each character in the rule string
while ( (current = *rule) ) {
rule++ ;
// convert the digit to an integer
current -= '0' ;
// set totalistic
setTotalistic(current, survival) ;
}
}
// set rule from birth or survival string
void liferules::setRuleFromString(const char *rule, bool survival) {
// current and nexr 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 ( (current = *rule) ) {
rule++ ;
// find the index in the valid character list
letterindex = strchr(valid_rule_letters, current) ;
lindex = letterindex - valid_rule_letters ;
// check if it is a digit
if ((lindex > 0 && lindex <= 8) || (lindex == 0 && survival)) {
// determine what follows the digit
next = *rule ;
nindex = -1 ;
if (next) {
letterindex = strchr(rule_letters[3], next) ;
if (letterindex) {
nindex = letterindex - rule_letters[3] ;
}
}
// is the next character another digit?
if (nindex == -1) {
// yes so set totalitic with the current digit
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(rule_letters[3], next) ;
nindex = -1 ;
if (letterindex) {
nindex = 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(rule_letters[3], next) ;
if (letterindex) {
nindex = letterindex - rule_letters[3] ;
}
}
}
}
}
}
}
// create the rule map from Wolfram number
void liferules::createWolframMap() {
int i = 0 ;
// clear the rule array
memset(rule3x3, 0, ALL3X3) ;
// set in the 3x3 map
for (i = 0; i < ALL3X3; i+= 1) {
if ((wolfram & (1 << (i & 7))) || (i & 16)) {
rule3x3[i] = 1 ;
}
}
}
// create the rule map from birth and survival strings
void liferules::createRuleMap(const char *birth, const char *survival) {
// clear the rule array
memset(rule3x3, 0, ALL3X3) ;
// check for totalistic rule
if (totalistic) {
// set the totalistic birth rule
setTotalisticRuleFromString(birth, false) ;
// set the totalistic surivival rule
setTotalisticRuleFromString(survival, true) ;
}
else {
// set the non-totalistic birth rule
setRuleFromString(birth, false) ;
// set the non-totalistic survival rule
setRuleFromString(survival, true) ;
}
}
// AKT: store valid rule in canonical format for getrule()
void liferules::createCanonicalName(lifealgo *algo) {
int p = 0 ;
int i = 0 ;
int j = 0 ;
// check for wolfram rule
if (wolfram >= 0) {
sprintf(canonrule, "W%d", wolfram) ;
while (canonrule[p]) p++ ;
} else {
// output birth part
canonrule[p++] = 'B' ;
for (i=0; i<=neighbors; i++) {
if (rulebits & (1 << i)) {
canonrule[p++] = '0' + (char)i ;
// check for non-totalistic
if (letter_bits[i]) {
for (j=0; j<13; j++) {
if (letter_bits[i] & (1 << j)) {
canonrule[p++] = rule_letters[3][j] ;
}
}
}
if (neg_letter_bits[i]) {
canonrule[p++] = '-' ;
for (j=0; j<13; j++) {
if (neg_letter_bits[i] & (1 << j)) {
canonrule[p++] = rule_letters[3][j] ;
}
}
}
}
}
// add slash
canonrule[p++] = '/' ;
// output survival part
canonrule[p++] = 'S' ;
for (i=0; i<=neighbors; i++) {
if (rulebits & (1 << (survival_offset+i))) {
canonrule[p++] = '0' + (char)i ;
// check for non-totalistic
if (letter_bits[survival_offset+i]) {
for (j=0; j<13; j++) {
if (letter_bits[survival_offset+i] & (1 << j)) {
canonrule[p++] = rule_letters[3][j] ;
}
}
}
if (neg_letter_bits[survival_offset+i]) {
canonrule[p++] = '-' ;
for (j=0; j<13; j++) {
if (neg_letter_bits[survival_offset+i] & (1 << j)) {
canonrule[p++] = rule_letters[3][j] ;
}
}
}
}
}
// add neighborhood
if (neighbormask == HEXAGONAL) canonrule[p++] = 'H' ;
if (neighbormask == VON_NEUMANN) canonrule[p++] = 'V' ;
}
// check for bounded grid
if (algo->gridwd > 0 || algo->gridht > 0) {
// algo->setgridsize() was successfully called above, so append suffix
const char* bounds = algo->canonicalsuffix() ;
int i = 0 ;
while (bounds[i]) canonrule[p++] = bounds[i++] ;
}
// null terminate
canonrule[p] = 0 ;
}
// convert the 3x3 map to the 4x4 map
void liferules::convertTo4x4Map(char *which) {
int i = 0 ;
int v = 0 ;
// create every possible cell combination for 4x4
for (i = 0; i < 65536; i += 1) {
// perform 4 lookups in the 3x3 map to create the 4x4 entry
// 15 14 13 x 7 6 5
// 11 10 9 x -> 11 10 9 -> 10' x 0 0 x x
// 7 6 5 x 15 14 13
// x x x x
v = rule3x3[((i & 57344) >> 13) | ((i & 3584) >> 6) | ((i & 224) << 1)] << 5 ;
// x 14 13 12 6 5 4
// x 10 9 8 -> 10 9 8 -> x 9' 0 0 x x
// x 6 5 4 14 13 12
// x x x x
v |= rule3x3[((i & 28672) >> 12) | ((i & 1792) >> 5) | ((i & 112) << 2)] << 4 ;
// x x x x
// 11 10 9 x -> 3 2 1 -> x x 0 0 6' x
// 7 6 5 x 7 6 5
// 3 2 1 x 11 10 9
v |= rule3x3[((i & 3584) >> 9) | ((i & 224) >> 2) | ((i & 14) << 5)] << 1 ;
// x x x x
// x 10 9 8 -> 2 1 0 -> x x 0 0 x 5'
// x 6 5 4 6 5 4
// x 2 1 0 10 9 8
v |= rule3x3[((i & 1792) >> 8) | ((i & 112) >> 1) | ((i & 7) << 6)] ;
// save the entry
which[i] = v ;
}
}
/*
Following routines emulate B0 rules using David Eppstein's idea to change
the current rule depending on generation parity.
If original rule has B0 but not S8:
For even generations, whenever the original rule has a Bx or Sx, omit that
bit from the modified rule, and whenever the original rule is missing a
Bx or Sx, add that bit to the modified rule.
eg. B03/S23 => B1245678/S0145678.
For odd generations, use Bx if and only if the original rule has S(8-x)
and use Sx if and only if the original rule has B(8-x).
eg. B03/S23 => B56/S58.
If original rule has B0 and S8:
Such rules don't strobe, so we just want to invert all the cells.
The trick is to do both changes: invert the bits, and swap Bx for S(8-x).
eg. B03/S238 => B123478/S0123467 (for ALL gens).
*/
// create the B0 (no Smax) even generation rule map
void liferules::createB0SmaxRuleMap(const char *birth, const char *survival) {
int b = 0 ;
int s = 0 ;
int i = 0 ;
char newbirth[10] ;
char newsurvival[10] ;
// invert neighbor counts and Bx->S(max-x) and Sx->B(max-x)
for (i = 0 ; i <= neighbors; i++) {
// check if the digit is in the birth part
if (strchr(birth, '0' + i) == 0) {
// compute Smax-x and add to survival part
newsurvival[s++] = '0' + (neighbors - i) ;
}
// check if the digit is in the survival part
if (strchr(survival, '0' + i) == 0) {
// compute Smax-x and add to birth part
newbirth[b++] = '0' + (neighbors - i) ;
}
}
// terminate strings
newbirth[b] = 0 ;
newsurvival[s] = 0 ;
// create the rule map
createRuleMap(newbirth, newsurvival) ;
}
// create the B0 (no Smax) odd generation rule map
void liferules::createB0OddRuleMap(const char *birth, const char *survival) {
int b = 0 ;
int s = 0 ;
int i = 0 ;
char newbirth[10] ;
char newsurvival[10] ;
// Bx->S(max-x) and Sx->B(max-x)
for (i = 0 ; i <= neighbors; i++) {
// check if the digit is in the birth part
if (strchr(birth, '0' + i) != 0) {
// Sx->B(max-x)
newsurvival[s++] = '0' + (neighbors - i) ;
}
// check if the digit is in the survival part
if (strchr(survival, '0' + i) != 0) {
// Bx->S(max-x)
newbirth[b++] = '0' + (neighbors - i) ;
}
}
// terminate strings
newbirth[b] = 0 ;
newsurvival[s] = 0 ;
// create the rule map
createRuleMap(newbirth, newsurvival) ;
}
// create the B0 Smax rule map
void liferules::createB0EvenRuleMap(const char *birth, const char *survival) {
int b = 0 ;
int s = 0 ;
int i = 0 ;
char newbirth[10] ;
char newsurvival[10] ;
// invert neighbor counts
for (i = 0 ; i <= neighbors; i++) {
// check if the digit is in the birth part
if (strchr(birth, '0' + i) == 0) {
// add to birth part
newbirth[b++] = '0' + i ;
}
// check if the digit is in the survival part
if (strchr(survival, '0' + i) == 0) {
// add to survival part
newsurvival[s++] = '0' + i ;
}
}
// terminate strings
newbirth[b] = 0 ;
newsurvival[s] = 0 ;
// create the rule map
createRuleMap(newbirth, newsurvival) ;
}
// remove character from a string in place
void liferules::removeChar(char *string, char skip) {
int src = 0 ;
int dst = 0 ;
int c ;
// copy characters other than skip
while( (c = string[src++]) ) {
if (c != skip) {
string[dst++] = c ;
}
}
// ensure null terminated
string[dst] = 0 ;
}
// check whether non-totalistic letters are valid for defined neighbor counts
bool liferules::lettersValid(const char *part) {
char c ;
int nindex = 0 ;
int currentCount = -1 ;
// get next character
while ( (c = *part) ) {
if (c >= '0' && c <= '8') {
currentCount = c - '0' ;
nindex = currentCount - 1;
if (nindex > 3) {
nindex = 6 - nindex ;
}
}
else {
// ignore minus
if (c != '-') {
// not valid if 0 or 8
if (currentCount == 0 || currentCount == 8) {
return false ;
}
// check against valid rule letters for this neighbor count
if (strchr(rule_letters[nindex], c) == 0) {
return false ;
}
}
}
part++ ;
}
return true ;
}
// set rule
const char *liferules::setrule(const char *rulestring, lifealgo *algo) {
char *r = (char *)rulestring ;
char tidystring[MAXRULESIZE] ; // tidy version of rule string
char *t = (char *)tidystring ;
char *end = r + strlen(r) ; // end of rule string
char len = 0 ; // length of tidy string
char c ;
char *charpos = 0 ;
int digit ;
int maxdigit = 0 ; // maximum digit value found
char *colonpos = 0 ; // position of colon
char *slashpos = 0 ; // position of slash
char *underscorepos = 0 ; // position of underscore
char *bpos = 0 ; // position of b
char *spos = 0 ; // position of s
// initialise rule type
initRule() ;
// we might need to emulate B0 rule by using two different rules for odd/even gens
alternate_rules = false ;
// check for colon
colonpos = strchr(r, ':') ;
if (colonpos) {
// only process up to the colon
end = colonpos ;
}
// create lower case version of rule name without spaces
while (r < end) {
// get the next character and convert to lowercase
c = tolower(*r) ;
// process the character
switch (c) {
// birth
case 'b':
if (bpos) {
// multiple b found
return "Only one B allowed." ;
}
bpos = t ;
*t = c ;
t++ ;
break ;
// survival
case 's':
if (spos) {
// multiple s found
return "Only one S allowed." ;
}
spos = t ;
*t = c ;
t++ ;
break ;
// slash
case '/':
if (slashpos) {
// multiple slashes found
return "Only one slash allowed." ;
}
slashpos = t ;
*t = c ;
t++ ;
break ;
// underscore
case '_':
if (underscorepos) {
// multiple underscores found
return "Only one underscore allowed." ;
}
underscorepos = t ;
*t = c ;
t++ ;
break ;
// hex
case 'h':
if (neighbormask != MOORE || wolfram != -1) {
// multiple neighborhoods specified
return "Only one neighborhood allowed." ;
}
neighbormask = HEXAGONAL ;
neighbors = 6 ;
*t = c ;
t++ ;
break ;
// von neumann
case 'v':
if (neighbormask != MOORE || wolfram != -1) {
// multiple neighborhoods specified
return "Only one neighborhood allowed." ;
}
neighbormask = VON_NEUMANN ;
neighbors = 4 ;
*t = c ;
t++ ;
break ;
// wolfram
case 'w':
// check if at beginning of string
if (t == tidystring) {
if (neighbormask != MOORE || wolfram != -1) {
// multiple neighborhoods specified
return "Only one neighborhood allowed." ;
}
wolfram = 0 ;
}
else {
// copy character
*t = c ;
t++ ;
totalistic = false ;
}
break ;
// other characters
default:
// ignore space
if (c != ' ') {
// check character is valid
charpos = strchr(valid_rule_letters, c) ;
if (charpos) {
// copy character
*t = c ;
t++ ;
// check if totalistic (i.e. found a valid non-digit)
digit = charpos - valid_rule_letters ;
if (digit > 8) {
totalistic = false ;
}
else {
// update maximum digit found
if (digit > maxdigit) {
maxdigit = digit ;
}
}
}
else {
return "Bad character found." ;
}
}
break ;
}
// next character
r++ ;
}
// ensure null terminated
*t = 0 ;
// don't allow empty rule string
t = tidystring ;
if (*t == 0) {
return "Rule cannot be empty string." ;
}
// can't have slash and underscore
if (underscorepos && slashpos) {
return "Can't have slash and underscore." ;
}
// underscore only valid for non-totalistic rules
if (underscorepos && totalistic) {
return "Underscore not valid for totalistic rules, use slash." ;
}
// if underscore defined then set the slash position
if (underscorepos) {
slashpos = underscorepos ;
}
// check for Wolfram
if (wolfram == 0) {
// parse Wolfram 1D rule
while (*t >= '0' && *t <= '9') {
wolfram = 10 * wolfram + *t - '0' ;
t++ ;
}
if (wolfram < 0 || wolfram > 254 || wolfram & 1) {
return "Wolfram rule must be an even number from 0 to 254." ;
}
if (*t) {
return "Bad character in Wolfram rule." ;
}
}
else {
// if neighborhood specified then must be last character
if (neighbormask != MOORE) {
len = strlen(t) ;
if (len) {
c = t[len - 1] ;
if (!((c == 'h') || (c == 'v'))) {
return "Neighborhood must be at end of rule." ;
}
// remove character
t[len - 1] = 0 ;
}
}
// at least one of slash, b or s must be present
if (!(slashpos || bpos || spos)) {
return "Rule must contain a slash or B or S." ;
}
// digits can not be greater than the number of neighbors for the defined neighborhood
if (maxdigit > neighbors) {
return "Digit greater than neighborhood allows." ;
}
// if slash present and both b and s then one must be each side of the slash
if (slashpos && bpos && spos) {
if ((bpos < slashpos && spos < slashpos) || (bpos > slashpos && spos > slashpos)) {
return "B and S must be either side of slash." ;
}
}
// check if there was a slash to divide birth from survival
if (!slashpos) {
// check if both b and s exist
if (bpos && spos) {
// determine whether b or s is first
if (bpos < spos) {
// skip b and cut the string using s
bpos++ ;
*spos = 0 ;
spos++ ;
}
else {
// skip s and cut the string using b
spos++ ;
*bpos = 0 ;
bpos++ ;
}
}
else {
// just bpos
if (bpos) {
bpos = t ;
removeChar(bpos, 'b') ;
spos = bpos + strlen(bpos) ;
}
else {
// just spos
spos = t;
removeChar(spos, 's') ;
bpos = spos + strlen(spos ) ;
}
}
}
else {
// slash exists so set determine which part is b and which is s
*slashpos = 0 ;
// check if b or s are defined
if (bpos || spos) {
// check for birth first
if ((bpos && bpos < slashpos) || (spos && spos > slashpos)) {
// birth then survival
bpos = t ;
spos = slashpos + 1 ;
}
else {
// survival then birth
bpos = slashpos + 1 ;
spos = t ;
}
// remove b or s from rule parts
removeChar(bpos, 'b') ;
removeChar(spos, 's') ;
}
else {
// no b or s so survival first
spos = t ;
bpos = slashpos + 1 ;
}
}
// if not totalistic and a part exists it must start with a digit
if (!totalistic) {
// check birth
c = *bpos ;
if (c && (c < '0' || c > '8')) {
return "Non-totalistic birth must start with a digit." ;
}
// check survival
c = *spos ;
if (c && (c < '0' || c > '8')) {
return "Non-totalistic survival must start with a digit." ;
}
}
// if not totalistic then neighborhood must be Moore
if (!totalistic && neighbormask != MOORE) {
return "Non-totalistic only supported with Moore neighborhood." ;
}
// validate letters used against each specified neighbor count
if (!lettersValid(bpos)) {
return "Letter not valid for birth neighbor count." ;
}
if (!lettersValid(spos)) {
return "Letter not valid for survival neighbor count." ;
}
}
// AKT: check for rule suffix like ":T200,100" to specify a bounded universe
if (colonpos) {
const char* err = algo->setgridsize(colonpos) ;
if (err) return err ;
} else {
// universe is unbounded
algo->gridwd = 0 ;
algo->gridht = 0 ;
}
// create the rule map
if (wolfram >= 0) {
// create the 3x3 map
createWolframMap() ;
// save the canonical rule name
createCanonicalName(algo) ;
// convert to the 4x4 map
convertTo4x4Map(rule0) ;
}
else {
// generate the 3x3 map (which also creates the data for the canonical format)
createRuleMap(bpos, spos) ;
// save the canonical rule name
createCanonicalName(algo) ;
// check for B0 rules
if (totalistic && strchr(bpos, '0')) {
// check for Smax
if (strchr(spos, '0' + neighbors)) {
// B0 Smax
createB0SmaxRuleMap(bpos, spos) ;
// convert to the even 4x4 map
convertTo4x4Map(rule0) ;
}
else {
// set alternate rules needed
alternate_rules = true ;
// B0 without Smax even generation
createB0EvenRuleMap(bpos, spos) ;
// convert to the even 4x4 map
convertTo4x4Map(rule0) ;
// B0 without Smax odd generation
createB0OddRuleMap(bpos, spos) ;
// convert to the odd 4x4 map
convertTo4x4Map(rule1) ;
}
}
else {
// non-B0 rule so convert 3x3 to 4x4 map
convertTo4x4Map(rule0) ;
}
}
// exit with success
return 0 ;
}
const char* liferules::getrule() {
return canonrule ;
}
// B3/S23 -> (1 << 3) + (1 << (9 + 2)) + (1 << (9 + 3)) = 0x1808
bool liferules::isRegularLife() {
return (neighbormask == MOORE && totalistic && rulebits == 0x1808 && wolfram < 0) ;
}
Code: Select all
/*** /
This file is part of Golly, a Game of Life Simulator.
Copyright (C) 2013 Andrew Trevorrow and Tomas Rokicki.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Web site: http://sourceforge.net/projects/golly
Authors: rokicki@gmail.com andrew@trevorrow.com
/ ***/
/**
* This class implements the rules supported by QuickLife and HashLife.
* A rule lookup table is used for computing a new 2x2 grid from
* a provided 4x4 grid (two tables are used to emulate B0-not-Smax rules).
* The input is a 16-bit integer, with the most significant bit the
* upper left corner, bits going horizontally and then down. The output
* is a 6-bit integer, with the top two bits indicating the top row of
* the 2x2 output grid and the least significant two bits indicating the
* bottom row. The middle two bits are always zero.
*/
#ifndef LIFERULES_H
#define LIFERULES_H
#include "lifealgo.h"
const int MAXRULESIZE = 200 ;
const int ALL3X3 = 512 ;
class liferules {
public:
liferules() ;
~liferules() ;
// string returned by setrule is any error
const char *setrule(const char *s, lifealgo *algo) ;
const char *getrule() ;
// AKT: we need 2 tables to support B0-not-Smax rule emulation
// where max is 8, 6 or 4 depending on the neighborhood
char rule0[65536]; // rule table for even gens if rule has B0 but not Smax,
// or for all gens if rule has no B0, or it has B0 *and* Smax
char rule1[65536]; // rule table for odd gens if rule has B0 but not Smax
bool alternate_rules; // set by setrule; true if rule has B0 but not Smax
// AKT: support for various neighborhoods
// rowett: support for hensel2
enum neighborhood_masks {
MOORE = 0x1ff, // all 8 neighbors
HEXAGONAL = 0x0fe, // ignore NE and SW neighbors
VON_NEUMANN = 0x0ba // 4 orthogonal neighbors
} ;
bool isRegularLife() ; // is this B3/S23?
bool isHexagonal() const { return neighbormask == HEXAGONAL ; }
bool isVonNeumann() const { return neighbormask == VON_NEUMANN ; }
bool isWolfram() const { return wolfram >= 0 ; }
private:
// canonical version of valid rule passed into setrule
char canonrule[MAXRULESIZE] ;
neighborhood_masks neighbormask ;
bool totalistic ; // is rule totalistic?
int neighbors ; // number of neighbors
int rulebits ; // bitmask of neighbor counts used (9 birth, 9 survival)
int letter_bits[18] ; // bitmask for non-totalistic letters used
int neg_letter_bits[18] ; // bitmask for non-totalistic negative letters used
int wolfram ; // >= 0 if Wn rule (n is even and <= 254)
int survival_offset ; // bit offset in rulebits for survival
void initRule() ;
void setTotalistic(int value, bool survival) ;
int flipBits(int x) ;
int rotateBits90Clockwise(int x) ;
void setSymmetrical512(int x, int b) ;
void setSymmetrical(int value, bool survival, int lindex, int normal) ;
void setTotalisticRuleFromString(const char *rule, bool survival) ;
void setRuleFromString(const char *rule, bool survival) ;
void createWolframMap() ;
void createRuleMap(const char *birth, const char *survival) ;
void createB0SmaxRuleMap(const char *birth, const char *survival) ;
void createB0EvenRuleMap(const char *birth, const char *survival) ;
void createB0OddRuleMap(const char *birth, const char *survival) ;
void convertTo4x4Map(char *which) ;
void createCanonicalName(lifealgo *algo) ;
void removeChar(char *string, char skip) ;
bool lettersValid(const char *part) ;
const char *valid_rule_letters ;
const char *rule_letters[4] ;
const int *rule_neighborhoods[4] ;
char rule3x3[ALL3X3] ; // all 3x3 cell mappings 012345678->4'
} ;
#endif
Okay, I'm still confused about N and S boundaries -- or else the Help for LifeViewer is. Seems like the "0 -1/12 -1/12 0" above should move the S and W boundaries, but it's the N and W boundaries that are moving.rowett wrote:[/list]Code: Select all
#C [[ TRACKBOX 0 -1/12 -1/12 0 ]] #C [[ THEME 4 STARS STOP 3430 AUTOSTART ]] x = 51, y = 34, rule = B3/S23 32bo$30b2ob2o$32bo$47b2o$46bo2bo$33b2o11b2ob2o$32bo2bo11bo$31b2o3bo10b 3o$31bo4bo$31bo3bo$32bo2bo$33bo3$21bo7bo14b2o$20b3o5bobo12bob2o$19b2ob 4o2bo2bo10b2o$20b3o2bo18bo$21bo2b2o2bo$25b2obobo$29bo10b2o$40b2o2$3o$ 4bo28bo$4b2o13bo12bobo$4bo2bo11bo12bobo$5bobo11bo13bo$5bo$4b2o15bo$3bo bo14bobo$5b2o14bo$2b2o$4bo!
Code: Select all
x = 21, y = 140, rule = B3-ekrj4-j5-enqy68/S2eki3-ek4cnqjz
6b2o$3bob3o3bo$2b3ob2o2bobo$2b3ob2o2bobobo$3bob3o3b2o$6b2o2b2o3bo$12bo
$12bo$6b2o2b2o3bo$3bob3o3b2o$2b3ob2o2bobobo$2b3ob2o2bobo$3bob3o3bo$6b
2o25$5b2o$4b2o$3b2o$4b2o9$5bobo$4bo3bo$4bo$4b4o8$5bo$4bo2bo$4bo$4b3o
20$12b2o$11b2o2bo$10bobo2bo$9bob2obo$8b2o2bo4bo$9bob2o4bo$10b7obo$13b
4o$14b4o11$9bobo$8b6o$10bo2bo$10bo2bo$8b6o$9bobo17$18bo$9b2o7b3o$bob3o
3b4o2b4o$3obo3b3o3b5o$7o3b4ob2o$bobob2obo$4b3obobo$bo3bo2bo$4b3obobo$b
obob2obo$7o3b4ob2o$3obo3b3o3b5o$bob3o3b4o2b4o$9b2o7b3o$18bo!
#C [[ AUTOSTART TRACKBOX -1/6 0 -1/2 0 LOOP 1000 ]]Counter-intuitively for LifeViewer N is down the screen (because Y coordinates increase as you go down).dvgrn wrote:Okay, I'm still confused about N and S boundaries -- or else the Help for LifeViewer is. Seems like the "0 -1/12 -1/12 0" above should move the S and W boundaries, but it's the N and W boundaries that are moving.
Ouch! No, I must respectfully disagree. If North is downward, West would have to be toward the right side of the screen (i.e., 90 degrees leftward from North). A "North" that has West 90 degrees rightward/clockwise of it isn't really north at all... or else you're looking at the map from underneath, and LifeViewer never warned me I was doing that.rowett wrote:Counter-intuitively for LifeViewer N is down the screen (because Y coordinates increase as you go down).dvgrn wrote:Okay, I'm still confused about N and S boundaries -- or else the Help for LifeViewer is. Seems like the "0 -1/12 -1/12 0" above should move the S and W boundaries, but it's the N and W boundaries that are moving.
I didn't say it was right... If you can't make up arbitrary nonsensical coordinate systems then what can you do?dvgrn wrote:Ouch! No, I must respectfully disagree.
Just ran into some odd behavior with HISTORYFIT --rowett wrote:Build 213 is now available on the lazyslug server...
Code: Select all
#C p96 8-engine Corderman rake
#C Dean Hickerson, 7/3/1998
x = 73, y = 73, rule = B3/S23
54bo$37b3o8bo5bo$36bo3bo6bo7bo$37b2o9b4o2b2o$39b2ob2o9b3o$41b2o11bobo$
55bo7b2o$55bo7b2o4$30b3o$29bo3bo$28bo3b2o17bo$29bo20bobo18b2o$30b4o16b
o2bo17b2o$32bob2o15b2o$32bo2bo$33b2o12b2o$47b2o3$40bo21bobo$39bobo19bo
$39bobo20bo2bo$40bo23bobo$67bo$66b3o$13bo52bo$12bobo49b2o$11bo3bo50bo$
11bo3bo48bo2bo$11bobob3o46bobo$12b2obo2bo45bo$16bobo31bo$16b2o31bobo$
2bo$bobo45bo2bo3bob3o$bobo47bo4bo$bo2bo18b2o27bob2obobo$2bobo17bo2bo
27b2o3bo$5bo17b2o29bo$4b2o$4bo4$2bo15b2o$bobo14b2o$3bo29b3o$3bo10b2o
21bo12b2o$3bo9bo2bo20b2o10bo$14bobo20bo2bo8b2o$4bo10bo22bobo$2ob3o32bo
$2b3ob2o29b2o12b2o$5bo30bobo12b2o$38b2o$35b2o$37bo2$24b3o$24b3o$6b2o
17bo$6b2o18b2o$27bo$26bo4$24b2o$14b2o8b2o$14b2o9bo!
[[ HISTORYFIT ]]There were two issues here:dvgrn wrote:Just ran into some odd behavior with HISTORYFIT
Looks like Build 214 fixes the case I ran into most recently:rowett wrote:There were two issues here:dvgrn wrote:Just ran into some odd behavior with HISTORYFIT
- [[ HISTORYFIT ]] just sets the AutoFit mode. It does not switch AutoFit on. You need [[ AUTOFIT ]] also.
- There was a bug in LifeViewer which I've just fixed - test
Code: Select all
x = 3, y = 3, rule = B3cekainyqjr/S2cekain3cekainyqjr
obo$obo$3o!
#C [[ AUTOFIT HISTORYFIT ]]See here.dvgrn wrote:Looks like there's either a problem with waypoint labels, or I have to update old waypoint scripts to match new parsing rules. This animation used to say "Construction complete" only after T=3535, but now that label is popping up almost instantly. What am I forgetting?
Dang. And it was even the same exact case, and I said "What am I forgetting?" last time, too. Sorry about that.rowett wrote:See here.dvgrn wrote:What am I forgetting?
Code: Select all
x = 897, y = 903, rule = B3/S23
2o$2o4$3o$o$bo8$21b3o$21bo$22bo28$17b3o$17bo$18bo8$38b3o$38bo$39bo28$
34b3o$34bo$35bo8$55b3o$55bo$56bo28$72b3o$72bo$73bo8$91b3o$91bo$92bo8$
104b3o$104bo$105bo28$119b2o$118b2o$120bo8$149b2o$148b2o$150bo18$179b2o
$178b2o$180bo33$212b3o$212bo$213bo28$250b3o$250bo$251bo28$281b3o$281bo
$282bo28$285b3o$285bo$286bo28$315b3o$315bo$316bo28$360b3o$360bo$361bo
28$375b3o$375bo$376bo28$412b2o$411b2o$413bo28$435b2o$434b2o$436bo8$
493b3o$493bo$494bo28$519b3o$519bo$520bo28$551b3o$551bo$552bo28$574b3o$
574bo$575bo28$615b2o$614b2o$616bo48$654b3o$654bo$655bo28$687b3o$687bo$
688bo28$724b3o$724bo$725bo28$753b2o$752b2o$754bo28$787b3o$787bo$788bo
28$803b2o$802b2o$804bo28$836b2o$835b2o$837bo28$865b2o$864b2o$866bo28$
894b3o$894bo$895bo!
#C [[ STEP 8 ZOOM 4 X -445 Y -415 ]]
#C [[ T 0 PAUSE 2 "loaf2GtoG slow salvo\n35 gliders total" LOOP 4000 ]]OK so that one is my fault. Fixed in the next build. That's 1 each.dvgrn wrote:How about this case? It looks fine the way I've left it, but if I remove AUTOSTART as shown below, LifeViewer only sets the zoom level for the initial view -- it ignores the X and Y settings until you hit the Play button.