Here's gensec, mapsub, and subsec.csh (Three programs for the price of one!). The way you'd typically use them in concert is like so: First you'd Generate a Sector and put the output in a file named "sector". Type: gensec >sector Or if you wanted only 20% of the hexes populated by worlds, the remainder being empty (50% is the default) gensec 20 >sector Then use subsec.csh to extract a single subsector's worth of worlds. For example, to extract the subsector (2, 3), that is, the X'ed subsector in this illustration: 0000 0000 0X00 0000 Type: subsec 2 3 subsector23 Now you can produce a hex-grid map with the Map Subsector program, to wit: mapsub "gensec.c" << '//E*O*F gensec.c//' /* gensec - a simplistic traveller sector generator Simplistic Traveller sector generator, based on Mark Miller, "Traveller Sector Generator", Using Your Model 2/BIS, Challenge No. 25; and other Traveller material (Books 3 and 6, and wherever T-norm, T-prime designations came from). The format of the output of gensec is a superset of the material produced by Mr. Miller's published program. A field is included for system name, and a "P" field in the last column notes the existence of a planetoid belt not in the main world orbit. This program rewritten C and Unix Aug 18 1987 by James T. Perkins. (jamesp@dadla.wr.tek.com, seismo!tektronix!dadla!jamesp) 4.2BSD Unix sites Compile/link with: cc -s -O -o gensec gensec.c Sys V Unix sites Compile/link with: cc -s -O -o gensec -DSYSV gensec.c ----------------------------------------------------------------- Copyright 1987 James T. Perkins This notice and any statement of authorship must be reproduced on all copies. The author does not make any warranty expressed or implied, or assumes any liability or responsiblity for the use of this software. Any distributor of copies of this software shall grant the recipient permission for further redistribution as permitted by this notice. Any distributor must distribute this software without any fee or other monetary gains, unless expressed written permission is granted by the author. This software or its use shall not be: sold, rented, leased, traded, or otherwise marketed without the expressed written permission of the author. If the software is modified in a manner creating derivative copyrights, appropriate legends may be placed on derivative work in addition to that set forth above. Permission is hereby granted to copy, reproduce, redistribute or otherwise use this software as long as the conditions above are met. All rights not granted by this notice are reserved. */ /* System include files */ #include #include #include /* Local macros */ #define D2 nd(2, 6) #define D1 d(6) #define DM(test, dm) ((test) ? (dm) : 0) #define limit(x, l, u) (((x) < (l)) ? (l) : (((x) > (u)) ? (u) : (x))) /* allows compilation on System V Unix */ #ifdef SYSV #define random rand #define srandom srand #endif /* Boolean type declaration */ typedef int bool; /* External (system) functions */ extern long srandom(), random(), time(); /* Forward declarations */ char hexchar(); /* Source module globals */ static char *copyright = "Copyright 1987 James Perkins"; main(ac, av) int ac; char **av; { int x, y; int genpctg = 50; char *progname; (void)srandom(time(NULL)); progname = *av; av++; ac--; if (ac > 0) { if (isdigit(**av)) genpctg = atoi(*av); else { fprintf(stderr, "usage: %s generation_percentage\n", progname); exit(1); } } for (x = 1; x <= 32; x++) { for (y = 1; y <= 40; y++) { if (d(100) < genpctg) { gensys(x, y, stdout); } } } exit(0); } gensys(x, y, fp) int x, y; FILE *fp; { static char *nam = "Unnamed", *ali = "IM"; char cla, bas, zon, tra[50], out[81]; int siz, atm, hyd, pop, gov, law, tl; bool gas, sco, nav, pla; /* Starport class */ cla = *("AAABBCCDEEX" + D2 - 2); /* Physical characteristics */ siz = D2 - 2; atm = D2 - 7 + siz; atm = ((siz == 0) ? 0 : atm); atm = limit(atm, 0, 15); hyd = D2 - 7 + siz + ((atm < 2 || atm > 9) ? -4 : 0); hyd = ((siz < 2) ? 0 : hyd); hyd = limit(hyd, 0, 10); /* Demographics */ pop = D2 - 2; gov = D2 - 7 + pop; gov = limit(gov, 0, 13); law = D2 - 7 + gov; law = limit(law, 0, 20); /* Technological level */ tl = D1 + DM(cla == 'A', 6) + DM(cla == 'B', 4) + DM(cla == 'C', 2) + DM(cla == 'X', -4); tl = tl + DM(siz < 5, 1) + DM(siz < 2, 1); tl = tl + DM(atm < 4, 1) + DM(atm > 9 && atm < 15, 1); tl = tl + DM(hyd == 8, 1) + DM(hyd == 9, 2); tl = tl + DM(pop > 0 && pop < 6, 1) + DM(pop == 9, 2) + DM(pop == 10, 4); tl = tl + DM(gov == 0 || gov == 5, 1) + DM(gov == 13, -2); tl = limit(tl, 0, 15); /* System caharacteristics */ gas = (D2 < 10); pla = (D2 < 7); /* Travel advisories */ zon = ((cla == 'X') ? 'R' : ((D2 > 10) ? 'A' : ' ')); /* Bases */ nav = (cla < 'C' && D2 > 7); sco = (cla < 'E' && (D2 + DM(cla == 'A', -3) + DM(cla == 'B', -2) + DM(cla == 'C', -1) > 6)); bas = (nav && sco ? 'A' : (nav ? 'N' : (sco ? 'S' : ' '))); /* Trade classifications */ *tra = '\0'; if (siz > 5 && atm > 3 && atm < 10 && hyd > 2 && hyd < 10) { if (siz > 6 && siz < 10 && atm > 5 && atm < 8 && hyd > 4 && hyd < 8) strcat(tra, "TN "); /* Terran-Norm */ else strcat(tra, "TP "); /* Terran-Prime */ } if (atm > 3 && atm < 10 && hyd > 3 && hyd < 9 && pop > 4 && pop < 8) strcat(tra, "AG "); /* Agricultural */ if (atm < 4 && hyd < 4 && pop > 5) strcat(tra, "NA "); /* Non-Agricultural */ if ((atm < 3 || atm == 4 || atm == 7 || atm == 9) && pop > 8) strcat(tra, "IN "); /* Industrial */ if (pop < 7) strcat(tra, "NI "); /* Non-Industrial */ if ((atm == 6 || atm == 8) && pop > 5 && pop < 9 && gov > 3 && gov < 10) strcat(tra, "RI "); /* Rich */ if (atm > 1 && atm < 6 && hyd < 4) strcat(tra, "PO "); /* Poor */ if (hyd == 10) strcat(tra, "WA "); /* Water World */ if (hyd == 0 && atm > 1) strcat(tra, "DE "); /* Desert World */ if (siz == 0) strcat(tra, "AS "); /* Asteroid Belt */ if (atm == 0 && siz > 0) strcat(tra, "VA "); /* Vaccuum World */ if (pop == 0 && gov == 0 && law == 0) strcat(tra, "BA "); /* ??? */ /* Create world data string */ sprintf(out, "%02d%02d %-18s %c%c%c%c%c%c%c-%c %c %-15s %2s %c %c%c\n", x, y, nam, cla, hexchar(siz), hexchar(atm), hexchar(hyd), hexchar(pop), hexchar(gov), hexchar(law), hexchar(tl), bas, tra, ali, zon, (gas ? 'G' : ' '), (pla ? 'P' : ' ')); /* Write it out to file (or stdout) */ fwrite(out, strlen(out), 1, fp); } char hexchar(i) int i; { if (i < 0 || i > 34) return '?'; else return *("0123456789ABCDEFGHJKLMNPQRSTUVWXYZ" + i); } int d(nsides) int nsides; { return (int)(random() % (long)nsides + 1L); } int nd(ndice, nsides) int ndice, nsides; { if (ndice < 1) { return 0; } else { return nd(ndice - 1, nsides) + d(nsides); } } //E*O*F gensec.c// echo x - mapsub.c cat > "mapsub.c" << '//E*O*F mapsub.c//' /* * mapsub - produce a subsector hex-grid map from subsector data * * SYNOPSIS * mapsub Subsector.hex * * DESCRIPTION * Mapsub processes the star system information on the standard input and * produces a hex-grid map on the standard output. The format expected is * very precise, consisting of one formatted line for each system, each * exactly 59 bytes followed by a newline. Example: * * "0102 Gruenwald B775400-9 N TN NI TI GP" * * XXYY System_Name_______ UPP______ B Trade_Codes___ Al Z GP * * Where: * XXYY is the system coordinates, in the range 0101-3240 * System_Name is the 18-character system name * UPP is the Universal Planetary profile * B is the base code: 'N' navy, 'S' scout, 'A' both * Trade_Codes is up to fifteen characters worth of Trading codes, * from this list: TN (Terran-Norm), TP (Terran-Prime), AG * (Agricultural), NA (Non-AG), IN (Industrial), NI (Non-IN), RI * (Rich), PO (Poor), WA (Water world), DE (Desert World), VA * (Vacuum World), AS (Asteroid Belt), IC (Ice-capped), CA (Subsector * Capitol) * The only Trade_Code that does anything is CA, which causes * "Capitol" to be written into the system's hex. * Al is the system alignment. "IM" is the official alignment for the * Imperium. I use "IN" for independent worlds. * Z is the travel zone, either " " (Green), "A" (Amber), "R" (Red). * G is the gas-giant-present flag, either " " (None), or "G" (exists). * P is the planetoids-present flag, either " " (None), or "P" (exists). * * The output is an array of empty and filled hexes, which look like this: * ______ * / XXYY \ XXYY - System coordinates * / * S T \ * - Navy base, S - Starport class, T - Tech level * / ^ O . \ ^ - Scout base, "O" "@" or "%" - Desert or Non-desert * \ System / world or Asteroid belt, . - Gas giant * \ Name / * \Zone__/ Zone - blank for green or "amber" or "RED" or "Capitol" * * WARNING * Mapsub assumes that the input is only one subsector's worth of worlds. * if you give it a sector's worth of worlds (Example: "gensec | mapsub") * it will print them all, overlapping many worlds! * * Ergo, if you want to generate worlds automatically, use gensec to * produce a sector file of systems, then glean all the worlds belonging * to a particular sector out of that file and put it in a subsector file, * then use mapsub on the new subsector file. * * TO COMPILE * On BSD systems, use: cc -O -s -o mapsub mapsub.c * On SYSV systems, use: cc -O -DSYSV -s -o mapsub mapsub.c * * FILES * Needs a blank hex map template, defined by default to be "./subhex". * Change the MAP_TEMPLATE parameter to change this file's location. * * SEE ALSO * gensec - the sector generator program (produces compatible format) * * AUTHOR * James T. Perkins, jamesp@dadla.wr.tek.com, tektronix!dadla!jamesp * * BUGS * The code is somewhat messy, as it was hacked out on a whim. * * As stated above, one cannot simply use the output of gensec as input * to mapsub -- the gensec output must be edited. */ /* Copyright 1987 James T. Perkins This notice and any statement of authorship must be reproduced on all copies. The author does not make any warranty expressed or implied, or assumes any liability or responsiblity for the use of this software. Any distributor of copies of this software shall grant the recipient permission for further redistribution as permitted by this notice. Any distributor must distribute this software without any fee or other monetary gains, unless expressed written permission is granted by the author. This software or its use shall not be: sold, rented, leased, traded, or otherwise marketed without the expressed written permission of the author. If the software is modified in a manner creating derivative copyrights, appropriate legends may be placed on derivative work in addition to that set forth above. Permission is hereby granted to copy, reproduce, redistribute or otherwise use this software as long as the conditions above are met. All rights not granted by this notice are reserved. */ #define MAP_TEMPLATE "./subhex" /* empty hex grid file */ #include #include #define mapxy(x, y, dx, dy) \ &map[(((y) - 1) % 10) * 6 + ((2 * ((x) / 2) == (x)) ? 1 : 4) + (dy)]\ [(((x) - 1) % 8) * 9 + 1 + (dx)] /* * Define SYSV if you are compiling on any machine that uses the string * library functions strchr() and strrchr(). Do not define SYSV for BSD-like * machines, which use index() and rindex(). */ #ifdef SYSV #define index strchr #endif char map[70][81]; /* in-memory copy of map that we operate on */ main(ac, av, envp) int ac; char *av[], *envp[]; { char *center_name(); char system[81], name[19], upp[10], trade[16], align[3]; char base, zone, gas, belt; char *s; int n_lines = 0, x, y, i; FILE *fp; /* * Read hex map template into memory */ if ((fp = fopen(MAP_TEMPLATE, "r")) == NULL) { fprintf(stderr, "%s: cannot open %s.\n", av[0], MAP_TEMPLATE); exit(1); } while (!feof(fp)) { fgets(map[n_lines++], 81, fp); } fclose(fp); /* * Start with empty, null-terminated strings */ bzero(name, sizeof(name)); bzero(upp, sizeof(upp)); bzero(trade, sizeof(trade)); bzero(align, sizeof(align)); /* * Read in each system and place it on the map */ while (!feof(stdin)) { fgets(system, sizeof(system), stdin); sscanf(system, "%2d%2d%*c%18c%*c%9c%*c%c%*c%15c%*c%2c%*c%c%*c%c%c\n", &x, &y, name, upp, &base, trade, align, &zone, &gas, &belt); rem_trail_sp(name); rem_trail_sp(trade); /* * write hex: * ______ * &/ XXYY \ & is where the x, y offset is * / * S T \ * / ^ @ . \ * \ System / * \ Name / * \______/ */ overwrite(mapxy(x, y, 3, 0), system, 4); overwrite(mapxy(x, y, 3, 1), ((base == 'N' || base == 'A') ? "*" : " "), 1); overwrite(mapxy(x, y, 5, 1), upp, 1); overwrite(mapxy(x, y, 7, 1), &upp[8], 1); overwrite(mapxy(x, y, 2, 2), ((base == 'S' || base == 'A') ? "^" : " "), 1); overwrite(mapxy(x, y, 4, 2), ((upp[1] == '0') ? "%" : ((upp[3] == '0') ? "O" : "@")), 1); overwrite(mapxy(x, y, 6, 2), ((gas == 'G') ? "." : " "), 1); s = center_name(name, x, y, 0, 3, 10); if (s) { (void)center_name(s + 1, x, y, 1, 4, 8); } switch (zone) { case 'A': /* Amber */ overwrite(mapxy(x, y, 2, 5), "amber", 5); break; case 'R': /* Red */ overwrite(mapxy(x, y, 2, 5), "RED", 3); break; } s = trade; while (s = index(s, 'C')) { if (strncmp(s, "CA", 2) == NULL) { overwrite(mapxy(x, y, 2, 5), "Capitol", 7); break; } else { s++; } } } /* * Write out finished map */ for (i = 0; i < n_lines; i++) { printf("%s", map[i]); } exit(0); } /* * Remove all trailing spaces from the given null-terminated string */ rem_trail_sp(s) char *s; { int i = strlen(s) - 1; while (i >= 0 && s[i] == ' ') { s[i--] = '\0'; } } /* * Overwrite memory, beginning at the given address, with the first n * characters of the given string. */ overwrite(p, s, n) char *p, *s; int n; { while (n-- > 0) { *p++ = *s++; } } /* * Take the given name and try to place as many words as will fit in given * width into the map at x, y, dx, dy. Also center each line of output in * the width. Unfortunately, a word may be too long to fit, in which case * place it anyway, overlapping. * * Returns 0 if it was able to place the entire name, or a pointer to the * space just following the last word placed. Nasty kludges, beware, */ char * center_name(name, x, y, dx, dy, width) char *name; int x, y, dx, dy, width; { char *end, *cur, *retval, *oldcur; int offset; cur = index(name, ' '); if (cur == NULL) { end = name + strlen(name) - 1; } else { end = cur - 1; oldcur = NULL; while (cur && cur - name <= width && cur != oldcur) { end = cur - 1; oldcur = cur; cur = index(cur + 1, ' '); if (cur == NULL) { cur = name + strlen(name); } } } offset = (width - (end - name + 1)) / 2; dx = dx + offset; overwrite(mapxy(x, y, dx, dy), name, end - name + 1); if (index(end, ' ')) { return end + 1; } else { return NULL; } } //E*O*F mapsub.c// echo x - subhex cat > "subhex" << '//E*O*F subhex//' ______ ______ ______ ______ / \ / \ / \ / \ / \ / \ / \ / \ ______/ \______/ \______/ \______/ \ / \ / \ / \ / \ / / \ / \ / \ / \ / / \______/ \______/ \______/ \______/ \ / \ / \ / \ / \ \ / \ / \ / \ / \ \______/ \______/ \______/ \______/ \ / \ / \ / \ / \ / / \ / \ / \ / \ / / \______/ \______/ \______/ \______/ \ / \ / \ / \ / \ \ / \ / \ / \ / \ \______/ \______/ \______/ \______/ \ / \ / \ / \ / \ / / \ / \ / \ / \ / / \______/ \______/ \______/ \______/ \ / \ / \ / \ / \ \ / \ / \ / \ / \ \______/ \______/ \______/ \______/ \ / \ / \ / \ / \ / / \ / \ / \ / \ / / \______/ \______/ \______/ \______/ \ / \ / \ / \ / \ \ / \ / \ / \ / \ \______/ \______/ \______/ \______/ \ / \ / \ / \ / \ / / \ / \ / \ / \ / / \______/ \______/ \______/ \______/ \ / \ / \ / \ / \ \ / \ / \ / \ / \ \______/ \______/ \______/ \______/ \ / \ / \ / \ / \ / / \ / \ / \ / \ / / \______/ \______/ \______/ \______/ \ / \ / \ / \ / \ \ / \ / \ / \ / \ \______/ \______/ \______/ \______/ \ / \ / \ / \ / \ / / \ / \ / \ / \ / / \______/ \______/ \______/ \______/ \ / \ / \ / \ / \ \ / \ / \ / \ / \ \______/ \______/ \______/ \______/ \ / \ / \ / \ / \ / / \ / \ / \ / \ / / \______/ \______/ \______/ \______/ \ / \ / \ / \ / \ \ / \ / \ / \ / \ \______/ \______/ \______/ \______/ \ / \ / \ / \ / \ / / \ / \ / \ / \ / / \______/ \______/ \______/ \______/ \ / \ / \ / \ / \ \ / \ / \ / \ / \ \______/ \______/ \______/ \______/ \ / \ / \ / \ / \ / / \ / \ / \ / \ / / \______/ \______/ \______/ \______/ \ / \ / \ / \ / \ / \ / \ / \ / \______/ \______/ \______/ \______/ //E*O*F subhex// echo x - subsec cat > "subsec" << '//E*O*F subsec//' #!/bin/csh -f # # subsec [-v] x y [filename...] # # -v option causes it to print the patterns it would use and exit. # # any extra args are used as filenames by EGREP instead of the standard # input. # # legal values of x and y are 1 2 3 and 4 . # # picks only those lines in GENSEC format which are within the (x,y)th # subsector, where 0101 is in the (1,1)th and 3240 is in the (4,4)th, # with 0840 in the (1,4)th and 3210 in the (4,1)th. # # designed for use as: # : sh shell script # gensec > sector.$n # for x in 1 2 3 4 ; do # for y in 1 2 3 4 ; do # subsec $x $y sector.$n > map.$n.$x$y # done ; done # # egrep patterns for 01-08, 09-16, 17-24, 25-32. set x=("0[1-8]" "09|1[0-6]" "1[7-9]|2[0-4]" "2[5-9]|3[0-2]") # # egrep patterns for 01-10, 11-20, 21-30, 31-40. set y=("0[1-9]|10" "1[1-9]|20" "2[1-9]|30" "3[1-9]|40") # if ($1 == "-v" ) then echo "$x[$2] $y[$3]" else egrep "^($x[$1])($y[$2])" $argv[3-] endif //E*O*F subsec// exit 0