tests/interactive-ascii: More tests
authorMatthew Mondor <mmondor@pulsar-zone.net>
Wed, 5 Apr 2023 12:16:52 +0000 (12:16 +0000)
committerMatthew Mondor <mmondor@pulsar-zone.net>
Wed, 5 Apr 2023 12:16:52 +0000 (12:16 +0000)
tests/interactive-ascii/test1.c
tests/interactive-ascii/test2.c
tests/interactive-ascii/test3.c
tests/interactive-ascii/test4.c
tests/interactive-ascii/test5.c [new file with mode: 0644]
tests/interactive-ascii/test6.c [new file with mode: 0644]

index 989a1c2..5ba7938 100644 (file)
@@ -17,6 +17,9 @@
 #include <unistd.h>
 
 
+#define ANIM_FPS       15
+
+
 int main(void);
 
 
@@ -33,7 +36,7 @@ main(void)
        (void)timeout(0);
 
        for (i = 0; i < 20; i++) {
-               (void)usleep(66666);
+               (void)usleep(1000000 / ANIM_FPS);
                c = wgetch(w); /* ERR == -1 */
                (void)printf("%d\r\n", c);
                (void)fflush(stdout);
index 2cca31e..280e594 100644 (file)
@@ -25,6 +25,9 @@
 #include <sys/time.h>
 
 
+#define ANIM_FPS       15
+
+
 int                    main(void);
 static void            cleanup(void);
 static void            sighandler(int);
@@ -54,7 +57,7 @@ main(void)
        timerclear(&itv.it_interval);
        timerclear(&itv.it_value);
        itv.it_interval.tv_sec = 0;
-       itv.it_interval.tv_usec = 66666;
+       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) == -1)
index 8b298a4..4950578 100644 (file)
@@ -37,6 +37,7 @@
 
 #define TERM_COLUMNS   79
 #define TERM_LINES     24
+#define ANIM_FPS       15
 #define NOBJECTS       46
 
 enum aobject_type {
@@ -110,7 +111,7 @@ main(void)
        timerclear(&itv.it_interval);
        timerclear(&itv.it_value);
        itv.it_interval.tv_sec = 0;
-       itv.it_interval.tv_usec = 66666;
+       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) == -1)
index 78dcfca..8e0d610 100644 (file)
@@ -44,6 +44,7 @@
 
 #define TERM_COLUMNS   79
 #define TERM_LINES     24
+#define ANIM_FPS       15
 #define NOBJECTS       46
 
 
@@ -110,7 +111,7 @@ main(void)
        timerclear(&itv.it_interval);
        timerclear(&itv.it_value);
        itv.it_interval.tv_sec = 0;
