mmlib: initial import of the mmrarray library
authorMatthew Mondor <mmondor@pulsar-zone.net>
Sat, 15 Apr 2023 18:23:43 +0000 (18:23 +0000)
committerMatthew Mondor <mmondor@pulsar-zone.net>
Sat, 15 Apr 2023 18:23:43 +0000 (18:23 +0000)
Ascender: design format and write a pad-based shape file loader

14 files changed:
mmsoftware/ascender/GNUmakefile
mmsoftware/ascender/src/clock.c [deleted file]
mmsoftware/ascender/src/clock.h [deleted file]
mmsoftware/ascender/src/collision.c [new file with mode: 0644]
mmsoftware/ascender/src/collision.h [new file with mode: 0644]
mmsoftware/ascender/src/config.h
mmsoftware/ascender/src/main.c
mmsoftware/ascender/src/nbrandom.c [new file with mode: 0644]
mmsoftware/ascender/src/nbrandom.h [new file with mode: 0644]
mmsoftware/ascender/src/screen.c
mmsoftware/ascender/src/screen.h
mmsoftware/ascender/src/shapes.txt [new file with mode: 0644]
mmsoftware/mmlib/mmrarray.c [new file with mode: 0644]
mmsoftware/mmlib/mmrarray.h [new file with mode: 0644]

index e21ffb6..37b6e4c 100644 (file)
@@ -7,6 +7,8 @@ INSTALL := install
 DATE := date
 GIT := git
 
+MMLIB_PATH := ../mmlib
+
 REVISION := $(shell $(DATE) +%Y%m%d)-$(shell $(GIT) 2>/dev/null describe --abbrev=1 --tags --always --match v)
 TMPDIR := /var/tmp
 
@@ -16,19 +18,20 @@ CFLAGS += -Wall -Isrc
 #CFLAGS += -v -H -g -O0
 #LDFLAGS += -v -g
 # Alternative
-#CFLAGS += -g -O0
-#LDFLAGS += -g
+CFLAGS += -g -O0
+LDFLAGS += -g
 # For inline functions with GCC5 or XCode
-CFLAGS += -std=gnu89 -O3
+#CFLAGS += -std=gnu89 -O3
 
 # And to disable assertions
-CFLAGS += -DNDEBUG
+#CFLAGS += -DNDEBUG
 
 # And to enable profiling
 #CFLAGS += -pg
 #LDFLAGS += -pg
 
-OBJS := $(addprefix src/,clock.o screen.o main.o)
+MMLIBS := $(addprefix $(MMLIB_PATH)/,mmrarray.o)
+OBJS := $(addprefix src/,screen.o collision.o nbrandom.o main.o)
 BIN := ascender
 
 # OS dependent settings follow
@@ -52,14 +55,6 @@ endif
 
 CURSES_CFLAGS :=
 CURSES_LDFLAGS := -lcurses
-ifneq (,$(findstring NetBSD,$(OS)))
-       CURSES_CFLAGS += -DTPARM_VARARGS
-       CURSES_LDFLAGS += -lterminfo
-endif
-
-# Architecture independent settings follow
-#CFLAGS += -DBIG_ENDIAN=4321 -DLITTLE_ENDIAN=1234
-#CFLAGS += -DBYTE_ORDER=LITTLE_ENDIAN
 
 CFLAGS += $(CURSES_CFLAGS)
 LDFLAGS += $(CURSES_LDFLAGS) -lm
@@ -69,14 +64,14 @@ all: $(BIN)
 $(OBJS): src/config.h
 
 %.o: %.c
-       $(CC) -c $(CFLAGS) -I. -o $@ $<
+       $(CC) -c $(CFLAGS) -I. -I$(MMLIB_PATH) -o $@ $<
 
-$(BIN): $(OBJS)
-       $(CC) -o $@ $(OBJS) $(LDFLAGS)
-       $(STRIP) $@
+$(BIN): $(MMLIBS) $(OBJS)
+       $(CC) -o $@ $(MMLIBS) $(OBJS) $(LDFLAGS)
+#      $(STRIP) $@
 
 install:
        $(INSTALL) -c -o 0 -g 0 -m 755 $(BIN) /usr/local/bin
 
 clean:
-       $(RM) -f $(BIN) $(BIN).core $(OBJS)
+       $(RM) -f $(BIN) $(BIN).core $(OBJS) $(MMLIBS)
diff --git a/mmsoftware/ascender/src/clock.c b/mmsoftware/ascender/src/clock.c
deleted file mode 100644 (file)
index 8635455..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (c) 2023, Matthew Mondor
- * ALL RIGHTS RESERVED.
- */
-
-
-#include <err.h>
-#include <signal.h>
-#include <stdbool.h>
-#include <stdlib.h>
-#include <sys/time.h>
-
-#include <clock.h>
-#include <config.h>
-
-
-bool           clock_expired = false;
-
-
-static void    sighandler(int);
-
-
-void
-clock_init(void)
-{
-       struct sigaction act;
-       struct itimerval itv;
-
-       /* XXX Probably some are unnecessary here */
-       /* Setup signal handler */
-       act.sa_handler = sighandler;
-       act.sa_flags = 0; /* ~SA_RESTART to detect interruptions */
-       if (sigemptyset(&act.sa_mask) != 0)
-               err(EXIT_FAILURE, "sigemptyset()");
-       if (sigaction(SIGALRM, &act, NULL) != 0)
-               err(EXIT_FAILURE, "sigaction(SIGALRM)");
-       if (sigaction(SIGINT, &act, NULL) != 0)
-               err(EXIT_FAILURE, "sigaction(SIGINT)");
-       if (sigaction(SIGTERM, &act, NULL) != 0)
-               err(EXIT_FAILURE, "sigaction(SIGTERM)");
-       if (sigaction(SIGHUP, &act, NULL) != 0)
-               err(EXIT_FAILURE, "sigaction(SIGHUP)");
-
-       /* Setup interval timer */
-       timerclear(&itv.it_interval);
-       timerclear(&itv.it_value);
-       itv.it_interval.tv_sec = 0;
-       itv.it_interval.tv_usec = 1000000 / ANIM_FPS;
-       itv.it_value.tv_sec = itv.it_interval.tv_sec;
-       itv.it_value.tv_usec = itv.it_interval.tv_usec;
-       if (setitimer(ITIMER_REAL, &itv, NULL) != 0)
-               err(EXIT_FAILURE, "setitimer()");
-}
-
-static void
-sighandler(int sig)
-{
-
-       switch (sig) {
-       case SIGALRM:
-               clock_expired = true;
-               break;
-       case SIGINT:
-       case SIGTERM: /* FALLTHROUGH */
-       case SIGHUP: /* FALLTHROUGH */
-               exit(EXIT_SUCCESS);
-               break;
-       }
-}
diff --git a/mmsoftware/ascender/src/clock.h b/mmsoftware/ascender/src/clock.h
deleted file mode 100644 (file)
index fb4c44e..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright (c) 2023, Matthew Mondor
- * ALL RIGHTS RESERVED.
- */
-
-
-#ifndef __CLOCK_H__
-#define __CLOCK_H__
-
-
-#include <stdbool.h>
-
-
-extern void    clock_init(void);
-
-
-extern bool    clock_expired;
-
-
-#endif
diff --git a/mmsoftware/ascender/src/collision.c b/mmsoftware/ascender/src/collision.c
new file mode 100644 (file)
index 0000000..b22b673
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2018, 2023, Matthew Mondor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY MATTHEW MONDOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL MATTHEW MONDOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdbool.h>
+#include <curses.h>
+
+
+inline bool
+point_in_rect(int x, int y, int rx1, int ry1, int rx2, int ry2)
+{
+
+       return (x > rx1 && x < rx2 && y > ry1 && y < ry2);
+}
+
+inline bool
+rect_overlap(int r1x1, int r1y1, int r1x2, int r1y2,
+    int r2x1, int r2y1, int r2x2, int r2y2)
+{
+
+       return (
+           point_in_rect(r1x1, r1y1, r2x1, r2y1, r2x2, r2y2) ||
+           point_in_rect(r1x2, r1y1, r2x1, r2y1, r2x2, r2y2) ||
+           point_in_rect(r1x2, r1y2, r2x1, r2y1, r2x2, r2y2) ||
+           point_in_rect(r1x1, r1y2, r2x1, r2y1, r2x2, r2y2) ||
+           point_in_rect(r2x1, r2y1, r1x1, r1y1, r1x2, r1y2) ||
+           point_in_rect(r2x2, r2y1, r1x1, r1y1, r1x2, r1y2) ||
+           point_in_rect(r2x2, r2y2, r1x1, r1y1, r1x2, r1y2) ||
+           point_in_rect(r2x1, r2y2, r1x1, r1y1, r1x2, r1y2));
+}
+
+bool
+point_in_shape(WINDOW *pad, int x, int y)
+{
+       chtype c;
+
+       if ((c = mvwinch(pad, y, x) != ERR) &&
+           (c & A_CHARTEXT) == ' ')
+               return true;
+
+       return false;
+}
diff --git a/mmsoftware/ascender/src/collision.h b/mmsoftware/ascender/src/collision.h
new file mode 100644 (file)
index 0000000..ac85499
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2018, 2023, Matthew Mondor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY MATTHEW MONDOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL MATTHEW MONDOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __COLLISION_H__
+#define __COLLISION_H__
+
+
+#include <stdbool.h>
+#include <curses.h>
+
+
+inline bool    point_in_rect(int, int, int, int, int, int);
+inline bool    rect_overlap(int, int, int, int, int, int, int, int);
+bool           point_in_shape(WINDOW *, int, int);
+
+
+#endif
index 4e0cf85..38a0e19 100644 (file)
@@ -1,3 +1,28 @@
+/*
+ * Copyright (c) 2023, Matthew Mondor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY MATTHEW MONDOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL MATTHEW MONDOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
 #ifndef __CONFIG_H__
 #define __CONFIG_H__
 
@@ -8,4 +33,10 @@
 #define ANIM_FPS       15
 
 
+/* XXX */
+#define DATA_PREFIX    "./src"
+#define DATA_SHAPES    "shapes.txt"
+#define DATA_SHAPESPATH        "" DATA_PREFIX "/" DATA_SHAPES ""
+
+
 #endif
