chiark / gitweb /
Correct URL and add it to output
[bedbugs.git] / src / bedbugs.c
index ef9fd3cec796f58a0132e2a5a9e1d0517e1f2ebf..c9e70e20e68c0388d3c2e31589965b278d71d422 100644 (file)
@@ -1,6 +1,55 @@
 /*
- * Relevant bits of original copyright notice from
- * http://bjh21.me.uk/bedstead/  bedstead.c  #137:
+ * Output bits of a rule file for Golly for a Conway-like cellular
+ * automaton displayed with smoothing reminiscent of the Mullard
+ * SAA5050 Teletext character generation (aka BBC "MODE 7").
+ *
+ * The resulting automaton flips the world between two states:
+ * one where everything is one of two states, and one where an
+ * expanded set of 32 states is used (16 'off', 16 'on') according
+ * to what diagonals are required for the smoothing. Each of the
+ * expanded states has an associated icon.
+ *
+ * In the first step we go from two states to 32 implementing the
+ * smoothing algorithm as a transition function on a 3x3 neighbourhood.
+ * In the second step we go from 32 back to 2 with a Conway-derived
+ * transition function, again on a 3x3 neighbourhood (this step is not
+ * in this source file).
+ * (FIXME: this would be more slick, although probably even less
+ * efficient, implemented in one go with a 5x5 neighbourhood, which
+ * Golly does support.)
+ *
+ * The smoothing algorithm and graphics are derived from 'Bedstead' by
+ * Ben Harris: http://bjh21.me.uk/bedstead/
+ * The idea of applying this smoothing to Conway's Life can be blamed
+ * on Simon Tatham.
+ */
+
+/*
+ * This file is part of Bedbugs.
+ * Copyright (C) 2014 Jacob Nevins.
+ * Portions copyright (C) 2009 Ben Harris (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Website: http://www.chiark.greenend.org.uk/ucgi/~jacobn/git/bedbugs.git/
+ *
+ *
+ * This source file, bedbugs.c, is based on
+ * http://bjh21.me.uk/bedstead/  bedstead.c  bzr r137
+ * from which the following bits of the copyright notice and documentation
+ * remain relevant:
  *
  * [...] the file is covered by the following:
  *
@@ -27,8 +76,8 @@
  * SOFTWARE.
  */
 /*
- * This is a program to construct an outline font from a bitmap.  It's
- * based on the character-rounding algorithm of the Mullard SAA5050
+ * This [was once] a program to construct an outline font from a bitmap.
+ * It's based on the character-rounding algorithm of the Mullard SAA5050
  * series of Teletext character generators, and thus works best on
  * character shapes in the same style of those of the SAA5050.  This
  * file includes all of the glyphs from the SAA5050, SAA5051, SAA5052,
  *          # # . . . .    / # / . . .    / # / . . .
  *          # # . . . .    # / . . . .    # / . . . .
  *
- * That is the interesting part of the program, and is in the dochar()
- * function.  Most of the rest is just dull geometry to join all the
- * bits together into a sensible outline.  Much of the code is wildly
- * inefficient -- O(n^2) algorithms aren't much of a problem when you
- * have at most a few thousand points to deal with.
- *
- * A rather nice feature of the outlines produced by this program is
- * that when rasterised at precisely 10 or 20 pixels high, they
- * produce the input and output respectively of the character-rounding
- * process.  While there are obious additional smoothings that could
- * be applied, doing so would probably lose this nice property.
- *
  * [...]
  */
 
+#include <assert.h>
+#include <errno.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -154,7 +193,8 @@ triangles(bool *r, int size, int startpos, int bl, int br, int tr, int tl)
 static void
 whitepixel(bool *r, int size, int bl, int br, int tr, int tl)
 {
-    /* wrt blackpixel(): -1 for adjacency, -1 for gridlines */
+    /* wrt blackpixel(): -1 for adjacency, -1 for gridlines
+     * (which Golly has no apparent way to suppress :/ ) */
     const int startpos = size-size/4-2;
     triangles(r, size, startpos, bl, br, tr, tl);
 }
@@ -170,8 +210,14 @@ blackpixel(bool *r, int size, int bl, int br, int tr, int tl)
     }
 }
 
