Random Number List Generator
[randlist.git] / randlist.c
1 static const char const  *copyright = 
2 "Random Number List Generator\n"
3 "Copyright 2016 TJ <hacker@iam.tj>\n"
4 "Licensed on the terms of the GNU General Public License version 3\n";
5
6 static const char const *description =
7 "Seed the C standard library's random number generator from /dev/random and generate one of more Groups of "
8 "Count pseudo-random 'long int' numbers. If a Range is specified reduce each number modulo the range. "
9 "If an Offset is specified add it to the random number. If Unique is specified ensure the number has not "
10 "already been generated. If Split is specified change to the Split Range after Split numbers have been "
11 "generated. If Sort is specified each Group (or Split sub-Groups) is sorted low to high. Pre-defined settings "
12 "matching popular lottery games are included. Result is written as text to standard output; Optionally it is "
13 "presented as seven segment LED digits. Verbose (debug) messages are written to standard error.";
14
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <unistd.h>
18 #include <fcntl.h>
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <string.h>
22
23 // useful default values
24 #define COUNT 1
25 #define RANGE 100
26 #define OFFSET 0
27 #define UNIQUE 1
28 #define GROUPS 1
29
30 // command line option
31 struct option {
32   const char *arg;
33   const char *usage;
34 };
35
36 // order items by longest 'arg' strings first to avoid sub-string matches
37 static struct option app_options[] = {
38  { "-ukloto", "Generate values for UK Lotto (c=6,r=59,o=1,u,s)" },
39  { "-euromillions", "Generate values for EuroMillions (c=7,r=50,o=1,u,s,split=5,sr=10)" },
40  { "-split", "Change range after generating value [number]" },
41  { "-sr", "new Range [number] after split" },
42  { "-c", "generate [number] Count of values" },
43  { "-g", "generate [number] Groups of Count values" },
44  { "-r", "numbers in Range from 0 to [number]-1" },
45  { "-o", "Offset each value by +[number]" },
46  { "-u", "only Unique values in each group" },
47  { "-s", "Sort each Group (or Split group) low to high" },
48  { "-t", "Show Title [string] before numbers" },
49  { "-7", "7-segment LED display" },
50  { "-h", "display this help" },
51  { "-d", "debug messages (to stderr)" },
52  { NULL, NULL }
53 };
54
55 // declare in the same order as 'struct options'
56 enum OPTS { O_UKLOTO, O_EUROMILLIONS, O_SPLIT, O_SPLITRANGE, O_COUNT, O_GROUP, O_RANGE, O_OFFSET, O_UNIQUE, O_SORT, O_TITLE, O_7SEG, O_HELP, O_DEBUG };
57
58 // 7 segment LED number patterns
59 enum SEG_PATTERN {S_TOP = 0x01, S_MID = 0x02, S_BOT = 0x04, S_TL = 0x08, S_TR = 0x10, S_BL = 0x20, S_BR = 0x40, S_DOT = 0x80};
60
61 enum SEGS {S_0, S_1, S_2, S_3, S_4, S_5, S_6, S_7, S_8, S_9, S_dot };
62
63 static unsigned char segments[] = {
64  /* 0 */ S_TOP | S_BOT | S_TL | S_TR | S_BL | S_BR,
65  /* 1 */ S_TR | S_BR,
66  /* 2 */ S_TOP | S_TR | S_MID | S_BL | S_BOT,
67  /* 3 */ S_TOP | S_TR | S_MID | S_BR | S_BOT,
68  /* 4 */ S_TL | S_TR | S_MID | S_BR,
69  /* 5 */ S_TOP | S_TL | S_MID | S_BR | S_BOT,
70  /* 6 */ S_TL | S_MID | S_BL | S_BR | S_BOT,
71  /* 7 */ S_TOP | S_TR | S_BR,
72  /* 8 */ S_TOP | S_TL | S_TR | S_MID | S_BL | S_BR | S_BOT,
73  /* 9 */ S_TOP | S_TL | S_TR | S_MID | S_BR,
74  /* A */ S_TOP | S_TL | S_TR | S_MID | S_BL | S_BR,
75  /* b */ S_TL | S_MID | S_TL | S_TR | S_BOT,
76  /* C */ S_TOP | S_TL | S_BL | S_BOT,
77  /* d */ S_TR | S_MID | S_BL | S_BR | S_BOT,
78  /* E */ S_TOP | S_TL | S_MID | S_BL | S_BOT,
79  /* F */ S_TOP | S_TL | S_MID | S_BL,
80  /* . */ S_DOT 
81 };
82
83 // display help
84 void
85 usage(void)
86 {
87  printf("%s\n", copyright);
88  for (int o = 0; app_options[o].arg; ++o)
89   printf(" %s\t\t%s\n", app_options[o].arg, app_options[o].usage);
90  printf("\n%s\n", description);
91 }
92
93 // integer powers of 10
94 long int 
95 powi(unsigned int base, unsigned int exponent)
96 {
97  for (int n = 0; n < exponent; ++n)
98   base *= 10;
99  return base;
100 }
101
102 // parse base 10 string into a number
103 long int
104 parse_long_int(const char *num)
105 {
106  const char *ch;
107  unsigned bytes = sizeof(long int);
108  int count = 0;
109  int valid = 0;
110  int negative = 0; // defaults to assuming a positive number
111  long int result = 0;
112  long int parsed = 0;
113
114  /* This initial parse extracts valid digits until the end of valid characters, or string, whichever happens first
115     Because at this point the number of valid digits is not known each digit's value is multiplied by 10 times its
116     position in the valid number. E.g. "12345" becomes 54,321:
117     (1*1) + (2*10) + (3*100) + (4*1000) + (5*10000)
118     Obviously this is inverted compared to the real value.
119   */
120  for (ch = num; ch && *ch; ++ch) {
121   if (*ch == ' ')
122    continue;
123   else if (*ch == '-' && count == 0)
124    negative = 1;
125   else if (*ch >= '0' && *ch <= '9') {
126    // add value of digit multiplied by 10 x 'column'
127    parsed += (*ch - '0') * powi(1, count);
128    ++count;
129    valid = 1;
130   }
131   else
132    break;
133  }
134  /* If the parsed value is valid the value-position of each digit is inverted so e.g. 54,321 becomes 12,345:
135     (1*10000) + (2*1000) + (3*100) + (4*10) + (5*1).
136   */
137  if (valid) {
138   int digit = 0;
139   for (count=count-1; count >= 0; --count) {
140    int div = powi(1, count);
141    int mul = powi(1, digit);
142    int val = parsed/div;
143    parsed -= val * div;
144    result += val * mul;
145    ++digit;
146   }
147   if (negative)
148    result = -result;
149  }
150  return result;
151 }
152
153 // count the base-10 digits in a number
154 int
155 digits_count(long int value)
156 {
157  int count = 1;
158  while (value > 9) {
159   value /= 10;
160   ++count;
161  }
162  return count;
163 }
164
165 // comparator for qsort()
166 static int
167 compar(const void *left, const void *right)
168 {
169  const long int *l = (long int *)left;
170  const long int *r = (long int *)right;
171  return *l == *r ? 0 : (*l < *r ? -1 : 1);
172 }
173
174 // entry point
175 int
176 main(int argc, char **argv, char **env)
177 {
178   long int r;
179   long int *unique;
180   ssize_t bytes;
181   unsigned int seed;
182   int not_unique;
183   int fd;
184   int result = 1;
185   int do_count  = COUNT;
186   int do_range  = RANGE;
187   int do_offset = OFFSET;
188   int do_unique = UNIQUE;
189   int do_groups = GROUPS;
190   int do_sort   = 0;
191   int do_debug  = 0;
192   int do_7seg   = 0;
193   int do_split  = 0; // index of 'count' where a new range applies
194   int do_split_range = 0; // the new range (useful for e.g. EuroMillions 5+2 (1-50, 1-10))
195   const char *dev_random = "/dev/random";
196   const char *title = NULL;
197
198   if (argc == 1) {
199    usage();
200    return 0;
201   }
202   
203   printf("%s\n", copyright);
204
205   for (int a = 1; a < argc; ++a) { // parse each command-line argument
206    if (do_debug) fprintf(stderr, "Testing arg %d %s\n", a, argv[a]);
207    for (int option = 0; app_options[option].arg; ++option) { // for each option...
208     if (do_debug) fprintf(stderr, "Testing option %d %s\n", option, app_options[option].arg);
209     if (strncmp(argv[a], app_options[option].arg, strlen(app_options[option].arg)) == 0) {
210      switch (option) {
211       case O_HELP:
212        usage();
213        return 0;
214       case O_DEBUG:
215        do_debug = 1;
216        break;
217       case O_COUNT:
218        do_count = parse_long_int(argv[++a]);
219        break;
220       case O_GROUP:
221        do_groups = parse_long_int(argv[++a]);
222        break;
223       case O_UNIQUE:
224        do_unique = 1;
225        break;
226       case O_RANGE:
227        do_range = parse_long_int(argv[++a]);
228        if (!do_split_range)
229         do_split_range = do_range;
230        break;
231       case O_OFFSET:
232        do_offset = parse_long_int(argv[++a]);
233        break;
234       case O_SORT:
235        do_sort = 1;
236        break;
237       case O_TITLE:
238        title = argv[++a];
239        break;
240       case O_7SEG:
241        do_7seg = 1;
242        break;
243       case O_SPLIT:
244        do_split = parse_long_int(argv[++a]);
245        break;
246       case O_SPLITRANGE:
247        do_split_range = parse_long_int(argv[++a]);
248        break;
249       case O_UKLOTO:
250        do_count = 6; 
251        do_range = 59;
252        do_offset = 1;
253        do_unique = 1;
254        do_sort = 1;
255        title = app_options[option].arg + 1;
256        break;
257       case O_EUROMILLIONS:
258        do_count = 7;
259        do_range = 50;
260        do_offset = 1;
261        do_unique = 1;
262        do_sort = 1;
263        do_split = 5;
264        do_split_range = 10;
265        title = app_options[option].arg + 1;
266        break;
267      } // switch
268      break;
269     } // arg == option comparison
270     else {
271      fprintf(stderr, "Error: unknown option %s\n", argv[a]);
272      return 1;
273     }
274    } // options
275   } // args
276
277   if (!do_split)
278    do_split = do_count; // no Split sub-group
279
280   if (do_debug)
281    setvbuf(stderr, NULL, _IONBF, 0); // don't buffer debug messages
282
283   unique = calloc(do_count, sizeof(*unique)); // reserve storage for a Group's values
284   if (unique) {
285    if ((fd = open(dev_random, O_RDONLY)) >= 0) {
286     bytes = read(fd, (void *)&seed, sizeof(seed)); // get a truly random value
287     if (do_debug) fprintf(stderr, "Seed %u of %ld bytes from %s\n", seed, bytes, dev_random);
288     close(fd);
289     if (bytes == sizeof(seed)) { // ensure bytes read from /dev/random fill the seed
290      srandom(seed); // seed te pseudo-random number generator
291
292      if (title)
293       printf("%s\n", title);
294
295      for (int group = 0; group < do_groups; ++group) { // generate each Group
296       if (do_debug) fprintf(stderr, "Generating group %d\n", group);
297       int digit_count = 0; // keep track of the number of digits in the Group
298
299       for (int n = 0; n < do_count; ++n) { // generate each value
300        // determine if this is now a Split sub-group
301        unsigned int in_split = (do_split < do_count && n >= do_split) ? 1 : 0;
302
303        if (do_debug) fprintf(stderr, "Generating value %d in range %d,", n, in_split ? do_split_range : do_range);
304
305        r = random(); // the (long int) random number
306        // adjust to fit Range
307        r = do_offset + (r % (in_split ? do_split_range : do_range));
308        unique[n] = r;
309        if (do_debug) fprintf(stderr, " value: %ld\n", unique[n]);
310
311        if (do_unique) {
312         if (do_debug) fprintf(stderr, "%s\n", "Unique check");
313         not_unique = 0;
314
315         for (int m = (in_split ? do_split : 0); m < n && !not_unique; ++m) { // detect repeated values in sub-group
316          if (do_debug) fprintf(stderr, " comparing value %d (%ld) with %d (%ld)\n", n, unique[n], m, unique[m]);
317          if (unique[m] == unique[n]) 
318           not_unique = 1;
319          if (do_debug) fprintf(stderr, "%sUnique\n", not_unique ? "NOT " : "");
320         }
321         if (not_unique) {
322          --n; // do this Count value again
323          continue;
324         }
325         digit_count += digits_count(unique[n]);
326        }
327       } // count
328       if (do_sort) {
329        if (do_debug) fprintf(stderr, "%s\n", "Sorting");
330        // if there is no split sub-group this will sort all values (since do_split == do_count)
331        // if there is a split sub-group this will only sort the values before the split
332        qsort(unique, do_split, sizeof(*unique), compar); 
333        if (do_split < do_count) // sort the values after the split
334         qsort(unique + do_split, do_count - do_split, sizeof(*unique), compar);
335       }
336
337       if (do_7seg) { // generate and draw the pseudo 7-segment 'LED' display
338        // size and spacing of digits
339        unsigned int d_rows = 9;
340        unsigned int d_cols = 5;
341        unsigned int d_space = 3;
342        // (x,y) coordinate maximums
343        int grid_y_max = d_rows;
344        int grid_x_max = (digit_count * d_cols) + (do_count * d_space);
345        if (do_split < do_count)
346         grid_x_max += d_space * 2; // space the Split sub-group clearly
347
348        // track current (x,y) coordinate
349        int grid_y = 0;
350        int grid_x = 0;
351
352        if (do_debug) fprintf(stderr, "Grid: digits: %d rows: %d cols: %d\n", digit_count, grid_y_max, grid_x_max);
353
354        unsigned char *grid = malloc(grid_x_max * grid_y_max);
355        if (grid) {
356         memset(grid, ' ', grid_y_max * grid_x_max); // default all 'LEDs' to unlit
357         int digit_i = 0; // keep track of which digit of the entire current group is being rendered
358
359         for (int y = 1; y <= grid_y_max; ++y) { // insert string terminators at the end of each row
360          char *p = grid + y * grid_x_max - 1;
361          *p = 0;
362         }
363         for (int c = 0; c < do_count; ++c) { // process each value in the group
364          long int val = unique[c];
365
366          for (int d = digits_count(unique[c]) - 1; d >= 0; --d) { // split value into individual digits
367           long int v = val / powi(1, d); // isolate a base-10 digit
368           val -= v * powi(1, d); // update the remaining value
369           unsigned char s = segments[v]; // get the 'LED' On flags for this digit
370           if (do_debug) fprintf(stderr, " digit: %d %d: %ld = %02x",digit_i,  d, v, s);
371
372           for (int x = grid_x; x < grid_x + d_cols; ++x) { // scan the columns...
373            for (int y = grid_y; y < grid_y_max; ++y) { // ... by row
374             int show = 0; // set to 1 if this 'LED' position should be 'lit'
375
376             // draw the horizontal bars
377             if (x >= grid_x && x < grid_x + d_cols - 1) {
378              if ((s & S_TOP && y == 0) ||
379                  (s & S_MID && y == (grid_y_max-1)/2) ||
380                  (s & S_BOT && y == grid_y_max-1))
381               show = 1;
382             }
383             // draw the vertical bars
384             if (s & S_TL && (x == grid_x && (y >= 0 && y <= (grid_y_max-1)/2)))
385              show = 1;
386             if (s & S_TR && (x == (grid_x + d_cols - 2) && (y >= 0 && y <= (grid_y_max-1)/2)))
387              show = 1;
388             if (s & S_BL && (x == grid_x && (y >= (grid_y_max-1)/2)))
389              show = 1;
390             if (s & S_BR && (x == (grid_x + d_cols - 2) && (y >= (grid_y_max-1)/2)))
391              show = 1;
392
393             if (show)
394              *(grid + (y * grid_x_max) + x ) = '0' + v; // use the current digit as the 'light'
395            }
396           }
397           grid_x += d_cols; // advance to next digit position
398           ++digit_i; // advance to next digit in group
399          } // digits
400          if (do_debug) fprintf(stderr, "%s", "\n");
401          // insert comma after digits of each value
402          *(grid + ((grid_y_max-1) * grid_x_max) + grid_x + 1) = ',';
403          grid_x += d_space; // advance with additional spacing between digits
404
405          if (do_split < do_count && c == do_split - 1)
406           grid_x += d_space * 2; // advance with addtional spacing between Split sub-groups
407          
408         } // count
409
410         // the completed 7-segment 'LED' display
411         printf("%s", "\n");
412         for (int y = 0; y < grid_y_max; ++y) {
413          char *line = grid + y * grid_x_max;
414          printf("%s\n", line);
415         }
416         printf("%s", "\n");
417
418         free(grid);
419         grid = NULL;
420        } // grid
421       } // 7-segment
422       else { // normal text output of values
423        for (int c = 0; c < do_count; ++c) {
424         printf(" %ld", unique[c]);
425        }
426       }
427       printf("%s", "\n");
428      } // group
429      result = 0;
430     } // seed is valid
431     else {
432      fprintf(stderr, "Error: insuffient random bytes returned from %s\n", dev_random);
433     }
434     free(unique);
435    } // /dev/random
436   } // memory allocated for values
437   else {
438    fprintf(stderr, "Error: failed to allocate %ld bytes of memory for values\n", do_count * sizeof(*unique));
439    result = 3;
440   }
441   return result;
442 }
443