-       itv.it_interval.tv_usec = 66666;
+       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) == -1)
diff --git a/tests/interactive-ascii/test5.c b/tests/interactive-ascii/test5.c
new file mode 100644 (file)
index 0000000..34b4c5c
--- /dev/null
@@ -0,0 +1,364 @@
+/*
+ * Copyright (c) 2023, Matthew Mondor
+ * ALL RIGHTS RESERVED.
+ *
+ * Interactive curses user input test 5
+ * Unlike other tests that used both curses for chget(3) and terminfo(3/5),
+ * this one uses curses abstractions only.  This permits to compare the
+ * performance and output.  So far this does an initial long screen
+ * initialization and initial full stage drawing, but then updates only what
+ * is necessary for each frame after, top down.  This means that for very
+ * little movement terminfo could be superior, but that curses is generally
+ * good enough for updates after the (long if at low bitrate) initialization.
+ *
+ * $ cc -Wall -O0 -g -o test5 test5.c -lcurses
+ */
+
+#include <curses.h>
+#include <err.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/time.h>
+
+
+#define TERM_COLUMNS   79
+#define TERM_LINES     24
+#define ANIM_FPS       15
+#define NOBJECTS       46
+
+
+/* Animation object */
+
+enum aobject_type {
+       AOT_AVATAR = 0,
+       AOT_STAR,
+       AOT_BUG
+};
+
+typedef struct aobject {
+       int             t, x, y, col;
+       char            c;
+} aobject_t;
+
+
+int                    main(void);
+static void            cleanup(void);
+static void            sighandler(int);
+static void            anim_init(void);
+static void            anim_refresh_full(void);
+static void            anim_refresh_update(void);
+
+
+/* To explicitly save/restore tty(4) state despite curses(3) */
+static struct termios  old_tios;
+
+/* Clock */
+static bool            refresh_expired = false;
+
+/* 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,
+                           0, '?' };
+
+
+int
+main(void)
+{
+       struct sigaction act;
+       struct itimerval itv;
+       WINDOW *w;
+       int c, uc;
+
+       /* Setup signal handler */
+       act.sa_handler = sighandler;
+       act.sa_flags = 0; /* ~SA_RESTART */
+       (void)sigemptyset(&act.sa_mask);
+       (void)sigaction(SIGALRM, &act, NULL);
+       (void)sigaction(SIGINT, &act, NULL);
+       (void)sigaction(SIGTERM, &act, NULL);
+       (void)sigaction(SIGHUP, &act, NULL);
+
+       /* 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) == -1)
+               goto err;
+
+       /* Setup curses */
+       (void)tcgetattr(STDIN_FILENO, &old_tios);
+       if ((w = initscr()) == NULL)
+               err(EXIT_FAILURE, "initscr()");
+       if (COLS < TERM_COLUMNS || LINES < TERM_LINES) {
+               cleanup();
+               (void)fprintf(stderr,
+                   "The terminal must support at least %d columns by %d"
+                   " lines for this program to work.\n",
+                   TERM_COLUMNS, TERM_LINES);
+               exit(EXIT_FAILURE);
+       }
+
+       /* Exit cleanup hook to restore normal terminal */
+       (void)atexit(cleanup);
+
+       if (start_color() == ERR)
+               err(EXIT_FAILURE, "start_color()");
+       (void)init_pair(0, COLOR_WHITE, COLOR_BLACK);
+       (void)init_pair(1, COLOR_WHITE, COLOR_RED);
+       (void)init_pair(2, COLOR_WHITE, COLOR_GREEN);
+       (void)init_pair(3, COLOR_WHITE, COLOR_YELLOW);
+       (void)init_pair(4, COLOR_WHITE, COLOR_BLUE);
+       (void)init_pair(5, COLOR_WHITE, COLOR_MAGENTA);
+       (void)init_pair(6, COLOR_WHITE, COLOR_CYAN);
+       (void)init_pair(7, COLOR_WHITE, COLOR_WHITE);
+
+       (void)raw();
+       (void)noecho();
+       (void)keypad(w, TRUE);
+       (void)timeout(-1);
+
+       /* Setup animation world */
+       anim_init();
+
+       /* Initialize screen */
+       (void)clear();
+       (void)curs_set(0);
+       (void)refresh();
+
+       /* Animate until user requests to quit with q */
+       anim_refresh_full();
+       refresh_expired = false;
+       uc = -1;
+       for (;;) {
+cont:
+               if ((c = wgetch(w)) != ERR)
+                       uc = c;
+               if (refresh_expired) {
+                       refresh_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;
+
+                       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;
+                       }
+                       /* Update avatar if necessary */
+                       if (x != -1 && y != -1) {
+                               /* XXX */
+                               avatar.x = x;
+                               avatar.y = y;
+                       }
+
+                       /* Animate */
+                       anim_refresh_update();
+                       uc = -1;
+               }
+       }
+end:
+
+       exit(EXIT_SUCCESS);
+
+err:
+       err(EXIT_FAILURE, "main()");
+}
+
+static void
+cleanup(void)
+{
+
+       (void)curs_set(1);
+       (void)endwin();
+       (void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_tios);
+}
+
+static void
+sighandler(int sig)
+{
+
+       switch (sig) {
+       case SIGALRM:
+               refresh_expired = true;
+               break;
+       case SIGINT:
+       case SIGTERM: /* FALLTHROUGH */
+       case SIGHUP: /* FALLTHROUGH */
+               exit(EXIT_SUCCESS);
+               break;
+       }
+}
+
+static void
+anim_init(void)
+{
+       int x, y, i, col;
+
+       for (y = 0; y < TERM_LINES; y++) {
+               for (x = 0; x < TERM_COLUMNS; x++) {
+                       map[y][x] = ' ';
+               }
+       }
+
+       srandom(13);
+       col = 0;
+       for (i = 0; i < NOBJECTS; i++) {
+               aobject_t *o = &objects[i];
+
+               if (i <= 26) {
+                       o->t = AOT_BUG;
+                       o->c = 'A' + i;
+                       o->col = ++col % 7;
+               } else {
+                       o->t = AOT_STAR;
+                       o->c = '.';
+                       o->col = 0;
+               }
+               o->x = random() % (TERM_COLUMNS - 1);
+               o->y = random() % (TERM_LINES - 1);
+       }
+
+       /* XXX Avatar */
+}
+
+static void
+anim_refresh_full(void)
+{
+       int x, y, i;
+
+       (void)clear();
+       (void)attrset(COLOR_PAIR(0));
+       for (y = 0; y < TERM_LINES; y++) {
+               for (x = 0; x < TERM_COLUMNS; x++) {
+                       (void)addch(map[y][x]);
+               }
+               (void)addstr("\r\n");
+       }
+       for (i = 0; i < NOBJECTS; i++) {
+               aobject_t *o = &objects[i];
+
+               (void)attrset(COLOR_PAIR(o->col));
+               (void)mvaddch(o->y, o->x, o->c);
+       }
+
+       /* Avatar */
+       (void)attr_on(A_REVERSE, NULL);
+       (void)mvaddch(avatar.y, avatar.x, avatar.c);
+       (void)attr_off(A_REVERSE, NULL);
+
+       (void)refresh();
+}
+
+static void
+anim_refresh_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;
+
+               if (o->t == AOT_STAR) {
+                       if (x > 0)
+                               x--;
+                       else
+                               x = TERM_COLUMNS - 1;
+               } else {
+                       switch (d) {
+                       case 0: /* Up */
+                               if (y > 0)
+                                       y--;
+                               break;
+                       case 1: /* Down */
+                               if (y < TERM_LINES - 1)
+                                       y++;
+                               break;
+                       case 2: /* Left */
+                               if (x > 0)
+                                       x--;
+                               break;
+                       case 3: /* Right */
+                               if (x < TERM_COLUMNS - 1)
+                                       x++;
+                               break;
+                       }
+               }
+
+               /* Erase from old pos */
+               map[o->y][o->x] = ' ';
+               (void)attrset(COLOR_PAIR(0));
+               (void)mvaddch(o->y, o->x, ' ');
+
+               /* Move/redraw at new pos */
+               map[y][x] = o->c;
+               (void)attrset(COLOR_PAIR(o->col));
+               (void)mvaddch(y, x, o->c);
+               o->x = x;
+               o->y = y;
+       }
+
+       /* Avatar */
+       (void)attr_on(A_REVERSE, NULL);
+       (void)mvaddch(avatar.y, avatar.x, avatar.c);
+       (void)attr_off(A_REVERSE, NULL);
+
+       (void)refresh();
+}
diff --git a/tests/interactive-ascii/test6.c b/tests/interactive-ascii/test6.c
new file mode 100644 (file)
index 0000000..961b357
--- /dev/null
@@ -0,0 +1,354 @@
+/*
+ * Copyright (c) 2023, Matthew Mondor
+ * ALL RIGHTS RESERVED.
+ *
+ * Interactive curses user input test 6, derived from test 5
+ * Unlike other tests that used both curses for chget(3) and terminfo(3/5),
+ * this one uses curses abstractions only.  This permits to compare the
+ * performance and output.  So far this does an initial long screen
+ * initialization and initial full stage drawing, but then updates only what
+ * is necessary for each frame after, top down.  This means that for very
+ * little movement terminfo could be superior, but that curses is generally
+ * good enough for updates after the (long if at low bitrate) initialization.
+ * Unlike test5 that still relied on its own memory map and dynamic partial
+ * updates for performance that was useful with terminfo, with curses top-down
+ * refresh and own video memory simulation this was now useless.  This
+ * simplified version redraws the scene completely for each frame, leaving
+ * curses do the top-down partial refresh optimizations.  The result is the
+ * same as test5 in terms of output performance.
+ *
+ * XXX TODO XXX
+ * - Consider suspending/masking the timer during screen refresh, or
+ *   switching it to restart mode...
+ * - Consider another test keeping the time to determine if to delay again,
+ *   instead of relying on signal interruption by an interval timer.
+ * - See if calling termios directly can be avoided with OSX still restoring
+ *   the tty settings properly.  Also look at
+ *   reset_shell_mode()/reset_prog_mode(), def_shell_mode()/def_prog_mode().
+ * 
+ * $ cc -Wall -O0 -g -o test6 test6.c -lcurses
+ */
+
+#include <curses.h>
+#include <err.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/time.h>
+
+
+#define TERM_COLUMNS   79
+#define TERM_LINES     24
+#define ANIM_FPS       15
+#define NOBJECTS       46
+
+
+/* Animation object */
+
+enum aobject_type {
+       AOT_AVATAR = 0,
+       AOT_STAR,
+       AOT_BUG
+};
+
+typedef struct aobject {
+       int             t, x, y, col;
+       char            c;
+} aobject_t;
+
+
+int                    main(void);
+static void            cleanup(void);
+static void            sighandler(int);
+static void            anim_init(void);
+static void            anim_refresh_full(void);
+static void            anim_refresh_update(void);
+
+
+/* To explicitly save/restore tty(4) state despite curses(3) */
+static struct termios  old_tios;
+
+/* Curses window */
+static WINDOW          *w = NULL;
+
+/* Clock */
+static bool            refresh_expired = false;
+
+/* Animation world */
+static aobject_t       objects[NOBJECTS];
+static aobject_t       avatar = { AOT_AVATAR,
+                           TERM_COLUMNS / 2, TERM_LINES / 2,
+                           0, '?' };
+
+
+int
+main(void)
+{
+       struct sigaction act;
+       struct itimerval itv;
+       int c, uc;
+
+       /* Setup signal handler */
+       act.sa_handler = sighandler;
+       act.sa_flags = 0; /* ~SA_RESTART */
+       (void)sigemptyset(&act.sa_mask);
+       (void)sigaction(SIGALRM, &act, NULL);
+       (void)sigaction(SIGINT, &act, NULL);
+       (void)sigaction(SIGTERM, &act, NULL);
+       (void)sigaction(SIGHUP, &act, NULL);
+
+       /* 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) == -1)
+               goto err;
+
+       /* Setup curses */
+       (void)tcgetattr(STDIN_FILENO, &old_tios);
+       if ((w = initscr()) == NULL)
+               err(EXIT_FAILURE, "initscr()");
+       if (COLS < TERM_COLUMNS || LINES < TERM_LINES) {
+               cleanup();
+               (void)fprintf(stderr,
+                   "The terminal must support at least %d columns by %d"
+                   " lines for this program to work.\n",
+                   TERM_COLUMNS, TERM_LINES);
+               exit(EXIT_FAILURE);
+       }
+
+       /* Exit cleanup hook to restore normal terminal */
+       (void)atexit(cleanup);
+
+       if (start_color() == ERR)
+               err(EXIT_FAILURE, "start_color()");
+       (void)init_pair(0, COLOR_WHITE, COLOR_BLACK);
+       (void)init_pair(1, COLOR_WHITE, COLOR_RED);
+       (void)init_pair(2, COLOR_WHITE, COLOR_GREEN);
+       (void)init_pair(3, COLOR_WHITE, COLOR_YELLOW);
+       (void)init_pair(4, COLOR_WHITE, COLOR_BLUE);
+       (void)init_pair(5, COLOR_WHITE, COLOR_MAGENTA);
+       (void)init_pair(6, COLOR_WHITE, COLOR_CYAN);
+       (void)init_pair(7, COLOR_WHITE, COLOR_WHITE);
+
+       (void)raw();
+       (void)noecho();
+       (void)keypad(w, TRUE);
+       (void)timeout(-1);
+
+       /* Setup animation world */
+       anim_init();
+
+       /* Initialize screen */
+       (void)clear();
+       (void)clearok(w, FALSE);
+       (void)refresh();
+       (void)curs_set(0);
+
+       /* Animate until user requests to quit with q */
+       anim_refresh_full();
+       refresh_expired = false;
+       uc = -1;
+       for (;;) {
+cont:
+               if ((c = wgetch(w)) != ERR)
+                       uc = c;
+               if (refresh_expired) {
+                       refresh_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;
+
+                       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;
+                       }
+                       /* Update avatar if necessary */
+                       if (x != -1 && y != -1) {
+                               /* XXX */
+                               avatar.x = x;
+                               avatar.y = y;
+                       }
+
+                       /* Animate */
+                       anim_refresh_update();
+                       anim_refresh_full();
+                       uc = -1;
+               }
+       }
+end:
+
+       exit(EXIT_SUCCESS);
+
+err:
+       err(EXIT_FAILURE, "main()");
+}
+
+static void
+cleanup(void)
+{
+
+       (void)curs_set(1);
+       (void)endwin();
+       (void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_tios);
+}
+
+static void
+sighandler(int sig)
+{
+
+       switch (sig) {
+       case SIGALRM:
+               refresh_expired = true;
+               break;
+       case SIGINT:
+       case SIGTERM: /* FALLTHROUGH */
+       case SIGHUP: /* FALLTHROUGH */
+               exit(EXIT_SUCCESS);
+               break;
+       }
+}
+
+static void
+anim_init(void)
+{
+       int i, col;
+
+       srandom(13);
+       col = 0;
+       for (i = 0; i < NOBJECTS; i++) {
+               aobject_t *o = &objects[i];
+
+               if (i <= 26) {
+                       o->t = AOT_BUG;
+                       o->c = 'A' + i;
+                       o->col = ++col % 7;
+               } else {
+                       o->t = AOT_STAR;
+                       o->c = '.';
+                       o->col = 0;
+               }
+               o->x = random() % (TERM_COLUMNS - 1);
+               o->y = random() % (TERM_LINES - 1);
+       }
+
+       /* XXX Avatar */
+}
+
+static void
+anim_refresh_full(void)
+{
+       int i;
+
+       (void)attrset(COLOR_PAIR(0));
+       (void)clear();
+       (void)clearok(w, FALSE);
+       for (i = 0; i < NOBJECTS; i++) {
+               aobject_t *o = &objects[i];
+
+               (void)attrset(COLOR_PAIR(o->col));
+               (void)mvaddch(o->y, o->x, o->c);
+       }
+
+       /* Avatar */
+       (void)attr_on(A_REVERSE, NULL);
+       (void)mvaddch(avatar.y, avatar.x, avatar.c);
+       (void)attr_off(A_REVERSE, NULL);
+
+       (void)wrefresh(w);
+}
+
+static void
+anim_refresh_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;
+
+               if (o->t == AOT_STAR) {
+                       if (x > 0)
+                               x--;
+                       else
+                               x = TERM_COLUMNS - 1;
+               } else {
+                       switch (d) {
+                       case 0: /* Up */
+                               if (y > 0)
+                                       y--;
+                               break;
+                       case 1: /* Down */
+                               if (y < TERM_LINES - 1)
+                                       y++;
+                               break;
+                       case 2: /* Left */
+                               if (x > 0)
+                                       x--;
+                               break;
+                       case 3: /* Right */
+                               if (x < TERM_COLUMNS - 1)
+                                       x++;
+                               break;
+                       }
+               }
+               o->x = x;
+               o->y = y;
+       }
+}