index a7a31b6..780318c 100644 (file)
 /*
  * Copyright (c) 2023, Matthew Mondor
- * ALL RIGHTS RESERVED.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY MATTHEW MONDOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL MATTHEW MONDOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-
+#include <ctype.h>
 #include <curses.h>
+#include <err.h>
 #include <stdbool.h>
 #include <stdlib.h>
 #include <stdio.h>
+#include <string.h>
+#include <unistd.h>
 
-#include <config.h>
-#include <clock.h>
 #include <screen.h>
+#include <nbrandom.h>
+#include <config.h>
 
+#include <mmrarray.h>
 
-#define NOBJECTS       46
 
+#define NOBJECTS       56
+
+enum enum_shapes {
+       SHAPE_A = 0,
+       SHAPE_B,
+       SHAPE_SHIP,
+       SHAPE_MAX
+};
+
+/* Animation object */
 enum aobject_type {
        AOT_AVATAR = 0,
        AOT_STAR,
-       AOT_BUG
+       AOT_BUG,
+       AOT_SHAPE
 };
 
-/* Animation object */
+/* Could be a union but not needed for this test */
 typedef struct aobject {
-       int             t, x, y, fcol, bcol;
+       int             t, x, y, col;
        char            c;
+       int             s;
+       int             d;
 } aobject_t;
 
 
 int                    main(void);
+static void            shape_draw(int, int, int);
 static void            anim_init(void);
-static void            anim_refresh_full(void);
-static void            anim_refresh_update(void);
+static void            anim_redraw(void);
+static void            anim_update(void);
+
+static void            rarray_empty(rarray_t *);
+static void            read_shapes(void);
 
 
+/* Color related */
+static short           ncolpairs = -1;
+
+/* Loaded shapes */
+WINDOW *spads[SHAPE_MAX];
+
 /* Animation world */
