bin_PROGRAMS = rrace-motif
-rrace_motif_SOURCES = src/game.c src/x11.c src/game.h src/motif.h
+rrace_motif_SOURCES = src/game.c src/x11.c src/game.h src/motif.h \
+ src/ewmhicon.c
rrace_motif_LDADD = $(libmotifmain_a_OBJECTS) $(libmotifui_a_OBJECTS) \
$(libglohelp_a_OBJECTS) libgnu.a $(MOTIF_LIBS)
$(rrace_motif_OBJECTS): $(gnulib_headers)
DISTCLEANFILES += $(GUIFILES:.dat=.h)
EXTRA_DIST += $(DX_BASEDIR)/scripts/gen-tree.awk $(GUIFILES)
-check_PROGRAMS = t/boardmove t/rng-test
+check_PROGRAMS = t/boardmove t/ewmhicon t/rng-test
EXTRA_DIST += t/xos256ss.c
t_boardmove_LDADD = src/game.$(OBJEXT) libgnu.a
$(t_boardmove_OBJECTS): $(gnulib_headers)
+t_ewmhicon_LDADD = src/ewmhicon.$(OBJEXT) libgnu.a $(MOTIF_LIBS)
+$(t_ewmhicon_OBJECTS): $(gnulib_headers)
+
+t_rng_test_LDADD = libgnu.a
+$(t_rng_test_OBJECTS): $(gnulib_headers)
+
include $(top_srcdir)/lib/gnulib.mk
include $(top_srcdir)/common/snippet/autotest.mk
--- /dev/null
+/*
+ * _NET_WM_ICON helpers for slide puzzle game
+ * Copyright © 2022 Nick Bowler
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <X11/Intrinsic.h>
+#include <X11/Xatom.h>
+#include "motif.h"
+
+static unsigned long scale16to8(unsigned x)
+{
+ return x*0xfful / 0xffff;
+}
+
+static unsigned long wm_pixel(const XColor *c)
+{
+ return 0xff000000
+ | scale16to8(c->red) << 16
+ | scale16to8(c->green) << 8
+ | scale16to8(c->blue);
+}
+
+/*
+ * The 16x16 icon is drawn with 1px shadow, 6x6 tiles, with 1 pixel cropped off
+ * all the edge tiles
+ */
+void ewmh_tile16(unsigned long *icon, const XColor *c, int tile_x, int tile_y)
+{
+ int out_x, out_y, out_w, x, y;
+ unsigned long row[6];
+
+ out_x = tile_x * 11 / 2;
+ out_y = tile_y * 11 / 2;
+ out_w = (5 + (tile_x == 1)) * sizeof row[0];
+
+ for (y = 0+(tile_y == 0); y < 6-(tile_y == 2); y++) {
+ for (x = 0+(tile_x == 0); x < 6-(tile_x==2); x++) {
+ if (x == 0 || y == 0)
+ row[x] = wm_pixel(&c[COLOUR_LIGHT]);
+ else if (x == 5 || y == 5)
+ row[x] = wm_pixel(&c[COLOUR_DARK]);
+ else
+ row[x] = wm_pixel(&c[COLOUR_PRIMARY]);
+ }
+ memcpy(&icon[16 * out_y++ + out_x], &row[tile_x == 0], out_w);
+ }
+}
+
+/*
+ * The 24x24 icon is drawn with 1px shadow and 8x8 tiles.
+ */
+void ewmh_tile24(unsigned long *icon, const XColor *c, int tile_x, int tile_y)
+{
+ int out_x, out_y, x, y;
+ unsigned long row[8];
+
+ out_x = tile_x * 8;
+ out_y = tile_y * 8;
+
+ for (y = 0; y < 8; y++) {
+ for (x = 0; x < 8; x++) {
+ if (x == 0 || y == 0)
+ row[x] = wm_pixel(&c[COLOUR_LIGHT]);
+ else if (x == 7 || y == 7)
+ row[x] = wm_pixel(&c[COLOUR_DARK]);
+ else
+ row[x] = wm_pixel(&c[COLOUR_PRIMARY]);
+ }
+ memcpy(&icon[24 * out_y++ + out_x], row, sizeof row);
+ }
+}
+
+/*
+ * The 32x32 icon is drawn with 1px shadow with slightly uneven tiles on
+ * an 11-10-11 pixel grid.
+ */
+void ewmh_tile32(unsigned long *icon, const XColor *c, int tile_x, int tile_y)
+{
+ int out_x, out_y, out_w, out_h, x, y;
+ unsigned long row[11];
+
+ out_x = 10*tile_x + (tile_x > 0);
+ out_y = 10*tile_y + (tile_y > 0);
+ out_w = 10 + (tile_x != 1);
+ out_h = 10 + (tile_y != 1);
+
+ for (y = 0; y < out_h; y++) {
+ for (x = 0; x < out_w; x++) {
+ if (x == 0 || y == 0)
+ row[x] = wm_pixel(&c[COLOUR_LIGHT]);
+ else if (x == out_w-1 || y == out_h-1)
+ row[x] = wm_pixel(&c[COLOUR_DARK]);
+ else
+ row[x] = wm_pixel(&c[COLOUR_PRIMARY]);
+ }
+ memcpy(&icon[32 * out_y++ + out_x], row, out_w * sizeof row[0]);
+ }
+}
+
+/*
+ * The 48x48 icon is drawn with 2px shadow and 16x16 tiles.
+ */
+void ewmh_tile48(unsigned long *icon, const XColor *c, int tile_x, int tile_y)
+{
+ int out_x, out_y, x, y;
+ unsigned long row[16];
+
+ out_x = tile_x * 16;
+ out_y = tile_y * 16;
+
+ for (y = 0; y < 16; y++) {
+ for (x = 0; x < 16; x++) {
+ if (x == 0 || y == 0)
+ row[x] = wm_pixel(&c[COLOUR_LIGHT]);
+ else if (x == 15 || y == 15)
+ row[x] = wm_pixel(&c[COLOUR_DARK]);
+ else if (x == 1 || y == 1)
+ row[x] = wm_pixel(&c[COLOUR_LIGHT]);
+ else if (x == 14 || y == 14)
+ row[x] = wm_pixel(&c[COLOUR_DARK]);
+ else
+ row[x] = wm_pixel(&c[COLOUR_PRIMARY]);
+ }
+ memcpy(&icon[48 * out_y++ + out_x], row, sizeof row);
+ }
+}
+
+int ewmh_probe_wm_icon(Widget shell)
+{
+ Display *display = XtDisplay(shell);
+ Screen *screen = XtScreen(shell);
+ Window root = RootWindowOfScreen(screen);
+ Atom net_supported, net_wm_icon, type;
+
+ unsigned long offset = 0, i, nitems, bytes_after, *props;
+ unsigned char *prop_return;
+ int format;
+
+ net_wm_icon = XInternAtom(display, "_NET_WM_ICON", FALSE);
+ net_supported = XInternAtom(display, "_NET_SUPPORTED", FALSE);
+ do {
+ XGetWindowProperty(display, root, net_supported, offset, 10,
+ FALSE, XA_ATOM, &type,
+ &format, &nitems, &bytes_after,
+ &prop_return);
+
+ if (format != 32 || type != XA_ATOM)
+ break;
+ offset += nitems;
+
+ props = (void *)prop_return;
+ for (i = 0; i < nitems; i++) {
+ if (props[i] == net_wm_icon) {
+ return 1;
+ }
+ }
+ } while (nitems > 0 && bytes_after > 0);
+
+ return 0;
+}
+
+void *ewmh_icon_alloc(unsigned long **sizes)
+{
+ unsigned long *buf;
+
+ buf = calloc(sizeof *buf, EWMH_ICON_NELEM);
+ if (buf) {
+ sizes[ICON_16x16] = buf;
+ *sizes[ICON_16x16]++ = 16;
+ *sizes[ICON_16x16]++ = 16;
+
+ sizes[ICON_24x24] = sizes[ICON_16x16] + 16*16;
+ *sizes[ICON_24x24]++ = 24;
+ *sizes[ICON_24x24]++ = 24;
+
+ sizes[ICON_32x32] = sizes[ICON_24x24] + 24*24;
+ *sizes[ICON_32x32]++ = 32;
+ *sizes[ICON_32x32]++ = 32;
+
+ sizes[ICON_48x48] = sizes[ICON_32x32] + 32*32;
+ *sizes[ICON_48x48]++ = 48;
+ *sizes[ICON_48x48]++ = 48;
+ }
+
+ return buf;
+}
static void proc_new_game(Widget w, XEvent *e, String *argv, Cardinal *argc)
{
+ Widget shell;
+
+ for (shell = w; !XtIsWMShell(shell);)
+ shell = XtParent(shell);
+
game_reset(&state.board);
x11_redraw_goal(&state);
x11_redraw_game(&state);
+ x11_redraw_icon(&state, shell);
}
static const XtActionsRec menu_actions[] = {
ui_initialize(&state, shell);
x11_initialize(&state, XtScreen(shell));
game_reset(&state.board);
+ state.use_ewmh_icons = ewmh_probe_wm_icon(shell);
XtRealizeWidget(shell);
+ x11_redraw_icon(&state, shell);
+
return app;
}
Widget game, goal;
+ /* Whether to set _NET_WM_ICON property on WMShell */
+ int use_ewmh_icons;
+
GC tile_gc;
+ Pixmap icon_pixmap;
uint_least32_t tile_colour[TILE_MAX-1][3];
};
void ui_initialize(struct app_state *state, Widget shell);
void x11_initialize(struct app_state *state, Screen *screen);
+void x11_redraw_icon(struct app_state *state, Widget shell);
void x11_redraw_goal(struct app_state *state);
void x11_redraw_game(struct app_state *state);
+/*
+ * Helpers for drawing window icons in various sizes. The tileXX functions
+ * draw one (out of 9) tiles at a particular size using a particular colour
+ * set. Call for each tile position to draw a complete icon.
+ */
+
+enum { ICON_16x16, ICON_24x24, ICON_32x32, ICON_48x48, ICON_MAX };
+#define EWMH_ICON_NELEM (2+16*16 + 2+24*24 + 2+32*32 + 2+48*48)
+
+void ewmh_tile16(unsigned long *icon, const XColor *c, int tile_x, int tile_y);
+void ewmh_tile24(unsigned long *icon, const XColor *c, int tile_x, int tile_y);
+void ewmh_tile32(unsigned long *icon, const XColor *c, int tile_x, int tile_y);
+void ewmh_tile48(unsigned long *icon, const XColor *c, int tile_x, int tile_y);
+
+/*
+ * Allocate storage for the EWMH _NET_WM_ICON array. The sizes array is
+ * populated with pointers to the beginning of each icon's pixel data. For
+ * example, sizes[ICON_24x24] points to the first pixel of the 24x24 image.
+ *
+ * The returned value can then be passed to XChangeProperty to set the icon,
+ * (use EWMH_ICON_NELEM for the number of elements) and must be freed by the
+ * caller.
+ */
+void *ewmh_icon_alloc(unsigned long **sizes);
+
+/*
+ * Check if the root window indicates support for EWMH icons. Returns 1 if
+ * supported, or 0 otherwise.
+ */
+int ewmh_probe_wm_icon(Widget shell);
+
#endif
--geometry=[W][xH][+X[+Y]]
Set initial window size (W by H) and/or position (X, Y).
+--iconic
+Start iconified.
+
--title=NAME
Set window title to NAME.
#include <config.h>
#include <stdio.h>
+#include <stdlib.h>
#include <assert.h>
#include <X11/Intrinsic.h>
#include <Xm/XmStrDefs.h>
+#include <X11/Xatom.h>
+#include <X11/Shell.h>
#include "motif.h"
+/* Size of the traditional icon pixmap (multiple of 3) */
+#define ICON_SIZE 48
+
/* TODO user-selectable colours */
static const char * const colours[][3] = {
/*primary bottom top */
gcv.line_width = 1;
state->tile_gc = XCreateGC(display, root, GCLineWidth, &gcv);
+
+ state->icon_pixmap = XCreatePixmap(display, root, ICON_SIZE, ICON_SIZE,
+ DefaultDepthOfScreen(screen));
}
static void draw_tile(struct app_state *state, Display *display, Drawable d,
XFillRectangle(display, d, state->tile_gc, tx+2, ty+2, tw-4, th-4);
}
-static void
+static int
redraw_tile(struct app_state *state, Display *display, Drawable d,
uint_fast32_t bit0, uint_fast32_t bit1, uint_fast32_t bit2,
int x, int y, Dimension w, Dimension h)
} else {
draw_tile(state, display, d, tile, x, y, w, h);
}
+
+ return tile;
+}
+
+static int
+redraw_goal_tile(struct app_state *state, Display *display, Drawable d,
+ int x, int y, Dimension w, Dimension h)
+{
+ uint_least16_t *gp = state->board.goal;
+
+ return redraw_tile(state, display, d, gp[0], gp[1], gp[2], x, y, w, h);
}
void x11_redraw_goal(struct app_state *state)
{
Display *display = XtDisplay(state->goal);
- Window goal = XtWindow(state->goal);
+ Window window = XtWindow(state->goal);
Dimension w, h;
int i;
XtVaGetValues(state->goal, XmNwidth, &w, XmNheight, &h, (char *)NULL);
- w /= 3; h /= 3;
+ for (i = 0; i < 9; i++)
+ redraw_goal_tile(state, display, window, i%3, i/3, w/3, h/3);
+}
+
+/* Render the goal area as the window's icon */
+void x11_redraw_icon(struct app_state *state, Widget shell)
+{
+ Display *display = XtDisplay(shell);
+ Dimension tilesz = ICON_SIZE/3;
+ int i, j, tile;
+
+ XColor colours[(TILE_MAX-1)*COLOUR_MAX];
+ unsigned long *icons[ICON_MAX];
+ void *wm_icon = NULL;
+
+ if (state->use_ewmh_icons && (wm_icon = ewmh_icon_alloc(icons))) {
+ Colormap cmap = DefaultColormapOfScreen(XtScreen(shell));
+
+ for (i = 0; i < TILE_MAX-1; i++) {
+ for (j = 0; j < COLOUR_MAX; j++) {
+ XColor *c = &colours[i*COLOUR_MAX+j];
+ c->pixel = state->tile_colour[i][j];
+ }
+ }
+
+ XQueryColors(display, cmap, colours, i*j);
+ }
for (i = 0; i < 9; i++) {
- uint_least16_t *gp = state->board.goal;
+ tile = redraw_goal_tile(state, display, state->icon_pixmap,
+ i%3, i/3, tilesz, tilesz);
+
+ if (wm_icon) {
+ XColor *c = &colours[(tile-1)*COLOUR_MAX];
+ ewmh_tile16(icons[ICON_16x16], c, i%3, i/3);
+ ewmh_tile24(icons[ICON_24x24], c, i%3, i/3);
+ ewmh_tile32(icons[ICON_32x32], c, i%3, i/3);
+ ewmh_tile48(icons[ICON_48x48], c, i%3, i/3);
+ }
+ }
- redraw_tile(state, display, goal,
- gp[0], gp[1], gp[2],
- i%3, i/3, w, h);
+ /*
+ * Clear and reset XmNiconPixmap otherwise it seems dtwm will not
+ * notice the changed icon.
+ */
+ XtVaSetValues(shell, XmNiconPixmap, None, (char *)NULL);
+ XtVaSetValues(shell, XmNiconPixmap, state->icon_pixmap, (char *)NULL);
+
+ if (wm_icon) {
+ Atom net_wm_icon = XInternAtom(display, "_NET_WM_ICON", FALSE);
+
+ XChangeProperty(display, XtWindow(shell), net_wm_icon,
+ XA_CARDINAL, 32, PropModeReplace,
+ wm_icon, EWMH_ICON_NELEM);
+
+ free(wm_icon);
}
}
/boardmove
+/ewmhicon
/rng-test
--- /dev/null
+/*
+ * Test app for _NET_WM_ICON formatting.
+ * Copyright © 2022 Nick Bowler
+ *
+ * Use a fake colour scheme to generate an icon of the chosen size (16x16,
+ * 24x24, 32x32 or 48x48) and display the pixels as characters.
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <X11/Intrinsic.h>
+#include "motif.h"
+
+static const char *progname = "ewmhicon";
+
+static void print_usage(FILE *f)
+{
+ fprintf(f, "Usage: %s size\n", progname);
+}
+
+static const char sizes[][6] = {
+ "16x16", "24x24", "32x32", "48x48"
+};
+
+int to_size_enum(const char *arg)
+{
+ char buf[8];
+ unsigned i;
+
+ if (!strchr(arg, 'x')) {
+ sprintf(buf, "%.3sx%.3s", arg, arg);
+ arg = buf;
+ }
+
+ for (i = 0; i < sizeof sizes / sizeof sizes[0]; i++) {
+ if (!strcmp(arg, sizes[i]))
+ return i;
+ }
+
+ return -1;
+}
+
+static unsigned long icon_buf[48*48];
+
+int main(int argc, char **argv)
+{
+ void (*tilefunc)(unsigned long *, const XColor *, int, int);
+ XColor c[COLOUR_MAX] = {0};
+ int size, x, y, w, h;
+
+ if (argc > 0)
+ progname = argv[0];
+
+ if (argc != 2) {
+ print_usage(stderr);
+ return EXIT_FAILURE;
+ }
+
+ size = to_size_enum(argv[1]);
+ if (size < 0) {
+ printf("%s: error: invalid size %s\n", progname, argv[1]);
+ return EXIT_FAILURE;
+ }
+
+ switch (size) {
+ case ICON_16x16: tilefunc = ewmh_tile16; w = h = 16; break;
+ case ICON_24x24: tilefunc = ewmh_tile24; w = h = 24; break;
+ case ICON_32x32: tilefunc = ewmh_tile32; w = h = 32; break;
+ case ICON_48x48: tilefunc = ewmh_tile48; w = h = 48; break;
+ default: assert(0);
+ }
+
+ c[COLOUR_PRIMARY].red = 0xffff;
+ c[COLOUR_DARK].green = 0xffff;
+ c[COLOUR_LIGHT].blue = 0xffff;
+
+ for (x = 0; x < 3; x++) {
+ for (y = 0; y < 3; y++) {
+ tilefunc(icon_buf, c, x, y);
+ }
+ }
+
+ for (y = 0; y < h; y++) {
+ for (x = 0; x < w; x++) {
+ unsigned long val = icon_buf[y*h+x];
+ int c = ' ';
+
+ if (val == 0xffff0000) c = '.'; // primary
+ else if (val == 0xff00ff00) c = '%'; // dark
+ else if (val == 0xff0000ff) c = '+'; // light
+
+ putchar(c);
+ }
+ putchar('\n');
+ }
+
+ return 0;
+}
+# Copyright © 2022 Nick Bowler
+#
+# 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 3 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, see <https://www.gnu.org/licenses/>.
+
AT_SETUP([game_do_move zigzag])
AT_CHECK([boardmove m4_do(
--- /dev/null
+# Copyright © 2022 Nick Bowler
+#
+# 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 3 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, see <https://www.gnu.org/licenses/>.
+
+AT_SETUP([_NET_WM_ICON tiles (16x16)])
+
+AT_CHECK([ewmhicon 16x16], [0], [dnl
+....%+....%+....
+....%+....%+....
+....%+....%+....
+....%+....%+....
+%%%%%+%%%%%+%%%%
+++++++++++++++++
+....%+....%+....
+....%+....%+....
+....%+....%+....
+....%+....%+....
+%%%%%+%%%%%+%%%%
+++++++++++++++++
+....%+....%+....
+....%+....%+....
+....%+....%+....
+....%+....%+....
+])
+
+AT_CLEANUP
+
+AT_SETUP([_NET_WM_ICON tiles (24x24)])
+
+AT_CHECK([ewmhicon 24x24], [0], [dnl
+++++++++++++++++++++++++
++......%+......%+......%
++......%+......%+......%
++......%+......%+......%
++......%+......%+......%
++......%+......%+......%
++......%+......%+......%
++%%%%%%%+%%%%%%%+%%%%%%%
+++++++++++++++++++++++++
++......%+......%+......%
++......%+......%+......%
++......%+......%+......%
++......%+......%+......%
++......%+......%+......%
++......%+......%+......%
++%%%%%%%+%%%%%%%+%%%%%%%
+++++++++++++++++++++++++
++......%+......%+......%
++......%+......%+......%
++......%+......%+......%
++......%+......%+......%
++......%+......%+......%
++......%+......%+......%
++%%%%%%%+%%%%%%%+%%%%%%%
+])
+
+AT_CLEANUP
+
+AT_SETUP([_NET_WM_ICON tiles (32x32)])
+
+AT_CHECK([ewmhicon 32x32], [0], [dnl
+++++++++++++++++++++++++++++++++
++.........%+........%+.........%
++.........%+........%+.........%
++.........%+........%+.........%
++.........%+........%+.........%
++.........%+........%+.........%
++.........%+........%+.........%
++.........%+........%+.........%
++.........%+........%+.........%
++.........%+........%+.........%
++%%%%%%%%%%+%%%%%%%%%+%%%%%%%%%%
+++++++++++++++++++++++++++++++++
++.........%+........%+.........%
++.........%+........%+.........%
++.........%+........%+.........%
++.........%+........%+.........%
++.........%+........%+.........%
++.........%+........%+.........%
++.........%+........%+.........%
++.........%+........%+.........%
++%%%%%%%%%%+%%%%%%%%%+%%%%%%%%%%
+++++++++++++++++++++++++++++++++
++.........%+........%+.........%
++.........%+........%+.........%
++.........%+........%+.........%
++.........%+........%+.........%
++.........%+........%+.........%
++.........%+........%+.........%
++.........%+........%+.........%
++.........%+........%+.........%
++.........%+........%+.........%
++%%%%%%%%%%+%%%%%%%%%+%%%%%%%%%%
+])
+
+AT_CLEANUP
+
+AT_SETUP([_NET_WM_ICON tiles (48x48)])
+
+AT_CHECK([ewmhicon 48x48], [0], [dnl
+++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++%+++++++++++++++%+++++++++++++++%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++%%%%%%%%%%%%%%++%%%%%%%%%%%%%%++%%%%%%%%%%%%%%
++%%%%%%%%%%%%%%%+%%%%%%%%%%%%%%%+%%%%%%%%%%%%%%%
+++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++%+++++++++++++++%+++++++++++++++%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++%%%%%%%%%%%%%%++%%%%%%%%%%%%%%++%%%%%%%%%%%%%%
++%%%%%%%%%%%%%%%+%%%%%%%%%%%%%%%+%%%%%%%%%%%%%%%
+++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++%+++++++++++++++%+++++++++++++++%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++............%%++............%%++............%%
+++%%%%%%%%%%%%%%++%%%%%%%%%%%%%%++%%%%%%%%%%%%%%
++%%%%%%%%%%%%%%%+%%%%%%%%%%%%%%%+%%%%%%%%%%%%%%%
+])
+
+AT_CLEANUP
AT_COLOR_TESTS
m4_include([tests/game.at])
+m4_include([tests/gui.at])