mjranum@gouldsd.UUCP (Captain Coredump) (10/28/86)
Here it is, since I am tired of sending individual copies around. Take it or leave it.... So far it runs on BSD UNIX and on IBM PCs under Lattice 3.0 (with stack modifications). Live Free mjr the following files are in this bundle: (Use them wisely) Makefile README food nationality pirate possesions rnd.c rnd.nro skill weapon cut here -------------------------------------------------- # To unbundle - sh this file echo unbundling Makefile if [ -f Makefile ] ; then echo cannot unbundle Makefile - file exists already exit 1 fi cat >Makefile <<'End of Makefile' CFLAGS= -O CC= /bin/cc STD= rnd DOC= README all: ${STD} ${DOC} ${STD}: rnd.c ${CC} ${CFLAGS} -o $@ $@.c ${DOC}: rnd.nro nroff -man rnd.nro >> README End of Makefile echo unbundling README if [ -f README ] ; then echo cannot unbundle README - file exists already exit 1 fi cat >README <<'End of README' rrrrnnnndddd((((1111)))) rrrrnnnndddd((((1111)))) rrrrnnnndddd ---- RRRRaaaannnnddddoooommmm ssssttttuuuuffffffff ggggeeeennnneeeerrrraaaattttoooorrrr ((((MMMMJJJJRRRR)))) SSSSYYYYNNNNTTTTAAAAXXXX rrrrnnnndddd file1 file2, etc... OOOOPPPPTTTTIIIIOOOONNNNSSSS Each file must be a 'table' file. DDDDEEEESSSSCCCCRRRRIIIIPPPPTTTTIIIIOOOONNNN _R_n_d reads a file and writes generated data to the standard output. If more than one file is specified, the files are used as descriptions in sequence and merged output is written to the standard output. Two types of files are recognized; table files, and subtable files. A table file can contain several types of information: o+ Text-formats and text: these are simply text that will be included in the output. There are, however, a few simple rules. Any text string ending in a ':' (no space afterwards) will NOT have a newline appended after it. This is to allow a user to have the output from a subtable placed after a text string. Other text that does not have a ':' as the last character before a newline is printed as is. o+ Tabs and Formfeeds: to insert tabs and formfeeds, a "\t" (tab) or a "\l" (formfeed ^L) is placed as the ONLY thing on that line. There must be no other blank spaces, etc. This is inelegant, but allows the program to run reasonably fast. If a tab is coded, the program does NOT automatically generate a newline after it. This is to allow a user to separate stats with tabs reasonably easily. o+ Stat commands: a stat command goes at the beginning of an otherwise empty line, in the format "stat <char string WITHOUT spaces> <# of dice> <type of dice>" Rnd will then roll the appropriate number of N-sided dice, and print the stat on the standard output. o+ Subtable commands: a subtable command goes at the beginning of an otherwise empty line, in the format UTX/32 User's Reference Manual -1- Commands rrrrnnnndddd((((1111)))) rrrrnnnndddd((((1111)))) "subtable <filename> <# of dice> <type of dice>" Rnd will then roll the appropriate number of N-sided dice, and open the subtable file. A subtable file is constructed as follows: <#upper limit> <text string> <#upper limit> <text string> Subtable files may not contain any extraneous text (unlike table files) and all the entries must be in order, with the highest numerical entry as the last line in the file, and the lowest numerical entry as the first. Each line must contain a number value and a text string, or the results may be unusual. o+ Table commands: a table can contain a list of tables as well as stats, format commands, text, and subtables. Each table is randomized recursively, until your stack overflows, or it returns, whichever comes first. Stacking tables deeply is usually not necessary, since several tables can be called on the command line, and that is much safer. When a table command is invoked, it must be followed by two numbers so that the semi-braindead parser recognizes it as a command. table <filename> <bogus #><bogus #> Each table can contain its own list of subtables and stats, as well as text and format commands. This is lots of fun, since it allows the discerning DM to make a variety of layered tables and gives excellent control over how much is generated. For example if you determine that an encounter consists of monsters, booty, and terrain, you could have a table that invoked the monster table, then the terrain one, then the booty. In this way, you would still have a perfectly usable set of tables for any time you wanted just a monster, or just terrain. The possibilities as far as maintaining standard libraries are considerable, obviously. The primary limitation is your machine's stack and disk space. EEEEXXXXAAAAMMMMPPPPLLLLEEEESSSS $ rrrrnnnndddd ppppiiiirrrraaaatttteeeessss rrrrnnnndddd reads the table named ppppiiiirrrraaaatttteeeessss and executes all the stats, subtables, and tables contained therein. UTX/32 User's Reference Manual -2- Commands rrrrnnnndddd((((1111)))) rrrrnnnndddd((((1111)))) ((((TTTThhhheeee ccccoooonnnntttteeeennnnttttssss ooooffff ffffiiiilllleeee """"ppppiiiirrrraaaatttteeeessss""""....)))) -------------------------------------- A sample pirate ----- stat strength 3 6 \t stat brains 3 6 possesions: subtable possesions 1 100 table anothertable 0 0 --------------------------------------- \l This would generate a pirate with 3d6 strength and brains, and would make a 1d100 roll on the possessions table. The table "anothertable" is then opened and treated as a new list of commands. Note that there are 2 numbers provided with the table command. This is MANDATORY, and due only to lazy programming. Live with it. Appropriate tabs and a formfeed are generated. ((((ccccoooonnnntttteeeennnnttttssss ooooffff ffffiiiilllleeee """"ppppoooosssssssseeeessssiiiioooonnnnssss"""")))) 10 Cutlass 20 Fists 30 Bucket of Grog 40 Belaying Pin 90 Just his clothes 100 Fancy clothes In this example, when the subtable was called, a 1d100 was rolled (be sure to specify that right!) 0-10 yields a Cutlass, 11-20 Fists, etc. SSSSPPPPEEEECCCCIIIIAAAALLLL CCCCOOOONNNNSSSSIIIIDDDDEEEERRRRAAAATTTTIIIIOOOONNNNSSSS o+ This program is machine dependent to the degree that your machine likes recursion. o+ A random seed is requested for portability's sake. Those of you on UNIX systems are urged to use something like: srand((unsigned)getpid()); instead. Any random stirrer is appropriate. UTX/32 User's Reference Manual -3- Commands rrrrnnnndddd((((1111)))) rrrrnnnndddd((((1111)))) o+ This program has run on Gould Powernode 9080, Sun workstations, and my IBM compatible clone (lattice 3.0). On the PCs, usually some provision will have to be made to increase the stack size. On lattice this is done by declaring a global "int _stack = 6000;" or about 6 kilobytes. You decide. No guarantees are made for other compilers/machines, but anything that is not braindead can handle it. o+ Capturing the output:: This program is designed for systems with I/O redirection. If you don't have it, I suggest you frob the code slightly by changing all printfs to fprintf, etc. For you with I/O redirection, the initial text, etc, is returned on the standard error, so it will not kluge up your characters as they are generated. Pipe the output through your favorite formatter, editor, printer, or null device. SSSSEEEEEEEE AAAALLLLSSSSOOOO no references AAAAuuuutttthhhhoooorrrr:::: Marcus J Ranum, Gould Inc. All rights reserved. Don't use this to make money, or sell. Otherwise it may be freely copied, deleted, and hacked. Give me credit for the idea, if not the code. UTX/32 User's Reference Manual -4- Commands End of README echo unbundling food if [ -f food ] ; then echo cannot unbundle food - file exists already exit 1 fi cat >food <<'End of food' 10 Sushi 20 Salad 30 Wiskey 40 Sandwitches 50 Port 60 Scotch 70 Vodka 80 Caviar 90 Gum 100 Beer End of food echo unbundling nationality if [ -f nationality ] ; then echo cannot unbundle nationality - file exists already exit 1 fi cat >nationality <<'End of nationality' 10 Spanish 20 French 30 English 40 Japanese 50 Dutch 60 Portugese 70 Italian 80 Russian 90 German 100 Other End of nationality echo unbundling pirate if [ -f pirate ] ; then echo cannot unbundle pirate - file exists already exit 1 fi cat >pirate <<'End of pirate' -------------------------------------------------------------- Sample Pirate ------------- stat strength 3 6 \t \t stat intelligence 3 6 \t \t stat seamanship 3 6 stat swordplay 3 6 \t \t stat looks 3 6 \t \t stat education 1 10 nationality: subtable nationality 1 100 Possessions: table possesions 0 0 End of pirate echo unbundling possesions if [ -f possesions ] ; then echo cannot unbundle possesions - file exists already exit 1 fi cat >possesions <<'End of possesions' Weapons: subtable weapon 1 100 Food: subtable food 1 100 subtable food 1 100 stat cash 3 30 Skills: subtable skill 1 100 \t subtable skill 1 100 \t subtable skill 1 100 End of possesions echo unbundling rnd.c if [ -f rnd.c ] ; then echo cannot unbundle rnd.c - file exists already exit 1 fi cat >rnd.c <<'End of rnd.c' #include "stdio.h" /* rnd.c : written by Marcus J Ranum. All rights reserved. You may freely copy, modify, distribute, and delete this program, but any use that will cause you to gain money is prohibited unless we work out a deal, or you ask me nicely. Any nifty ideas/modifications, please let me know. -mjr */ main(argc,argv) int argc; char *argv[]; { int a; char seed[100]; char word[200]; int iterations; fprintf(stderr,"Please enter a random # and press <return>: "); srand((unsigned)atoi(gets(seed))); fprintf(stderr,"How many runs do you want and press <return>: "); iterations=atoi(gets(word)); while(iterations--) { for(a=1 ; a < argc; a++) { dotable(argv[a]); } } } /* roll a <sides> sided die <num> times */ roll(num,sides) int num; int sides; { int a=0; while (num-- >0) a += rnd(sides); return(a); } /* roll a <die> sided die once */ rnd(die) register die; { return(((rand()>>3) % die)+1); } /* reads a table. calls doparse for every line in the table. note, if doparse hits another table name, this will be recursively called. max recursions seems to be around 10.... */ dotable(table) char table[]; { FILE *file; char junk[BUFSIZ]; if((file = fopen(table,"r")) ==NULL) { fprintf(stderr,"can't open %s\n",table); exit(1); } while((fgets(junk,BUFSIZ-3,file)) !=NULL){ doparse(junk); } if(fclose(file) == EOF) { fprintf(stderr,"unusual file-close error!\n"); exit(1); } } /* reads a subtable, and looks for a numbered entry that matches the given selection criterion. <stat> is the table name, <num> is the number of dice, and <die> is the number of sides on the die. the subtables must be in the form of <#> entry (text string terminated with newline) */ dosubtable(stat,num,die) char stat[]; int num; int die; { FILE *tbl; char junk[BUFSIZ]; char word[100]; int old; int new; int pick; old=0; if((tbl=fopen(stat,"r")) == NULL){ fprintf(stderr,"can't open subtable %s\n",stat); } else { pick = roll(num,die); while((fgets(junk,BUFSIZ-3,tbl)) != NULL){ if(sscanf(junk,"%d",&new)) { if( pick > old && pick <= new) { if (sscanf(junk,"%s",word)==0) { fprintf(stderr, "invalid format\n"); exit(1); } junk[strlen(junk)-1] = ' '; dohack(junk); break; } old = new; } } } if(fclose(tbl)== EOF) { fprintf(stderr,"unusual file-close error\n"); exit(1); } } /* prints a stat <stat> is statname, <num> is number of dice, and <die> is how many sides the die has. */ dostat(stat,num,die) char stat[]; int num; int die; { printf("%s:_______ \b\b\b\b\b\b\b%d",stat,roll(num,die)); } /* doparse looks for the magic words "table" "subtable" "\t" or "\l" and does the appropriate thing if it encounters them. if it does not, it dumps it as text */ doparse(junk) char junk[]; { char stat[200]; char word[200]; int die; int num; if( junk[0] == '\\') { switch(junk[1]) { case 'l': putchar(12); break; case 't': printf("\t"); break; } } else { if((sscanf(junk,"%s %s %d %d", word,stat,&num,&die))!= 4) { dotext(junk); } else { if(!strcmp(word,"subtable")) dosubtable(stat,num,die); else if(!strcmp(word,"stat")) dostat(stat,num,die); else if(!strcmp(word,"table")) dotable(stat); else dotext(junk); } } } /* dohack takes a string and dumps all of it except the FIRST word. this is basically useful for printing subtable entries. while there are more elegant ways of doing this in C, my Lattice compiler dies when I try it. */ dohack(string) char string[BUFSIZ]; { int a = 0; int b = 0; while(b == 0) if(string[a++] == ' ') b++; for(b = a; b < strlen(string); b++) putchar(string[b]); } /* dotext basically dumps a text string. if the 2nd to last char (newline is last) is a ":" it does NOT put a newline on. this is to allow you to have items from subtables appear after a : */ dotext(string) char string[BUFSIZ]; { int c; c = (strlen(string)-2); if (string[c] == ':') string[++c] = ' '; printf("%s",string); } End of rnd.c echo unbundling rnd.nro if [ -f rnd.nro ] ; then echo cannot unbundle rnd.nro - file exists already exit 1 fi cat >rnd.nro <<'End of rnd.nro' .EV .T1 rnd 1 2/14/84 .SH "rnd \- Random stuff generator" (MJR) .SY .B rnd file1 file2, etc... .OP Each file must be a 'table' file. .DS .I Rnd reads a file and writes generated data to the standard output. If more than one file is specified, the files are used as descriptions in sequence and merged output is written to the standard output. Two types of files are recognized; table files, and subtable files. A table file can contain several types of information: .TP 8 \(bu Text-formats and text: these are simply text that will be included in the output. There are, however, a few simple rules. Any text string ending in a ':' (no space afterwards) will NOT have a newline appended after it. This is to allow a user to have the output from a subtable placed after a text string. Other text that does not have a ':' as the last character before a newline is printed as is. .TP \(bu Tabs and Formfeeds: to insert tabs and formfeeds, a "\\t" (tab) or a "\\l" (formfeed ^L) is placed as the ONLY thing on that line. There must be no other blank spaces, etc. This is inelegant, but allows the program to run reasonably fast. If a tab is coded, the program does NOT automatically generate a newline after it. This is to allow a user to separate stats with tabs reasonably easily. .TP \(bu Stat commands: a stat command goes at the beginning of an otherwise empty line, in the format .nf .na "stat <char string WITHOUT spaces> <# of dice> <type of dice>" .fi .ad Rnd will then roll the appropriate number of N-sided dice, and print the stat on the standard output. .TP \(bu Subtable commands: a subtable command goes at the beginning of an otherwise empty line, in the format .nf .na "subtable <filename> <# of dice> <type of dice>" .fi .ad Rnd will then roll the appropriate number of N-sided dice, and open the subtable file. A subtable file is constructed as follows: .nf .na <#upper limit> <text string> <#upper limit> <text string> ... etc. .fi .ad Subtable files may not contain any extraneous text (unlike table files) and all the entries must be in order, with the highest numerical entry as the last line in the file, and the lowest numerical entry as the first. Each line must contain a number value and a text string, or the results may be unusual. .TP \(bu Table commands: a table can contain a list of tables as well as stats, format commands, text, and subtables. Each table is randomized recursively, until your stack overflows, or it returns, whichever comes first. Stacking tables deeply is usually not necessary, since several tables can be called on the command line, and that is much safer. When a table command is invoked, it must be followed by two numbers so that the semi-braindead parser recognizes it as a command. .nf .na table <filename> <bogus #><bogus #> .fi .ad Each table can contain its own list of subtables and stats, as well as text and format commands. This is lots of fun, since it allows the discerning DM to make a variety of layered tables and gives excellent control over how much is generated. For example if you determine that an encounter consists of monsters, booty, and terrain, you could have a table that invoked the monster table, then the terrain one, then the booty. In this way, you would still have a perfectly usable set of tables for any time you wanted just a monster, or just terrain. The possibilities as far as maintaining standard libraries are considerable, obviously. The primary limitation is your machine's stack and disk space. .EX $ \fBrnd pirates\fR .EE .B rnd reads the table named .B pirates and executes all the stats, subtables, and tables contained therein. .E2 .sp .sp .sp .sp \fB(The contents of file "pirates".)\fR .EE .nf .na -------------------------------------- A sample pirate ----- stat strength 3 6 \\t stat brains 3 6 possesions: subtable possesions 1 100 table anothertable 0 0 --------------------------------------- \\l .fi .ad This would generate a pirate with 3d6 strength and brains, and would make a 1d100 roll on the possessions table. The table "anothertable" is then opened and treated as a new list of commands. Note that there are 2 numbers provided with the table command. This is MANDATORY, and due only to lazy programming. Live with it. Appropriate tabs and a formfeed are generated. .E2 \fB(contents of file "possesions")\fP .EE .nf .na 10 Cutlass 20 Fists 30 Bucket of Grog 40 Belaying Pin 90 Just his clothes 100 Fancy clothes .fi .ad In this example, when the subtable was called, a 1d100 was rolled (be sure to specify that right!) 0-10 yields a Cutlass, 11-20 Fists, etc. .SC .TP 2 \(bu This program is machine dependent to the degree that your machine likes recursion. .TP \(bu A random seed is requested for portability's sake. Those of you on UNIX systems are urged to use something like: .nf .na srand((unsigned)getpid()); .fi .ad instead. Any random stirrer is appropriate. .TP \(bu This program has run on Gould Powernode 9080, Sun workstations, and my IBM compatible clone (lattice 3.0). On the PCs, usually some provision will have to be made to increase the stack size. On lattice this is done by declaring a global "int _stack = 6000;" or about 6 kilobytes. You decide. No guarantees are made for other compilers/machines, but anything that is not braindead can handle it. .TP \(bu Capturing the output:: This program is designed for systems with I/O redirection. If you don't have it, I suggest you frob the code slightly by changing all printfs to fprintf, etc. For you with I/O redirection, the initial text, etc, is returned on the standard error, so it will not kluge up your characters as they are generated. Pipe the output through your favorite formatter, editor, printer, or null device. .IN no references .I0 Author: Marcus J Ranum, Gould Inc. All rights reserved. Don't use this to make money, or sell. Otherwise it may be freely copied, deleted, and hacked. Give me credit for the idea, if not the code. End of rnd.nro echo unbundling skill if [ -f skill ] ; then echo cannot unbundle skill - file exists already exit 1 fi cat >skill <<'End of skill' 5 pickpocketing 10 torture 30 basic seamanship 35 zymurgy 40 gunnery 45 rope-tying 50 liguistics 55 reading/writing 60 bookeeping 65 farming 70 gunsmithing 72 metalsmithing 80 woodworking 85 whaling 90 shipbuilding 95 navigation 97 storytelling/music 100 cartography End of skill echo unbundling weapon if [ -f weapon ] ; then echo cannot unbundle weapon - file exists already exit 1 fi cat >weapon <<'End of weapon' 10 Bad Breath 20 Musket 30 Cutlass 40 Belaying Pin. 50 Cane 60 Bottle 100 Fists End of weapon