如何在Python中生成类烟雾/精灵形状的随机网格图案?
问题描述
用户希望生成类似如下的网格形状:
# # # # # # # # # # # # #
# # # # # # # # # # # # # #
需求:在height×width的网格中随机分布x个元素,需满足:
- 顶部行的元素出现概率更高;
- 每行元素大概率靠近但偏移上一行的位置。
用户原本考虑用钟形分布实现,但希望寻求更具编程性、可读性的非数学化解决方案。
非数学化解决方案
方案一:继承式随机偏移
核心逻辑是从上到下逐行生成,每一行的元素位置依赖上一行的位置做小范围偏移,同时给顶部行更高的元素密度,完全不用数学分布公式,逻辑直白。
步骤:
- 初始化空网格:用二维列表表示,每个位置默认是空格。
- 顶部行优先填充:第一行直接随机生成较多元素(比如占总元素的25%,或者按行数从顶到底递减分配数量)。
- 逐行偏移生成:从第二行开始,遍历上一行的每个元素位置,在其左右±1或±2的范围内随机选位置作为当前行的元素;同时可以随机跳过部分位置,或者新增少量随机位置,避免完全复制。
- 补全/裁剪元素数量:如果生成的元素总数超过
x,就随机删除多余的;如果不够,就在上方行优先补加。
代码示例:
import random def generate_shape(height, width, total_elements): # 初始化空网格 grid = [[' ' for _ in range(width)] for _ in range(height)] # 按行分配元素数量:顶部行最多,往下递减 element_counts = [] for i in range(height): # 顶部行占比最高,每行递减一定比例 ratio = 1 - (i / height) * 0.7 count = max(1, int(total_elements * ratio)) element_counts.append(count) # 调整总数量,避免超过或不足 total_assigned = sum(element_counts) if total_assigned > total_elements: # 从底部行开始减 for i in range(height-1, -1, -1): if total_assigned <= total_elements: break element_counts[i] = max(1, element_counts[i]-1) total_assigned -=1 elif total_assigned < total_elements: # 从顶部行开始加 for i in range(height): if total_assigned >= total_elements: break element_counts[i] +=1 total_assigned +=1 # 第一行随机生成元素 positions = random.sample(range(width), element_counts[0]) for pos in positions: grid[0][pos] = '#' # 逐行偏移生成 for row in range(1, height): prev_positions = [i for i, val in enumerate(grid[row-1]) if val == '#'] current_positions = [] # 基于上一行位置偏移 for pos in prev_positions: # 80%概率在±1范围内偏移,20%概率随机选位置 if random.random() < 0.8: new_pos = pos + random.choice([-1, 0, 1]) new_pos = max(0, min(new_pos, width-1)) current_positions.append(new_pos) else: current_positions.append(random.randint(0, width-1)) # 补充数量(如果不够当前行需要的数量) while len(current_positions) < element_counts[row]: current_positions.append(random.randint(0, width-1)) # 去重(避免同一位置重复设置) current_positions = list(set(current_positions)) # 如果超过数量,随机删掉一些 if len(current_positions) > element_counts[row]: current_positions = random.sample(current_positions, element_counts[row]) # 设置当前行元素 for pos in current_positions: grid[row][pos] = '#' # 打印网格 for line in grid: print(''.join(line)) # 调用示例:生成7行10列,共15个元素 generate_shape(7, 10, 15)
方案二:路径扩散式生成
把元素分布看成从顶部起始点开始的“扩散路径”,每一行的元素是上一行元素的分支,逻辑类似树枝生长,同样不需要数学分布。
步骤:
- 初始化空网格,顶部行随机选多个起始点(数量多于下面的行)。
- 逐行处理:对上一行的每个元素,有较高概率(比如80%)在其上下左右小范围生成下一行的元素,同时顶部行的元素有更高的“扩散概率”;另外可以随机新增少量元素,增加随机性。
- 控制总元素数量:如果超过
x就随机删除,不够就从顶部补。
代码示例:
import random def generate_shape_v2(height, width, total_elements): grid = [[' ' for _ in range(width)] for _ in range(height)] current_elements = 0 # 顶部行先放一批元素(占总数量的30%) top_count = max(2, int(total_elements * 0.3)) top_positions = random.sample(range(width), top_count) for pos in top_positions: grid[0][pos] = '#' current_elements +=1 # 逐行扩散 for row in range(1, height): prev_positions = [i for i, val in enumerate(grid[row-1]) if val == '#'] # 顶部行的扩散概率更高,往下递减 spread_prob = 0.9 - (row / height) * 0.4 for pos in prev_positions: if current_elements >= total_elements: break # 按概率生成下一行的元素 if random.random() < spread_prob: # 随机偏移±1或不偏移 new_pos = pos + random.choice([-1, 0, 1]) new_pos = max(0, min(new_pos, width-1)) if grid[row][new_pos] == ' ': grid[row][new_pos] = '#' current_elements +=1 # 如果当前行元素太少,随机补一些(优先上面的行) if current_elements < total_elements and row < height//2: add_count = min(2, total_elements - current_elements) for _ in range(add_count): pos = random.randint(0, width-1) if grid[row][pos] == ' ': grid[row][pos] = '#' current_elements +=1 # 如果还不够,在任意行补(优先顶部) while current_elements < total_elements: row = random.randint(0, height//2) pos = random.randint(0, width-1) if grid[row][pos] == ' ': grid[row][pos] = '#' current_elements +=1 # 打印网格 for line in grid: print(''.join(line)) # 调用示例 generate_shape_v2(7, 10, 15)
方案优势
这两种方案都不需要理解钟形分布等数学概念,代码逻辑直观:
- 核心都是从上到下继承位置关系,保证每行元素靠近上一行;
- 通过行级别的数量比例/扩散概率控制顶部行的高密度;
- 参数(比如偏移范围、概率值、数量占比)都可以直接调整,非常灵活。
内容的提问来源于stack exchange,提问作者user3310334
相关产品推荐
相关产品推荐

