diff --git a/87_3-D_Plot/d/.gitignore b/87_3-D_Plot/d/.gitignore new file mode 100644 index 00000000..d969f6b2 --- /dev/null +++ b/87_3-D_Plot/d/.gitignore @@ -0,0 +1,2 @@ +*.exe +*.obj diff --git a/87_3-D_Plot/d/README.md b/87_3-D_Plot/d/README.md new file mode 100644 index 00000000..8a01faf3 --- /dev/null +++ b/87_3-D_Plot/d/README.md @@ -0,0 +1,182 @@ +Original source downloaded from [Vintage Basic](http://www.vintage-basic.net/games.html) + +Converted to [D](https://dlang.org/) by [Bastiaan Veelo](https://github.com/veelo). + +## Running the code + +Assuming the reference [dmd](https://dlang.org/download.html#dmd) compiler: +```shell +dmd -dip1000 -run threedeeplot.d +``` + +[Other compilers](https://dlang.org/download.html) also exist. + +## On rounding floating point values to integer values + +The D equivalent of Basic `INT` is [`floor`](https://dlang.org/phobos/std_math_rounding.html#.floor), +which rounds towards negative infinity. If you change occurrences of `floor` to +[`lrint`](https://dlang.org/phobos/std_math_rounding.html#.lrint), you'll see that the plots show a bit more detail, +as is done in the bonus below. + +## Bonus: Self-writing programs + +With a small modification to the source, the program can be extended to **plot a random function**, and **print its formula**. + +```shell +rdmd -dip1000 threedeeplot_random.d +``` +(`rdmd` caches the executable, which results in speedy execution when the source does not change.) + +### Example output +``` + 3D Plot + (After Creative Computing Morristown, New Jersey) + + + f(z) = 30 * sin(z / 10.0) + + * + * * * * + * * * * * + * * * * * + * * * * * * + * * * * * * + * * * * * ** + * * * * * * * + * * * * * * ** + * * * * * * * * + * * * * * * * * + * * * * * * * ** + * * * * * * * * * + * * * * ** * * * * + * * * * ** * * * + * * * * * * * * + * * * * * * * * + * * * ** * * ** + * * * ** * ** + * * * * * ** + * * * * * ** + * * * * * ** + * * * ** * ** + * * * ** * * ** + * * * * * * * * + * * * * * * * * + * * * * ** * * * + * * * * ** * * * * + * * * * * * * * * + * * * * * * * ** + * * * * * * * * + * * * * * * * * + * * * * * * ** + * * * * * * * + * * * * * ** + * * * * * * + * * * * * * + * * * * * + * * * * * + * * * * + * +``` + +### Breakdown of differences + +Have a look at the relevant differences between `threedeeplot.d` and `threedeeplot_random.d`. +This is the original function with the single expression that is evaluated for the plot: +```d + static float fna(float z) + { + return 30.0 * exp(-z * z / 100.0); + } +``` +Here `static` means that the nested function does not need acces to its enclosing scope. + +Now, by inserting the following: +```d + enum functions = ["30.0 * exp(-z * z / 100.0)", + "sqrt(900.01 - z * z) * .9 - 2", + "30 * (cos(z / 16.0) + .5)", + "30 - 30 * sin(z / 18.0)", + "30 * exp(-cos(z / 16.0)) - 30", + "30 * sin(z / 10.0)"]; + + size_t index = uniform(0, functions.length); + writeln(center("f(z) = " ~ functions[index], width), "\n"); +``` +and changing the implementation of `fna` to +```d + float fna(float z) + { + final switch (index) + { + static foreach (i, f; functions) + case i: + mixin("return " ~ f ~ ";"); + } + } +``` +we unlock some very special abilities of D. Let's break it down: + +```d + enum functions = ["30.0 * exp(-z * z / 100.0)", /*...*/]; +``` +This defines an array of strings, each containing a mathematical expression. Due to the `enum` keyword, this is an +array that really only exists at compile-time. + +```d + size_t index = uniform(0, functions.length); +``` +This defines a random index into the array. `functions.length` is evaluated at compile-time, due to D's compile-time +function evaluation (CTFE). + +```d + writeln(center("f(z) = " ~ functions[index], width), "\n"); +``` +Unmistakenly, this prints the formula centered on a line. What happens behind the scenes is that `functions` (which +only existed at compile-time before now) is pasted in, so that an instance of that array actually exists at run-time +at this spot, and is instantly indexed. + +```d + float fna(float z) + { + final switch (index) + { + // ... + } + } +``` +`static` has been dropped from the nested function because we want to evaluate `index` inside it. The function contains +an ordinary `switch`, with `final` providing some extra robustness. It disallows a `default` case and produces an error +when the switch doesn't handle all cases. The `switch` body is where the magic happens and consists of these three +lines: +```d + static foreach (i, f; functions) + case i: + mixin("return " ~ f ~ ";"); +``` +The `static foreach` iterates over `functions` at compile-time, producing one `case` for every element in `functions`. +`mixin` takes a string, which is constructed at compile-time, and pastes it right into the source. + +In effect, the implementation of `float fna(float z)` unrolls itself into +```d + float fna(float z) + { + final switch (index) + { + case 0: + return 30.0 * exp(-z * z / 100.0); + case 1: + return sqrt(900.01 - z * z) * .9 - 2; + case 2: + return 30 * (cos(z / 16.0) + .5); + case 3: + return 30 - 30 * sin(z / 18.0); + case 4: + return 30 * exp(-cos(z / 16.0)) - 30; + case 5: + return 30 * sin(z / 10.0)"; + } + } +``` + +So if you feel like adding another function, all you need to do is append it to the `functions` array, and the rest of +the program *rewrites itself...* diff --git a/87_3-D_Plot/d/threedeeplot.d b/87_3-D_Plot/d/threedeeplot.d new file mode 100644 index 00000000..a3660321 --- /dev/null +++ b/87_3-D_Plot/d/threedeeplot.d @@ -0,0 +1,35 @@ +@safe: // Make @safe the default for this file, enforcing memory-safety. +import std.stdio, std.string, std.math, std.range, std.conv, std.algorithm; + +void main() +{ + enum width = 80; + writeln(center("3D Plot", width)); + writeln(center("(After Creative Computing Morristown, New Jersey)\n\n\n", width)); + + static float fna(float z) + { + return 30.0 * exp(-z * z / 100.0); + } + + char[] row; + + for (float x = -30.0; x <= 30.0; x += 1.5) + { + size_t max_z = 0L; + auto y1 = 5 * floor((sqrt(900 - x * x)) / 5.0); + for (float y = y1; y >= -y1; y -= 5) + { + auto z = to!size_t(max(0, floor(25 + fna(sqrt(x * x + y * y)) - .7 * y))); + if (z > max_z) // Visible + { + max_z = z; + if (z + 1 > row.length) // row needs to grow + row ~= ' '.repeat(z + 1 - row.length).array; + row[z] = '*'; + } + } + writeln(row); + row = null; + } +} diff --git a/87_3-D_Plot/d/threedeeplot_random.d b/87_3-D_Plot/d/threedeeplot_random.d new file mode 100644 index 00000000..3db1a2cd --- /dev/null +++ b/87_3-D_Plot/d/threedeeplot_random.d @@ -0,0 +1,50 @@ +@safe: // Make @safe the default for this file, enforcing memory-safety. +import std.stdio, std.string, std.math, std.range, std.conv, std.random, std.algorithm; + +void main() +{ + enum width = 80; + writeln(center("3D Plot", width)); + writeln(center("(After Creative Computing Morristown, New Jersey)\n\n", width)); + + enum functions = ["30.0 * exp(-z * z / 100.0)", + "sqrt(900.01 - z * z) * .9 - 2", + "30 * (cos(z / 16.0) + .5)", + "30 - 30 * sin(z / 18.0)", + "30 * exp(-cos(z / 16.0)) - 30", + "30 * sin(z / 10.0)"]; + + size_t index = uniform(0, functions.length); + writeln(center("f(z) = " ~ functions[index], width), "\n"); + + float fna(float z) + { + final switch (index) + { + static foreach (i, f; functions) + case i: + mixin("return " ~ f ~ ";"); + } + } + + char[] row; + + for (float x = -30.0; x <= 30.0; x += 1.5) + { + size_t max_z = 0L; + auto y1 = 5 * lrint((sqrt(900 - x * x)) / 5.0); + for (float y = y1; y >= -y1; y -= 5) + { + auto z = to!size_t(max(0, lrint(25 + fna(sqrt(x * x + y * y)) - .7 * y))); + if (z > max_z) // Visible + { + max_z = z; + if (z + 1 > row.length) // row needs to grow + row ~= ' '.repeat(z + 1 - row.length).array; + row[z] = '*'; + } + } + writeln(row); + row = null; + } +}