Department of Computer Science | Institute of Theoretical Computer Science | CADMO

Theory of Combinatorial Algorithms

Prof. Emo Welzl and Prof. Bernd Gärtner

// Computer Science - Series 11 - Challenge Exercise 127 // Program: LeroyRobin.cpp // Author: Robin Leroy // Draws a neat picture using a Lindenmayer scheme. // Also, in the spirit of xkcd 974, allows the user // to choose any context-free deterministic Linden- // mayer system with up to 127 symbols. // Syntax: // LeroyRobin [iterations | iterations angle start [predecessor successor]*] #include #include // Retrieves production rules given by the user as arguments. // Those should comply with the above syntax. unsigned char** get_rules (int argc, unsigned char* argv[]) { // We will only use rules for printable non-space characters, // as it would be rather inconvenient to have to express the // rules using BELs, ACKs and whatnot, but we keep a range // of 128 for readability. unsigned char** rules = new unsigned char*[128]; // Initialise the production rules. for (unsigned char i = 0; i < 128; ++i) { rules[i] = new unsigned char[2]; // Terminal, i.e., i => i. rules[i][0] = i; rules[i][1] = '\0'; } // Customise. for (int i = 4; i < argc; i += 2) { unsigned char predecessor = argv[i][0]; unsigned char* successor = argv[i + 1]; rules[predecessor] = successor; } return rules; } // Parses a positive integer in base 10 from a null-terminated string. // Characters other than 0-9 are ignored. unsigned int parse_int(unsigned char* s) { unsigned int result = 0; for (; *s != '\0'; ++s) if (*s >= '0' && *s <= '9') { result *= 10; result += *s -'0'; } return result; } // Prints syntax information. void syntax() { std::cout << "--- Syntax:" << std::endl << "amazing [iterations | iterations angle start [rule]*]" << std::endl << "iterations : uint, number of iterations." << std::endl << "angle : uint, turning angle for '[' and ']'." << std::endl << "start : string starting string." << std::endl << "rule : char string, production rule." << std::endl << std::endl << "The following symbols have a graphical interpretation:" << std::endl << "'F', 'G' : Draw forward;" << std::endl << "'+'/'-' : Turn left/right;" << std::endl << "'['/']' : Push/pop;" << std::endl << "'f' : Move forward." << std::endl << "Note that in order to take advantage of the full" << std::endl << "127-symbol range, control characters need to be" << std::endl << "used. These can be entered in escaped strings as" << std::endl << "\\nnn, where nnn is an octal ASCII code (see examples" << std::endl << "below)." << std::endl << std::endl << "--- Examples" << std::endl << "> amazing 15 90 FX X $'X+\\001F' $'\\001' $'FX-\\001'" << std::endl << "Dragon curve. Variables are 'X', '\\001'." << std::endl << "> amazing 5 60 F++F++F F F-F++F-F" << std::endl << "Koch snowflake. Variable is 'F'." << std::endl << "> amazing 10 60 F F G-F-G G F+G+F" << std::endl << "Sierpinski triangle. Variables are 'F' and 'G'." << std::endl << std::endl; } // Recursively draws a Lindemayer system. // rules should be of the form returned by getRules. void lindenmayer( unsigned char** rules, unsigned char start, unsigned int angle, unsigned int iterations) { if (iterations == 0) { // Only the symbols F, G, +, -, [, ], f are drawn. switch (start) { case 'F': case 'G': ifm::forward(); break; case '+': ifm::left(angle); break; case '-': ifm::right(angle); break; case '[': ifm::save(); break; case ']': ifm::restore(); break; case 'f': ifm::jump(); break; default: break; } } else { // Recurse. unsigned char* next = rules[start]; for (; *next != '\0'; ++next) { lindenmayer(rules, *next, angle, iterations - 1); } } } // Prints an escaped representation of c to standard output. // Returns true if c has been escaped, false otherwise. bool print(unsigned char c) { if (c >= ' ' && c !='\\' && c != '\"' && c != '\'') { std::cout << c; return false; } else { // Escape. std::cout << '\\' << char (c / 64 % 8 + '0') << char (c / 8 % 8 + '0') << char (c % 8 + '0'); return true; } } // Prints an escaped representation of the null-terminated string s // to standard output. void print(unsigned char* c) { for (; *c != '\0'; ++c) { print(*c); } } // Prints a set of rules to standard output. rules should be of the // form returned by get_rules. void print_rules(unsigned char** rules) { for (unsigned char p = 0; p < 128; ++p) { if(rules[p][0] == p && rules[p][1] == '\0') { // Terminal. continue; } else { std::cout << " '"; if(print(p)) { std::cout << "' => \""; } else { std::cout << "' => \""; } print(rules[p]); std::cout << '\"' << std::endl; } } } // Returns a copy of the null-terminated string s. unsigned char* copy_string(const char* s) { int length = 0; do { ++length; } while (*(s++) != '\0'); unsigned char* result = new unsigned char[length]; for (int i = length - 1; i >= 0; --i) { result[i] = *(--s); } return result; } int main(int argc, char* argv[]) { if (argc > 2 && argc % 2 == 1) { std::cout << "*** Invalid syntax." << std::endl; syntax(); return 0; } unsigned int iterations = 6; unsigned int angle; unsigned char* start; unsigned char** args = (unsigned char**) argv; unsigned char** rules = get_rules(argc, args); // Figure out what we got as arguments. switch (argc) { case 2: // iterations. iterations = parse_int(args[1]); case 1: // No arguments. // Default system. angle = 30; delete[] rules['=']; delete[] rules['B']; delete[] rules['F']; delete[] rules['I']; delete[] rules['L']; delete[] rules['M']; delete[] rules['P']; delete[] rules['f']; delete[] rules['l']; delete[] rules['m']; rules['='] = copy_string("------"); rules['B'] = copy_string("+[F-MP]"); rules['F'] = copy_string("F+[F--F]--F++F-F"); rules['I'] = copy_string("+++"); rules['L'] = copy_string("F[+F-F-F][-F+F+F]FL"); rules['M'] = copy_string("[++L]M[-l]mF"); rules['P'] = copy_string("[=BBBBBBBBBBB]"); rules['f'] = copy_string("[=[=B][f][=--[l]BB]-]"); rules['l'] = copy_string("L"); rules['m'] = copy_string("M"); start = copy_string("IfMP"); // Advertising. std::cout << std::endl << "--- Note that you can set a custom Lindemayer system." << std::endl << std::endl; syntax(); break; default: // iterations angle start [rule]*. iterations = parse_int(args[1]); angle = parse_int(args[2]); start = args[3]; break; } std::cout << std::endl << "Drawing the Lindenmayer system with" << std::endl << "alphabet : {'\\001', ..., '~'}" << std::endl << "axiom : \""; print(start); std::cout << '\"' << std::endl << "rules :" << std::endl; print_rules(rules); std::cout << "angle : " << angle << " degrees" << std::endl << "to " << iterations << " iterations..." << std::endl; // Draw. for (; *start != '\0'; ++start) { lindenmayer(rules, *start, angle, iterations); } return 0; }