// 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;
}