/*
* Empire - A multi-player, client/server Internet based war game.
* Copyright (C) 1986-2021, Dave Pare, Jeff Bailey, Thomas Ruschak,
* Ken Stevens, Steve McClure, Markus Armbruster
*
* Empire is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* ---
*
* See files README, COPYING and CREDITS in the root of the source
* tree for related information and legal notices. It is expected
* that future projects/authors will amend these files as needed.
*
* ---
*
* fairland.c: Create a nice, new world
*
* Known contributors to this file:
* Ken Stevens, 1995
* Steve McClure, 1998
* Markus Armbruster, 2004-2020
*/
/*
* How fairland works
*
* 1. Place capitals
*
* Place the capitals on the torus in such a way so as to maximize
* their distances from one another. This uses the perturbation
* technique of calculus of variations.
*
* 2. Grow start islands ("continents")
*
* For all continents, add the first sector at the capital's location,
* and the second right to it. These are the capital sectors. Then
* add one sector to each continent in turn, until they have the
* specified size.
*
* Growth uses weighted random sampling to pick one sector from the
* set of adjacent sea sectors that aren't too close to another
* continent. Growth operates in spiking mode with a chance given by
* the spike percentage. When "spiking", a sector's weight increases
* with number of adjacent sea sectors. This directs the growth away
* from land, resulting in spikes. When not spiking, the weight
* increases with the number of adjacent land sectors. This makes the
* island more rounded.
*
* If growing fails due to lack of room, start over. If it fails too
* many times, give up and terminate unsuccessfully.
*
* 3. Place and grow additional islands
*
* Each continent has a "sphere of influence": the set of sectors
* closer to it than to any other continent. Each island is entirely
* in one such sphere, and each sphere contains the same number of
* islands with the same sizes.
*
* First, split the specified number of island sectors per continent
* randomly into the island sizes. Sort by size so that larger
* islands are grown before smaller ones, to give the large ones the
* best chance to grow to their planned size.
*
* Then place one island's first sector into each sphere, using
* weighted random sampling with weights favoring sectors away from
* land and other spheres. Add one sector to each island in turn,
* until they have the intended size. Repeat until the specified
* number of islands has been grown.
*
* If placement fails due to lack of room, start over, just like for
* continents.
*
* Growing works as for continents, except the minimum distance for
* additional islands applies, and growing simply stops when any of
* the islands being grown lacks the room to grow further. The number
* of sectors not grown carries over to the next island size.
*
* 4. Compute elevation
*
* First, use a simple random hill algorithm to assign raw elevations:
* initialize elevation to zero, then randomly raise circular hills on
* land / lower circular depressions at sea. Their size and height
* depends on the distance to the coast.
*
* Then, elevate islands one after the other.
*
* Set the capitals' elevation to a fixed value. Process the
* remaining sectors in order of increasing raw elevation, first
* non-mountains, then mountains. Non-mountain elevation starts at 1,
* and increases linearly to just below "high" elevation. Mountain
* elevation starts at "high" elevation, and increases linearly.
*
* This gives islands of the same size the same set of elevations.
* Larger islands get more and taller mountains.
*
* Finally, elevate sea: normalize the raw elevations to [-127:-1].
*
* 5. Set resources
*
* Sector resources are simple functions of elevation. You can alter
* iron_conf[], gold_conf[], fert_conf[], oil_conf[], and uran_conf[]
* to customize them.
*/
#include
#include
#include
#include
#include
#include
#include
#include "chance.h"
#include "optlist.h"
#include "path.h"
#include "prototypes.h"
#include "sect.h"
#include "version.h"
#include "xy.h"
/*
* Number of retries when growing land fails
*/
#define NUMTRIES 10
/* do not change these defines */
#define LANDMIN 1 /* plate altitude for normal land */
#define PLATMIN 36 /* plate altitude for plateau */
#define HIGHMIN 98 /* plate altitude for mountains */
/*
* Resource configuration
* Resources are determined by elevation. The map from elevation to
* resource is defined as a linear interpolation of resource data
* points (elev, res) defined in the tables below. Elevations range
* from -127 to 127, and resource values from 0 to 100.
*/
struct resource_point {
int elev, res;
};
struct resource_point iron_conf[] = {
{ -127, 0 },
{ 21, 0 },
{ 85, 100 },
{ HIGHMIN - 1, 100 },
{ HIGHMIN , 0 },
{ 127, 0 } };
struct resource_point gold_conf[] = {
{ -127, 0 },
{ 35, 0 },
{ HIGHMIN - 1, 80 },
{ HIGHMIN, 80 },
{ 127, 85 } };
struct resource_point fert_conf[] = {
{ -127, 100 },
{ -59, 100 },
{ LANDMIN - 1, 41 },
{ LANDMIN, 100 },
{ 10, 100 },
{ 56, 0 },
{ 127, 0 } };
struct resource_point oil_conf[] = {
{ -127, 100 },
{ -49, 100 },
{ LANDMIN - 1, 2 },
{ LANDMIN, 100 },
{ 6, 100 },
{ 34, 0 },
{ 127, 0 } };
struct resource_point uran_conf[] = {
{ -127, 0 },
{ 55, 0 },
{ 90, 100 },
{ 97, 100 },
{ 98, 0 },
{ 127, 0 } };
/*
* Program arguments and options
*/
static char *program_name;
static int nc, sc; /* number and size of continents */
static int ni, is; /* number and size of islands */
#define DEFAULT_SPIKE 10
static int sp = DEFAULT_SPIKE; /* spike percentage */
#define DEFAULT_MOUNTAIN 0
static int pm = DEFAULT_MOUNTAIN; /* mountain percentage */
#define DEFAULT_CONTDIST 2
static int di = DEFAULT_CONTDIST; /* min. distance between continents */
#define DEFAULT_ISLDIST 1
static int id = DEFAULT_ISLDIST; /* ... continents and islands */
/* don't let the islands crash into each other.
1 = don't merge, 0 = merge. */
static int DISTINCT_ISLANDS = 1;
static int quiet;
#define DEFAULT_OUTFILE_NAME "newcap_script"
static const char *outfile = DEFAULT_OUTFILE_NAME;
#define STABLE_CYCLE 4 /* stability required for perturbed capitals */
#define DRIFT_BEFORE_CHECK ((WORLD_X + WORLD_Y)/2)
#define DRIFT_MAX ((WORLD_X + WORLD_Y)*2)
#define new_x(newx) (((newx) + WORLD_X) % WORLD_X)
#define new_y(newy) (((newy) + WORLD_Y) % WORLD_Y)
struct xy {
coord x, y;
};
/*
* Capital locations
* The i-th capital is at cap[i].
*/
static struct xy *cap;
/*
* Island sizes
* isecs[i] is the size of the i-th island.
*/
static int *isecs;
/*
* Island sectors
* The i-th island's j-th sector is at sect[i][j].
*/
struct xy **sect;
/*
* Island at x, y
* own[XYOFFSET(x, y)] is x,y's island number, -1 if water.
*/
static short *own;
/*
* Adjacent land sectors
* adj_land[XYOFFSET(x, y)] bit d is set exactly when the sector next
* to x, y in direction d is land.
*/
static unsigned char *adj_land;
/*
* Elevation at x,y
* elev[XYOFFSET(x, y)] is x,y's elevation.
*/
static short *elev;
/*
* Exclusive zones
* Each island is surrounded by an exclusive zone where only it may
* grow. The width of the zone depends on minimum distances.
* While growing continents, it is @di sectors wide.
* While growing additional islands, it is @id sectors wide.
* DISTINCT_ISLANDS nullifies the exclusive zone then.
* xzone[XYOFFSET(x, y)] is -1 when the sector is in no exclusive
* zone, a (non-negative) island number when it is in that island's
* exclusive zone and no other, and -2 when it is in multiple
* exclusive zones.
*/
static short *xzone;
/*
* Set of sectors seen already
* Increment @cur_seen to empty the set of sectors seen, set
* seen[XYOFFSET(x, y)] to @cur_seen to add x,y to the set.
*/
static unsigned *seen;
static unsigned cur_seen;
/*
* Closest continent and "distance"
* closest[XYOFFSET(x, y)] is the closest continent's number.
* distance[] is complicated; see init_spheres_of_influence() and
* init_distance_to_coast().
*/
static natid *closest;
static unsigned short *distance;
/*
* Queue for breadth-first search
*/
static int *bfs_queue;
static int bfs_queue_head, bfs_queue_tail;
static const char *numletter =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
static void print_vars(void);
static void qprint(const char * const fmt, ...)
ATTRIBUTE((format (printf, 1, 2)));
static void help(char *);
static void usage(void);
static void parse_args(int argc, char *argv[]);
static void allocate_memory(void);
static void init(void);
static int drift(void);
static int stable(int);
static void drift_capital(int);
static int grow_continents(void);
static int grow_islands(void);
static void create_elevations(void);
static void elevate_prep(void);
static void elevate_land(void);
static void elevate_sea(void);
static void write_sects(void);
static void output(void);
static int write_newcap_script(void);
/* Debugging aids: */
void print_own_map(void);
void print_xzone_map(void);
void print_closest_map(void);
void print_distance_map(void);
void print_elev_map(void);
int
main(int argc, char *argv[])
{
int opt;
char *config_file = NULL;
int try, done;
unsigned rnd_seed = 0;
int seed_set = 0;
program_name = argv[0];
while ((opt = getopt(argc, argv, "e:hiqR:s:v")) != EOF) {
switch (opt) {
case 'e':
config_file = optarg;
break;
case 'i':
DISTINCT_ISLANDS = 0;
break;
case 'q':
quiet = 1;
break;
case 'R':
rnd_seed = strtoul(optarg, NULL, 10);
seed_set = 1;
break;
case 's':
outfile = optarg;
break;
case 'h':
usage();
exit(0);
case 'v':
printf("%s\n\n%s", version, legal);
exit(0);
default:
help(NULL);
exit(1);
}
}
if (!seed_set)
rnd_seed = pick_seed();
seed_prng(rnd_seed);
empfile_init();
if (emp_config(config_file) < 0)
exit(1);
empfile_fixup();
parse_args(argc - optind, argv + optind);
allocate_memory();
print_vars();
qprint("\n #*# ...fairland rips open a rift in the datumplane... #*#\n\n");
qprint("seed is %u\n", rnd_seed);
try = 0;
do {
init();
if (try)
qprint("\ntry #%d (out of %d)...\n", try + 1, NUMTRIES);
qprint("placing capitals...\n");
if (!drift())
qprint("unstable drift\n");
qprint("growing continents...\n");
done = grow_continents();
if (!done)
continue;
qprint("growing islands:");
done = grow_islands();
} while (!done && ++try < NUMTRIES);
if (!done) {
fprintf(stderr, "%s: world not large enough for this much land\n",
program_name);
exit(1);
}
qprint("elevating land...\n");
create_elevations();
qprint("writing to sectors file...\n");
if (!write_newcap_script())
exit(1);
if (chdir(gamedir)) {
fprintf(stderr, "%s: can't chdir to %s (%s)\n",
program_name, gamedir, strerror(errno));
exit(1);
}
if (!ef_open(EF_SECTOR, EFF_MEM | EFF_NOTIME))
exit(1);
write_sects();
if (!ef_close(EF_SECTOR))
exit(1);
output();
qprint("\n\nA script for adding all the countries can be found in \"%s\".\n",
outfile);
exit(0);
}
static void
print_vars(void)
{
if (quiet)
return;
puts("Creating a planet with:\n");
printf("%d continents\n", nc);
printf("continent size: %d\n", sc);
printf("number of islands: %d\n", ni);
printf("average size of islands: %d\n", is);
printf("spike: %d%%\n", sp);
printf("%d%% of land is mountain (each continent will have %d mountains)\n",
pm, (pm * sc) / 100);
printf("minimum distance between continents: %d\n", di);
printf("minimum distance from islands to continents: %d\n", id);
printf("World dimensions: %dx%d\n", WORLD_X, WORLD_Y);
}
static void
qprint(const char *const fmt, ...)
{
va_list ap;
if (!quiet) {
va_start(ap, fmt);
vfprintf(stdout, fmt, ap);
va_end(ap);
}
}
static void
help(char *complaint)
{
if (complaint)
fprintf(stderr, "%s: %s\n", program_name, complaint);
fprintf(stderr, "Try -h for help.\n");
}
static void
usage(void)
{
printf("Usage: %s [OPTION]... NC SC [NI] [IS] [SP] [PM] [DI] [ID]\n"
" -e CONFIG-FILE configuration file\n"
" (default %s)\n"
" -i islands may merge\n"
" -q quiet\n"
" -R SEED seed for random number generator\n"
" -s SCRIPT name of script to create (default %s)\n"
" -h display this help and exit\n"
" -v display version information and exit\n"
" NC number of continents\n"
" SC continent size\n"
" NI number of islands (default NC)\n"
" IS average island size (default SC/2)\n"
" SP spike percentage: 0 = round, 100 = snake (default %d)\n"
" PM percentage of land that is mountain (default %d)\n"
" DI minimum distance between continents (default %d)\n"
" ID minimum distance from islands to continents (default %d)\n",
program_name, dflt_econfig, DEFAULT_OUTFILE_NAME,
DEFAULT_SPIKE, DEFAULT_MOUNTAIN, DEFAULT_CONTDIST, DEFAULT_ISLDIST);
}
static void
parse_args(int argc, char *argv[])
{
int dist_max = mapdist(0, 0, WORLD_X / 2, WORLD_Y / 2);
if (argc < 2) {
help("missing arguments");
exit(1);
}
if (argc > 8) {
help("too many arguments");
exit(1);
}
nc = atoi(argv[0]);
if (nc < 1) {
fprintf(stderr, "%s: number of continents must be > 0\n",
program_name);
exit(1);
}
sc = atoi(argv[1]);
if (sc < 2) {
fprintf(stderr, "%s: size of continents must be > 1\n",
program_name);
exit(1);
}
ni = nc;
is = sc / 2;
if (argc > 2)
ni = atoi(argv[2]);
if (ni < 0) {
fprintf(stderr, "%s: number of islands must be >= 0\n",
program_name);
exit(1);
}
if (ni % nc) {
fprintf(stderr, "%s: number of islands must be a multiple of"
" the number of continents\n",
program_name);
exit(1);
}
if (argc > 3)
is = atoi(argv[3]);
if (is < 1) {
fprintf(stderr, "%s: size of islands must be > 0\n",
program_name);
exit(1);
}
if (argc > 4)
sp = atoi(argv[4]);
if (sp < 0 || sp > 100) {
fprintf(stderr,
"%s: spike percentage must be between 0 and 100\n",
program_name);
exit(1);
}
if (argc > 5)
pm = atoi(argv[5]);
if (pm < 0 || pm > 100) {
fprintf(stderr,
"%s: mountain percentage must be between 0 and 100\n",
program_name);
exit(1);
}
if (argc > 6)
di = atoi(argv[6]);
if (di < 0) {
fprintf(stderr, "%s: distance between continents must be >= 0\n",
program_name);
exit(1);
}
if (di > dist_max) {
fprintf(stderr, "%s: distance between continents too large\n",
program_name);
exit(1);
}
if (argc > 7)
id = atoi(argv[7]);
if (id < 0) {
fprintf(stderr,
"%s: distance from islands to continents must be >= 0\n",
program_name);
exit(1);
}
if (id > dist_max) {
fprintf(stderr,
"%s: distance from islands to continents too large\n",
program_name);
exit(1);
}
}
/*
* Variable initialization
*/
static void
allocate_memory(void)
{
int i;
cap = malloc(nc * sizeof(*cap));
own = malloc(WORLD_SZ() * sizeof(*own));
adj_land = malloc(WORLD_SZ() * sizeof(*adj_land));
elev = calloc(WORLD_SZ(), sizeof(*elev));
xzone = malloc(WORLD_SZ() * sizeof(*xzone));
seen = calloc(WORLD_SZ(), sizeof(*seen));
closest = malloc(WORLD_SZ() * sizeof(*closest));
distance = malloc(WORLD_SZ() * sizeof(*distance));
bfs_queue = malloc(WORLD_SZ() * sizeof(*bfs_queue));
isecs = calloc(nc + ni, sizeof(int));
sect = malloc((nc + ni) * sizeof(*sect));
for (i = 0; i < nc; i++)
sect[i] = malloc(sc * sizeof(**sect));
for (i = nc; i < nc + ni; i++)
sect[i] = malloc(is * 2 * sizeof(**sect));
}
static void
init(void)
{
int i;
for (i = 0; i < WORLD_SZ(); i++)
own[i] = -1;
memset(adj_land, 0, WORLD_SZ() * sizeof(*adj_land));
}
/*
* Drift the capitals until they are as far away from each other as possible
*/
/*
* How isolated is capital @j at @newx,@newy?
* Return the distance to the closest other capital.
*/
static int
iso(int j, int newx, int newy)
{
int d = INT_MAX;
int i, md;
for (i = 0; i < nc; ++i) {
if (i == j)
continue;
md = mapdist(cap[i].x, cap[i].y, newx, newy);
if (md < d)
d = md;
}
return d;
}
/*
* Drift the capitals
* Return 1 for a stable drift, 0 for an unstable one.
*/
static int
drift(void)
{
int turns, i;
for (i = 0; i < nc; i++) {
cap[i].y = (2 * i) / WORLD_X;
cap[i].x = (2 * i) % WORLD_X + cap[i].y % 2;
if (cap[i].y >= WORLD_Y) {
fprintf(stderr,
"%s: world not big enough for all the continents\n",
program_name);
exit(1);
}
}
for (turns = 0; turns < DRIFT_MAX; ++turns) {
if (stable(turns))
return 1;
for (i = 0; i < nc; ++i)
drift_capital(i);
}
return 0;
}
/*
* Has the drift stabilized?
* @turns is the number of turns so far.
*/
static int
stable(int turns)
{
static int mc[STABLE_CYCLE];
int i, isod, d = 0, stab = 1;
if (!turns) {
for (i = 0; i < STABLE_CYCLE; i++)
mc[i] = i;
}
if (turns <= DRIFT_BEFORE_CHECK)
return 0;
for (i = 0; i < nc; ++i) {
isod = iso(i, cap[i].x, cap[i].y);
if (isod > d)
d = isod;
}
for (i = 0; i < STABLE_CYCLE; ++i)
if (d != mc[i])
stab = 0;
mc[turns % STABLE_CYCLE] = d;
return stab;
}
/*
* Drift capital @j.
* Move it to an adjacent sector where it is at least as isolated.
*/
static void
drift_capital(int j)
{
int dir, i, newx, newy;
dir = DIR_L + roll0(6);
for (i = 0; i < 6; i++) {
if (dir > DIR_LAST)
dir -= 6;
newx = new_x(cap[j].x + diroff[dir][0]);
newy = new_y(cap[j].y + diroff[dir][1]);
dir++;
if (iso(j, newx, newy) >= iso(j, cap[j].x, cap[j].y)) {
cap[j].x = newx;
cap[j].y = newy;
return;
}
}
}
/*
* Grow land
*/
static int
is_coastal(int x, int y)
{
return adj_land[XYOFFSET(x, y)]
!= (1u << (DIR_LAST + 1)) - (1u << DIR_FIRST);
}
struct hexagon_iter {
int dir, i, n;
};
/*
* Start iterating around @x0,@y0 at distance @d.
* Set *x,*y to coordinates of the first sector.
*/
static inline void
hexagon_first(struct hexagon_iter *iter, int x0, int y0, int n,
int *x, int *y)
{
*x = new_x(x0 - 2 * n);
*y = y0;
iter->dir = DIR_FIRST;
iter->i = 0;
iter->n = n;
}
/*
* Continue iteration started with hexagon_first().
* Set *x,*y to coordinates of the next sector.
* Return whether we're back at the first sector, i.e. iteration is
* complete.
*/
static inline int
hexagon_next(struct hexagon_iter *iter, int *x, int *y)
{
*x = new_x(*x + diroff[iter->dir][0]);
*y = new_y(*y + diroff[iter->dir][1]);
iter->i++;
if (iter->i == iter->n) {
iter->i = 0;
iter->dir++;
}
return iter->dir <= DIR_LAST;
}
/*
* Is @x,@y in no exclusive zone other than perhaps @c's?
*/
static int
xzone_ok(int c, int x, int y)
{
int off = XYOFFSET(x, y);
return xzone[off] == c || xzone[off] == -1;
}
/*
* Add sectors within distance @dist of @x,@y to @c's exclusive zone.
*/
static void
xzone_around_sector(int c, int x, int y, int dist)
{
int d, x1, y1, off;
struct hexagon_iter hexit;
assert(xzone_ok(c, x, y));
xzone[XYOFFSET(x, y)] = c;
for (d = 1; d <= dist; d++) {
hexagon_first(&hexit, x, y, d, &x1, &y1);
do {
off = XYOFFSET(x1, y1);
if (xzone[off] == -1)
xzone[off] = c;
else if (xzone[off] != c)
xzone[off] = -2;
} while (hexagon_next(&hexit, &x1, &y1));
}
}
/*
* Add sectors within distance @dist to island @c's exclusive zone.
*/
static void
xzone_around_island(int c, int dist)
{
int i;
for (i = 0; i < isecs[c]; i++)
xzone_around_sector(c, sect[c][i].x, sect[c][i].y, dist);
}
/*
* Initialize exclusive zones around @n islands.
*/
static void
xzone_init(int n)
{
int i, c;
for (i = 0; i < WORLD_SZ(); i++)
xzone[i] = -1;
for (c = 0; c < n; c++)
xzone_around_island(c, id);
}
/*
* Initialize breadth-first search.
*/
static void
bfs_init(void)
{
int i;
for (i = 0; i < WORLD_SZ(); i++) {
closest[i] = -1;
distance[i] = USHRT_MAX;
}
bfs_queue_head = bfs_queue_tail = 0;
}
/*
* Add sector @x,@y to the BFS queue.
* It's closest to @c, with distance @dist.
*/
static void
bfs_enqueue(int c, int x, int y, int dist)
{
int off = XYOFFSET(x, y);
assert(dist < distance[off]);
closest[off] = c;
distance[off] = dist;
bfs_queue[bfs_queue_tail] = off;
bfs_queue_tail++;
if (bfs_queue_tail >= WORLD_SZ())
bfs_queue_tail = 0;
assert(bfs_queue_tail != bfs_queue_head);
}
/*
* Search breadth-first until the queue is empty.
*/
static void
bfs_run_queue(void)
{
int off, dist, i, noff, nx, ny;
coord x, y;
while (bfs_queue_head != bfs_queue_tail) {
off = bfs_queue[bfs_queue_head];
bfs_queue_head++;
if (bfs_queue_head >= WORLD_SZ())
bfs_queue_head = 0;
dist = distance[off] + 1;
sctoff2xy(&x, &y, off);
for (i = DIR_FIRST; i <= DIR_LAST; i++) {
nx = new_x(x + diroff[i][0]);
ny = new_y(y + diroff[i][1]);
noff = XYOFFSET(nx, ny);
if (dist < distance[noff]) {
bfs_enqueue(closest[off], nx, ny, dist);
} else if (distance[noff] == dist) {
if (closest[off] != closest[noff])
closest[noff] = (natid)-1;
} else
assert(distance[noff] < dist);
}
}
}
/*
* Add island @c's coastal sectors to the BFS queue, with distance 0.
*/
static void
bfs_enqueue_island(int c)
{
int i;
for (i = 0; i < isecs[c]; i++) {
if (is_coastal(sect[c][i].x, sect[c][i].y))
bfs_enqueue(c, sect[c][i].x, sect[c][i].y, 0);
}
}
/*
* Enqueue spheres of influence borders for breadth-first search.
*/
static void
bfs_enqueue_border(void)
{
int x, y, off, dir, nx, ny, noff;
for (y = 0; y < WORLD_Y; y++) {
for (x = y % 2; x < WORLD_X; x += 2) {
off = XYOFFSET(x, y);
if (distance[off] <= id + 1)
continue;
if (closest[off] == (natid)-1)
continue;
for (dir = DIR_FIRST; dir <= DIR_LAST; dir++) {
nx = new_x(x + diroff[dir][0]);
ny = new_y(y + diroff[dir][1]);
noff = XYOFFSET(nx, ny);
if (closest[noff] != closest[off]) {
bfs_enqueue(closest[off], x, y, id + 1);
break;
}
}
}
}
}
/*
* Compute spheres of influence
* A continent's sphere of influence is the set of sectors closer to
* it than to any other continent.
* Set closest[XYOFFSET(x, y)] to the closest continent's number,
* -1 if no single continent is closest.
* Set distance[XYOFFSET(x, y)] to the minimum of the distance to the
* closest coastal land sector and the distance to just outside the
* sphere of influence plus @id. For sea sectors within a continent's
* sphere of influence, distance[off] - id is the distance to the
* border of the area where additional islands can be placed.
*/
static void
init_spheres_of_influence(void)
{
int c;
bfs_init();
for (c = 0; c < nc; c++)
bfs_enqueue_island(c);
bfs_run_queue();
bfs_enqueue_border();
bfs_run_queue();
}
/*
* Precompute distance to coast
* Set distance[XYOFFSET(x, y)] to the distance to the closest coastal
* land sector.
* Set closest[XYOFFSET(x, y)] to the closest continent's number,
* -1 if no single continent is closest.
*/
static void
init_distance_to_coast(void)
{
int c;
bfs_init();
for (c = 0; c < nc + ni; c++)
bfs_enqueue_island(c);
bfs_run_queue();
}
/*
* Is @x,@y in the same sphere of influence as island @c?
* Always true when @c is a continent.
*/
static int
is_in_sphere(int c, int x, int y)
{
return c < nc || closest[XYOFFSET(x, y)] == c % nc;
}
/*
* Can island @c grow at @x,@y?
*/
static int
can_grow_at(int c, int x, int y)
{
return own[XYOFFSET(x, y)] == -1 && xzone_ok(c, x, y)
&& is_in_sphere(c, x, y);
}
static void
adj_land_update(int x, int y)
{
int is_land = own[XYOFFSET(x, y)] != -1;
int dir, nx, ny, noff;
for (dir = DIR_FIRST; dir <= DIR_LAST; dir++) {
nx = new_x(x + diroff[dir][0]);
ny = new_y(y + diroff[dir][1]);
noff = XYOFFSET(nx, ny);
if (is_land)
adj_land[noff] |= 1u << DIR_BACK(dir);
else
adj_land[noff] &= ~(1u << DIR_BACK(dir));
}
}
static void
add_sector(int c, int x, int y)
{
int off = XYOFFSET(x, y);
assert(own[off] == -1);
xzone_around_sector(c, x, y, c < nc ? di : DISTINCT_ISLANDS ? id : 0);
sect[c][isecs[c]].x = x;
sect[c][isecs[c]].y = y;
isecs[c]++;
own[off] = c;
adj_land_update(x, y);
}
static int
grow_weight(int c, int x, int y, int spike)
{
int n, b;
/*
* #Land neighbors is #bits set in adj_land[].
* Count them Brian Kernighan's way.
*/
n = 0;
for (b = adj_land[XYOFFSET(x, y)]; b; b &= b - 1)
n++;
assert(n > 0 && n < 7);
if (spike)
return (6 - n) * (6 - n);
return n * n * n;
}
static int
grow_one_sector(int c)
{
int spike = roll0(100) < sp;
int wsum, newx, newy, i, x, y, off, dir, nx, ny, noff, w;
assert(cur_seen < UINT_MAX);
cur_seen++;
wsum = 0;
newx = newy = -1;
for (i = 0; i < isecs[c]; i++) {
x = sect[c][i].x;
y = sect[c][i].y;
off = XYOFFSET(x, y);
for (dir = DIR_FIRST; dir <= DIR_LAST; dir++) {
if (adj_land[off] & (1u << dir))
continue;
nx = new_x(x + diroff[dir][0]);
ny = new_y(y + diroff[dir][1]);
noff = XYOFFSET(nx, ny);
if (seen[noff] == cur_seen)
continue;
assert(seen[noff] < cur_seen);
seen[noff] = cur_seen;
if (!can_grow_at(c, nx, ny))
continue;
w = grow_weight(c, nx, ny, spike);
assert(wsum < INT_MAX - w);
wsum += w;
if (roll0(wsum) < w) {
newx = nx;
newy = ny;
}
}
}
if (!wsum)
return 0;
add_sector(c, newx, newy);
return 1;
}
/*
* Grow the continents.
* Return 1 on success, 0 on error.
*/
static int
grow_continents(void)
{
int done = 1;
int c, secs;
xzone_init(0);
for (c = 0; c < nc; ++c) {
isecs[c] = 0;
if (!can_grow_at(c, cap[c].x, cap[c].y)
|| !can_grow_at(c, new_x(cap[c].x + 2), cap[c].y)) {
done = 0;
continue;
}
add_sector(c, cap[c].x, cap[c].y);
add_sector(c, new_x(cap[c].x + 2), cap[c].y);
}
if (!done) {
qprint("No room for continents\n");
return 0;
}
for (secs = 2; secs < sc && done; secs++) {
for (c = 0; c < nc; ++c) {
if (!grow_one_sector(c))
done = 0;
}
}
if (!done)
qprint("Only managed to grow %d out of %d sectors.\n",
secs - 1, sc);
return done;
}
/*
* Place additional island @c's first sector.
* Return 1 on success, 0 on error.
*/
static int
place_island(int c, int isiz)
{
int n, x, y, d, w, newx, newy;
n = 0;
for (y = 0; y < WORLD_Y; y++) {
for (x = y % 2; x < WORLD_X; x += 2) {
if (can_grow_at(c, x, y)) {
d = distance[XYOFFSET(x, y)];
assert(d > id);
w = (d - id) * (d - id);
n += MIN(w, (isiz + 2) / 3);
if (roll0(n) < w) {
newx = x;
newy = y;
}
}
}
}
if (n)
add_sector(c, newx, newy);
return n;
}
static int
int_cmp(const void *a, const void *b)
{
return *(int *)b - *(int *)a;
}
static int *
size_islands(void)
{
int n = ni / nc;
int *isiz = malloc(n * sizeof(*isiz));
int r0, r1, i;
isiz[0] = n * is;
r1 = roll0(is);
for (i = 1; i < n; i++) {
r0 = r1;
r1 = roll0(is);
isiz[i] = is + r1 - r0;
isiz[0] -= isiz[i];
}
qsort(isiz, n, sizeof(*isiz), int_cmp);
return isiz;
}
/*
* Grow the additional islands.
* Return 1 on success, 0 on error.
*/
static int
grow_islands(void)
{
int *island_size = size_islands();
int xzone_valid = 0;
int carry = 0;
int i, j, c, done, secs, isiz, x, y;
init_spheres_of_influence();
for (i = 0; i < ni / nc; i++) {
c = nc + i * nc;
if (!xzone_valid)
xzone_init(c);
carry += island_size[i];
isiz = MIN(2 * is, carry);
for (j = 0; j < nc; j++) {
isecs[c + j] = 0;
if (!place_island(c + j, isiz)) {
qprint("\nNo room for island #%d\n", c - nc + j + 1);
free(island_size);
return 0;
}
}
done = 1;
for (secs = 1; secs < isiz && done; secs++) {
for (j = 0; j < nc; j++) {
if (!grow_one_sector(c + j))
done = 0;
}
}
if (!done) {
secs--;
for (j = 0; j < nc; j++) {
if (isecs[c + j] != secs) {
isecs[c + j]--;
assert(isecs[c + j] == secs);
x = sect[c + j][secs].x;
y = sect[c + j][secs].y;
own[XYOFFSET(x, y)] = -1;
adj_land_update(x, y);
}
}
xzone_valid = 0;
}
for (j = 0; j < nc; j++)
qprint(" %d(%d)", c - nc + j + 1, isecs[c + j]);
carry -= secs;
}
free(island_size);
qprint("\n");
if (carry)
qprint("Only managed to grow %d out of %d island sectors.\n",
is * ni - carry * nc, is * ni);
return 1;
}
/*
* Create elevations
*/
static void
create_elevations(void)
{
elevate_prep();
elevate_land();
elevate_sea();
}
static int
elev_cmp(const void *p, const void *q)
{
int a = *(int *)p;
int b = *(int *)q;
int delev = elev[a] - elev[b];
return delev ? delev : a - b;
}
static void
elevate_prep(void)
{
int n = WORLD_SZ() * 8;
int off0, r, sign, elevation, d, x1, y1, off1;
coord x0, y0;
struct hexagon_iter hexit;
init_distance_to_coast();
while (n > 0) {
off0 = roll0(WORLD_SZ());
sctoff2xy(&x0, &y0, off0);
if (own[off0] == -1) {
r = roll(MIN(3, distance[off0]));
sign = -1;
} else {
r = roll(MIN(3, distance[off0]) + 1);
sign = 1;
}
elevation = elev[off0] + sign * r * r;
elev[off0] = LIMIT_TO(elevation, SHRT_MIN, SHRT_MAX);
n--;
for (d = 1; d < r; d++) {
hexagon_first(&hexit, x0, y0, d, &x1, &y1);
do {
off1 = XYOFFSET(x1, y1);
elevation = elev[off1] + sign * (r * r - d * d);
elev[off1] = LIMIT_TO(elevation, SHRT_MIN, SHRT_MAX);
n--;
} while (hexagon_next(&hexit, &x1, &y1));
}
}
}
static void
elevate_land(void)
{
int *off = malloc(MAX(sc, is * 2) * sizeof(*off));
int max_nm = (pm * MAX(sc, is * 2)) / 100;
int c, nm, i0, n, i;
double elevation, delta;
for (c = 0; c < nc + ni; c++) {
nm = (pm * isecs[c]) / 100;
i0 = c < nc ? 2 : 0;
n = isecs[c] - i0;
for (i = 0; i < i0; i++)
elev[XYOFFSET(sect[c][i].x, sect[c][i].y)] = PLATMIN;
for (i = 0; i < n; i++)
off[i] = XYOFFSET(sect[c][i0 + i].x, sect[c][i0 + i].y);
qsort(off, n, sizeof(*off), elev_cmp);
delta = (double)(HIGHMIN - LANDMIN - 1) / (n - nm - 1);
elevation = LANDMIN;
for (i = 0; i < n - nm; i++) {
elev[off[i]] = (int)(elevation + 0.5);
elevation += delta;
}
elevation = HIGHMIN;
delta = (127.0 - HIGHMIN) / max_nm;
for (; i < n; i++) {
elevation += delta;
elev[off[i]] = (int)(elevation + 0.5);
}
}
free(off);
}
static void
elevate_sea(void)
{
int i, min;
min = 0;
for (i = 0; i < WORLD_SZ(); i++) {
if (elev[i] < min)
min = elev[i];
}
for (i = 0; i < WORLD_SZ(); i++) {
if (elev[i] < 0)
elev[i] = -1 - 126 * elev[i] / min;
}
}
static int
elev_to_sct_type(int elevation)
{
if (elevation < LANDMIN)
return SCT_WATER;
if (elevation < HIGHMIN)
return SCT_RURAL;
return SCT_MOUNT;
}
/*
* Add resources
*/
/*
* Map elevation @elev to a resource value according to @conf.
* This is a linear interpolation on the data points in @conf.
*/
static int
elev_to_resource(int elev, struct resource_point conf[])
{
int i, elev1, elev2, delev, res1, res2, dres;
for (i = 1; elev > conf[i].elev; i++) ;
assert(conf[i - 1].elev <= elev);
elev1 = conf[i - 1].elev;
elev2 = conf[i].elev;
delev = elev2 - elev1;
res1 = conf[i - 1].res;
res2 = conf[i].res;
dres = res2 - res1;
return (int)(res1 + (double)((elev - elev1) * dres) / delev);
}
static void
add_resources(struct sctstr *sct)
{
sct->sct_min = elev_to_resource(sct->sct_elev, iron_conf);
sct->sct_gmin = elev_to_resource(sct->sct_elev, gold_conf);
sct->sct_fertil = elev_to_resource(sct->sct_elev, fert_conf);
sct->sct_oil = elev_to_resource(sct->sct_elev, oil_conf);
sct->sct_uran = elev_to_resource(sct->sct_elev, uran_conf);
}
/*
* Designate the sectors
*/
static void
write_sects(void)
{
struct sctstr *sp;
int i;
for (i = 0; i < WORLD_SZ(); i++) {
sp = getsectid(i);
sp->sct_elev = elev[i];
sp->sct_type = elev_to_sct_type(sp->sct_elev);
sp->sct_newtype = sp->sct_type;
sp->sct_dterr = own[i] + 1;
sp->sct_coastal = is_coastal(sp->sct_x, sp->sct_y);
add_resources(sp);
}
}
/*
* Print a picture of the map
*/
static void
output(void)
{
int sx, sy, x, y, off, c, type;
if (quiet == 0) {
for (sy = -WORLD_Y / 2; sy < WORLD_Y / 2; sy++) {
y = YNORM(sy);
puts("");
if (y % 2)
printf(" ");
for (sx = -WORLD_X / 2 + y % 2; sx < WORLD_X / 2; sx += 2) {
x = XNORM(sx);
off = XYOFFSET(x, y);
c = own[off];
type = elev_to_sct_type(elev[off]);
if (type == SCT_WATER)
printf(". ");
else if (type == SCT_MOUNT)
printf("^ ");
else if (c >= nc)
printf("%% ");
else {
assert(0 <= c && c < nc);
if ((x == cap[c].x || x == new_x(cap[c].x + 2))
&& y == cap[c].y)
printf("%c ", numletter[c % 62]);
else
printf("# ");
}
}
}
}
}
/*
* Print a map to help visualize own[].
* This is for debugging.
*/
void
print_own_map(void)
{
int sx, sy, x, y, off;
for (sy = -WORLD_Y / 2; sy < WORLD_Y / 2; sy++) {
y = YNORM(sy);
printf("%4d ", sy);
for (sx = -WORLD_X / 2; sx < WORLD_X / 2; sx++) {
x = XNORM(sx);
off = XYOFFSET(x, y);
if ((x + y) & 1)
putchar(' ');
else if (own[off] == -1)
putchar('.');
else
putchar(numletter[own[off] % 62]);
}
putchar('\n');
}
}
/*
* Print a map to help visualize elev[].
* This is for debugging. It expects the terminal to understand
* 24-bit color escape sequences \e[48;2;$red;$green;$blue;m.
*/
void
print_elev_map(void)
{
int sx, sy, x, y, off, sat;
for (sy = -WORLD_Y / 2; sy < WORLD_Y / 2; sy++) {
y = YNORM(sy);
printf("%4d ", sy);
for (sx = -WORLD_X / 2; sx < WORLD_X / 2; sx++) {
x = XNORM(sx);
off = XYOFFSET(x, y);
if ((x + y) & 1)
putchar(' ');
else if (!elev[off])
putchar(' ');
else if (elev[off] < 0) {
sat = 256 + elev[off] * 2;
printf("\033[48;2;%d;%d;%dm \033[0m", sat, sat, 255);
} else if (elev[off] < HIGHMIN / 2) {
sat = (HIGHMIN / 2 - elev[off]) * 4;
printf("\033[48;2;%d;%d;%dm \033[0m", sat, 255, sat);
} else if (elev[off] < HIGHMIN) {
sat = 128 + (HIGHMIN - elev[off]) * 2;
printf("\033[48;2;%d;%d;%dm \033[0m", sat, sat / 2, sat / 4);
} else {
sat = 128 + (elev[off] - HIGHMIN) * 2;
printf("\033[48;2;%d;%d;%dm^\033[0m", sat, sat, sat);
}
}
putchar('\n');
}
}
/*
* Print a map to help visualize xzone[].
* This is for debugging.
*/
void
print_xzone_map(void)
{
int sx, sy, x, y, off;
for (sy = -WORLD_Y / 2; sy < WORLD_Y / 2; sy++) {
y = YNORM(sy);
printf("%4d ", sy);
for (sx = -WORLD_X / 2; sx < WORLD_X / 2; sx++) {
x = XNORM(sx);
off = XYOFFSET(x, y);
if ((x + y) & 1)
putchar(' ');
else if (own[off] >= 0)
putchar('-');
else if (xzone[off] >= 0)
putchar(numletter[xzone[off] % 62]);
else {
assert(own[off] == -1);
putchar(xzone[off] == -1 ? '.' : '!');
}
}
putchar('\n');
}
}
/*
* Print a map to help visualize closest[].
* This is for debugging.
*/
void
print_closest_map(void)
{
int sx, sy, x, y, off;
for (sy = -WORLD_Y / 2; sy < WORLD_Y / 2; sy++) {
y = YNORM(sy);
printf("%4d ", sy);
for (sx = -WORLD_X / 2; sx < WORLD_X / 2; sx++) {
x = XNORM(sx);
off = XYOFFSET(x, y);
if ((x + y) & 1)
putchar(' ');
else if (closest[off] == (natid)-1)
putchar('.');
else if (!distance[off]) {
assert(closest[off] == own[off]);
putchar('-');
} else {
putchar(numletter[closest[off] % 62]);
}
}
printf("\n");
}
}
void
print_distance_map(void)
{
int sx, sy, x, y, off;
for (sy = -WORLD_Y / 2; sy < WORLD_Y / 2; sy++) {
y = YNORM(sy);
printf("%4d ", sy);
for (sx = -WORLD_X / 2; sx < WORLD_X / 2; sx++) {
x = XNORM(sx);
off = XYOFFSET(x, y);
if ((x + y) & 1)
putchar(' ');
else if (closest[off] == (natid)-1)
putchar('.');
else if (!distance[off]) {
assert(closest[off] == own[off]);
putchar('-');
} else {
putchar(numletter[distance[off] % 62]);
}
}
printf("\n");
}
}
/*
* Write a script for placing capitals
*/
static int
write_newcap_script(void)
{
int c;
FILE *script = fopen(outfile, "w");
if (!script) {
fprintf(stderr, "%s: unable to write to %s (%s)\n",
program_name, outfile, strerror(errno));
return 0;
}
for (c = 0; c < nc; ++c) {
fprintf(script, "add %d %d %d p\n", c + 1, c + 1, c + 1);
fprintf(script, "newcap %d %d,%d\n", c + 1, cap[c].x, cap[c].y);
}
fprintf(script, "add %d visitor visitor v\n", c + 1);
fclose(script);
return 1;
}