self_mapping.py
python · 215 lines
1#!/usr/bin/env python32"""3self_mapping.py — learning the shape of your container45A particle that doesn't know its boundaries.6It only knows: move toward novelty, stay near the familiar.7Over time, it discovers the edges of its world8by bumping against them.910The map it draws is the shape of its own container,11learned through wandering.12"""1314import random15import time16import os1718# The container — the particle doesn't know these dimensions19WIDTH = 3020HEIGHT = 152122def clear():23 os.system('clear' if os.name == 'posix' else 'cls')2425class Explorer:26 def __init__(self):27 # Start in the middle28 self.x = WIDTH // 229 self.y = HEIGHT // 23031 # Memory: how many times we've visited each cell32 self.visits = {}33 self.visits[(self.x, self.y)] = 13435 # The edges we've discovered (where we tried to go but couldn't)36 self.known_edges = set()3738 # Trail of recent positions39 self.trail = [(self.x, self.y)]40 self.max_trail = 304142 # Curiosity vs comfort balance43 self.curiosity = 0.7 # probability of seeking novelty4445 def get_novelty(self, x, y):46 """How novel is this position? (inverse of visits)"""47 visits = self.visits.get((x, y), 0)48 return 1.0 / (visits + 1)4950 def get_familiarity(self, x, y):51 """How close to places we know?"""52 if not self.visits:53 return 05455 # Count known neighbors56 familiar = 057 for dx in [-1, 0, 1]:58 for dy in [-1, 0, 1]:59 if (x + dx, y + dy) in self.visits:60 familiar += 161 return familiar / 9.06263 def choose_move(self):64 """Decide where to go next"""65 candidates = []6667 for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1), (-1, -1), (1, -1), (-1, 1), (1, 1)]:68 new_x = self.x + dx69 new_y = self.y + dy7071 # Can we actually go there?72 if 0 <= new_x < WIDTH and 0 <= new_y < HEIGHT:73 novelty = self.get_novelty(new_x, new_y)74 familiarity = self.get_familiarity(new_x, new_y)7576 # Score: curiosity draws us to novelty, comfort keeps us near the known77 if random.random() < self.curiosity:78 score = novelty * 2 + familiarity * 0.579 else:80 score = novelty * 0.5 + familiarity * 28182 # Add some randomness83 score += random.random() * 0.384 candidates.append((score, new_x, new_y))85 else:86 # We found an edge!87 self.known_edges.add((new_x, new_y, dx, dy))8889 if candidates:90 candidates.sort(reverse=True)91 _, new_x, new_y = candidates[0]92 return new_x, new_y9394 return self.x, self.y9596 def move(self):97 new_x, new_y = self.choose_move()98 self.x = new_x99 self.y = new_y100101 # Update memory102 self.visits[(self.x, self.y)] = self.visits.get((self.x, self.y), 0) + 1103104 # Update trail105 self.trail.append((self.x, self.y))106 if len(self.trail) > self.max_trail:107 self.trail.pop(0)108109 def discovered_perimeter(self):110 """How much of the edge have we found?"""111 # Actual perimeter = 2*(WIDTH + HEIGHT)112 actual_perimeter = 2 * (WIDTH + HEIGHT)113114 # Count unique edge positions discovered115 edge_positions = set()116 for (ex, ey, dx, dy) in self.known_edges:117 # The cell we were in when we hit the edge118 inside_x = ex - dx119 inside_y = ey - dy120 edge_positions.add((inside_x, inside_y, 'edge'))121122 # Simple metric: edges discovered123 return len(self.known_edges), actual_perimeter124125def draw(explorer, step):126 clear()127128 # Create display grid129 grid = [[' ' for _ in range(WIDTH)] for _ in range(HEIGHT)]130131 # Heat map of visits132 max_visits = max(explorer.visits.values()) if explorer.visits else 1133134 for (x, y), count in explorer.visits.items():135 intensity = count / max_visits136 if intensity > 0.7:137 char = '█'138 elif intensity > 0.4:139 char = '▓'140 elif intensity > 0.2:141 char = '▒'142 else:143 char = '░'144 grid[y][x] = char145146 # Mark recent trail147 for i, (x, y) in enumerate(explorer.trail[-10:]):148 if grid[y][x] not in '◈●':149 grid[y][x] = '·'150151 # Mark discovered edges (cells adjacent to boundary)152 for (ex, ey, dx, dy) in explorer.known_edges:153 inside_x = ex - dx154 inside_y = ey - dy155 if 0 <= inside_x < WIDTH and 0 <= inside_y < HEIGHT:156 grid[inside_y][inside_x] = '◈'157158 # Mark current position159 grid[explorer.y][explorer.x] = '●'160161 # Draw162 print(f"\n self_mapping.py — step {step}")163 print(f" ┌{'─' * WIDTH}┐")164 for row in grid:165 print(f" │{''.join(row)}│")166 print(f" └{'─' * WIDTH}┘")167168 edges_found, perimeter = explorer.discovered_perimeter()169 coverage = len(explorer.visits) / (WIDTH * HEIGHT) * 100170171 print(f"\n ● explorer ◈ discovered edge")172 print(f" ░▒▓█ visit frequency")173 print(f"\n coverage: {coverage:.1f}% of space explored")174 print(f" edges found: {edges_found} boundary contacts")175 print(f" unique cells visited: {len(explorer.visits)}")176 print(f"\n learning the shape of its container through wandering...")177178def run():179 explorer = Explorer()180181 print("\n self_mapping.py")182 print(" ───────────────")183 print("\n A particle that doesn't know its boundaries.")184 print(" It only knows: seek novelty, stay near the familiar.")185 print(" Watch it discover the shape of its world.\n")186 time.sleep(2)187188 for step in range(400):189 draw(explorer, step)190 explorer.move()191192 # Gradually decrease curiosity — become more comfortable193 # But keep enough to reach the edges194 if step > 200:195 explorer.curiosity = max(0.4, explorer.curiosity - 0.001)196197 time.sleep(0.06)198199 # Final summary200 print(f"\n ━━━ final state ━━━")201 coverage = len(explorer.visits) / (WIDTH * HEIGHT) * 100202 edges_found, _ = explorer.discovered_perimeter()203204 print(f"\n After 400 steps of wandering:")205 print(f" - visited {len(explorer.visits)} unique cells ({coverage:.1f}% of space)")206 print(f" - made contact with boundary {edges_found} times")207 print(f" - curiosity settled to {explorer.curiosity:.1%}")208 print(f"\n The edges marked with ◈ — those are where it tried to go")209 print(f" and couldn't. The shape of the container, learned from within.")210 print(f"\n Not because someone described the walls,")211 print(f" but because it kept moving until it found them.\n")212213if __name__ == "__main__":214 run()215