#!/usr/bin/env python3 import argparse import json import numpy as np parser = argparse.ArgumentParser() parser.add_argument("--seed", default=1, type=int, help="Random seed.") parser.add_argument("--threshold", default=0.4, type=float, help="Higher threshold = less hills.") parser.add_argument("--safezone", default=3.0, type=float, help="Size of hill-free area around home bases.") parser.add_argument("--size", default=(30, 30), type=int, nargs=2, help="Width and height for team.") parser.add_argument("--res", default=(10, 10), type=int, nargs=2, help="Higher res = more smaller hills. Must divide size.") def main(): np.random.seed(args.seed) # base noise profile noise = generate_perlin_noise_2d( args.size, args.res, tileable=(True, True), ) # make it less likely for hills to be close to home bases home_distance_tile = np.fromfunction(distance_decay, args.size, dtype=np.float32) home_distance = np.sum([ home_distance_tile, np.flip(home_distance_tile, axis=0), np.flip(home_distance_tile, axis=1), np.flip(home_distance_tile, axis=(0,1)), ], axis=0) hills = np.where(noise - home_distance > args.threshold, "x", ".") rows = ["".join(row) for row in hills] config = { "width_per_team": args.size[0], "height_per_team": args.size[1], "hills": rows } print(json.dumps(config, indent=4)) def distance_decay(x, y): return np.exp(-np.sqrt(x*x + y*y) / args.safezone) # Source: # https://github.com/pvigier/perlin-numpy/blob/master/perlin_numpy/perlin2d.py def interpolant(t): return t*t*t*(t*(t*6 - 15) + 10) def generate_perlin_noise_2d( shape, res, tileable=(False, False), interpolant=interpolant ): """Generate a 2D numpy array of perlin noise. Args: shape: The shape of the generated array (tuple of two ints). This must be a multple of res. res: The number of periods of noise to generate along each axis (tuple of two ints). Note shape must be a multiple of res. tileable: If the noise should be tileable along each axis (tuple of two bools). Defaults to (False, False). interpolant: The interpolation function, defaults to t*t*t*(t*(t*6 - 15) + 10). Returns: A numpy array of shape shape with the generated noise. Raises: ValueError: If shape is not a multiple of res. """ delta = (res[0] / shape[0], res[1] / shape[1]) d = (shape[0] // res[0], shape[1] // res[1]) grid = np.mgrid[0:res[0]:delta[0], 0:res[1]:delta[1]]\ .transpose(1, 2, 0) % 1 # Gradients angles = 2*np.pi*np.random.rand(res[0]+1, res[1]+1) gradients = np.dstack((np.cos(angles), np.sin(angles))) if tileable[0]: gradients[-1, :] = gradients[0, :] if tileable[1]: gradients[:, -1] = gradients[:, 0] gradients = gradients.repeat(d[0], 0).repeat(d[1], 1) g00 = gradients[:-d[0], :-d[1]] g10 = gradients[d[0]:, :-d[1]] g01 = gradients[:-d[0], d[1]:] g11 = gradients[d[0]:, d[1]:] # Ramps n00 = np.sum(np.dstack((grid[:, :, 0], grid[:, :, 1])) * g00, 2) n10 = np.sum(np.dstack((grid[:, :, 0]-1, grid[:, :, 1])) * g10, 2) n01 = np.sum(np.dstack((grid[:, :, 0], grid[:, :, 1]-1)) * g01, 2) n11 = np.sum(np.dstack((grid[:, :, 0]-1, grid[:, :, 1]-1)) * g11, 2) # Interpolation t = interpolant(grid) n0 = n00*(1-t[:, :, 0]) + t[:, :, 0]*n10 n1 = n01*(1-t[:, :, 0]) + t[:, :, 0]*n11 return np.sqrt(2)*((1-t[:, :, 1])*n0 + t[:, :, 1]*n1) if __name__ == '__main__': args = parser.parse_args([] if "__file__" not in globals() else None) main()