aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ahoy/config.h13
-rw-r--r--src/ahoy/emulator.c10
-rw-r--r--src/ahoy/main.c55
3 files changed, 71 insertions, 7 deletions
diff --git a/src/ahoy/config.h b/src/ahoy/config.h
new file mode 100644
index 0000000..9a78f59
--- /dev/null
+++ b/src/ahoy/config.h
@@ -0,0 +1,13 @@
+#ifndef AHOY_AHOY_CONFIG_H
+#define AHOY_AHOY_CONFIG_H
+
+#include <stdint.h>
+
+struct config {
+ /* Valid ranges are 0–UINT16_MAX. >UINT16_MAX is for random seed. */
+ uint32_t seed;
+};
+
+extern struct config cfg;
+
+#endif /* !AHOY_AHOY_CONFIG_H */
diff --git a/src/ahoy/emulator.c b/src/ahoy/emulator.c
index 749e3a2..cffc4a9 100644
--- a/src/ahoy/emulator.c
+++ b/src/ahoy/emulator.c
@@ -6,6 +6,7 @@
#include <mbstring.h>
#include "cerr.h"
+#include "config.h"
#include "emulator.h"
#include "macros.h"
@@ -54,9 +55,12 @@ emuinit(struct u8view prog, const char *fn)
c8.PC = MEM_RESERVED;
memcpy(mem + c8.PC, prog.p, prog.len);
- if (clock_gettime(CLOCK_REALTIME, &tp) == -1)
- die("clock_gettime");
- srand(tp.tv_sec ^ tp.tv_nsec);
+ if (cfg.seed > UINT16_MAX) {
+ if (clock_gettime(CLOCK_REALTIME, &tp) == -1)
+ die("clock_gettime");
+ srand((tp.tv_sec ^ tp.tv_nsec) & UINT16_MAX);
+ } else
+ srand(cfg.seed);
}
void
diff --git a/src/ahoy/main.c b/src/ahoy/main.c
index e52e6bd..581078b 100644
--- a/src/ahoy/main.c
+++ b/src/ahoy/main.c
@@ -2,25 +2,41 @@
#include <fcntl.h>
#include <getopt.h>
-#include <stdint.h>
+#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <builder.h>
+#include <mbstring.h>
#include <SDL.h>
#include "cerr.h"
+#include "config.h"
#include "emulator.h"
#include "gui.h"
#include "macros.h"
+#if __has_include(<stdckdint.h>)
+# include <stdckdint.h>
+# warning "stdckdint.h now available; remove manual ckd_*() implementations"
+#elifdef __GNUC__
+# define ckd_add(r, a, b) ((bool)__builtin_add_overflow(a, b, r))
+# define ckd_mul(r, a, b) ((bool)__builtin_mul_overflow(a, b, r))
+#else
+# error "Platform not supported at the moment"
+#endif
+
#define FPS 60
#define INSTS_PER_SEC 700
[[noreturn]] static void usage(void);
static void run(int, const char *);
+static uint16_t strtou16(const char8_t *, rune *);
+struct config cfg = {
+ .seed = UINT16_MAX + 1,
+};
static const char *argv0;
void
@@ -35,18 +51,32 @@ main(int argc, char **argv)
{
int opt;
const struct option longopts[] = {
- {"help", no_argument, nullptr, 'h'},
- {nullptr, no_argument, nullptr, 0 },
+ {"help", no_argument, nullptr, 'h'},
+ {"seed", required_argument, nullptr, 's'},
+ {nullptr, no_argument, nullptr, 0 },
};
argv0 = argv[0];
cerrinit(*argv);
- while ((opt = getopt_long(argc, argv, "h", longopts, nullptr)) != -1) {
+ while ((opt = getopt_long(argc, argv, "hs:", longopts, nullptr)) != -1) {
switch (opt) {
case 'h':
execlp("man", "man", "1", argv[0], nullptr);
die("execlp: man 1 %s", argv[0]);
+ case 's': {
+ rune ch;
+ uint16_t n = strtou16(optarg, &ch);
+ if (ch >= '0' && ch <= '9')
+ diex("seed too large; may not exceed %" PRIu16, UINT16_MAX);
+ else if (ch) {
+ char8_t buf[U8_LEN_MAX];
+ int w = rtou8(buf, ch, sizeof(buf));
+ diex("invalid character ‘%.*s’; seed must be numeric", w, buf);
+ }
+ cfg.seed = n;
+ break;
+ }
default:
usage();
}
@@ -139,3 +169,20 @@ run(int fd, const char *fn)
u8strfree(sb);
winfree();
}
+
+uint16_t
+strtou16(const char8_t *s, rune *ch)
+{
+ uint16_t n = 0;
+ size_t len = strlen(s);
+
+ while (u8next(ch, &s, &len)) {
+ if (!(*ch >= '0' && *ch <= '9'))
+ return -1;
+ if (ckd_mul(&n, n, 10) || ckd_add(&n, n, *ch - '0'))
+ return -1;
+ }
+
+ *ch = '\0';
+ return n;
+}