-int main(int argc, char *argv[])
+void bedstead (void)
 {
+        /* Output smoothing rules as a Golly rule table mapping from
+         * (0,1) to an expanded set of states that we can hang icons
+         * from. (The smoothing algorithm requires a 3x3 neighbourhood,
+         * which is fortuitous.) */
+
+        /* Index into small bitmap */
 #define GETPIX(x,y) (!!(iter & 1u<<((y)*3+(x))))
 #define L GETPIX(x-1, y)
 #define R GETPIX(x+1, y)
@@ -182,9 +228,12 @@ int main(int argc, char *argv[])
 #define DL GETPIX(x-1, y+1)
 #define DR GETPIX(x+1, y+1)
 
+        /* Iterate over all neighbourhoods (9 cells) */
         int iter;
         for (iter = 0; iter < 1u<<9; iter++) {
                         int state, x = 1, y = 1;
+                        /* The core of the Bedstead smoothing algorithm
+                         * from Ben Harris. */
                        if (GETPIX(x, y)) {
                                bool tl, tr, bl, br;
 
@@ -200,6 +249,7 @@ int main(int argc, char *argv[])
                                if (R || UR || U) tr = true;
                                if (L || DL || D) bl = true;
                                if (R || DR || D) br = true;
+                                /* On states are 18..33 */
                                 state = 2 + 16 + (bl | br<<1 | tr<<2 | tl<<3);
                        } else {
                                bool tl, tr, bl, br;
@@ -211,11 +261,34 @@ int main(int argc, char *argv[])
                                if (R && U && !UR) tr = true;
                                if (L && D && !DL) bl = true;
                                if (R && D && !DR) br = true;
+                                /* Off states are 2..17 */
                                 state = 2 + (bl | br<<1 | tr<<2 | tl<<3);
                        }
+                        if (iter == 0) {
+                                /* Special case: we don't want to try to
+                                 * flip all of infinite empty space between
+                                 * 0 and 2 every step (this has odd effects
+                                 * on Golly) */
+                                assert(state == 2);
+                                state = 0;
+                        }
+                        /* Output rule in Golly rule format
+                         * (FIXME completely unoptimised) */
                         printf("%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
                                GETPIX(x,y),U,UR,R,DR,D,DL,L,UL,state);
         }
+}
+
+void icons(void)
+{
+        /* Bedbugs icons. In each size supported by Golly,
+         * draw one icon per state, in the XPM format Golly wants.
+         * This does not actually depend on bedstead() at all.
+         * (FIXME: but it could. Some of the states we generate icons
+         * and rules for are not actually reachable. We could spot
+         * unused states above and avoid generating icons or rules for
+         * them.)
+         * (FIXME: we could also output pre-Golly-2.5 format icons) */
         {
             const int sizes[] = {7, 15, 31};
             int size_index;
@@ -225,19 +298,26 @@ int main(int argc, char *argv[])
                 int size = sizes[size_index], state;
                 printf("XPM\n/* width height num_colors chars_per_pixel */\n");
                 printf("\"%d %d 2 1\"\n", size, size*33);
-                printf("/* colors */\n\". c #000000\"\n\"A c #FFFFFF\"\n");
+                /* We have to have a non-greyscale colour to trigger Golly's
+                 * "multi-colour icon" mode, so that we can make some states'
+                 * non-icon versions black. And it's not sufficient to
+                 * include an unreferenced, colour, so our 'on' state is
+                 * off-white. */
+                printf("/* colors */\n\". c #000000\"\n\"A c #FEFEFF\"\n");
                 /* icons never used for state 0 */
                 for (state = 1; state < 34; state++) {
                     bool *r = blank(size);
                     if (state == 0) {
-                        ; /* nothing */
+                        ; /* Conway 'off': nothing */
                     } else if (state == 1) {
-                        /* everything */
+                        /* Conway 'on': everything */
                         memset(r, 1, size*size*sizeof(*r));
                     } else if (state < 16+2) {
+                        /* Bedstead 'off' */
                         int bits = ~(state-2);
                         whitepixel(r, size, bits&1, bits&2, bits&4, bits&8);
                     } else {
+                        /* Bedstead 'on' */
                         int bits = state-16-2;
                         blackpixel(r, size, bits&1, bits&2, bits&4, bits&8);
                     }
@@ -247,5 +327,72 @@ int main(int argc, char *argv[])
                 }
             }
         }
-        return 0;
+}
+
+void colours(void)
+{
+    int i;
+    for (i=0; i<34; i++) {
+        int lvl;
+        switch (i) {
+            case 0:
+                lvl = 0;
+                break;
+            case 1:
+                lvl = 255;
+                break;
+            default:
+                lvl = (i >= 2+16) ? 255 : 0;
+                break;
+        }
+        printf("%2d %3d %3d %3d\n", i, lvl, lvl, lvl);
+    }
+}
+
+int main(int argc, char *argv[])
+{
+    while (!feof(stdin)) {
+        char l[100]; /* bleah */
+        if (!fgets(l, sizeof(l), stdin)) {
+            break;
+        }
+        if (strlen(l) == sizeof(l)-1) {
+            fprintf(stderr, "Long input line; possible truncation; I suck\n");
+            exit(1);
+        }
+        if (strlen(l) >= 2 && l[0] == '@' && l[1] == '@') {
+            size_t n = strcspn(l+2, "\n");
+            if (strncmp(l+2, "BEDSTEAD", n) == 0) {
+                bedstead();
+            } else if (strncmp(l+2, "ICONS", n) == 0) {
+                icons();
+            } else if (strncmp(l+2, "COLORS", n) == 0) {
+                colours();
+            } else {
+                /* Bodily insert named file on stdout. */
+                FILE *f;
+                l[n+2]='\0';
+                if (!(f = fopen(l+2, "r"))) {
+                    fprintf(stderr, "Couldn't open '%s': %s\n", l+2,
+                            strerror(errno));
+                    exit(1);
+                } else {
+                    do {
+                        char buf[2048]; /* also bleah */
+                        size_t n = fread(buf, 1, sizeof(buf), f);
+                        fwrite(buf, 1, n, stdout);
+                    } while (!feof(f) && !ferror(f));
+                    if (ferror(f)) {
+                        fprintf(stderr, "Error reading '%s': %s\n", l+2,
+                                strerror(errno));
+                        exit(1);
+                    }
+                    fclose(f);
+                }
+            }
+        } else {
+            printf("%s", l);
+        }
+    }
+    return 0;
 }