3 [This file is part of Bedbugs. All I've done is made it implement
4 the Conway transition function. Original copyright notice follows:]
6 This file is part of Golly, a Game of Life Simulator.
7 Copyright (C) 2008 Andrew Trevorrow and Tomas Rokicki.
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 Web site: http://sourceforge.net/projects/golly
24 Authors: rokicki@gmail.com andrew@trevorrow.com
41 Makes a rule-table for your transition function.
44 g++ -O5 -o make-ruletable make-ruletable.cpp
45 or in Microsoft Visual Studio, add to an empty CLR project.
48 1) fill slowcalc with your own transition function.
49 2) set the parameters in main() at the bottom.
50 3) execute the program from the command line (no options taken)
52 For a 32-state 5-neighbourhood rule it took 16mins on a 2.2GHz machine.
53 For a 4-state 9-neighbourhood rule it took 4s.
55 The merging is very simple - if a transition is compatible with the first rule,
56 then merge the transition into the rule. If not, try the next rule. If there are
57 no more rules, add a new rule containing just the transition.
59 === Worked example: ===
65 Start. First transition taken as first rule:
67 Next transition - is it compatible with rule 1?
68 i.e. is 1,[0,2],0->3 valid for all permutations? Yes.
69 rule 1 now: 1,[0,2],0->3
70 Next transition - is it compatible with rule 1?
71 i.e. is [1,2],[0,2],0->3 valid for all permutations?
72 no - because 2,2,0 ->1, not ->3. so add the transition as a new rule:
73 rule 1 still: 1,[0,2],0 -> 3
75 Next transition - is it compatible with rule 1? no - output is different.
76 Is it compatible with rule 2? no - output is different.
81 Written with variables:
88 In the function produce_rule_table, the state space is exhaustively traversed.
89 If your transition function consists of transition rules already then you can
90 optimise by running through the transitions instead. You might also want to
91 turn off the optimisation in rule::can_merge, to see if it gives you better
94 Also note: I feel sure there are better ways to compress rule tables than this...
96 Contact: Tim Hutton <tim.hutton@gmail.com>
100 // some typedefs and compile-time constants
101 typedef unsigned short state;
102 enum TSymm { none, rotate4, rotate8, reflect, rotate4reflect, rotate8reflect };
103 static const string symmetry_strings[] = {"none","rotate4","rotate8","reflect","rotate4reflect","rotate8reflect"};
105 // fill in this function with your desired transition rules
106 // (for von Neumann neighbourhoods, just ignore the nw,se,sw,ne inputs)
107 state slowcalc(state nw,state n,state ne,state w,state c,state e,state sw,state s,state se)
109 int neighbours = nw+n+ne+e+se+s+sw+w;
114 if (neighbours == 3) {
120 if (neighbours == 2 || neighbours == 3) {
126 return 0 ; // should throw an error here
130 vector<state> rotate_inputs(const vector<state>& inputs,int rot)
132 vector<state> rotinp(inputs);
133 rotate_copy(inputs.begin()+1,inputs.begin()+1+rot,inputs.end(),rotinp.begin()+1);
137 vector<state> reflect_inputs(const vector<state>& inputs,int neighbourhood_size)
139 vector<state> refinp(inputs);
140 if(neighbourhood_size==5) // CNESW
142 refinp[2]=inputs[4]; // swap E and W
145 else // neighbourhood_size==9 (C,N,NE,E,SE,S,SW,W,NW)
152 refinp[6]=inputs[4]; // swap all E and W
157 // simple rule structure, e.g. 1,2,[4,5],8,2 -> 0
161 set<state> inputs[9]; // c,n,ne,e,se,s,sw,w,nw or c,n,e,s,w
162 state ns; // new state
164 int n_inputs; // 5: von Neumann; 9: Moore
169 rule(const rule& r) : ns(r.ns),n_inputs(r.n_inputs),symm(r.symm)
171 for(int i=0;i<n_inputs;i++)
172 inputs[i]=r.inputs[i];
174 rule& operator=(const rule& r)
179 for(int i=0;i<n_inputs;i++)
180 inputs[i]=r.inputs[i];
183 rule(const vector<state>& inputs,int n_inputs,state ns1,TSymm symm1)
184 : ns(ns1),n_inputs(n_inputs),symm(symm1)
189 // if we merge the rule and the supplied transition, will the rule remain true for all cases?
190 bool can_merge(const vector<state>& test_inputs,state ns1) const
192 if(ns1!=ns) return false; // can't merge if the outputs are different
194 // If you are running through your own transitions, or for small state spaces,
195 // you might want to turn off this optimisation, to get better compression.
196 // On JvN29 it doesn't make any difference but on Nobili32 it does.
197 const bool forbid_multiple_input_differences = true;
199 if(forbid_multiple_input_differences)
201 // optimisation: we skip this rule if more than 2 entries are different, we
202 // assume we will have considered the relevant one-change rules before this.
204 for(int i=0;i<n_inputs;i++)
205 if(inputs[i].find(test_inputs[i])==inputs[i].end())
208 // just check the new permutations
209 for(int i=0;i<n_inputs;i++)
211 if(inputs[i].find(test_inputs[i])==inputs[i].end())
214 r1.inputs[i].clear(); // (since we don't need to re-test all the other permutations)
215 r1.inputs[i].insert(test_inputs[i]);
216 if(!r1.all_true()) return false;
222 // need to check all combinations - this can be very slow for large state spaces
223 for(int i=0;i<n_inputs;i++)
225 if(inputs[i].find(test_inputs[i])==inputs[i].end())
228 r1.merge(test_inputs); // this is what makes it slow, we may introduce many new permutations
229 r1.inputs[i].clear(); // (since we don't need to re-test all the old permutations)
230 r1.inputs[i].insert(test_inputs[i]);
231 if(!r1.all_true()) return false;
237 // merge the inputs with this rule
238 void merge(const vector<state>& new_inputs)
240 for(int i=0;i<n_inputs;i++)
241 inputs[i].insert(new_inputs[i]); // may already exist, set ignores if so
244 // is this set of inputs a match for the rule, for the given symmetry?
245 bool matches(const vector<state>& test_inputs) const
247 int n_rotations,rotation_skip;
252 case none: n_rotations=1; rotation_skip=1; do_reflect=false; break;
256 n_rotations=4; rotation_skip=1; do_reflect=false;
260 n_rotations=4; rotation_skip=2; do_reflect=false;
263 case rotate8: n_rotations=8; rotation_skip=1; do_reflect=false; break;
264 case reflect: n_rotations=1; rotation_skip=1; do_reflect=true; break;
268 n_rotations=4; rotation_skip=1; do_reflect=true;
272 n_rotations=4; rotation_skip=2; do_reflect=true;
275 case rotate8reflect: n_rotations=8; rotation_skip=1; do_reflect=true; break;
277 for(int iRot=0;iRot<n_rotations;iRot++)
279 if(nosymm_matches(rotate_inputs(test_inputs,iRot*rotation_skip)))
281 if(do_reflect && nosymm_matches(reflect_inputs(rotate_inputs(test_inputs,iRot*rotation_skip),n_inputs)))
284 return false; // no match found
289 // ignoring symmetry, does this set of inputs match the rule?
290 bool nosymm_matches(const vector<state>& test_inputs) const
292 for(int i=0;i<n_inputs;i++)
293 if(inputs[i].find(test_inputs[i])==inputs[i].end())
298 // is the rule true in all permutations?
299 bool all_true() const
301 set<state>::const_iterator c_it,n_it,ne_it,e_it,se_it,s_it,sw_it,w_it,nw_it;
304 for(c_it = inputs[0].begin();c_it!=inputs[0].end();c_it++)
305 for(n_it = inputs[1].begin();n_it!=inputs[1].end();n_it++)
306 for(ne_it = inputs[2].begin();ne_it!=inputs[2].end();ne_it++)
307 for(e_it = inputs[3].begin();e_it!=inputs[3].end();e_it++)
308 for(se_it = inputs[4].begin();se_it!=inputs[4].end();se_it++)
309 for(s_it = inputs[5].begin();s_it!=inputs[5].end();s_it++)
310 for(sw_it = inputs[6].begin();sw_it!=inputs[6].end();sw_it++)
311 for(w_it = inputs[7].begin();w_it!=inputs[7].end();w_it++)
312 for(nw_it = inputs[8].begin();nw_it!=inputs[8].end();nw_it++)
313 if(slowcalc(*nw_it,*n_it,*ne_it,*w_it,*c_it,*e_it,*sw_it,*s_it,*se_it)!=ns)
318 for(c_it = inputs[0].begin();c_it!=inputs[0].end();c_it++)
319 for(n_it = inputs[1].begin();n_it!=inputs[1].end();n_it++)
320 for(e_it = inputs[2].begin();e_it!=inputs[2].end();e_it++)
321 for(s_it = inputs[3].begin();s_it!=inputs[3].end();s_it++)
322 for(w_it = inputs[4].begin();w_it!=inputs[4].end();w_it++)
323 if(slowcalc(0,*n_it,0,*w_it,*c_it,*e_it,0,*s_it,0)!=ns)
330 // makes a unique variable name for a given value
331 string get_variable_name(unsigned int iVar)
333 const char VARS[52]={'a','b','c','d','e','f','g','h','i','j',
334 'k','l','m','n','o','p','q','r','s','t','u','v','w','x',
335 'y','z','A','B','C','D','E','F','G','H','I','J','K','L',
336 'M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'};
341 oss << VARS[(iVar-(iVar%52))/52 - 1] << VARS[iVar%52];
343 oss << "!"; // we have a 52*52 limit ("should be enough for anyone")
347 void print_rules(const vector<rule>& rules,ostream& out)
349 // first collect all variables (makes reading easier)
350 map< string , set<state> > vars;
351 ostringstream rules_out;
352 for(vector<rule>::const_iterator r_it=rules.begin();r_it!=rules.end();r_it++)
354 vector<string> variables_used;
355 for(int i=0;i<r_it->n_inputs;i++)
357 // if more than one state for this input, we need a variable
358 if(r_it->inputs[i].size()>1)
361 // is there a variable that matches these inputs, and that we haven't used?
362 bool found_unused_var=false;
363 for(map<string, set<state> >::const_iterator v_it=vars.begin();v_it!=vars.end();v_it++)
365 if(v_it->second==r_it->inputs[i] && find(variables_used.begin(),variables_used.end(),v_it->first)==variables_used.end())
367 found_unused_var = true;
372 if(!found_unused_var)
374 // we need to make a new one for this set of inputs
375 var = get_variable_name(vars.size());
376 // add it to the list of made variables
377 vars[var] = r_it->inputs[i];
379 out << "var " << var << "={";
380 set<state>::const_iterator it=r_it->inputs[i].begin();
385 if(it!=r_it->inputs[i].end()) out << ",";
390 // add the variable to the list of used ones
391 variables_used.push_back(var);
392 rules_out << var << ",";
396 // just a state, output it
397 rules_out << (int)*r_it->inputs[i].begin() << ",";
400 rules_out << (int)r_it->ns << endl;
402 out << rules_out.str();
405 void produce_rule_table(vector<rule>& rules,int N,int nhood_size,TSymm symm,bool remove_stasis)
407 int n_rotations,rotation_skip;
412 case none: n_rotations=1; rotation_skip=1; do_reflect=false; break;
416 n_rotations=4; rotation_skip=1; do_reflect=false;
420 n_rotations=4; rotation_skip=2; do_reflect=false;
423 case rotate8: n_rotations=8; rotation_skip=1; do_reflect=false; break;
424 case reflect: n_rotations=1; rotation_skip=1; do_reflect=true; break;
428 n_rotations=4; rotation_skip=1; do_reflect=true;
432 n_rotations=4; rotation_skip=2; do_reflect=true;
435 case rotate8reflect: n_rotations=8; rotation_skip=1; do_reflect=true; break;
438 state c,n,ne,nw,sw,s,se,e,w,ns;
439 vector<rule>::iterator it;
443 cout << "\nProcessing for c=" << (int)c << ", " << rules.size() << " rules so far." << endl;
447 vector<state> inputs(9);
474 ns = slowcalc(nw,n,ne,w,c,e,sw,s,se);
475 if(remove_stasis && ns == c)
476 continue; // we can ignore stasis transitions
477 // can we merge this transition with any existing rule?
480 for(it=rules.begin();!merged && it!=rules.end();it++)
483 for(int iRot=0;!merged && iRot<n_rotations;iRot++)
485 if(r.can_merge(rotate_inputs(inputs,iRot*rotation_skip),ns))
487 r.merge(rotate_inputs(inputs,iRot*rotation_skip));
490 else if(do_reflect && r.can_merge(reflect_inputs(rotate_inputs(inputs,iRot*rotation_skip),nhood_size),ns))
492 r.merge(reflect_inputs(rotate_inputs(inputs,iRot*rotation_skip),nhood_size));
499 // need to make a new rule starting with this transition
500 rule r(inputs,nhood_size,ns,symm);
512 else // nhood_size==5
514 vector<state> inputs(5);
529 ns = slowcalc(0,n,0,w,c,e,0,s,0);
530 if(remove_stasis && ns == c)
531 continue; // we can ignore stasis transitions
533 // can we merge this transition with any existing rule?
536 for(it=rules.begin();!merged && it!=rules.end();it++)
539 for(int iRot=0;!merged && iRot<n_rotations;iRot++)
541 if(r.can_merge(rotate_inputs(inputs,iRot*rotation_skip),ns))
543 r.merge(rotate_inputs(inputs,iRot*rotation_skip));
546 else if(do_reflect && r.can_merge(reflect_inputs(rotate_inputs(inputs,iRot*rotation_skip),nhood_size),ns))
548 r.merge(reflect_inputs(rotate_inputs(inputs,iRot*rotation_skip),nhood_size));
555 // need to make a new rule starting with this transition
556 rule r(inputs,nhood_size,ns,symm);
567 // here we use the computed rule table as a replacement slowcalc, for checking correctness
568 state new_slowcalc(const vector<rule>& rules,const vector<state>& inputs)
570 for(vector<rule>::const_iterator it=rules.begin();it!=rules.end();it++)
571 if(it->matches(inputs))
573 return inputs[0]; // default: no change
576 bool is_correct(const vector<rule>&rules,int N,int neighbourhood_size)
579 state c,n,ne,nw,sw,s,se,e,w;
580 if(neighbourhood_size==9)
582 vector<state> inputs(9);
610 if(new_slowcalc(rules,inputs)
611 != slowcalc(nw,n,ne,w,c,e,sw,s,se))
625 vector<state> inputs(5);
641 if(new_slowcalc(rules,inputs)
642 != slowcalc(0,n,0,w,c,e,0,s,0))
655 // parameters for use:
656 const int N_STATES = 2;
657 // Bedbugs: FIXME: output would probably be more efficient if we used
658 // this, but also complicated
659 const TSymm symmetry = none;
660 const int nhood_size = 9;
661 const string output_filename = "life.table";
662 // Bedbugs: we need these because they will be munged into real
664 const bool remove_stasis_transitions = false;
669 produce_rule_table(rules,N_STATES,nhood_size,symmetry,remove_stasis_transitions);
671 int n_secs = (int)difftime(t2,t1);
672 cout << "\nProcessing took: " << n_secs << " seconds." << endl;
675 ofstream out(output_filename.c_str());
676 out << "# rules: " << rules.size() << "\n#";
677 out << "\n# Golly rule-table format.\n# Each rule: C,";
681 out << "N,NE,E,SE,S,SW,W,NW";
683 out << "\n# N.B. Where the same variable appears multiple times in a transition,\n# it takes the same value each time.\n#";
684 if(remove_stasis_transitions)
685 out << "\n# Default for transitions not listed: no change\n#";
687 out << "\n# All transitions should be included below, including no-change ones.\n#";
688 out << "\nn_states:" << N_STATES;
689 out << "\nneighborhood:" << ((nhood_size==5)?("vonNeumann"):("Moore"));
690 out << "\nsymmetries:" << symmetry_strings[symmetry] << "\n";
691 print_rules(rules,out);
693 cout << rules.size() << " rules written." << endl;
695 // optional: run through the entire state space, checking that new_slowcalc matches slowcalc
696 cout << "Verifying is correct (can abort if you're confident)...";
698 if(is_correct(rules,N_STATES,nhood_size))
699 cout << "yes." << endl;
701 cout << "no! Either there's a bug in the code, or the transition function does not have the symmetry you selected." << endl;