-static char            map[TERM_LINES][TERM_COLUMNS];
 static aobject_t       objects[NOBJECTS];
 static aobject_t       avatar = { AOT_AVATAR,
                            TERM_COLUMNS / 2, TERM_LINES / 2,
-                           1, 0, '?' };
+                           0, '?', -1, -1 };
 
 
 int
 main(void)
 {
-       int c, uc;
-
-       /* Setup interval timer and signal handler */
-       clock_init();
-
-       /* And screen */
        screen_init();
 
+       /*
+        * XXX Useful but relies on an ncurses extension, also supported by
+        * NetBSD curses.  init_pair() cannot accept -1 according to X/Open.
+        * But this special value means to keep the default foreground or
+        * background color of the terminal configuration.  This also permits
+        * enforced color pairs to really only define the foreground or
+        * background color.
+        */
+       (void)use_default_colors();
+       if (start_color() == ERR)
+               err(EXIT_FAILURE, "start_color()");
+
+       /*
+        * Allocate a few basic pairs before loading shapes that will also
+        * allocate new pairs as needed.
+        */
+       (void)init_pair(++ncolpairs, -1, -1);
+       (void)init_pair(++ncolpairs, COLOR_WHITE, COLOR_BLACK);
+
+       /* Read shape definitions from file */
+       read_shapes();
+
        /* Setup animation world */
        anim_init();
 
-       /* Initialize screen */
-       ti_put(&ti_clear);
-       ti_put(&ti_civis);
-       (void)fflush(stdout);
-
        /* Animate until user requests to quit with q */
-       anim_refresh_full();
-       clock_expired = false;
-       uc = -1;
+       anim_redraw();
        for (;;) {
-cont:
-               if ((c = screen_getchar()) != ERR)
-                       uc = c;
-               if (clock_expired) {
-                       clock_expired = false;
-                       int x = -1, y = -1;
-
-                       /* Process user input if any */
-                       /* XXX Should probably be in a function */
-                       switch (uc) {
-                       case -1:
-                               break;
-                       case 'q':
-                               goto end;
-                               break;
-                       case 12:
-                               anim_refresh_full();
-                               uc = -1;
-                               goto cont;
-                               break;
-
-                       case KEY_UP:
-                       case '8': /* FALLTHROUGH */
-                       case 'i': /* FALLTHROUGH */
-                       case 'a': /* FALLTHROUGH */
-                               if (avatar.y > 0) {
-                                       y = avatar.y - 1;
-                                       x = avatar.x;
-                               }
-                               break;
+               int x = -1, y = -1, c = -1, uc = -1;
 
-                       case KEY_DOWN:
-                       case '2': /* FALLTHROUGH */
-                       case 'k': /* FALLTHROUGH */
-                       case 'm': /* FALLTHROUGH */
-                       case 'z': /* FALLTHROUGH */
-                               if (avatar.y < TERM_LINES - 1) {
-                                       y = avatar.y + 1;
-                                       x = avatar.x;
-                               }
-                               break;
+               /* Sleep for most of the frame */
+               (void)usleep(1000000 / ANIM_FPS);
 
-                       case KEY_LEFT:
-                       case '4': /* FALLTHROUGH */
-                       case 'j': /* FALLTHROUGH */
-                       case ',': /* FALLTHROUGH */
-                               if (avatar.x > 0) {
-                                       x = avatar.x - 1;
-                                       y = avatar.y;
-                               }
-                               break;
+               /* Non-blocking check for user input */
+               if ((c = screen_getchar()) != ERR) {
+                       uc = c;
+                       /*
+                        * If we don't flush and keyboard input/repeat is
+                        * faster than our refresh rate, the input queue grows
+                        * meaning that the avatar takes a while to react to
+                        * immediate user input.
+                        */
+                       (void)flushinp();
+               }
 
-                       case KEY_RIGHT:
-                       case '6': /* FALLTHROUGH */
-                       case 'l': /* FALLTHROUGH */
-                       case '.': /* FALLTHROUGH */
-                               if (avatar.x < TERM_COLUMNS - 1) {
-                                       x = avatar.x + 1;
-                                       y = avatar.y;
-                               }
-                               break;
+               /* Process user input if any */
+               switch (uc) {
+               case -1:
+                       break;
+               case 'q':
+                       goto end;
+                       break;
+
+               case KEY_UP:
+               case '8': /* FALLTHROUGH */
+               case 'i': /* FALLTHROUGH */
+               case 'a': /* FALLTHROUGH */
+                       if (avatar.y > 0) {
+                               y = avatar.y - 1;
+                               x = avatar.x;
                        }
-                       /* Update avatar if necessary */
-                       if (x != -1 && y != -1) {
-                               /* XXX */
-                               avatar.x = x;
-                               avatar.y = y;
+                       break;
+
+               case KEY_DOWN:
+               case '2': /* FALLTHROUGH */
+               case 'k': /* FALLTHROUGH */
+               case 'm': /* FALLTHROUGH */
+               case 'z': /* FALLTHROUGH */
+                       if (avatar.y < TERM_LINES - 1) {
+                               y = avatar.y + 1;
+                               x = avatar.x;
                        }
+                       break;
+
+               case KEY_LEFT:
+               case '4': /* FALLTHROUGH */
+               case 'j': /* FALLTHROUGH */
+               case ',': /* FALLTHROUGH */
+                       if (avatar.x > 0) {
+                               x = avatar.x - 1;
+                               y = avatar.y;
+                       }
+                       break;
+
+               case KEY_RIGHT:
+               case '6': /* FALLTHROUGH */
+               case 'l': /* FALLTHROUGH */
+               case '.': /* FALLTHROUGH */
+                       if (avatar.x < TERM_COLUMNS - 1) {
+                               x = avatar.x + 1;
+                               y = avatar.y;
+                       }
+                       break;
+               }
 
-                       /* Animate */
-                       anim_refresh_update();
-                       uc = -1;
+               /* Update avatar if necessary */
+               if (x != -1 && y != -1) {
+                       avatar.x = x;
+                       avatar.y = y;
                }
+
+               /* Animate */
+               anim_update();
+               anim_redraw();
+               uc = -1;
        }
 end:
 
        exit(EXIT_SUCCESS);
 }
 
+/* 2d text shape blitting routine */
 static void
-anim_init(void)
+shape_draw(int shape, int y, int x)
 {
-       int x, y, i, col;
 
-       for (y = 0; y < TERM_LINES; y++) {
-               for (x = 0; x < TERM_COLUMNS; x++) {
-                       map[y][x] = ' ';
-               }
-       }
+       screen_blitpad(spads[shape], y, x);
+}
+
+static void
+anim_init(void)
+{
+       int i, col;
 
-       srandom(13);
+       nbsrandom(13);
        col = 0;
        for (i = 0; i < NOBJECTS; i++) {
                aobject_t *o = &objects[i];
 
+               /* 24 bugs, 20 stars, 10 shapes */
                if (i <= 26) {
                        o->t = AOT_BUG;
                        o->c = 'A' + i;
-                       o->fcol = ++col % 7;
-                       o->bcol = ++col % 7;
-               } else {
+                       o->col = ++col % ncolpairs;
+                       o->s = -1;
+                       o->d = nbrandom() % 4;
+               } else if (i < 46) {
                        o->t = AOT_STAR;
                        o->c = '.';
-                       o->fcol = 2;
-                       o->bcol = 0;
+                       o->col = 0;
+                       o->s = -1;
+                       o->d = -1;
+               } else {
+                       o->t = AOT_SHAPE;
+                       o->c = '\0';
+                       o->col = -1;
+                       o->s = nbrandom() % SHAPE_MAX;
+                       o->d = nbrandom() % 4;
                }
-               o->x = random() % (TERM_COLUMNS - 1);
-               o->y = random() % (TERM_LINES - 1);
+               o->x = nbrandom() % (TERM_COLUMNS - 1);
+               o->y = nbrandom() % (TERM_LINES - 1);
        }
-
-       /* XXX Avatar */
 }
 
 static void
-anim_refresh_full(void)
+anim_redraw(void)
 {
-       int x, y, i;
+       int i;
 
-       ti_put(&ti_clear);
-       ti_bgcolor(0);
-       for (y = 0; y < TERM_LINES; y++) {
-               for (x = 0; x < TERM_COLUMNS; x++) {
-                       (void)putchar(map[y][x]);
-               }
-               (void)fwrite("\r\n", 2, 1, stdout);
-       }
+       /* Redraw screen with all objects */
+       (void)attrset(COLOR_PAIR(0));
+       (void)erase();
        for (i = 0; i < NOBJECTS; i++) {
                aobject_t *o = &objects[i];
 
-               ti_goto(o->x, o->y);
-               ti_fgcolor(o->fcol);
-               ti_bgcolor(o->bcol);
-               putchar(o->c);
+               if (o->t == AOT_SHAPE)
+                       shape_draw(o->s, o->y, o->x);
+               else {
+                       (void)attrset(COLOR_PAIR(o->col));
+                       (void)mvaddch(o->y, o->x, o->c);
+               }
        }
 
        /* Avatar */
-       ti_goto(avatar.x, avatar.y);
-       ti_put(&ti_sgr0);
-       ti_put(&ti_rev);
-       (void)putchar(avatar.c);
-       ti_put(&ti_sgr0);
+       (void)attr_on(A_REVERSE, NULL);
+       (void)mvaddch(avatar.y, avatar.x, avatar.c);
+       (void)attr_off(A_REVERSE, NULL);
 
-       (void)fflush(stdout);
+       (void)doupdate();
 }
 
 static void
-anim_refresh_update(void)
+anim_update(void)
 {
        int i;
 
        for (i = 0; i < NOBJECTS; i++) {
                aobject_t *o = &objects[i];
-               int x = o->x, y = o->y, d = random() % 4;
+               int x = o->x, y = o->y, d = nbrandom() % 16;
 
                if (o->t == AOT_STAR) {
                        if (x > 0)
@@ -227,7 +289,11 @@ anim_refresh_update(void)
                        else
                                x = TERM_COLUMNS - 1;
                } else {
-                       switch (d) {
+                       /* Change direction 1/4 of the time */
+                       if (d > -1 && d < 4)
+                               o->d = d;
+                       /* Move in current direction */
+                       switch (o->d) {
                        case 0: /* Up */
                                if (y > 0)
                                        y--;
@@ -246,29 +312,230 @@ anim_refresh_update(void)
                                break;
                        }
                }
-
-               /* Erase from old pos */
-               map[o->y][o->x] = ' ';
-               ti_goto(o->x, o->y);
-               ti_bgcolor(0);
-               (void)putchar(' ');
-
-               /* Move/redraw at new pos */
-               map[y][x] = o->c;
-               ti_goto(x, y);
-               ti_fgcolor(o->fcol);
-               ti_bgcolor(o->bcol);
-               (void)putchar(o->c);
                o->x = x;
                o->y = y;
        }
+}
 
-       /* Avatar */
-       ti_goto(avatar.x, avatar.y);
-       ti_put(&ti_sgr0);
-       ti_put(&ti_rev);
-       (void)putchar(avatar.c);
-       ti_put(&ti_sgr0);
+#define SHAPECOLSMAX   80
+#define SHAPELINESMAX  80
+
+static void
+rarray_empty(rarray_t *rarray)
+{
+       int i;
+
+       for (i = 0; i < rarray->count; i++) {
+               if ((void *)rarray->array[i] != NULL)
+                       free((void *)rarray->array[i]);
+       }
+       rarray_reset(rarray);
+}
+
+static void
+read_shapes(void)
+{
+       FILE *fh;
+       char line[SHAPECOLSMAX + 3], *cptr;
+       int nline = 0, nshapes = 0, len, xs, ys;
+
+       if ((fh = fopen(DATA_SHAPESPATH, "r")) == NULL)
+               err(EXIT_FAILURE, "Cannot open %s", DATA_SHAPESPATH);
+
+       /* About current shape */
+       rarray_t tlines, flines, blines, alines;
+
+       rarray_init(&tlines, SHAPELINESMAX);
+       rarray_init(&flines, SHAPELINESMAX);
+       rarray_init(&blines, SHAPELINESMAX);
+       rarray_init(&alines, SHAPELINESMAX);
+
+       for (;;) {
+               int x, y;
+               WINDOW *pad;
+
+               /* Attempt to read one shape worth's data */
+               for (;;) {
+                       if (fgets(line, SHAPECOLSMAX, fh) == NULL)
+                               break;
+                       nline++;
+
+                       /* Strip trailing whitespace characters */
+                       for (len = strlen(line), cptr = &line[len - 1];
+                            isspace((int)*cptr); cptr--, len--)
+                               *cptr = '\0';
+
+                       /* Comment, skip */
+                       if (*line == '#')
+                               continue;
+
+                       /* End of shape marker */
+                       if (*line == '\0')
+                               break;
+
+                       /* As a result of trimming fix empty-data lines */
+                       {
+                               char c = *line;
+
+                               if ((c == 'T' || c == 'F' || c == 'B' ||
+                                   c == 'A') && line[1] == '\0') {
+                                       line[1] = ' ';
+                                       line[2] = '\0';
+                               }
+                       }
+
+                       if (line[0] == 'T' && line[1] == ' ') {
+                               rarray_append(&tlines,
+                                   (intptr_t)strdup(&line[2]));
+                               continue;
+                       }
+                       if (line[0] == 'F' && line[1] == ' ') {
+                               rarray_append(&flines,
+                                   (intptr_t)strdup(&line[2]));
+                               continue;
+                       }
+                       if (line[0] == 'B' && line[1] == ' ') {
+                               rarray_append(&blines,
+                                   (intptr_t)strdup(&line[2]));
+                               continue;
+                       }
+                       if (line[0] == 'A' && line[1] == ' ') {
+                               rarray_append(&alines,
+                                   (intptr_t)strdup(&line[2]));
+                               continue;
+                       }
+
+                       err(EXIT_FAILURE,
+                           "Unexpected shape format line %d in %s",
+                           nline, DATA_SHAPESPATH);
+               }
+
+               /* Process lines and create new shape instance */
+
+               if (tlines.count < 1)
+                       err(EXIT_FAILURE, "Empty shape %d in %s line %d",
+                           nshapes, DATA_SHAPESPATH, nline);
+               if ((flines.count > 0 && flines.count != tlines.count) ||
+                   (blines.count > 0 && blines.count != tlines.count) ||
+                   (alines.count > 0 && alines.count != tlines.count))
+                       err(EXIT_FAILURE,
+                           "F B A present but not matching T lines count %d "
+                           "for shape %d at line %d in %s",
+                           tlines.count, nshapes, nline, DATA_SHAPESPATH);
+
+               /* Count shape dimensions */
+               for (xs = 0, y = 0; y < tlines.count; y++)
+                       if (xs < (len = strlen((const char *)tlines.array[y])))
+                               xs = len;
+               ys = tlines.count;
+
+               /* Allocate new pad */
+               if ((pad = newpad(ys, xs)) == NULL)
+                       err(EXIT_FAILURE, "newpad()");
+               (void)werase(pad);
+
+               /* Fill with characters marked with attributes as needed */
+               /* XXX Also avoid to apply colors or attributes depending on
+                * options like mono/nocolor */
+               for (y = 0; y < tlines.count; y++) {
+                       for (len = strlen((const char *)tlines.array[y]),
+                           x = 0; x < len; x++) {
+                               chtype ch;
+                               int fg = -1, bg = -1;
+                               char *cptr, c;
+
+                               cptr = (char *)tlines.array[y];
+                               ch = cptr[x];
+
+                               if (flines.count != 0) {
+                                       cptr = (char *)flines.array[y];
+                                       if (strlen(cptr) >= x) {
+                                               c = cptr[x];
+                                               if (c >= '0' && c <= '7')
+                                                       fg = c - '0';
+                                       }
+                               }
+                               if (blines.count != 0) {
+                                       cptr = (char *)blines.array[y];
+                                       if (strlen(cptr) >= x) {
+                                               c = cptr[x];
+                                               if (c >= '0' && c <= '7')
+                                                       bg = c - '0';
+                                       }
+                               }
+                               if (alines.count != 0) {
+                                       cptr = (char *)alines.array[y];
+                                       if (strlen(cptr) >= x) {
+                                               c = cptr[x];
+                                               switch (c) {
+                                               case 'E':
+                                                       ch |= A_BOLD;
+                                                       break;
+                                               case 'I':
+                                                       ch |= A_REVERSE;
+                                                       break;
+                                               case 'U':
+                                                       ch |= A_UNDERLINE;
+                                                       break;
+                                               case 'B':
+                                                       ch |= A_BLINK;
+                                                       break;
+                                               }
+                                       }
+                               }
+                               /* Associate color pair, allocate if needed */
+                               if (fg != -1 || bg != -1) {
+                                       short p = -1, i, f, b;
+
+                                       if (ncolpairs != -1) {
+                                               for (i = 0; i <= ncolpairs;
+                                                    i++) {
+                                                       (void)pair_content(i,
+                                                           &f, &b);
+                                                       if (fg == f &&
+                                                           bg == b) {
+                                                               p = i;
+                                                               break;
+                                                       };
+                                               }
+                                       }
+                                       if (p == -1) {
+                                               if (init_pair(++ncolpairs, fg,
+                                                   bg) == ERR)
+                                                       screen_error("init_pair()");
+                                               p = ncolpairs;
+                                       }
+                                       ch |= COLOR_PAIR(p);
+                               }
+
+                               /* Finally add character to pad */
+                               (void)mvwaddch(pad, y, x, ch);
+                       }
+               }
+               /* Finally add pad to shapes */
+               spads[nshapes++] = pad;
+
+               /* Reset for next shape */
+               rarray_empty(&tlines);
+               rarray_empty(&flines);
+               rarray_empty(&blines);
+               rarray_empty(&alines);
+
+               /* Enough shapes read, stop */
+               if (nshapes == SHAPE_MAX)
+                       break;
+       }
+
+       (void)fclose(fh);
+       rarray_destroy(&tlines);
+       rarray_destroy(&flines);
+       rarray_destroy(&blines);
+       rarray_destroy(&alines);
+
+       if (nshapes != SHAPE_MAX)
+               err(EXIT_FAILURE, "Shapes short count in %s "
+                   "(expected %d, read %d).", DATA_SHAPESPATH, SHAPE_MAX,
+                   nshapes);
 
-       (void)fflush(stdout);
+       /* Success */
 }
diff --git a/mmsoftware/ascender/src/nbrandom.c b/mmsoftware/ascender/src/nbrandom.c
new file mode 100644 (file)
index 0000000..6d3fe85
--- /dev/null
@@ -0,0 +1,501 @@
+/*     Adapted for embedding in Ascender game by Matthew Mondor, 2023 */
+/*     Adapted for embedding in Espadon game by Matthew Mondor, 2018 */
+/*     $NetBSD: random.c,v 1.4 2014/06/12 20:59:46 christos Exp $      */
+
+/*
+ * Copyright (c) 1983, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h> /* size_t */
+#include <stdlib.h>
+
+/* If reentrancy is needed, define these to lock/unlock a lock/mutex */
+#define NBRANDOM_LOCK()                (void)0
+#define NBRANDOM_UNLOCK()      (void)0
+
+static void                    srandom_unlocked(unsigned int);
+static long                    random_unlocked(void);
+
+/*
+ * random.c:
+ *
+ * An improved random number generation package.  In addition to the standard
+ * rand()/srand() like interface, this package also has a special state info
+ * interface.  The initstate() routine is called with a seed, an array of
+ * bytes, and a count of how many bytes are being passed in; this array is
+ * then initialized to contain information for random number generation with
+ * that much state information.  Good sizes for the amount of state
+ * information are 32, 64, 128, and 256 bytes.  The state can be switched by
+ * calling the setstate() routine with the same array as was initiallized
+ * with initstate().  By default, the package runs with 128 bytes of state
+ * information and generates far better random numbers than a linear
+ * congruential generator.  If the amount of state information is less than
+ * 32 bytes, a simple linear congruential R.N.G. is used.
+ *
+ * Internally, the state information is treated as an array of ints; the
+ * zeroeth element of the array is the type of R.N.G. being used (small
+ * integer); the remainder of the array is the state information for the
+ * R.N.G.  Thus, 32 bytes of state information will give 7 ints worth of
+ * state information, which will allow a degree seven polynomial.  (Note:
+ * the zeroeth word of state information also has some other information
+ * stored in it -- see setstate() for details).
+ * 
+ * The random number generation technique is a linear feedback shift register
+ * approach, employing trinomials (since there are fewer terms to sum up that
+ * way).  In this approach, the least significant bit of all the numbers in
+ * the state table will act as a linear feedback shift register, and will
+ * have period 2^deg - 1 (where deg is the degree of the polynomial being
+ * used, assuming that the polynomial is irreducible and primitive).  The
+ * higher order bits will have longer periods, since their values are also
+ * influenced by pseudo-random carries out of the lower bits.  The total
+ * period of the generator is approximately deg*(2**deg - 1); thus doubling
+ * the amount of state information has a vast influence on the period of the
+ * generator.  Note: the deg*(2**deg - 1) is an approximation only good for
+ * large deg, when the period of the shift register is the dominant factor.
+ * With deg equal to seven, the period is actually much longer than the
+ * 7*(2**7 - 1) predicted by this formula.
+ *
+ * Modified 28 December 1994 by Jacob S. Rosenberg.
+ * The following changes have been made:
+ * All references to the type u_int have been changed to unsigned long.
+ * All references to type int have been changed to type long.  Other
+ * cleanups have been made as well.  A warning for both initstate and
+ * setstate has been inserted to the effect that on Sparc platforms
+ * the 'arg_state' variable must be forced to begin on word boundaries.
+ * This can be easily done by casting a long integer array to char *.
+ * The overall logic has been left STRICTLY alone.  This software was
+ * tested on both a VAX and Sun SpacsStation with exactly the same
+ * results.  The new version and the original give IDENTICAL results.
+ * The new version is somewhat faster than the original.  As the
+ * documentation says:  "By default, the package runs with 128 bytes of
+ * state information and generates far better random numbers than a linear
+ * congruential generator.  If the amount of state information is less than
+ * 32 bytes, a simple linear congruential R.N.G. is used."  For a buffer of
+ * 128 bytes, this new version runs about 19 percent faster and for a 16
+ * byte buffer it is about 5 percent faster.
+ *
+ * Modified 07 January 2002 by Jason R. Thorpe.
+ * The following changes have been made:
+ * All the references to "long" have been changed back to "int".  This
+ * fixes memory corruption problems on LP64 platforms.
+ */
+
+/*
+ * For each of the currently supported random number generators, we have a
+ * break value on the amount of state information (you need at least this
+ * many bytes of state info to support this random number generator), a degree
+ * for the polynomial (actually a trinomial) that the R.N.G. is based on, and
+ * the separation between the two lower order coefficients of the trinomial.
+ */
+#define        TYPE_0          0               /* linear congruential */
+#define        BREAK_0         8
+#define        DEG_0           0
+#define        SEP_0           0
+
+#define        TYPE_1          1               /* x**7 + x**3 + 1 */
+#define        BREAK_1         32
+#define        DEG_1           7
+#define        SEP_1           3
+
+#define        TYPE_2          2               /* x**15 + x + 1 */
+#define        BREAK_2         64
+#define        DEG_2           15
+#define        SEP_2           1
+
+#define        TYPE_3          3               /* x**31 + x**3 + 1 */
+#define        BREAK_3         128
+#define        DEG_3           31
+#define        SEP_3           3
+
+#define        TYPE_4          4               /* x**63 + x + 1 */
+#define        BREAK_4         256
+#define        DEG_4           63
+#define        SEP_4           1
+
+/*
+ * Array versions of the above information to make code run faster --
+ * relies on fact that TYPE_i == i.
+ */
+#define        MAX_TYPES       5               /* max number of types above */
+
+static const int degrees[MAX_TYPES] =  { DEG_0, DEG_1, DEG_2, DEG_3, DEG_4 };
+static const int seps[MAX_TYPES] =     { SEP_0, SEP_1, SEP_2, SEP_3, SEP_4 };
+
+/*
+ * Initially, everything is set up as if from:
+ *
+ *     initstate(1, &randtbl, 128);
+ *
+ * Note that this initialization takes advantage of the fact that srandom()
+ * advances the front and rear pointers 10*rand_deg times, and hence the
+ * rear pointer which starts at 0 will also end up at zero; thus the zeroeth
+ * element of the state information, which contains info about the current
+ * position of the rear pointer is just
+ *
+ *     MAX_TYPES * (rptr - state) + TYPE_3 == TYPE_3.
+ */
+
+/* LINTED */
+static int randtbl[DEG_3 + 1] = {
+       TYPE_3,
+       0x991539b1, 0x16a5bce3, 0x6774a4cd,
+       0x3e01511e, 0x4e508aaa, 0x61048c05,
+       0xf5500617, 0x846b7115, 0x6a19892c,
+       0x896a97af, 0xdb48f936, 0x14898454,
+       0x37ffd106, 0xb58bff9c, 0x59e17104,
+       0xcf918a49, 0x09378c83, 0x52c7a471,
+       0x8d293ea9, 0x1f4fc301, 0xc3db71be,
+       0x39b44e1c, 0xf8a44ef9, 0x4c8b80b1,
+       0x19edc328, 0x87bf4bdd, 0xc9b240e5,
+       0xe9ee4b1b, 0x4382aee7, 0x535b6b41,
+       0xf3bec5da
+};
+
+/*
+ * fptr and rptr are two pointers into the state info, a front and a rear
+ * pointer.  These two pointers are always rand_sep places aparts, as they
+ * cycle cyclically through the state information.  (Yes, this does mean we
+ * could get away with just one pointer, but the code for random() is more
+ * efficient this way).  The pointers are left positioned as they would be
+ * from the call
+ *
+ *     initstate(1, randtbl, 128);
+ *
+ * (The position of the rear pointer, rptr, is really 0 (as explained above
+ * in the initialization of randtbl) because the state table pointer is set
+ * to point to randtbl[1] (as explained below).
+ */
+static int *fptr = &randtbl[SEP_3 + 1];
+static int *rptr = &randtbl[1];
+
+/*
+ * The following things are the pointer to the state information table, the
+ * type of the current generator, the degree of the current polynomial being
+ * used, and the separation between the two pointers.  Note that for efficiency
+ * of random(), we remember the first location of the state information, not
+ * the zeroeth.  Hence it is valid to access state[-1], which is used to
+ * store the type of the R.N.G.  Also, we remember the last location, since
+ * this is more efficient than indexing every time to find the address of
+ * the last element to see if the front and rear pointers have wrapped.
+ */
+static int *state = &randtbl[1];
+static int rand_type = TYPE_3;
+static int rand_deg = DEG_3;
+static int rand_sep = SEP_3;
+static int *end_ptr = &randtbl[DEG_3 + 1];
+
+/*
+ * srandom:
+ *
+ * Initialize the random number generator based on the given seed.  If the
+ * type is the trivial no-state-information type, just remember the seed.
+ * Otherwise, initializes state[] based on the given "seed" via a linear
+ * congruential generator.  Then, the pointers are set to known locations
+ * that are exactly rand_sep places apart.  Lastly, it cycles the state
+ * information a given number of times to get rid of any initial dependencies
+ * introduced by the L.C.R.N.G.  Note that the initialization of randtbl[]
+ * for default usage relies on values produced by this routine.
+ */
+static void
+srandom_unlocked(unsigned int x)
+{
+       int i;
+
+       if (rand_type == TYPE_0)
+               state[0] = x;
+       else {
+               state[0] = x;
+               for (i = 1; i < rand_deg; i++) {
+                       int x1, hi, lo, t;
+
+                       /*
+                        * Compute x[n + 1] = (7^5 * x[n]) mod (2^31 - 1).
+                        * From "Random number generators: good ones are hard
+                        * to find", Park and Miller, Communications of the ACM,
+                        * vol. 31, no. 10,
+                        * October 1988, p. 1195.
+                        */
+                       x1 = state[i - 1];
+                       hi = x1 / 127773;
+                       lo = x1 % 127773;
+                       t = 16807 * lo - 2836 * hi;
+                       if (t <= 0)
+                               t += 0x7fffffff;
+                       state[i] = t;
+               }
+               fptr = &state[rand_sep];
+               rptr = &state[0];
+               for (i = 0; i < 10 * rand_deg; i++)
+                       (void)random_unlocked();
+       }
+}
+
+void
+nbsrandom(unsigned int x)
+{
+
+       NBRANDOM_LOCK();
+       srandom_unlocked(x);
+       NBRANDOM_UNLOCK();
+}
+
+/*
+ * initstate:
+ *
+ * Initialize the state information in the given array of n bytes for future
+ * random number generation.  Based on the number of bytes we are given, and
+ * the break values for the different R.N.G.'s, we choose the best (largest)
+ * one we can and set things up for it.  srandom() is then called to
+ * initialize the state information.
+ * 
+ * Note that on return from srandom(), we set state[-1] to be the type
+ * multiplexed with the current value of the rear pointer; this is so
+ * successive calls to initstate() won't lose this information and will be
+ * able to restart with setstate().
+ * 
+ * Note: the first thing we do is save the current state, if any, just like
+ * setstate() so that it doesn't matter when initstate is called.
+ *
+ * Returns a pointer to the old state.
+ *
+ * Note: The Sparc platform requires that arg_state begin on an int
+ * word boundary; otherwise a bus error will occur. Even so, lint will
+ * complain about mis-alignment, but you should disregard these messages.
+ */
+char *
+nbinitstate(
+       unsigned int seed,              /* seed for R.N.G. */
+       char *arg_state,                /* pointer to state array */
+       size_t n)                       /* # bytes of state info */
+{
+       void *ostate = (void *)(&state[-1]);
+       int *int_arg_state;
+
+       int_arg_state = (int *)(void *)arg_state;
+
+       NBRANDOM_LOCK();
+       if (rand_type == TYPE_0)
+               state[-1] = rand_type;
+       else
+               state[-1] = MAX_TYPES * (int)(rptr - state) + rand_type;
+       if (n < BREAK_0) {
+               NBRANDOM_UNLOCK();
+               return NULL;
+       } else if (n < BREAK_1) {
+               rand_type = TYPE_0;
+               rand_deg = DEG_0;
+               rand_sep = SEP_0;
+       } else if (n < BREAK_2) {
+               rand_type = TYPE_1;
+               rand_deg = DEG_1;
+               rand_sep = SEP_1;
+       } else if (n < BREAK_3) {
+               rand_type = TYPE_2;
+               rand_deg = DEG_2;
+               rand_sep = SEP_2;
+       } else if (n < BREAK_4) {
+               rand_type = TYPE_3;
+               rand_deg = DEG_3;
+               rand_sep = SEP_3;
+       } else {
+               rand_type = TYPE_4;
+               rand_deg = DEG_4;
+               rand_sep = SEP_4;
+       }
+       state = (int *) (int_arg_state + 1); /* first location */
+       end_ptr = &state[rand_deg];     /* must set end_ptr before srandom */
+       srandom_unlocked(seed);
+       if (rand_type == TYPE_0)
+               int_arg_state[0] = rand_type;
+       else
+               int_arg_state[0] = MAX_TYPES * (int)(rptr - state) + rand_type;
+       NBRANDOM_UNLOCK();
+       return (char *)ostate;
+}
+
+/*
+ * setstate:
+ *
+ * Restore the state from the given state array.
+ *
+ * Note: it is important that we also remember the locations of the pointers
+ * in the current state information, and restore the locations of the pointers
+ * from the old state information.  This is done by multiplexing the pointer
+ * location into the zeroeth word of the state information.
+ *
+ * Note that due to the order in which things are done, it is OK to call
+ * setstate() with the same state as the current state.
+ *
+ * Returns a pointer to the old state information.
+ *
+ * Note: The Sparc platform requires that arg_state begin on a long
+ * word boundary; otherwise a bus error will occur. Even so, lint will
+ * complain about mis-alignment, but you should disregard these messages.
+ */
+char *
+nbsetstate(char *arg_state)            /* pointer to state array */
+{
+       int *new_state;
+       int type;
+       int rear;
+       void *ostate = (void *)(&state[-1]);
+
+       new_state = (int *)(void *)arg_state;
+       type = (int)(new_state[0] % MAX_TYPES);
+       rear = (int)(new_state[0] / MAX_TYPES);
+
+       NBRANDOM_LOCK();
+       if (rand_type == TYPE_0)
+               state[-1] = rand_type;
+       else
+               state[-1] = MAX_TYPES * (int)(rptr - state) + rand_type;
+       switch(type) {
+       case TYPE_0:
+       case TYPE_1:
+       case TYPE_2:
+       case TYPE_3:
+       case TYPE_4:
+               rand_type = type;
+               rand_deg = degrees[type];
+               rand_sep = seps[type];
+               break;
+       default:
+               NBRANDOM_UNLOCK();
+               return NULL;
+       }
+       state = (int *) (new_state + 1);
+       if (rand_type != TYPE_0) {
+               rptr = &state[rear];
+               fptr = &state[(rear + rand_sep) % rand_deg];
+       }
+       end_ptr = &state[rand_deg];             /* set end_ptr too */
+       NBRANDOM_UNLOCK();
+       return (char *)ostate;
+}
+
+/*
+ * random:
+ *
+ * If we are using the trivial TYPE_0 R.N.G., just do the old linear
+ * congruential bit.  Otherwise, we do our fancy trinomial stuff, which is
+ * the same in all the other cases due to all the global variables that have
+ * been set up.  The basic operation is to add the number at the rear pointer
+ * into the one at the front pointer.  Then both pointers are advanced to
+ * the next location cyclically in the table.  The value returned is the sum
+ * generated, reduced to 31 bits by throwing away the "least random" low bit.
+ *
+ * Note: the code takes advantage of the fact that both the front and
+ * rear pointers can't wrap on the same call by not testing the rear
+ * pointer if the front one has wrapped.
+ *
+ * Returns a 31-bit random number.
+ */
+static long
+random_unlocked(void)
+{
+       int i;
+       int *f, *r;
+
+       if (rand_type == TYPE_0) {
+               i = state[0];
+               state[0] = i = (i * 1103515245 + 12345) & 0x7fffffff;
+       } else {
+               /*
+                * Use local variables rather than static variables for speed.
+                */
+               f = fptr; r = rptr;
+               *f += *r;
+               /* chucking least random bit */
+               i = ((unsigned int)*f >> 1) & 0x7fffffff;
+               if (++f >= end_ptr) {
+                       f = state;
+                       ++r;
+               }
+               else if (++r >= end_ptr) {
+                       r = state;
+               }
+
+               fptr = f; rptr = r;
+       }
+       return i;
+}
+
+long
+nbrandom(void)
+{
+       long r;
+
+       NBRANDOM_LOCK();
+       r = random_unlocked();
+       NBRANDOM_UNLOCK();
+       return r;
+}
+
+/* These are only so we avoid them */
+long
+random(void)
+{
+
+       abort();
+       /* NOTREACHED */
+       return -1;
+}
+
+/* ARGSUSED */
+void
+srandom(unsigned int seed)
+{
+
+       abort();
+       /* NOTREACHED */
+}
+
+/* ARGSUSED */
+char *
+initstate(unsigned int i, char *c, size_t s)
+{
+
+       abort();
+       /* NOTREACHED */
+       return NULL;
+}
+
+/* ARGSUSED */
+char *
+#ifdef __MACH__
+setstate(const char *c)
+#else
+setstate(char *c)
+#endif
+{
+
+       abort();
+       /* NOTREACHED */
+       return NULL;
+}
+
diff --git a/mmsoftware/ascender/src/nbrandom.h b/mmsoftware/ascender/src/nbrandom.h
new file mode 100644 (file)
index 0000000..7a331a6
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef __NBRANDOM_H__
+#define __NBRANDOM_H__
+
+#include <sys/types.h>
+
+extern long    nbrandom(void);
+extern void    nbsrandom(unsigned int);
+extern char    *nbinitstate(unsigned int, char *, size_t);
+extern char    *nbsetstate(char *);
+
+extern long    random(void);
+extern void    srandom(unsigned int);
+extern char    *initstate(unsigned int, char *, size_t);
+#ifdef __MACH__
+extern char    *setstate(const char *);
+#else
+extern char    *setstate(char *);
+#endif
+
+#endif
index cc794a5..4d395b7 100644 (file)
@@ -1,28 +1,28 @@
 /*
  * Copyright (c) 2023, Matthew Mondor
- * ALL RIGHTS RESERVED.
+ * All rights reserved.
  *
- * Uses curses(3) for initialization and to decode user input sequences but
- * relies on terminfo(3) for the animation to avoid unnecessary overhead.
- * Puts the terminal in raw and noecho mode then uses blocking reads that
- * regularly get interrupted, returning ERR when animation clock tick
- * signals occur.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
  *
- * Note that on NetBSD and Linux curses endwin(3) was enough to properly
- * restore the terminal state on exit, but not on OSX.  savetty(3)/resetty(3)
- * using attempts did not solve the problem, so despite the redundancy
- * resorting to explicit termios use was the solution.
- *
- * tiparm(3) also seemed to be missing in OSX curses(3), but it has the
- * equivalent older tparm(3).  For that to work on NetBSD without needing to
- * supply all parameters per X/Open, TPARM_VARARGS was defined to provide OSX
- * and Linux ncurses default behavior using varars.
- *
- * On NetBSD, both -lcurses and -lterminfo are required.  On Linux and OSX,
- * -lcurses suffices, terminfo being part of ncurses.
+ * THIS SOFTWARE IS PROVIDED BY MATTHEW MONDOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL MATTHEW MONDOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-
 #include <curses.h>
 #include <errno.h>
 #include <err.h>
@@ -31,8 +31,6 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
-#include <term.h>
-#include <termios.h>
 #include <unistd.h>
 
 #include <screen.h>
 #define ERR_BUFSIZE    1024
 
 
-static void            ti_seq(ti_seq_t *, const char *);
 static void            screen_cleanup(void);
+static inline int      int_min(int, int);
 
 
 /* Error to display after screen cleanup if any */
 static char            error_buffer[ERR_BUFSIZE];
 static int             error_errno = 0;
 
-/* To explicitly save/restore tty(4) state despite curses(3) */
-static struct termios  old_tios;
 static bool            curses_initialized = false;
-static bool            terminfo_initialized = false;
 static WINDOW          *screen_window = NULL;
 
-/* Terminfo/termcap preloaded sequences */
-ti_seq_t               ti_clear, ti_sgr0, ti_cup, ti_rev, ti_civis, ti_cnorm,
-                       ti_setaf, ti_setab;
 
 void
 screen_init(void)
 {
-       int i;
 
        /* No error yet */
        *error_buffer = '\0';
@@ -70,9 +61,6 @@ screen_init(void)
 
        /* Setup curses */
 
-       /* Remember tty(4) state we'll restore explicitly */
-       if (tcgetattr(STDIN_FILENO, &old_tios) == -1)
-               err(EXIT_FAILURE, "tcgetattr()");
        /* Exit cleanup hook to restore normal terminal */
        (void)atexit(screen_cleanup);
 
@@ -81,52 +69,51 @@ screen_init(void)
        curses_initialized = true;
 
        /* Use screen_error() from now on for errors */
-       if (COLS < TERM_COLUMNS || LINES < TERM_LINES)
-               screen_error("The terminal must support at least\r\n"
-                   "%d columns and %d lines for this program to work.",
+       if (COLS < TERM_COLUMNS || LINES < TERM_LINES) {
+               errno = 0;
+               screen_error("For this program to work the terminal must "
+                   "support at least:\r\n"
+                   "%d columns and %d lines.",
                    TERM_COLUMNS, TERM_LINES);
+       }
 
        /* XXX Maybe add error checking here */
-       (void)raw();
+       (void)cbreak();
        (void)noecho();
        (void)keypad(screen_window, TRUE);
-       (void)timeout(-1);
-       (void)clear();
-       (void)refresh();
-
-       /* Query terminfo for the sequences we need. */
-       if (setupterm(NULL, STDIN_FILENO, &i) == -1 || i != 1)
-               screen_error("setupterm()");
-       ti_seq(&ti_clear, "clear");     /* Clear screen */
-       ti_seq(&ti_sgr0, "sgr0");       /* Reset text attributes */
-       ti_seq(&ti_cup, "cup");         /* Position cursor */
-       ti_seq(&ti_rev, "rev");         /* Enable reverse text attribute */
-       ti_seq(&ti_civis, "civis");     /* Invisible cursor */
-       ti_seq(&ti_cnorm, "cnorm");     /* Normal cursor */
-       ti_seq(&ti_setaf, "setaf");     /* Set foreground color attribute */
-       ti_seq(&ti_setab, "setab");     /* Set background color attribute */
-
-       /* Initialize screen, hide cursor */
-       ti_put(&ti_clear);
-       ti_put(&ti_civis);
-       (void)fflush(stdout);
-       terminfo_initialized = true;
+       (void)nonl();
+       (void)intrflush(stdscr, FALSE);
+       (void)timeout(0);
+
+       /*
+        * Enable hardware features if available.
+        * Apparently sometimes beneficial other times not.
+        * XXX Could be a command line option/configuration.
+        */
+       /*
+       if (has_ic())
+               idcok(screen_window, TRUE);
+       if (has_il())
+               idlok(screen_window, TRUE);
+       */
+
+       /* Disable cursor */
+       (void)curs_set(0);
+       (void)leaveok(screen_window, TRUE);
+
+       (void)erase();
+       (void)doupdate();
 }
 
 static void
 screen_cleanup(void)
 {
 
-       if (terminfo_initialized) {
-               ti_put(&ti_cnorm);
-               ti_put(&ti_sgr0);
-       }
-
-       if (curses_initialized)
+       if (curses_initialized) {
+               (void)curs_set(1);
                (void)endwin();
-
-       /* Restore original tty(4) state we saved at init */
-       (void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_tios);
+               curses_initialized = false;
+       }
 
        /* Display stored fatal error if needed */
        if (*error_buffer != '\0') {
@@ -148,6 +135,25 @@ screen_getchar(void)
        return wgetch(screen_window);
 }
 
+int
+screen_getchar_block(void)
+{
+       int c;
+
+       if (!curses_initialized)
+               return ERR;
+
+       (void)curs_set(1);
+       (void)timeout(-1);
+
+       c = wgetch(screen_window);
+
+       (void)timeout(0);
+       (void)curs_set(0);
+
+       return c;
+}
+
 /* Queue error to be displayed after screen cleanup then exit */
 void
 screen_error(const char *fmt, ...)
@@ -163,58 +169,25 @@ screen_error(const char *fmt, ...)
        exit(EXIT_FAILURE);
 }
 
-/* Query terminfo and prepare sequence */
-static void
-ti_seq(ti_seq_t *seq, const char *name)
-{
-       char *s;
-
-       s = tigetstr(name);
-
-       /* -1 means unrecognized name, 0 means no definition */
-       if (s == (char *)-1 || s == (char *)0)
-               screen_error(
-                   "tigetstr(): failed to query terminfo for required "
-                   "capability '%s'", name);
-
-       seq->seq = s;
-       seq->len = strlen(s);
-}
-
-/* Use prepared terminfo sequence */
-void
-ti_put(ti_seq_t *seq)
-{
-
-       (void)fwrite(seq->seq, seq->len, 1, stdout);
-}
-
-
-/* Misc. terminfo/termcap utilities */
-
-void
-ti_goto(int x, int y)
+static inline int
+int_min(int a, int b)
 {
-       const char *s;
 
-       s = tparm(ti_cup.seq, y, x);
-       (void)fwrite(s, strlen(s), 1, stdout);
+       return (a < b ? a : b);
 }
 
+/* 2d text shape blitting routine */
 void
-ti_fgcolor(int c)
+screen_blitpad(WINDOW *pad, int y, int x)
 {
-       const char *s;
-
-       s = tparm(ti_setaf.seq, c);
-       (void)fwrite(s, strlen(s), 1, stdout);
-}
+       int xs, ys, xo, yo;
 
-void
-ti_bgcolor(int c)
-{
-       const char *s;
+       getmaxyx(pad, ys, xs);
+       xo = xs / 2;
+       yo = ys / 2;
 
-       s = tparm(ti_setab.seq, c);
-       (void)fwrite(s, strlen(s), 1, stdout);
+       (void)copywin(pad, screen_window, 0, 0, y - yo, x - xo,
+           int_min((y - yo) + ys - 1, TERM_LINES - 1),
+           int_min((x - xo) + xs - 1, TERM_COLUMNS - 1),
+           TRUE);
 }
index 5b183c4..bde8e2f 100644 (file)
@@ -1,35 +1,45 @@
 /*
  * Copyright (c) 2023, Matthew Mondor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY MATTHEW MONDOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL MATTHEW MONDOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Copyright (c) 2023, Matthew Mondor
  * ALL RIGHTS RESERVED.
  */
-
  
 #ifndef __SCREEN_H__
 #define __SCREEN_H__
 
 
-#include <config.h>
-
-
-/* Prepared terminfo sequence */
-typedef struct ti_seq {
-       const char      *seq;
-       size_t          len;
-} ti_seq_t;
-
+#include <curses.h>
 
-extern void    screen_init(void);
-extern int     screen_getchar(void);
-extern void    screen_error(const char *, ...);
-
-extern void    ti_put(ti_seq_t *);
-extern void    ti_goto(int, int);
-extern void    ti_fgcolor(int);
-extern void    ti_bgcolor(int);
+#include <config.h>
 
 
-extern ti_seq_t        ti_clear, ti_sgr0, ti_cup, ti_rev, ti_civis, ti_cnorm,
-       ti_setaf, ti_setab;
+void   screen_init(void);
+int    screen_getchar(void);
+void   screen_error(const char *, ...);
+void   screen_blitpad(WINDOW *, int, int);
 
 
 #endif
diff --git a/mmsoftware/ascender/src/shapes.txt b/mmsoftware/ascender/src/shapes.txt
new file mode 100644 (file)
index 0000000..874de03
--- /dev/null
@@ -0,0 +1,67 @@
+# Shapes intended to be easily modifiable.
+# Format:
+# - Lines starting with # are comments.
+# - Blank lines delimit shapes.
+# - All shapes must have T lines, defining their characters.
+# - Optional are F and B lines, for foreground and background colors,
+#   respectively (0-7, space for default).
+# - Also optional are A lines, for attributes:
+#   - E bold
+#   - I inverse
+#   - U underline
+#   - B blink
+#   - Space for normal text
+# - A space is required after T F B A lines before the data.
+# - F B A lines can only occur for already defined shapes with T and must hold
+#   the same amount of lines than for T.
+T  /\
+T |__|
+T |  |
+F
+F  11
+F
+
+T  __ 
+T |__>
+T |__>
+F
+F  22
+F
+
+# https://www.asciiart.eu/vehicles/boats
+T                  |~
+T            |/    w
+T           / (   (|   \
+T          /( (/   |)  |\
+T   ____  ( (/    (|   | )  ,
+T  |----\ (/ |    /|   |'\ /^;
+T \---*---Y--+-----+---+--/(
+T  \------*---*--*---*--/
+T   '~~ ~~~~~~~~~~~~~~~
+F                  24
+F            2     3
+F                  2    
+F                  2   2 
+F                  2   2    2
+F   1111     2     2   2   2  
+F  333333333333333333333332 
+F   33333333333333333333 
+F
+B                   6
+B                   
+B                       
+B                        
+B                            
+B                             
+B                           
+B   000000 000 00 000 00 
+B                      
+A                   B
+A                   
+A                       
+A                        
+A                            
+A                             
+A     E                     
+A         E   I  E   E   
+A                      
diff --git a/mmsoftware/mmlib/mmrarray.c b/mmsoftware/mmlib/mmrarray.c
new file mode 100644 (file)
index 0000000..bfccc06
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2015, 2023, Matthew Mondor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY MATTHEW MONDOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL MATTHEW MONDOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Simple implementation of dynamically grown reusable arrays with fast
+ * population and reset.  Using intptr_t it is generic and can be used for
+ * numbers or already allocated objects by supplying their pointer.
+ * Any memory allocation error is fatal in the current implementation.
+ * It was used in games, where with typical usage for per-frame processing
+ * allocations were rare.  The fast reset implies that it's used for frequent
+ * temporary collection, use and flushing, where it suits better the needed
+ * algorithm than other data structures like FIFO/LIFO queues or linked
+ * lists.  Limits are typically known in advance and growing is usually not
+ * needed, but is there for convenience.
+ */
+
+#include <stdlib.h>
+
+#include <mmrarray.h>
+
+
+static void    rarray_grow(rarray_t *);
+
+
+void
+rarray_init(rarray_t *array, int isize)
+{
+
+       if ((array->array = malloc(sizeof(intptr_t) * isize)) == NULL)
+               abort(); /* XXX Fatal */
+       array->size = isize;
+       array->count = 0;
+}
+
+/* Very efficiently empty array */
+inline void
+rarray_reset(rarray_t *array)
+{
+
+       array->count = 0;
+}
+
+/* Grow buffer * 2 */
+static void
+rarray_grow(rarray_t *array)
+{
+       intptr_t *narray;
+
+       array->size *= 2;
+       if ((narray = realloc(array->array, array->size * sizeof(intptr_t)))
+           == NULL)
+               abort(); /* XXX Fatal */
+       array->array = narray;
+}
+
+/* Append new object to array, growing it internally if required */
+inline void
+rarray_append(rarray_t *array, intptr_t obj)
+{
+
+       /* Grow if full */
+       if (array->count == array->size)
+               rarray_grow(array);
+
+       /* Insert */
+       array->array[array->count++] = obj;
+}
+
+/* Free the internal array, disregarding contents. */
+void
+rarray_destroy(rarray_t *array)
+{
+
+       if (array->array != NULL) {
+               free(array->array);
+               array->array = NULL;
+       }
+       array->count = array->size = 0;
+}
diff --git a/mmsoftware/mmlib/mmrarray.h b/mmsoftware/mmlib/mmrarray.h
new file mode 100644 (file)
index 0000000..3ae3e13
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2015, 2023, Matthew Mondor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY MATTHEW MONDOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL MATTHEW MONDOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MMRARRAY_H__
+#define __MMRARRAY_H__
+
+
+#include <stdint.h>
+#include <stdlib.h>
+
+
+typedef struct rarray {
+       intptr_t        *array;
+       int             size, count;
+} rarray_t;
+
+
+void   rarray_init(rarray_t *, int);
+void   rarray_reset(rarray_t *);
+void   rarray_append(rarray_t *, intptr_t);
+void   rarray_destroy(rarray_t *);
+
+
+#endif