Source: subclasses/lifelike.js

"use strict";
var Automaton = require("../automaton.js");

/** A class representing a lifelike cellular automaton.
 *
 * When specifying an initializer function or an initial data array, 
 * "alive" cells should be represented as true, and "dead" cells as false.
 *
 * @memberof module:cellerity
 * @extends module:cellerity.Automaton
 * @inheritdoc
 */
class LifelikeAutomaton extends Automaton {
    /** Construct a lifelike cellular automaton.
     *
     * @param {string} ruleString A string describing the rule for the
     *   automaton, in born/survive format (e.g. "B3/S23" or just
     *   "3/23" for Conway's Game of Life).
     * @param {number} [rows=20] The number of rows to use.
     * @param {number} [cols=20] The number of columns to use.
     * @throws {string} An error message if the rule is not understood.
     */
    constructor(ruleString,rows,cols){
        var ruleset = LifelikeAutomaton.parseRule(ruleString);
        super(ruleset,rows,cols);
        this.setInitializer(x => false);
    }//constructor

    /** Change the automaton's ruleset.
     * @param {string | string[]} ruleset The rule or rules to use
     *   for the automaton, in "B/S" format (e.g. "B3/S23" or just 
     *   "3/23" for Conway's Game of Life).  If a list is provided, 
     *   the rules will be composed in order.
     * @throws {string} An error message if the rule is not valid.
     */
    setRuleset(ruleset){
        if(typeof ruleset == "string"){
            this.ruleset = LifelikeAutomaton.parseRule(ruleset);
        }else{
            var rules = [];
            ruleset.forEach(function(ruleString){
                rules.push(LifelikeAutomaton.parseRule(ruleString));
            });
            this.ruleset = rules;
        }
    }//setRuleset

    /** Parse a string describing an update rule into a function.
     *
     * @param {string} ruleString The rule to parse.
     * @return {updateFunction} The function resulting from parsing the rule.
     * @throws {string} An error message if the rule is not understood.
     * @private
     */
    static parseRule(ruleString){
        var re = /^B?(\d*)[^\d]S?(\d*)$/i;
        var m = re.exec(ruleString);
        if(m){
            var born = [];
            var survive = [];
            m[1].split("").forEach((digit) => born.push(parseInt(digit)));
            m[2].split("").forEach((digit) => survive.push(parseInt(digit)));
            //Create rule (assuming true = alive)
            function rule(neighbourhood){
                var count = 0;
                for(var i = 0; i < 3; i++){
                    for(var j = 0; j < 3; j++){
                        count += (neighbourhood[i][j]?1:0);
                    }
                }
                if(neighbourhood[1][1]){
                    count--;
                    return (survive.indexOf(count) > -1);
                }else{
                    return (born.indexOf(count) > -1);
                }
            }//rule
            return rule;
        }else{
            //In future, check against known rule names.
            // For now, just throw error.
            throw "Rule '" + ruleString + "' not recognized.";
        }
    }//parseRule

    /** Get a nice string representation of the state of the automaton.
     * Same as {@link module:cellerity.Automaton#prettyPrint}, but with
     * a default map function that works well for lifelike automata.
     *
     * @param {function} [map] A function to apply to each cell value,
     *  returning the string to be used to represent the cell.
     * @return {string} A string representation of the automaton's state.
     */
    prettyPrint(map){
        if(!map){
            map = (cell) => (cell?"[]":"  ");
        }
        return super.prettyPrint(map);
    }
}//LifelikeAutomaton

module.exports = LifelikeAutomaton;