# -*- coding: utf-8 -*- # # Copyright © 2024 Simon Forman # # This file is part of game # # game 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. # # game 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 game. If not see . # ''' Poisson Distribution ''' from math import pi, sqrt, sin, cos, hypot from random import random, randint tau = 2 * pi def poisson(w, h, d, test_points=30): ''' Return a list of coordinates generated by a Poisson point process. Return a list of two-tuples, pairs of ints, representing points in a 2D plane. The points are bounded by the width and height (w and h) and separated by a minimum distance of d. ''' grid, x, y = Grid(d), randint(0, w), randint(0, h) grid.add(x, y) todo = [(x, y)] while todo: x, y = todo.pop(0) todo.extend( (nx, ny) for nx, ny in candidates(x, y, d, test_points) if 0 <= nx <= w and 0 <= ny <= h and grid.add(nx, ny) ) return list(grid.grid.values()) class Grid(object): ''' Keep track of points in a plane and efficiently check that added points are not nearer than a certain distance d to any others. ''' def __init__(self, d): self.d = d self.r = d / sqrt(2) # A square of side r and diagonal d. self.grid = {} # Map from grid "bins" to points, 1-to-1. def add(self, x, y): ''' Add a point pair if it's not too close to any others and return a bool: True if the point was added, False if it was rejected. ''' grid_coords = round(x / self.r), round(y / self.r) if any(hypot(x - nx, y - ny) < self.d for nx, ny in self._neighborhood(grid_coords)): return False self.grid[grid_coords] = x, y return True def _neighborhood(self, coordinates): '''Return a list of the points in the neighborhood of (x, y).''' # Note, x and y here are grid coordinates, not plane coordinates. # This method returns plane coordinates. x, y = coordinates return filter(None, map(self.grid.get, [ (x-1, y-2), (x, y-2), (x+1, y-2), (x-2, y-1), (x-1, y-1), (x, y-1), (x+1, y-1), (x+2, y-1), (x-2, y), (x-1, y), (x, y), (x+1, y), (x+2, y), (x-2, y+1), (x-1, y+1), (x, y+1), (x+1, y+1), (x+2, y+1), (x-1, y+2), (x, y+2), (x+1, y+2), ])) def candidates(x, y, d, test_points): '''Generate points at a distance from (x, y) from d to 2*d.''' for _ in range(test_points): radius = d + random() * d angle = random() * tau yield ( int(round(x + radius * cos(angle))), int(round(y + radius * sin(angle))), ) if __name__ == '__main__': from random import seed seed(23) WIDTH, HEIGHT, DISTANCE = 80, 24, 4 P = set(poisson(WIDTH, HEIGHT, DISTANCE)) for y in range(HEIGHT + 1): print(''.join(' *'[(x, y) in P] for x in range(WIDTH + 1)))