I am learning C and, for learning purposes, I implemented a dice roller.
It uses arc4random(3) for random number generation, as I couldn't find something better.
It works both interactively, if no argument is given, reading dice string from stdin, one per line (I used getline(3) for this); and reading dice string from arguments.
A dice string is a dice specification similar to those used in most role playing games, for example, rolldice 3d6 rolls 3 6-sized dice and sum them up, and rolldice 4d8+2s1 rolls 4 8-sized dice, discards one roll, sum the rolls, and add 2 to the result. More information on this on the manual.
I wrote a manual page for it, the manual page is based on the manual of Stevie Strickland's rolldice, but the code I wrote from scratch.
Here's the manual:
rolldice(6) Games Manual rolldice(6) NAME rolldice - rolls virtual dice SYNOPSIS rolldice [-s] [dice string...] DESCRIPTION rolldice rolls virtual dice. The dice strings passed on the command line contain information on the dice to roll in a format comparable to the format used in most role playing games. If no dice strings are provided as command line arguments, rolldice uses stdin as input and runs interactivelly. The options are as follows: -s Print out the result of each individual die separately, as well as the operations and totals. DICE STRING FORMAT The dice string uses the exact format outlined below. Optional parts are between square brackets. A # must be replaced by a num‐ ber. [#x][#]d[#|%][*#][+#|-#][s#] [#x] How many times to roll. If ommited, defaults to 1 roll. [#]d[#|%] Main part of the dice string. The first number is the number of dice to roll in each roll, if ommited, roll just one die. The second number is the number of sides the dice have, if ommited, roll 6-sided die. The second number can be replaced by a percent sign, implying a 100-sided die. The numbers rolled on each die are then added up and given as the result. [*#] How many times to multiply the result of each roll. [+#|-#] Number to be added or subtracted, depending on the sign, from each roll. This step is handled after the multiplication. [s#] How many lowest dice rolls to drop. This step is handled be‐ fore the multiplication. EXIT STATUS 0 Success. >0 Error occurred. EXAMPLES Roll three six-sided dice and sum the results: rolldice 3d Roll four eight-sided dice and sum the results, them multiply the result by 2 and add 2 to it: rolldice 4d8*2+2 Roll four six-sided dice, drop the lowest result and add the remain‐ ing results. Do this three times: rolldice 3x4d6s1 HISTORY This version of rolldice was written as an exercise for practicing C. The idea for getnumber() was from an anon from /g/'s dpt. I could've used strtol(3) but, as I said, I did it for practicing. rolldice(6) Here's the code:
#include <err.h> #include <ctype.h> #include <stdio.h> #include <stdlib.h> #include <limits.h> #include <unistd.h> #include <bsd/stdlib.h> #define DEFROLLS 1 #define DEFDICE 1 #define DEFFACES 6 #define DEFMULTIPLIER 1 #define DEFMODIFIER 0 #define DEFDROP 0 static int separate; /* structure of a dice string */ struct dice { int rolls; int dice; int faces; int multiplier; int modifier; int drop; }; static void rolldice(struct dice); static struct dice getdice(char *); static int getnumber(char **); static void usage(void); /* roll a virtual dice */ int main(int argc, char *argv[]) { struct dice *d; int c, i, exitval; char *line = NULL; size_t linesize = 0; ssize_t linelen; separate = 0; while ((c = getopt(argc, argv, "s")) != -1) { switch (c) { case 's': separate = 1; break; default: usage(); break; } } argc -= optind; argv += optind; exitval = EXIT_SUCCESS; if (argc == 0) { /* no arguments, run interactivelly */ if ((d = reallocarray(NULL, 1, sizeof(*d))) == NULL) err(1, NULL); while ((linelen = getline(&line, &linesize, stdin)) != -1) { *d = getdice(line); if (d->rolls == 0) { warnx("%s: malformed dice string", line); exitval = EXIT_FAILURE; } else { rolldice(*d); } } free(line); if (ferror(stdin)) err(1, "stdin"); } else { /* run parsing the arguments */ if ((d = reallocarray(NULL, argc, sizeof(*d))) == NULL) err(1, NULL); for (i = 0; i < argc; i++) { d[i] = getdice(*argv); if ((d[i]).rolls == 0) errx(1, "%s: malformed dice string", *argv); argv++; } for (i = 0; i < argc; i++) rolldice(d[i]); } free(d); if (ferror(stdout)) err(1, "stdout"); return exitval; } /* get a random roll given a dice structure */ static void rolldice(struct dice d) { int i, j, min, drop; int *roll, rollcount, rollsum; if ((roll = reallocarray(NULL, d.dice, sizeof(*roll))) == NULL) err(1, NULL); rollcount = 1; while (d.rolls-- > 0) { rollsum = 0; if (separate) printf("Roll #%d: (", rollcount++); /* get random values */ for (i = 0; i < d.dice; i++) { roll[i] = 1 + arc4random() % d.faces; rollsum += roll[i]; if (separate) printf("%d%s", roll[i], (i == d.dice-1) ? "" : " "); } /* drop smallest values */ drop = d.drop; while (drop-- > 0) { min = INT_MAX; for (i = 0; i < d.dice; i++) { if (roll[i] != 0 && min > roll[i]) { min = roll[i]; j = i; } } rollsum -= roll[j]; if (separate) printf(" -%d", roll[j]); } /* sum rolls, apply multiplier and modifier */ rollsum = rollsum * d.multiplier + d.modifier; if (separate) { printf(")"); if (d.multiplier != 1) printf(" * %d", d.multiplier); if (d.modifier != 0) printf(" %c %u", (d.modifier < 0) ? '-' : '+', abs(d.modifier)); printf(" = "); } /* print final roll */ printf("%d%c", rollsum, (d.rolls == 0 || separate) ? '\n' : ' '); } free(roll); } /* get dice in format [#x][#]d[#|%][*#][+#|-#][s#], where # is a number */ static struct dice getdice(char *s) { struct dice d; int n, sign; /* set number of rolls */ if ((n = getnumber(&s)) < 0) goto error; d.rolls = DEFROLLS; if (*s == 'x') { d.rolls = (n == 0) ? DEFROLLS : n; s++; if (n < 1) goto error; if ((n = getnumber(&s)) < 0) goto error; } /* set number of dices */ if (*s != 'd') goto error; d.dice = (n == 0) ? DEFDICE : n; n = 0; s++; /* set number of faces */ if (*s == '%') { n = 100; s++; } else if ((n = getnumber(&s)) < 0) goto error; d.faces = (n == 0) ? DEFFACES : n; n = 0; /* set multiplier */ if (*s == '*') { s++; if ((n = getnumber(&s)) < 1) goto error; } d.multiplier = (n == 0) ? DEFMULTIPLIER : n; n = 0; /* set modifier */ if (*s == '+' || *s == '-') { sign = (*s++ == '-') ? -1 : 1; if ((n = getnumber(&s)) < 1) goto error; } d.modifier = (n == 0) ? DEFMODIFIER : sign * n; n = 0; /* set number of drops */ if (*s == 's') { s++; if ((n = getnumber(&s)) < 1) goto error; } d.drop = (n == 0) ? DEFDROP : n; if (d.drop >= d.dice) goto error; if (*s != '\0' && *s != '\n') goto error; return d; error: return (struct dice) {0, 0, 0, 0, 0, 0}; } /* get number from *s; return -1 in case of overflow, return 0 by default */ static int getnumber(char **s) { int n; n = 0; while (isdigit(**s)) { if (n > (INT_MAX - 10) / 10) return -1; else n = n * 10 + **s - '0'; (*s)++; } return n; } static void usage(void) { (void) fprintf(stderr, "usage: rolldice [-s] [dice-string...]\n"); exit(EXIT_FAILURE); } Is my solution portable? And is it well commented?
I think I rely to much on BSD extensions, such as err(3), and on POSIX extensions, such as getopt(3). It must be compiled with -lbsd on Linux. Is this bad?
Is my code comparable to Stevie's? Or is it worse?
Please, see also Stevie's rolldice, and compare my code to his.
I think Stevie's rolldice contains serious input bugs, such as accepting any string not containing d as 1d6, for example rolldice foo is the same as rolldice 1d6 for Stevie's. And his implementation accepts multiple modifiers, but only uses the last one (rolldice 1d6-3+2+1 is the same as rolldice 1d6+1). My version does not have those bugs.
(Note: I had access to Stevie's rolldice program before writing mine, but I have only seen Stevie's code after finalizing mine, which I wrote from scratch).
EDIT: After reading the answers, I've refactored the code and written a new version of rolldice(6).