← Back to Home

Developer Portal

A curated repository of full hosted applications and handy utility scripts.

"

"Imagine a technological advancement coming along that is so profound, the aspirations of the individual are only oppressed by the stretch of their imaginationβ€”like the AI Mad Hatter skipping through the rabbit hole of ones and zeros, creating anything that comes to mind, creating binary chaos for the hell of it, to learn from it, to do it just because you can."

Creator's Musing

Hosted Applications

Utility Scripts

Top 25 Programming Languages

Language Reference Guide

βš”οΈ

Aetherforge β€” Procedural Dungeon Engine

An over-engineered procedural multi-layer dungeon generator featuring Binary Space Partitioning (BSP), Cellular Automata, A* corridor pathfinding, dynamic themes, weighted entity placement, and ANSI/PPM rendering.

πŸš€Launch Lab697 linespython
aetherforge.py
1#!/usr/bin/env python3
2"""
3╔══════════════════════════════════════════════════════════════════════════════╗
4β•‘ AETHERFORGE β€” Procedural Multi-Layer Dungeon Generator β•‘
5β•‘ Extremely over-engineered for fun β•‘
6β•‘ β•‘
7β•‘ Features: β•‘
8β•‘ β€’ Binary Space Partitioning (BSP) room generation β•‘
9β•‘ β€’ Cellular automata cave refinement β•‘
10β•‘ β€’ Multi-level dungeons with vertical connectivity β•‘
11β•‘ β€’ A* pathfinding for intelligent corridor carving β•‘
12β•‘ β€’ Procedural theme system (Stone, Crystal, Biomechanical, Eldritch) β•‘
13β•‘ β€’ Seeded RNG for full reproducibility β•‘
14β•‘ β€’ Entity placement (monsters, loot, traps, stairs) with weighted rarity β•‘
15β•‘ β€’ ASCII + ANSI truecolor rendering β•‘
16β•‘ β€’ Export to JSON / PPM image / interactive terminal viewer β•‘
17β•‘ β€’ Full CLI with subcommands + config profiles β•‘
18β•‘ β€’ Logging, progress, statistics, and validation β•‘
19β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
20"""
21
22from __future__ import annotations
23
24import argparse
25import hashlib
26import json
27import logging
28import math
29import random
30import sys
31import time
32from collections import deque
33from dataclasses import dataclass, field, asdict
34from enum import Enum, auto
35from pathlib import Path
36from typing import Dict, List, Optional, Set, Tuple, Iterator
37
38# ─────────────────────────────────────────────────────────────────────────────
39# Core geometry & helpers
40# ─────────────────────────────────────────────────────────────────────────────
41
42@dataclass(frozen=True, order=True)
43class Point:
44 x: int
45 y: int
46
47 def __add__(self, other: Point) -> Point:
48 return Point(self.x + other.x, self.y + other.y)
49
50 def neighbors4(self) -> Iterator[Point]:
51 yield Point(self.x + 1, self.y)
52 yield Point(self.x - 1, self.y)
53 yield Point(self.x, self.y + 1)
54 yield Point(self.x, self.y - 1)
55
56 def neighbors8(self) -> Iterator[Point]:
57 for dx in (-1, 0, 1):
58 for dy in (-1, 0, 1):
59 if dx == 0 and dy == 0:
60 continue
61 yield Point(self.x + dx, self.y + dy)
62
63@dataclass
64class Rect:
65 x: int
66 y: int
67 w: int
68 h: int
69
70 @property
71 def center(self) -> Point:
72 return Point(self.x + self.w // 2, self.y + self.h // 2)
73
74 @property
75 def area(self) -> int:
76 return self.w * self.h
77
78 def intersects(self, other: Rect) -> bool:
79 return not (self.x + self.w <= other.x or other.x + other.w <= self.x or
80 self.y + self.h <= other.y or other.y + other.h <= self.y)
81
82 def inflate(self, amount: int) -> Rect:
83 return Rect(self.x - amount, self.y - amount, self.w + 2 * amount, self.h + 2 * amount)
84
85class Tile(Enum):
86 VOID = auto()
87 WALL = auto()
88 FLOOR = auto()
89 DOOR = auto()
90 STAIRS_DOWN = auto()
91 STAIRS_UP = auto()
92 WATER = auto()
93 LAVA = auto()
94 CRYSTAL = auto()
95 CORRUPTION = auto()
96
97class EntityType(Enum):
98 MONSTER = auto()
99 LOOT = auto()
100 TRAP = auto()
101 SHRINE = auto()
102 NPC = auto()
103
104@dataclass
105class Entity:
106 kind: EntityType
107 name: str
108 rarity: float # 0.0 – 1.0
109 glyph: str
110 color: Tuple[int, int, int]
111 data: Dict = field(default_factory=dict)
112
113# ─────────────────────────────────────────────────────────────────────────────
114# Themes
115# ─────────────────────────────────────────────────────────────────────────────
116
117@dataclass
118class Theme:
119 name: str
120 wall_glyph: str
121 floor_glyph: str
122 door_glyph: str
123 wall_color: Tuple[int, int, int]
124 floor_color: Tuple[int, int, int]
125 door_color: Tuple[int, int, int]
126 special_tiles: Dict[Tile, Tuple[str, Tuple[int, int, int]]]
127 monster_pool: List[Tuple[str, str, float]] # name, glyph, rarity
128 loot_pool: List[Tuple[str, str, float]]
129
130THEMES = {
131 "stone": Theme(
132 name="Ancient Stone",
133 wall_glyph="β–ˆ", floor_glyph="Β·", door_glyph="+",
134 wall_color=(90, 90, 110), floor_color=(50, 50, 60),
135 door_color=(140, 100, 60),
136 special_tiles={
137 Tile.WATER: ("β‰ˆ", (40, 90, 160)),
138 Tile.CRYSTAL: ("✧", (180, 220, 255)),
139 },
140 monster_pool=[
141 ("Goblin", "g", 0.6), ("Skeleton", "s", 0.45),
142 ("Orc", "O", 0.3), ("Wraith", "W", 0.12), ("Ancient Guardian", "G", 0.04)
143 ],
144 loot_pool=[
145 ("Copper Coin", "$", 0.7), ("Iron Sword", "/", 0.35),
146 ("Healing Potion", "!", 0.4), ("Spell Scroll", "?", 0.18),
147 ("Amulet of Yendor", "Ξ©", 0.01)
148 ]
149 ),
150 "crystal": Theme(
151 name="Crystal Caverns",
152 wall_glyph="β–“", floor_glyph="β–‘", door_glyph="β—Š",
153 wall_color=(120, 60, 180), floor_color=(40, 20, 70),
154 door_color=(200, 150, 255),
155 special_tiles={
156 Tile.CRYSTAL: ("✦", (220, 180, 255)),
157 Tile.WATER: ("β‰ˆ", (80, 180, 255)),
158 },
159 monster_pool=[
160 ("Crystal Spider", "c", 0.55), ("Geode Golem", "G", 0.25),
161 ("Prism Drake", "D", 0.08), ("Void Shard", "v", 0.15)
162 ],
163 loot_pool=[
164 ("Prism Shard", "*", 0.5), ("Resonance Crystal", "β—†", 0.25),
165 ("Ethereal Blade", "†", 0.12), ("Heart of the Cavern", "β™₯", 0.03)
166 ]
167 ),
168 "biomech": Theme(
169 name="Biomechanical Hive",
170 wall_glyph="╬", floor_glyph="═", door_glyph="β• ",
171 wall_color=(80, 140, 90), floor_color=(30, 50, 35),
172 door_color=(180, 220, 100),
173 special_tiles={
174 Tile.CORRUPTION: ("β–“", (120, 40, 40)),
175 Tile.LAVA: ("β‰ˆ", (220, 80, 20)),
176 },
177 monster_pool=[
178 ("Flesh Drone", "d", 0.6), ("Spinal Crawler", "C", 0.35),
179 ("Hive Queen", "Q", 0.07), ("Nanite Swarm", "n", 0.2)
180 ],
181 loot_pool=[
182 ("Bio-Gel", "o", 0.55), ("Neural Implant", "Β€", 0.22),
183 ("Organic Blade", "∫", 0.15), ("Core Sample", "●", 0.05)
184 ]
185 ),
186 "eldritch": Theme(
187 name="Eldritch Depths",
188 wall_glyph="β–’", floor_glyph=" ", door_glyph="Θ",
189 wall_color=(60, 20, 80), floor_color=(15, 5, 25),
190 door_color=(160, 40, 180),
191 special_tiles={
192 Tile.CORRUPTION: ("β–‘", (90, 20, 110)),
193 Tile.WATER: ("β‰ˆ", (40, 10, 70)),
194 },
195 monster_pool=[
196 ("Shoggoth Spawn", "s", 0.5), ("Cultist", "k", 0.4),
197 ("Star Vampire", "V", 0.18), ("The Unnamed", "?", 0.05)
198 ],
199 loot_pool=[
200 ("Tome of the Void", "T", 0.3), ("Eye of the Deep", "β—‰", 0.2),
201 ("Black Star", "β˜…", 0.08), ("Fragment of Azathoth", "β—ˆ", 0.02)
202 ]
203 ),
204}
205
206# ─────────────────────────────────────────────────────────────────────────────
207# BSP Node
208# ─────────────────────────────────────────────────────────────────────────────
209
210class BSPNode:
211 def __init__(self, rect: Rect):
212 self.rect = rect
213 self.left: Optional[BSPNode] = None
214 self.right: Optional[BSPNode] = None
215 self.room: Optional[Rect] = None
216
217 @property
218 def is_leaf(self) -> bool:
219 return self.left is None and self.right is None
220
221 def split(self, min_size: int, rng: random.Random) -> bool:
222 if not self.is_leaf:
223 return False
224
225 # Decide split direction
226 if self.rect.w > self.rect.h and self.rect.w / self.rect.h >= 1.25:
227 horizontal = False
228 elif self.rect.h > self.rect.w and self.rect.h / self.rect.w >= 1.25:
229 horizontal = True
230 else:
231 horizontal = rng.random() < 0.5
232
233 max_size = (self.rect.h if horizontal else self.rect.w) - min_size
234 if max_size <= min_size:
235 return False
236
237 split_pos = rng.randint(min_size, max_size)
238
239 if horizontal:
240 self.left = BSPNode(Rect(self.rect.x, self.rect.y, self.rect.w, split_pos))
241 self.right = BSPNode(Rect(self.rect.x, self.rect.y + split_pos, self.rect.w, self.rect.h - split_pos))
242 else:
243 self.left = BSPNode(Rect(self.rect.x, self.rect.y, split_pos, self.rect.h))
244 self.right = BSPNode(Rect(self.rect.x + split_pos, self.rect.y, self.rect.w - split_pos, self.rect.h))
245
246 return True
247
248 def create_room(self, rng: random.Random, min_room: int, max_room: int) -> None:
249 if not self.is_leaf:
250 if self.left:
251 self.left.create_room(rng, min_room, max_room)
252 if self.right:
253 self.right.create_room(rng, min_room, max_room)
254 return
255
256 room_w = rng.randint(min_room, min(max_room, self.rect.w - 2))
257 room_h = rng.randint(min_room, min(max_room, self.rect.h - 2))
258 room_x = self.rect.x + rng.randint(1, self.rect.w - room_w - 1)
259 room_y = self.rect.y + rng.randint(1, self.rect.h - room_h - 1)
260 self.room = Rect(room_x, room_y, room_w, room_h)
261
262 def get_leaves(self) -> List[BSPNode]:
263 if self.is_leaf:
264 return [self]
265 leaves = []
266 if self.left:
267 leaves.extend(self.left.get_leaves())
268 if self.right:
269 leaves.extend(self.right.get_leaves())
270 return leaves
271
272 def get_room(self) -> Optional[Rect]:
273 if self.room:
274 return self.room
275 if self.left:
276 r = self.left.get_room()
277 if r:
278 return r
279 if self.right:
280 return self.right.get_room()
281 return None
282
283# ─────────────────────────────────────────────────────────────────────────────
284# A* Pathfinding (for corridors)
285# ─────────────────────────────────────────────────────────────────────────────
286
287def astar(start: Point, goal: Point, walkable: Set[Point], width: int, height: int) -> List[Point]:
288 def heuristic(a: Point, b: Point) -> float:
289 return abs(a.x - b.x) + abs(a.y - b.y)
290
291 open_set = {start}
292 came_from: Dict[Point, Point] = {}
293 g_score = {start: 0.0}
294 f_score = {start: heuristic(start, goal)}
295
296 while open_set:
297 current = min(open_set, key=lambda p: f_score.get(p, float("inf")))
298 if current == goal:
299 path = []
300 while current in came_from:
301 path.append(current)
302 current = came_from[current]
303 path.append(start)
304 return path[::-1]
305
306 open_set.remove(current)
307 for neighbor in current.neighbors4():
308 if not (0 <= neighbor.x < width and 0 <= neighbor.y < height):
309 continue
310 # Prefer already walkable tiles but allow walls (we will carve)
311 tentative_g = g_score[current] + (1.0 if neighbor in walkable else 1.8)
312
313 if tentative_g < g_score.get(neighbor, float("inf")):
314 came_from[neighbor] = current
315 g_score[neighbor] = tentative_g
316 f_score[neighbor] = tentative_g + heuristic(neighbor, goal)
317 open_set.add(neighbor)
318
319 return [] # no path (should be rare)
320
321# ─────────────────────────────────────────────────────────────────────────────
322# The Dungeon itself
323# ─────────────────────────────────────────────────────────────────────────────
324
325class Level:
326 def __init__(self, width: int, height: int, depth: int, theme: Theme, rng: random.Random):
327 self.width = width
328 self.height = height
329 self.depth = depth
330 self.theme = theme
331 self.rng = rng
332 self.tiles: List[List[Tile]] = [[Tile.VOID for _ in range(width)] for _ in range(height)]
333 self.entities: Dict[Point, Entity] = {}
334 self.rooms: List[Rect] = []
335 self.stairs_down: Optional[Point] = None
336 self.stairs_up: Optional[Point] = None
337
338 def in_bounds(self, p: Point) -> bool:
339 return 0 <= p.x < self.width and 0 <= p.y < self.height
340
341 def set_tile(self, p: Point, tile: Tile) -> None:
342 if self.in_bounds(p):
343 self.tiles[p.y][p.x] = tile
344
345 def get_tile(self, p: Point) -> Tile:
346 if self.in_bounds(p):
347 return self.tiles[p.y][p.x]
348 return Tile.VOID
349
350 def carve_room(self, room: Rect) -> None:
351 for y in range(room.y, room.y + room.h):
352 for x in range(room.x, room.x + room.w):
353 self.set_tile(Point(x, y), Tile.FLOOR)
354
355 def carve_corridor(self, path: List[Point]) -> None:
356 for p in path:
357 self.set_tile(p, Tile.FLOOR)
358 # occasionally make it wider
359 if self.rng.random() < 0.15:
360 for n in p.neighbors4():
361 if self.get_tile(n) == Tile.WALL:
362 self.set_tile(n, Tile.FLOOR)
363
364 def place_walls(self) -> None:
365 for y in range(self.height):
366 for x in range(self.width):
367 p = Point(x, y)
368 if self.get_tile(p) == Tile.FLOOR:
369 for n in p.neighbors8():
370 if self.get_tile(n) == Tile.VOID:
371 self.set_tile(n, Tile.WALL)
372
373 def cellular_automata_pass(self, birth_limit: int = 4, death_limit: int = 3) -> None:
374 """One pass of cellular automata to make caves more organic."""
375 new_tiles = [[Tile.VOID for _ in range(self.width)] for _ in range(self.height)]
376 for y in range(self.height):
377 for x in range(self.width):
378 p = Point(x, y)
379 if self.get_tile(p) not in (Tile.FLOOR, Tile.WALL):
380 new_tiles[y][x] = self.get_tile(p)
381 continue
382 wall_count = sum(1 for n in p.neighbors8() if self.get_tile(n) in (Tile.WALL, Tile.VOID))
383 if self.get_tile(p) == Tile.FLOOR:
384 new_tiles[y][x] = Tile.WALL if wall_count > death_limit else Tile.FLOOR
385 else:
386 new_tiles[y][x] = Tile.FLOOR if wall_count < birth_limit else Tile.WALL
387 self.tiles = new_tiles
388
389 def place_doors(self) -> None:
390 for room in self.rooms:
391 for y in range(room.y - 1, room.y + room.h + 1):
392 for x in range(room.x - 1, room.x + room.w + 1):
393 p = Point(x, y)
394 if self.get_tile(p) != Tile.FLOOR:
395 continue
396 # Door candidate: floor tile adjacent to exactly two walls in a line
397 walls = [n for n in p.neighbors4() if self.get_tile(n) == Tile.WALL]
398 if len(walls) == 2 and (walls[0].x == walls[1].x or walls[0].y == walls[1].y):
399 if self.rng.random() < 0.35:
400 self.set_tile(p, Tile.DOOR)
401
402 def place_special_tiles(self) -> None:
403 for room in self.rooms:
404 if self.rng.random() < 0.25:
405 # small water/lava/crystal patch
406 cx, cy = room.center.x, room.center.y
407 for _ in range(self.rng.randint(3, 12)):
408 p = Point(cx + self.rng.randint(-3, 3), cy + self.rng.randint(-3, 3))
409 if self.get_tile(p) == Tile.FLOOR:
410 special = self.rng.choice(list(self.theme.special_tiles.keys()))
411 self.set_tile(p, special)
412
413 def place_entities(self, density: float = 0.04) -> None:
414 floor_tiles = [Point(x, y) for y in range(self.height) for x in range(self.width)
415 if self.get_tile(Point(x, y)) == Tile.FLOOR]
416 self.rng.shuffle(floor_tiles)
417
418 count = int(len(floor_tiles) * density)
419 for p in floor_tiles[:count]:
420 if p in self.entities:
421 continue
422 roll = self.rng.random()
423 if roll < 0.55:
424 # monster
425 name, glyph, rarity = self.rng.choices(
426 self.theme.monster_pool,
427 weights=[1.0 - r for _, _, r in self.theme.monster_pool]
428 )[0]
429 if self.rng.random() < rarity + 0.3:
430 self.entities[p] = Entity(EntityType.MONSTER, name, rarity, glyph,
431 (220, 60, 60))
432 elif roll < 0.85:
433 name, glyph, rarity = self.rng.choices(
434 self.theme.loot_pool,
435 weights=[1.0 - r for _, _, r in self.theme.loot_pool]
436 )[0]
437 self.entities[p] = Entity(EntityType.LOOT, name, rarity, glyph,
438 (255, 215, 0))
439 else:
440 self.entities[p] = Entity(EntityType.TRAP, "Hidden Trap", 0.4, "^",
441 (180, 40, 40))
442
443 def place_stairs(self, is_first: bool, is_last: bool) -> None:
444 if not self.rooms:
445 return
446 # Prefer larger rooms
447 candidates = sorted(self.rooms, key=lambda r: r.area, reverse=True)
448 if not is_first:
449 room = candidates[0]
450 self.stairs_up = room.center
451 self.set_tile(self.stairs_up, Tile.STAIRS_UP)
452 if not is_last:
453 room = candidates[1] if len(candidates) > 1 else candidates[0]
454 self.stairs_down = room.center
455 self.set_tile(self.stairs_down, Tile.STAIRS_DOWN)
456
457class Dungeon:
458 def __init__(self, width: int, height: int, levels: int, theme_name: str,
459 seed: Optional[int] = None, min_room: int = 5, max_room: int = 12):
460 self.width = width
461 self.height = height
462 self.levels_count = levels
463 self.theme = THEMES[theme_name]
464 self.seed = seed if seed is not None else random.randint(0, 2**32 - 1)
465 self.rng = random.Random(self.seed)
466 self.min_room = min_room
467 self.max_room = max_room
468 self.levels: List[Level] = []
469
470 def generate(self) -> None:
471 logging.info(f"Forging {self.levels_count}-level dungeon | seed={self.seed} | theme={self.theme.name}")
472 start = time.perf_counter()
473
474 for depth in range(self.levels_count):
475 logging.info(f" β†’ Generating level {depth + 1}/{self.levels_count}...")
476 level = Level(self.width, self.height, depth, self.theme, self.rng)
477 self._generate_level(level)
478 self.levels.append(level)
479
480 elapsed = time.perf_counter() - start
481 logging.info(f"Dungeon forged in {elapsed:.2f}s")
482
483 def _generate_level(self, level: Level) -> None:
484 # 1. BSP
485 root = BSPNode(Rect(0, 0, self.width, self.height))
486 self._split_bsp(root, max_depth=6)
487
488 # 2. Rooms
489 root.create_room(self.rng, self.min_room, self.max_room)
490 leaves = root.get_leaves()
491 for leaf in leaves:
492 if leaf.room:
493 level.carve_room(leaf.room)
494 level.rooms.append(leaf.room)
495
496 # 3. Connect rooms with A*
497 walkable = {Point(x, y) for y in range(self.height) for x in range(self.width)
498 if level.get_tile(Point(x, y)) == Tile.FLOOR}
499 rooms = [leaf.room for leaf in leaves if leaf.room]
500 for i in range(len(rooms) - 1):
501 start = rooms[i].center
502 goal = rooms[i + 1].center
503 path = astar(start, goal, walkable, self.width, self.height)
504 if path:
505 level.carve_corridor(path)
506 walkable.update(path)
507
508 # 4. Walls + optional cellular pass for organic feel
509 level.place_walls()
510 if self.rng.random() < 0.6:
511 level.cellular_automata_pass()
512
513 # 5. Doors, specials, entities, stairs
514 level.place_doors()
515 level.place_special_tiles()
516 level.place_entities(density=0.035 + level.depth * 0.008)
517 level.place_stairs(is_first=(level.depth == 0),
518 is_last=(level.depth == self.levels_count - 1))
519
520 def _split_bsp(self, node: BSPNode, max_depth: int, depth: int = 0) -> None:
521 if depth >= max_depth:
522 return
523 if node.split(min_size=self.min_room + 4, rng=self.rng):
524 if node.left:
525 self._split_bsp(node.left, max_depth, depth + 1)
526 if node.right:
527 self._split_bsp(node.right, max_depth, depth + 1)
528
529 # ── Rendering ────────────────────────────────────────────────────────────
530
531 def render_ascii(self, level_idx: int = 0, use_color: bool = True) -> str:
532 level = self.levels[level_idx]
533 lines = []
534 for y in range(level.height):
535 row = []
536 for x in range(level.width):
537 p = Point(x, y)
538 tile = level.get_tile(p)
539 entity = level.entities.get(p)
540
541 if entity:
542 glyph = entity.glyph
543 color = entity.color
544 else:
545 glyph, color = self._tile_to_glyph(tile, level.theme)
546
547 if use_color and sys.stdout.isatty():
548 r, g, b = color
549 row.append(f"\033[38;2;{r};{g};{b}m{glyph}\033[0m")
550 else:
551 row.append(glyph)
552 lines.append("".join(row))
553 return "\n".join(lines)
554
555 def _tile_to_glyph(self, tile: Tile, theme: Theme) -> Tuple[str, Tuple[int, int, int]]:
556 if tile == Tile.WALL:
557 return theme.wall_glyph, theme.wall_color
558 if tile == Tile.FLOOR:
559 return theme.floor_glyph, theme.floor_color
560 if tile == Tile.DOOR:
561 return theme.door_glyph, theme.door_color
562 if tile == Tile.STAIRS_DOWN:
563 return ">", (255, 255, 100)
564 if tile == Tile.STAIRS_UP:
565 return "<", (255, 255, 100)
566 if tile in theme.special_tiles:
567 return theme.special_tiles[tile]
568 return " ", (0, 0, 0)
569
570 def export_json(self, path: Path) -> None:
571 data = {
572 "seed": self.seed,
573 "theme": self.theme.name,
574 "width": self.width,
575 "height": self.height,
576 "levels": []
577 }
578 for lvl in self.levels:
579 level_data = {
580 "depth": lvl.depth,
581 "tiles": [[t.name for t in row] for row in lvl.tiles],
582 "entities": {
583 f"{p.x},{p.y}": {
584 "kind": e.kind.name,
585 "name": e.name,
586 "rarity": e.rarity,
587 "glyph": e.glyph
588 } for p, e in lvl.entities.items()
589 },
590 "stairs_up": asdict(lvl.stairs_up) if lvl.stairs_up else None,
591 "stairs_down": asdict(lvl.stairs_down) if lvl.stairs_down else None,
592 }
593 data["levels"].append(level_data)
594 path.write_text(json.dumps(data, indent=2), encoding="utf-8")
595 logging.info(f"Exported JSON β†’ {path}")
596
597 def export_ppm(self, path: Path, level_idx: int = 0, scale: int = 4) -> None:
598 level = self.levels[level_idx]
599 w, h = level.width * scale, level.height * scale
600 header = f"P3\n{w} {h}\n255\n"
601 pixels = []
602 for y in range(level.height):
603 for _ in range(scale):
604 row = []
605 for x in range(level.width):
606 p = Point(x, y)
607 tile = level.get_tile(p)
608 entity = level.entities.get(p)
609 if entity:
610 color = entity.color
611 else:
612 _, color = self._tile_to_glyph(tile, level.theme)
613 row.extend([f"{color[0]} {color[1]} {color[2]}"] * scale)
614 pixels.append(" ".join(row))
615 path.write_text(header + "\n".join(pixels), encoding="utf-8")
616 logging.info(f"Exported PPM image β†’ {path}")
617
618 def stats(self) -> str:
619 lines = [f"Seed: {self.seed}", f"Theme: {self.theme.name}", f"Levels: {len(self.levels)}"]
620 for i, lvl in enumerate(self.levels):
621 floors = sum(1 for row in lvl.tiles for t in row if t == Tile.FLOOR)
622 entities = len(lvl.entities)
623 monsters = sum(1 for e in lvl.entities.values() if e.kind == EntityType.MONSTER)
624 loot = sum(1 for e in lvl.entities.values() if e.kind == EntityType.LOOT)
625 lines.append(f" Level {i}: {len(lvl.rooms)} rooms | {floors} floor tiles | "
626 f"{entities} entities ({monsters} monsters, {loot} loot)")
627 return "\n".join(lines)
628
629# ─────────────────────────────────────────────────────────────────────────────
630# CLI
631# ─────────────────────────────────────────────────────────────────────────────
632
633def setup_logging(verbose: bool) -> None:
634 level = logging.DEBUG if verbose else logging.INFO
635 logging.basicConfig(
636 level=level,
637 format="%(asctime)s β”‚ %(levelname)-8s β”‚ %(message)s",
638 datefmt="%H:%M:%S"
639 )
640
641def main() -> None:
642 if hasattr(sys.stdout, 'reconfigure'):
643 sys.stdout.reconfigure(encoding='utf-8')
644 if hasattr(sys.stderr, 'reconfigure'):
645 sys.stderr.reconfigure(encoding='utf-8')
646
647 parser = argparse.ArgumentParser(
648 description="Aetherforge β€” Procedural Multi-Layer Dungeon Generator",
649 formatter_class=argparse.RawDescriptionHelpFormatter,
650 epilog="""
651Examples:
652 python aetherforge.py generate --width 80 --height 40 --levels 3 --theme crystal
653 python aetherforge.py generate -s 42 --theme eldritch --export-json dungeon.json
654 python aetherforge.py generate --theme biomech --export-ppm map.ppm --scale 6
655 """
656 )
657 sub = parser.add_subparsers(dest="command", required=True)
658
659 gen = sub.add_parser("generate", help="Forge a new dungeon")
660 gen.add_argument("-W", "--width", type=int, default=70, help="Map width")
661 gen.add_argument("-H", "--height", type=int, default=35, help="Map height")
662 gen.add_argument("-l", "--levels", type=int, default=1, help="Number of levels")
663 gen.add_argument("-t", "--theme", choices=list(THEMES.keys()), default="stone")
664 gen.add_argument("-s", "--seed", type=int, default=None, help="RNG seed")
665 gen.add_argument("--min-room", type=int, default=5)
666 gen.add_argument("--max-room", type=int, default=11)
667 gen.add_argument("--export-json", type=Path, help="Export full dungeon as JSON")
668 gen.add_argument("--export-ppm", type=Path, help="Export level 0 as PPM image")
669 gen.add_argument("--scale", type=int, default=5, help="PPM pixel scale")
670 gen.add_argument("--no-color", action="store_true", help="Disable ANSI colors")
671 gen.add_argument("-v", "--verbose", action="store_true")
672
673 args = parser.parse_args()
674 setup_logging(args.verbose)
675
676 if args.command == "generate":
677 dungeon = Dungeon(
678 width=args.width,
679 height=args.height,
680 levels=args.levels,
681 theme_name=args.theme,
682 seed=args.seed,
683 min_room=args.min_room,
684 max_room=args.max_room
685 )
686 dungeon.generate()
687
688 print("\n" + dungeon.stats() + "\n")
689 print(dungeon.render_ascii(0, use_color=not args.no_color))
690
691 if args.export_json:
692 dungeon.export_json(args.export_json)
693 if args.export_ppm:
694 dungeon.export_ppm(args.export_ppm, scale=args.scale)
695
696if __name__ == "__main__":
697 main()
Scroll inside window to view remaining lines (697 total)
πŸ“

Semi-complex File Organizer

Organizes loose files into categorized folders based on file extensions with collision protection, dry-run safety previews, and recursive scanning.

179 linespython
file-organizer.py
1#!/usr/bin/env python3
2"""
3Semi-complex File Organizer Utility
4Organizes files into categorized folders based on extension.
5"""
6
7import argparse
8import logging
9import shutil
10from collections import defaultdict
11from pathlib import Path
12from typing import Dict, List, Set
13
14# Category mapping (extension β†’ folder name)
15CATEGORIES: Dict[str, Set[str]] = {
16 "Images": {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".tiff", ".svg", ".heic"},
17 "Documents": {".pdf", ".doc", ".docx", ".txt", ".rtf", ".odt", ".xls", ".xlsx", ".ppt", ".pptx", ".csv", ".md"},
18 "Videos": {".mp4", ".mkv", ".avi", ".mov", ".wmv", ".flv", ".webm", ".m4v"},
19 "Audio": {".mp3", ".wav", ".flac", ".aac", ".ogg", ".m4a", ".wma"},
20 "Archives": {".zip", ".rar", ".7z", ".tar", ".gz", ".bz2", ".xz"},
21 "Code": {".py", ".js", ".ts", ".html", ".css", ".java", ".cpp", ".c", ".h", ".go", ".rs", ".sh", ".json", ".yaml", ".yml", ".toml"},
22}
23
24def setup_logging(log_file: Path, verbose: bool) -> None:
25 """Configure logging to console + file."""
26 level = logging.DEBUG if verbose else logging.INFO
27 logging.basicConfig(
28 level=level,
29 format="%(asctime)s | %(levelname)-8s | %(message)s",
30 datefmt="%Y-%m-%d %H:%M:%S",
31 handlers=[
32 logging.FileHandler(log_file, encoding="utf-8"),
33 logging.StreamHandler(),
34 ],
35 )
36
37def get_category(extension: str) -> str:
38 """Return the category folder name for a given extension."""
39 ext = extension.lower()
40 for category, extensions in CATEGORIES.items():
41 if ext in extensions:
42 return category
43 return "Other"
44
45def unique_path(destination: Path) -> Path:
46 """Generate a unique path if the target already exists."""
47 if not destination.exists():
48 return destination
49
50 stem = destination.stem
51 suffix = destination.suffix
52 parent = destination.parent
53 counter = 1
54
55 while True:
56 new_name = f"{stem}_{counter}{suffix}"
57 candidate = parent / new_name
58 if not candidate.exists():
59 return candidate
60 counter += 1
61
62def organize_files(
63 source: Path,
64 recursive: bool,
65 dry_run: bool,
66) -> Dict[str, List[Path]]:
67 """
68 Scan the source directory and move files into category folders.
69 Returns a dict of category β†’ list of moved files.
70 """
71 moved: Dict[str, List[Path]] = defaultdict(list)
72 pattern = "**/*" if recursive else "*"
73
74 files = [p for p in source.glob(pattern) if p.is_file()]
75
76 if not files:
77 logging.warning("No files found to organize.")
78 return moved
79
80 logging.info(f"Found {len(files)} file(s) to process...")
81
82 for file_path in files:
83 # Skip the log file itself and any category folders we create
84 if file_path.name.startswith("organize_") or file_path.parent.name in CATEGORIES or file_path.parent.name == "Other":
85 continue
86
87 category = get_category(file_path.suffix)
88 target_dir = source / category
89 target_path = unique_path(target_dir / file_path.name)
90
91 if dry_run:
92 logging.info(f"[DRY-RUN] Would move: {file_path.relative_to(source)} β†’ {category}/{target_path.name}")
93 else:
94 target_dir.mkdir(exist_ok=True)
95 try:
96 shutil.move(str(file_path), str(target_path))
97 logging.info(f"Moved: {file_path.relative_to(source)} β†’ {category}/{target_path.name}")
98 moved[category].append(target_path)
99 except Exception as e:
100 logging.error(f"Failed to move {file_path}: {e}")
101
102 return moved
103
104def print_summary(moved: Dict[str, List[Path]], dry_run: bool) -> None:
105 """Print a nice summary of the operation."""
106 action = "Would move" if dry_run else "Moved"
107 total = sum(len(files) for files in moved.values())
108
109 print("\n" + "=" * 50)
110 print(f"{'DRY-RUN SUMMARY' if dry_run else 'SUMMARY'}")
111 print("=" * 50)
112
113 if total == 0:
114 print("No files were processed.")
115 return
116
117 for category in sorted(moved.keys()):
118 count = len(moved[category])
119 print(f" {category:<12} : {count:>4} file(s)")
120
121 print("-" * 50)
122 print(f" {'Total':<12} : {total:>4} file(s)")
123 print("=" * 50)
124
125def main() -> None:
126 parser = argparse.ArgumentParser(
127 description="Organize files into categorized folders by extension.",
128 formatter_class=argparse.RawDescriptionHelpFormatter,
129 epilog="""
130Examples:
131 python file_organizer.py ~/Downloads
132 python file_organizer.py . --recursive --dry-run
133 python file_organizer.py /path/to/folder -v
134 """,
135 )
136 parser.add_argument(
137 "directory",
138 type=Path,
139 help="Directory to organize (default: current directory)",
140 nargs="?",
141 default=Path.cwd(),
142 )
143 parser.add_argument(
144 "-r", "--recursive",
145 action="store_true",
146 help="Process subdirectories recursively",
147 )
148 parser.add_argument(
149 "-d", "--dry-run",
150 action="store_true",
151 help="Show what would be done without actually moving files",
152 )
153 parser.add_argument(
154 "-v", "--verbose",
155 action="store_true",
156 help="Enable verbose (debug) logging",
157 )
158
159 args = parser.parse_args()
160 source = args.directory.resolve()
161
162 if not source.is_dir():
163 print(f"Error: '{source}' is not a valid directory.")
164 return
165
166 log_file = source / "organize_log.txt"
167 setup_logging(log_file, args.verbose)
168
169 logging.info(f"Starting organization of: {source}")
170 logging.info(f"Recursive: {args.recursive} | Dry-run: {args.dry_run}")
171
172 moved = organize_files(source, args.recursive, args.dry_run)
173 print_summary(moved, args.dry_run)
174
175 if not args.dry_run and moved:
176 logging.info(f"Log saved to: {log_file}")
177
178if __name__ == "__main__":
179 main()
Scroll inside window to view remaining lines (179 total)
🎡

Extract Audio from Video

A quick FFmpeg one-liner to extract high-quality MP3 audio from any MP4 video file.

1 linesbash
extract-audio.sh
1ffmpeg -i input.mp4 -q:a 0 -map a output.mp3
πŸ’§

UART Hardware Connection

A self-contained script using Mix.install to spawn a GenServer worker and communicate with hardware over a serial connection.

18 lineselixir
elixir-uart.exs
1# uart_demo.exs
2
3Mix.install([
4 {:circuits_uart, "~> 1.5"}
5])
6
7alias Circuits.UART
8
9{:ok, pid} = UART.start_link()
10
11IO.inspect(UART.enumerate(), label: "Available Ports")
12
13UART.open(pid, "COM3", speed: 115200, active: false)
14
15UART.write(pid, "PING\r\n")
16
17{:ok, data} = UART.read(pid, 1000)
18IO.inspect(data, label: "Response")