diff --git a/1995/vanschnitz/README.md b/1995/vanschnitz/README.md index 6180eef8d..23544dd91 100644 --- a/1995/vanschnitz/README.md +++ b/1995/vanschnitz/README.md @@ -1,24 +1,38 @@ # Worst Abuse of the C Preprocessor and Most Likely To Amaze - Mark Schnitzius & David Van Brackle - ISX Corporation - 1165 Northchase Parkway - Suite 120 - Marietta, GA 30067 - USA +Mark Schnitzius & David Van Brackle +ISX Corporation +1165 Northchase Parkway +Suite 120 +Marietta, GA 30067 +USA ## To build: - make all +```sh +make all +``` ## To run: - ./vanschnitz +```sh +./vanschnitz +``` ## Try: - make clobber all LEVEL=6 - ./vanschnitz +```sh +make clobber all LEVEL=6 +./vanschnitz +``` + +### Alternate code: + +The authors provided a spoiler version of the program. Originally uuencoded we +have decoded it for the wider audience in [spoiler.c](spoiler.c). The +`uuencode`d blob is still available in their remarks below. They encourage you +to first try and figure it out without it. + ## Judges' remarks: @@ -44,30 +58,32 @@ solution. How do you trick a compiler into actually solving the problem? First, you must tell it how many disks you wish to solve it -for. This is done by defining the symbol 'n' on the compile +for. This is done by defining the symbol `n` on the compile line. For instance, to cause the compiler to solve the Towers of Hanoi problem with four disks, you would compile the program like this: - gcc hanoi.c -o hanoi -Dn=4 +```sh +gcc hanoi.c -o hanoi -Dn=4 +``` -A default value of 5 will be used for n if you do not define -it on the command line. The value of n cannot be greater than +A default value of `5` will be used for `n` if you do not define +it on the command line. The value of `n` cannot be greater than fifteen (the compiler we used to test has a limit on the #include depth). The compiler then solves the problem using binary arithmetic based on whether particular symbols are defined or not. -To loop, the program #include's itself. This is, of course, -expensive; one compile we did with n=14 took about fifty -minutes to compile on our system (compiling with n=15 -caused our system to crash). +To loop, the program `#include`s itself. This is, of course, +expensive; one compile we did with `n=14` took about fifty +minutes to compile on our system (compiling with `n=15` caused our system to +crash). The resulting program that the compiler generates simply prints out the answer. Did I say "simply"? Actually, the whole resulting program consists of a single printf statement, consisting of a massive string constant of length `35*(2^n-1)`, followed by `3*(2^n-1)` integers which get formatted into the -string. For our n=14 run, this adds up to a string constant +string. For our `n=14` run, this adds up to a string constant of length 573405, followed by 49149 integers delimited by commas. (Generating the string constant depends on the ANSI C feature in which adjacent character strings are @@ -75,14 +91,16 @@ catenated; a version that does not use this feature has been included for people who can only run K&R). A good way to see the resulting program (on a Unix system) is to do the command - gcc hanoi.c -E -Dn=5 | grep -v \# | grep -v ^\$ +```sh +gcc hanoi.c -E -Dn=5 | grep -v \# | grep -v ^\$ +``` For an odd number of disks, the program will provide a solution wherein the disks end up on peg 2; for an even number of disks, they will end on peg 3. This should provide some hint as to what sort of algorithm is used. -We have included a "spoiler" version of the program, with +We have included a [spoiler](spoiler.c) version of the program, with meaningful symbol names and comments, but we encourage you to try to decipher the program without it... diff --git a/1995/vanschnitz/spoiler.c b/1995/vanschnitz/spoiler.c new file mode 100644 index 000000000..d22d60cd5 --- /dev/null +++ b/1995/vanschnitz/spoiler.c @@ -0,0 +1,239 @@ +#ifndef INITIALIZED + +/* + * If n isn't defined, make it 5 + */ +#ifndef n +#define n 05 +#endif + +/* + * Some useful variables + */ +#define THIS_FILE __FILE__ +#define FORMAT_STRING "Move disk %d from peg %d to peg %d\n" +#define INITIALIZED + +/* + * Translate n into bits + */ +#if n&01 +#define bit0 +#endif +#if n&02 +#define bit1 +#endif +#if n&04 +#define bit2 +#endif +#if n>>3 +#define bit3 +#endif + +/* + * Here's the program! + */ +main(){ +printf( +#include THIS_FILE +#define NOW_DO_THE_NUMBERS +#include THIS_FILE +);} + +#else + +/* + * Check to see if the number is zero (bottom of recursion) + */ +#ifdef bit0 +#define N_IS_NOT_ZERO +#endif +#ifdef bit1 +#define N_IS_NOT_ZERO +#endif +#ifdef bit2 +#define N_IS_NOT_ZERO +#endif +#ifdef bit3 +#define N_IS_NOT_ZERO +#endif + +#ifdef N_IS_NOT_ZERO +#undef N_IS_NOT_ZERO + +/* + * Decrement n + */ +#ifndef bit0 +#define bit0 +#ifndef bit1 +#define bit1 +#ifndef bit2 +#define bit2 +#ifndef bit3 +#define bit3 +#else +#undef bit3 +#endif +#else +#undef bit2 +#endif +#else +#undef bit1 +#endif +#else +#undef bit0 +#endif + +/* + * Here's recursion + */ +#include THIS_FILE + +/* + * Note: Odd numbered disks always cycle from 1 => 2 => 3 => 1 => 2 => 3 + * Even numbered disks always cycle from 1 => 3 => 2 => 1 => 3 => 2 + */ + +/* + * Check to see if n is zero. If so, we're moving disk 1. + * In general, for any n we're moving disk n+1. That means that + * when n is odd, we're moving an even disk, and vice versa. + */ +#ifndef bit0 +#ifndef bit1 +#ifndef bit2 +#ifndef bit3 +#define N_IS_ZERO +#endif +#endif +#endif +#endif + +#ifdef N_IS_ZERO +#undef N_IS_ZERO + +#ifndef NOW_DO_THE_NUMBERS +FORMAT_STRING +#else + +/* + * If disk 1 is on peg 2, move it 2 => 3 + */ +#ifdef DISK_ONE_IS_ON_PEG_TWO +,1,2,3 +#undef DISK_ONE_IS_ON_PEG_TWO +#define DISK_ONE_IS_ON_PEG_THREE +#else + +/* + * If disk 1 is on peg 3, move it 3 => 1 + */ +#ifdef DISK_ONE_IS_ON_PEG_THREE +,1,3,1 +#undef DISK_ONE_IS_ON_PEG_THREE +#else + +/* + * Disk 1 must be on peg 1. Move it 1 => 2 + */ +,1,1,2 +#define DISK_ONE_IS_ON_PEG_TWO +#endif +#endif +#endif +#else + +/* + * Here, we handle disks >1. Remember that odds cycle 1 => 2 => 3 => 1, + * and evens cycle 1 => 2 => 3 => 1. Combine this with the knowledge + * of where disk 1 is, and we can determine how to move any disk. + */ +#ifndef NOW_DO_THE_NUMBERS +FORMAT_STRING +#else + +/* + * Print the disk number. Remember, disk number = n+1 + */ +,1 +#ifdef bit0 ++1 +#endif +#ifdef bit1 ++2 +#endif +#ifdef bit2 ++4 +#endif +#ifdef bit3 ++8 +#endif + +/* + * Disk 1 on peg 2 + */ +#ifdef DISK_ONE_IS_ON_PEG_TWO +#ifdef bit0 +,1,3 /* Even: 1 => 3 (n is odd, so the disk is even) */ +#else +,3,1 /* Odd: 3 => 1 */ +#endif +#else + +/* + * Disk 1 on peg 3 + */ +#ifdef DISK_ONE_IS_ON_PEG_THREE +#ifdef bit0 +,2,1 /* Even: 2 => 1 */ +#else +,1,2 /* Odd: 1 => 2 */ +#endif +#else + +/* + * Disk 1 on peg 1 + */ +#ifdef bit0 +,3,2 /* Even: 3 => 2 */ +#else +,2,3 /* Odd: 2 => 3 */ +#endif +#endif +#endif +#endif +#endif + +/* + * More recursion + */ +#include THIS_FILE + +/* + * Increment n + */ +#ifdef bit0 +#undef bit0 +#ifdef bit1 +#undef bit1 +#ifdef bit2 +#undef bit2 +#ifdef bit3 +#undef bit3 +#else +#define bit3 +#endif +#else +#define bit2 +#endif +#else +#define bit1 +#endif +#else +#define bit0 +#endif + +#endif /* #ifdef N_IS_NOT_ZERO */ + +#endif /* #else of #ifndef INITIALIZED */