--- /dev/null
+*.a
+*.o
+.deps
+.dirstamp
+/Makefile
+/Makefile.in
+/aclocal.m4
+/autom4te.cache
+/compile
+/config.*
+/configure
+/depcomp
+/install-sh
+/libtool
+/ltmain.sh
+/missing
+/rrace-motif
+/stamp-h1
--- /dev/null
+[submodule "common"]
+ path = common
+ url = https://git.draconx.ca/dxcommon.git
--- /dev/null
+# Copyright © 2022 Nick Bowler
+#
+# License WTFPL2: Do What The Fuck You Want To Public License, version 2.
+# This is free software: you are free to do what the fuck you want to.
+# There is NO WARRANTY, to the extent permitted by law.
+
+ACLOCAL_AMFLAGS = -I m4 -I common/m4
+
+EXTRA_DIST =
+EXTRA_LIBRARIES =
+MAINTAINERCLEANFILES =
+DISTCLEANFILES =
+CLEANFILES = $(EXTRA_LIBRARIES)
+
+AM_CPPFLAGS = -I$(DX_BASEDIR)/src
+AM_CFLAGS = $(MOTIF_CFLAGS)
+
+bin_PROGRAMS = rrace-motif
+
+rrace_motif_SOURCES = #src/motif.c common/src/help.c
+rrace_motif_LDADD = $(libmotifmain_a_OBJECTS) $(libmotifui_a_OBJECTS) \
+ $(libglohelp_a_OBJECTS) $(MOTIF_LIBS)
+
+EXTRA_LIBRARIES += libmotifmain.a
+libmotifmain_a_SOURCES = src/motif.c
+$(libmotifmain_a_OBJECTS): src/options.h
+
+EXTRA_LIBRARIES += libmotifui.a
+libmotifui_a_SOURCES = src/motif_ui.c
+$(libmotifui_a_OBJECTS): src/motifgui.h
+
+EXTRA_LIBRARIES += libglohelp.a
+libglohelp_a_SOURCES = common/src/help.c
+libglohelp_a_CFLAGS = -DHELP_GETOPT_LONG_ONLY
+libglohelp_a_SHORTNAME = glo
+
+OPTFILES = src/options.opt
+.opt.h:
+ $(AM_V_GEN) $(AWK) -f $(DX_BASEDIR)/scripts/gen-options.awk $< >$@.tmp
+ $(AM_V_at) mv -f $@.tmp $@
+$(OPTFILES:.opt=.h): $(DX_BASEDIR)/scripts/gen-options.awk
+DISTCLEANFILES += $(OPTFILES:.opt=.h)
+EXTRA_DIST += $(DX_BASEDIR)/scripts/gen-options.awk $(OPTFILES)
+
+GUIFILES = src/motifgui.dat
+.dat.h:
+ $(AM_V_GEN) $(AWK) -f $(DX_BASEDIR)/scripts/gen-tree.awk $< >$@.tmp
+ $(AM_V_at) mv -f $@.tmp $@
+$(GUIFILES:.dat=.h): $(DX_BASEDIR)/scripts/gen-tree.awk
+DISTCLEANFILES += $(GUIFILES:.dat=.h)
+EXTRA_DIST += $(DX_BASEDIR)/scripts/gen-tree.awk $(GUIFILES)
--- /dev/null
+Subproject commit a7cabb5d0f067e78afd029d8ec41d14660d8f9e2
--- /dev/null
+dnl Copyright © 2022 Nick Bowler
+dnl
+dnl License WTFPL2: Do What The Fuck You Want To Public License, version 2.
+dnl This is free software: you are free to do what the fuck you want to.
+dnl There is NO WARRANTY, to the extent permitted by law.
+
+AC_INIT([rrace], [0], [nbowler@draconx.ca])
+AC_CONFIG_HEADERS([config.h])
+
+AM_INIT_AUTOMAKE([-Wall -Wno-portability foreign subdir-objects])
+AM_SILENT_RULES([yes])
+DX_AUTOMAKE_COMPAT
+
+AC_PROG_CC_C99
+LT_INIT
+
+AC_PATH_XTRA
+AS_IF([test x"$no_x" != x"yes"],
+[AC_CACHE_CHECK([for Motif], [dx_cv_have_motif],
+[save_CFLAGS=$CFLAGS save_LIBS=$LIBS
+dx_cv_motif_cflags="$X_CFLAGS"
+dx_cv_motif_libs="$X_LIBS $X_PRE_LIBS -lXm -lXt -lX11 $X_EXTRA_LIBS"
+CFLAGS="$dx_cv_motif_cflags $CFLAGS"
+LIBS="$dx_cv_motif_libs $LIBS"
+AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <Xm/XmAll.h>],
+ [XmCreateMainWindow(0, 0, 0, 0); XtAppSetExitFlag(0)])],
+ [dx_cv_have_motif=yes], [dx_cv_have_motif=no])
+CFLAGS=$save_CFLAGS LIBS=$save_LIBS])])
+
+AS_IF([test x"$with_x" = x"yes" && test "$dx_cv_have_motif" != x"yes"],
+ [AC_MSG_FAILURE([--with-x requested but Motif was not found])])
+
+AC_SUBST([MOTIF_CFLAGS], [@&t@])
+AC_SUBST([MOTIF_LIBS], [@&t@])
+AS_IF([test x"$dx_cv_have_motif" = x"yes"],
+ [MOTIF_CFLAGS=$dx_cv_motif_cflags MOTIF_LIBS=$dx_cv_motif_libs])
+
+AC_CONFIG_FILES([Makefile])
+AC_OUTPUT
--- /dev/null
+/libtool.m4
+/ltoptions.m4
+/ltsugar.m4
+/ltversion.m4
+/lt~obsolete.m4
--- /dev/null
+/motifgui.h
+/options.h
--- /dev/null
+/*
+ * X11 GUI 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 <getopt.h>
+
+#include <Xm/XmAll.h>
+
+#include "help.h"
+#include "motif.h"
+#include "options.h"
+
+#define PROGNAME "rrace"
+static const char *progname = PROGNAME;
+static const struct option lopts[] = { LOPTS_INITIALIZER, {0} };
+
+static char * const default_resources[] = {
+ PROGNAME "*game.height: 371", // 365 + 2*shadowThickness
+ PROGNAME "*game.width: 498",
+
+ PROGNAME "*game.XmFrame.shadowThickness: 3",
+ PROGNAME "*game.XmFrame.shadowType: shadow_in",
+ PROGNAME "*goalArea.leftOffset: 1",
+
+ NULL
+};
+
+static void print_version(void)
+{
+ printf("%s %s\n", PROGNAME, PACKAGE_VERSION);
+ printf("Copyright (C) 2022 Nick Bowler\n");
+ puts("License GPLv3+: GNU GPL version 3 or any later version");
+ puts("This is free software: you are free to change and redistribute it.");
+ puts("There is NO WARRANTY, to the extent permitted by law.");
+}
+
+static void print_usage(FILE *f)
+{
+ fprintf(f, "Usage: %s [options]\n", progname);
+ if (f != stdout)
+ fprintf(f, "Try %s --help for more information.\n", progname);
+}
+
+static void print_help(void)
+{
+ const struct option *opt;
+
+ print_usage(stdout);
+
+ putchar('\n');
+ puts("Options:");
+ for (opt = lopts; opt->name; opt++) {
+ struct lopt_help help;
+ int w;
+
+ if (!lopt_get_help(opt, &help))
+ continue;
+ help_print_option(opt, help.arg, help.desc, 20);
+ }
+ putchar('\n');
+
+ printf("Report bugs to <%s>.\n", PACKAGE_BUGREPORT);
+}
+
+static Widget early_setup(XtAppContext *app, int argc, char **argv)
+{
+ Widget shell;
+ int opt;
+
+ /* Check for --help/--version early (before X connection) */
+ opterr = 0;
+ while ((opt = getopt_long_only(argc, argv, "", lopts, NULL)) != -1) {
+ switch (opt) {
+ case LOPT_VERSION:
+ print_version();
+ exit(EXIT_SUCCESS);
+ case LOPT_HELP:
+ print_help();
+ exit(EXIT_SUCCESS);
+ }
+ }
+
+ shell = XtOpenApplication(app, PROGNAME, NULL, 0,
+ &argc, argv, (String *)default_resources,
+ sessionShellWidgetClass, NULL, 0);
+
+ opterr = optind = 1;
+ while ((opt = getopt_long_only(argc, argv, "", lopts, NULL)) != -1) {
+ switch (opt) {
+ default:
+ print_usage(stderr);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (argv[optind]) {
+ fprintf(stderr, "%s: excess command-line arguments.\n",
+ progname);
+ print_usage(stderr);
+ exit(EXIT_FAILURE);
+ }
+
+ return shell;
+}
+
+static XtAppContext app_initialize(int argc, char **argv)
+{
+ XtAppContext app;
+ Widget shell;
+
+ if (argc > 0)
+ progname = argv[0];
+
+ shell = early_setup(&app, argc, argv);
+ ui_initialize(shell);
+ XtRealizeWidget(shell);
+
+ return app;
+}
+
+int main(int argc, char **argv)
+{
+ XtAppMainLoop(app_initialize(argc, argv));
+ return 0;
+}
--- /dev/null
+/*
+ * X11 GUI 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/>.
+ */
+
+#ifndef RRACE_MOTIF_H_
+#define RRACE_MOTIF_H_
+
+#include <X11/Intrinsic.h>
+
+void ui_initialize(Widget shell);
+
+#endif
--- /dev/null
+/*
+ * X11 GUI 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 <inttypes.h>
+#include <assert.h>
+#include <Xm/XmAll.h>
+
+#include "motif.h"
+#include "motifgui.h"
+
+#define SPLIT_NUMERATOR 75
+#define SPLIT_DENOMINATOR 100
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+static void game_configure(Widget w);
+
+static const struct ui_widget {
+ uint_least16_t name;
+ uint_least16_t subtree;
+ WidgetClass *class;
+ void (*configure)(Widget w);
+} mainwin[] = { MAINWIN_INITIALIZER };
+
+static void
+ResizeGameArea(Widget form, XEvent *e, String *args, Cardinal *num_args)
+{
+ Widget game = XtNameToWidget(form, &tree_strtab[gameArea]);
+ Widget goal = XtNameToWidget(form, &tree_strtab[goalArea]);
+ Dimension w, h, gamesz, gameborder, goalsz, goalborder;
+ int x, y, gap;
+
+ XtVaGetValues(form, XmNwidth, &w, XmNheight, &h, (char *)NULL);
+ XtVaGetValues(game, XmNshadowThickness, &gameborder, (char *)NULL);
+ XtVaGetValues(goal, XmNshadowThickness, &goalborder,
+ XmNleftOffset, &gap,
+ (char *)NULL);
+
+ gamesz = MIN(h, w * SPLIT_NUMERATOR / SPLIT_DENOMINATOR);
+ gamesz = 5*( (gamesz - 2*gameborder)/5 ) + 2*gameborder;
+
+ goalsz = MIN(gamesz*3/5, w - gamesz - gap);
+ goalsz = 3*( (goalsz - 2*goalborder)/3 ) + 2*goalborder;
+
+ x = (w - gamesz - goalsz - gap) / 2;
+ if (x < 2) x = 0;
+
+ y = (h - gamesz) / 2;
+ if (y < 3) y = 0;
+
+ XtVaSetValues(game, XmNleftOffset, x, XmNtopOffset, y, (char *)NULL);
+ XtVaSetValues(game, XmNwidth, gamesz, XmNheight, gamesz, (char *)NULL);
+ XtVaSetValues(goal, XmNwidth, goalsz, XmNheight, goalsz, (char *)NULL);
+}
+
+static void game_configure(Widget form)
+{
+ Widget gamearea = XtNameToWidget(form, &tree_strtab[gameArea]);
+ Widget goalarea = XtNameToWidget(form, &tree_strtab[goalArea]);
+ XtActionsRec resize_rec;
+
+ assert(gamearea && goalarea);
+ XtVaSetValues(form, XmNfractionBase, SPLIT_DENOMINATOR, (char *)NULL);
+
+ XtVaSetValues(gamearea, //XmNrightAttachment, XmATTACH_POSITION,
+ //XmNrightPosition, SPLIT_NUMERATOR,
+ XmNleftAttachment, XmATTACH_FORM,
+ XmNtopAttachment, XmATTACH_FORM,
+ (char *)NULL);
+
+ XtVaSetValues(goalarea, XmNleftAttachment, XmATTACH_WIDGET,
+ XmNleftWidget, gamearea,
+ XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET,
+ XmNtopWidget, gamearea,
+ (char *)NULL);
+
+ resize_rec.string = "ResizeGameArea";
+ resize_rec.proc = ResizeGameArea;
+ XtAppAddActions(XtWidgetToApplicationContext(form), &resize_rec, 1);
+ XtOverrideTranslations(form, XtParseTranslationTable(
+ "<Configure>: ResizeGameArea()\n"
+ "<Map>: ResizeGameArea()\n"
+ ));
+}
+
+static void
+construct_widgets(const struct ui_widget *root, Widget parent, unsigned i)
+{
+ const struct ui_widget *item;
+
+ for (item = &root[i]; item->name; item++) {
+ Widget w;
+
+ w = XtCreateWidget(&tree_strtab[item->name], *item->class,
+ parent, NULL, 0);
+ if (item->subtree) {
+ construct_widgets(root, w, item->subtree);
+ }
+
+ if (item->configure) {
+ item->configure(w);
+ }
+
+ XtManageChild(w);
+ }
+}
+
+static void game_resize(Widget w, void *data, void *cb_data)
+{
+ Dimension width, height;
+
+ XtVaGetValues(w, XmNwidth, &width, XmNheight, &height, (char *)NULL);
+
+ printf("game %ux%u\n", width, height);
+}
+
+static void goal_resize(Widget w, void *data, void *cb_data)
+{
+ Dimension width, height;
+
+ XtVaGetValues(w, XmNwidth, &width, XmNheight, &height, (char *)NULL);
+
+ printf("goal %ux%u\n", width, height);
+}
+
+void ui_initialize(Widget shell)
+{
+ Widget game, goal;
+
+ construct_widgets(mainwin, shell, 0);
+
+ game = XtNameToWidget(shell, "*gameCanvas");
+ goal = XtNameToWidget(shell, "*goalCanvas");
+
+ XtAddCallback(game, XmNresizeCallback, game_resize, NULL);
+ XtAddCallback(goal, XmNresizeCallback, goal_resize, NULL);
+}
--- /dev/null
+MAINWIN
+ main, main_OFFSET, &xmMainWindowWidgetClass
+ game, game_OFFSET, &xmFormWidgetClass, game_configure
+ gameArea, gameArea_OFFSET, &xmFrameWidgetClass
+ gameCanvas, 0, &xmDrawingAreaWidgetClass
+ goalArea, goalArea_OFFSET, &xmFrameWidgetClass
+ goalCanvas, 0, &xmDrawingAreaWidgetClass
--- /dev/null
+--display=DISPLAY
+X server DISPLAY name to use.
+
+--geometry=[W][xH][+X[+Y]]
+Set initial window size (W by H) and/or position (X, Y).
+
+--title=NAME
+Set window title to NAME.
+
+--xrm=STRING
+Set the X resource described by STRING, overriding defaults.
+
+--version
+Print a version message and then exit.
+
+--help
+Print this message and then exit.