summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Wolff <janw@mailbox.org>2025-12-10 15:52:34 +0100
committerJan Wolff <janw@mailbox.org>2025-12-10 15:52:34 +0100
commitad89e1050da046f195b683d765d7060e32b71d8a (patch)
treed7dfded8880e00c7a01dac0c7de8e9a44c20a08a
initial commitHEADmain
-rw-r--r--.clang-format10
-rw-r--r--.gitignore2
-rw-r--r--Makefile24
-rw-r--r--roll.c156
4 files changed, 192 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..b88d285
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,10 @@
+# Linux-like style
+BasedOnStyle: LLVM
+IndentWidth: 4
+UseTab: Never
+BreakBeforeBraces: Linux
+AllowShortIfStatementsOnASingleLine: true
+IndentCaseLabels: false
+# Force pointers to the type
+DerivePointerAlignment: false
+PointerAlignment: Left
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ec886bb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+*.o
+roll
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..150c5c4
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,24 @@
+CC ?= gcc
+CFLAGS ?= -ansi -Wall -Werror
+OBJ = roll.o
+BIN = roll
+PREFIX ?= /usr
+
+prog: $(OBJ)
+ $(CC) $(CFLAGS) $(OBJ) -o $(BIN)
+
+%.o: %.c
+ $(CC) $(CFLAGS) -c $<
+
+install: prog
+ mkdir -p ${PREFIX}/bin
+ install -m 755 -o root -g root -s ${BIN} ${PREFIX}/bin
+
+format:
+ clang-format -i *.c
+
+clean:
+ rm *.o
+ rm $(BIN)
+
+.PHONY: install format clean
diff --git a/roll.c b/roll.c
new file mode 100644
index 0000000..660eeab
--- /dev/null
+++ b/roll.c
@@ -0,0 +1,156 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+enum parser_state_e {
+ ST_NUMBER_BEFORE_DICE = 0,
+ ST_NUMBER_AFTER_DICE = 1,
+};
+
+struct state_s {
+ enum parser_state_e parser_state;
+ unsigned number_before_dice;
+ unsigned number_after_dice;
+ unsigned result;
+ int eof;
+ int syntax_error;
+};
+
+void print_usage(char* argv0)
+{
+ printf("usage: %s <command>\n", argv0);
+ puts(" examples: d20, 2d8+4, 1d6+d8+5");
+}
+
+unsigned diceroll(unsigned dice, unsigned rolls)
+{
+ unsigned result = 0;
+
+ for (; rolls > 0; rolls--) {
+ result += 1 + rand() % dice;
+ }
+
+ return result;
+}
+
+struct state_s eval_step(struct state_s state, char c)
+{
+ switch (c) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ /* anything numeric */
+ if (state.parser_state == ST_NUMBER_BEFORE_DICE) {
+ state.number_before_dice *= 10;
+ state.number_before_dice += c - '0';
+ } else if (state.parser_state == ST_NUMBER_AFTER_DICE) {
+ state.number_after_dice *= 10;
+ state.number_after_dice += c - '0';
+ }
+ break;
+ case 'd':
+ case 'D':
+ /* dice indicator */
+ if (state.parser_state == ST_NUMBER_BEFORE_DICE) {
+ if (state.number_before_dice == 0) {
+ /* allow shorthand, treat e.g. "d20" as "1d20" */
+ state.number_before_dice = 1;
+ }
+ state.parser_state = ST_NUMBER_AFTER_DICE;
+ } else {
+ /* not in the format ABdXY */
+ state.syntax_error = 1;
+ }
+ break;
+ case '+':
+ case '\0':
+ /* end of input or just end of an element */
+ if (state.number_after_dice > 0) {
+ state.result +=
+ diceroll(state.number_after_dice, state.number_before_dice);
+ } else if (state.parser_state == ST_NUMBER_BEFORE_DICE) {
+ /* we don't have a number after the dice */
+ /* in this case we just have a "naked" value that we take as-is */
+ state.result += state.number_before_dice;
+ } else {
+ /* missing number after dice indicator */
+ state.syntax_error = 1;
+ break;
+ }
+
+ state.number_before_dice = 0;
+ state.number_after_dice = 0;
+ state.parser_state = ST_NUMBER_BEFORE_DICE;
+
+ if (c == '\0') {
+ state.eof = 1;
+ }
+ break;
+ default:
+ state.syntax_error = 1;
+ break;
+ }
+
+ return state;
+}
+
+int eval(char* c)
+{
+ struct state_s state = {0};
+
+ while (1) {
+ state = eval_step(state, *c);
+
+ if (state.syntax_error) {
+ puts("syntax error");
+ return 1;
+ }
+ if (state.eof) {
+ break;
+ }
+
+ c++;
+ }
+
+ printf("%d\n", state.result);
+
+ return 0;
+}
+
+/**
+ * platform dependent way to generate a good seed for the random number
+ * generator
+ */
+unsigned get_seed()
+{
+ unsigned seed;
+
+#ifdef __linux__
+ FILE* fp = fopen("/dev/urandom", "rb");
+ fread(&seed, sizeof(unsigned), 1, fp);
+ fclose(fp);
+#else
+ seed = time(NULL);
+#endif
+
+ return seed;
+}
+
+int main(int argc, char* argv[])
+{
+ if (argc < 2) {
+ print_usage(argv[0]);
+ return 1;
+ }
+
+ srand(get_seed());
+
+ return eval(argv[1]);
+}