1. 11

I’ve been playing a lot of roguelikes lately (for those interested: mostly ADOM, a little bit of Sil) and because those games use a lot of dice rolls in their gameplay mechanics, I wrote myself a small utility to compute the minimum, maximum, and expected value of a dice roll in order to make more informed decisions (e.g., which weapon is likely to be better) before I inevitably YASD. For example:

~$ ev 1d6 3d4+1 INVALID 2d10-1
1d6
    min: 1
    max: 6
    ev : 3.5
3d4+1
    min: 4
    max: 13
    ev : 8.5
ev: invalid format: INVALID
2d10-1
    min: 1
    max: 19
    ev : 10
~$

The program iterates over the arguments, tries to parse them as rolls and prints the roll statistics or an error message. I’ve been wondering what the program should do when no arguments are passed. I figure I have three possible options:

  1. Do nothing (current);
  2. Display usage information;
  3. Read rolls from stdin.

I’m curious to know what people here feel would be the more UNIXy and/or natural choice? Certainly (1) was easiest to implement since it treats zero arguments the exact same way it treats any other number of arguments.

  1.  

  2. 8

    Depends on the way you expect the program to be most commonly interacted with. Comparable programs like bc, awk, and sed each act differently when invoked without arguments. If you expect rolls usually be provided as arguments, be like awk and use an -f FILE option to accept rolls over a file instead (with -f - being stdin). Go with option 2 by printing usage if no files or expressions were provided. If you expect rolls to usually come from a file, be like bc and interpret each positional argument as a filename, and default to stdin if no positional filenames are provided.

    As far as output goes, your current format is not easy for other tools to parse. Tools usually expect a record-and-field format to output, typically with newlines separating records and tabs or spaces separating fields. I would expect your output to look more like this:

    1d6 1 6 3.5
    3d4+1 4 13 8.5
    ev: INVALID: invalid format
    2d10-1 1 19 10
    

    I could see how some people would recoil from a dense format like that, so you could make it align more nicely, or add column headers on stderr, or whatever. Or add an option for –pretty-print/–no-pretty-print. Just don’t make it impossible to apply awk, cut, sed, etc. to those results.

    1. 3

      Seconding this. My lazy heuristic for unixiness of a program is: can i feed it from sed/awk and consume it from sed/awk?

      So, stdin in for inputting lines, stdout for uglified values as above, maybe -e to eval immediately and return, and -f for slurping a file. No args should behave like cat, probably.

      1. 2

        There’s a few things you can do to deal with the “dense presentation” issue.

        • Always print a header line first
        • Document that people can pass the output through column -t to get a pretty table
        • Separate the fields on each line with a tab (\t) rather than a space. Since each field is very likely less than 8 columns wide, it will very likely always line up automatically

        Also, you probably already do this, but just to be sure: the “invalid format” error message should be written to stderr rather than stdout.

        (there’s a case to be made for always writing the header to stderr, too, but (a) that makes it more difficult to pipe through column -t, (b) it’s easy enough to strip from stdout with tail -n +2, and © some programs interpret “anything written to stderr” as “something bad has happened”)

      2. 3

        Whichever choice is most useful. If you don’t plan on feeding it stdin, that seems an unnecessary complication. But if, as seems likely, a long running game may require many inputs and it will be easier to provide them via stdin instead of editing a command line.

        1. 2

          From the example you showed, it looks like you don’t validate the individual inputs until you get to them? I would probably expect the options parser to check all the args first…

          Anyway, your question - definitely not 1) but I would say there’s nothing wrong with 2) or 3) - if I had to choose I would say 3) because then you can pipe input from another process.

          1. 2

            A problem with command lines is that most operating systems have some limit on the length of command line. Hence, I think it would be great if you provide the ability to read from stdin, which is expected in a unix program. Keep the help to –help or -h. You are not modifying the system (such as by writing to a file or modifying one). Hence there is little need for caution – that is for printing usage for invocation without flags – when there is a valid use case for reading from stdin.

            1. 2

              Thank you all for the valuable comments. I had already made up my mind to read from stdin when no arguments were provided, and I’m happy to see that many people agreed. I had actually tried printing on a single line (similar to wc), however for a human (or at least, this human) it made the output harder to read so I reverted back to the multi-line output. Here’s an example of the command now:

              ~$ ev -h
              Usage: ev [options] [rolls ...]
              
              Options:
                  -h, --help          display this help message
                  -s, --single-line   single line display
              
              ~$ ev 1d4+3 1d6+1
              1d4+3:
                  min: 4
                  max: 7
                  ev : 5.5
              1d6+1:
                  min: 2
                  max: 7
                  ev : 4.5
              
              ~$ echo 2d20-4 | ev -s
              2d20-4 -2 36 17
              
              ~$ ev -s < /tmp/rolls.txt | column -t
              1d4     1  4   2.5
              2d6+1   3  13  8
              5d10-1  4  49  26.5
              

              I made the pretty output the default and added a flag for single-line display because I feel that I’m much more likely to use this program interactively rather than in a pipeline.