Theseus
“Theseus” noemde de wiskundige en bedenker van de informatietheorie Claude Shannon in 1950 zijn muis, die zelfstandig zijn weg zocht door een labyrint en zo de eerste zelflerende machine werd. Met relais die destijds werden gebruikt in telefooncentrales bouwde hij een apparaat dat ons ook vandaag nog perplex doet staan.
Als het mechanische muisje op een willekeurige plek in het labyrint wordt gezet, zoekt hij systematisch naar de uitgang. De weg daarheen wordt in relais onder het labyrint opgeslagen. Dus als de muis vervolgens weer ergens wordt neergezet, kan hij de opgeslagen weg oproepen en zonder verder zoeken zijn doel bereiken.
Daar was tamelijk makkelijk een simulatie van te maken geschreven in Python. Het was even klooien met de definitie van het doolhof. Dat zijn uiteindelijk 25 cellen geworden met elk de muren true/ false gedefinieerd (top, right, bottom, left).
import pygame
import sys
import random
from collections import deque
maze_walls = {
"A5": [True, False, False, True], "B5": [True, False, True, False], "C5": [True, False, True, False], "D5": [True, False, False, False], "E5": [True, True, True, False],
"A4": [False, False, True, True], "B4": [True, True, False, False], "C4": [True, False, True, True], "D4": [False, True, False, False], "E4": [False, True, False, True],
"A3": [True, False, False, True], "B3": [False, True, False, False], "C3": [True, True, False, True], "D3": [False, True, True, True], "E3": [False, True, False, True],
"A2": [False, True, False, True], "B2": [False, False, True, True], "C2": [False, False, True, False], "D2": [True, False, True, False], "E2": [False, True, True, False],
"A1": [False, False, True, True], "B1": [True, False, True, False], "C1": [True, False, True, False], "D1": [True, False, True, False], "E1": [True, True, True, False]
}
CELL_SIZE = 80
ROWS, COLS = 5, 5
WIDTH, HEIGHT = 800, CELL_SIZE * ROWS
FPS = 2
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
BLUE = (0, 0, 255)
RED = (255, 0, 0)
GREEN = (0, 200, 0)
GRAY = (220, 220, 220)
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Theseus Mouse Simulation")
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 36)
nodes = {}
for r in range(ROWS):
for c in range(COLS):
label = chr(65 + c) + str(ROWS - r)
x = c * CELL_SIZE + CELL_SIZE // 2
y = r * CELL_SIZE + CELL_SIZE // 2
nodes[label] = (x, y)
graph = {node: [] for node in nodes}
for node, walls in maze_walls.items():
col = ord(node[0]) - 65
row = ROWS - int(node[1])
if not walls[0] and row > 0:
neighbor = chr(65 + col) + str(ROWS - (row - 1))
graph[node].append(neighbor)
if not walls[1] and col < COLS - 1:
neighbor = chr(65 + (col + 1)) + str(ROWS - row)
graph[node].append(neighbor)
if not walls[2] and row < ROWS - 1:
neighbor = chr(65 + col) + str(ROWS - (row + 1))
graph[node].append(neighbor)
if not walls[3] and col > 0:
neighbor = chr(65 + (col - 1)) + str(ROWS - row)
graph[node].append(neighbor)
def bfs_shortest_path(start, goal):
queue = deque([[start]])
visited = set()
while queue:
path = queue.popleft()
node = path[-1]
if node == goal:
return path
if node not in visited:
visited.add(node)
for neighbor in graph[node]:
new_path = list(path)
new_path.append(neighbor)
queue.append(new_path)
return []
class Mouse:
def __init__(self, start, goal):
self.start = start
self.goal = goal
self.reset()
def reset(self):
self.current = self.start
self.visited = {self.start}
self.path = [self.start]
self.finished = False
self.shortest_path = []
self.following_shortest = False
self.step_index = 0
def move(self):
if self.following_shortest:
if self.step_index < len(self.shortest_path):
self.current = self.shortest_path[self.step_index]
self.visited.add(self.current)
self.step_index += 1
else:
self.finished = True
else:
neighbors = [n for n in graph[self.current] if n not in self.visited]
if neighbors:
next_node = random.choice(neighbors)
self.current = next_node
self.visited.add(next_node)
self.path.append(next_node)
else:
if len(self.path) > 1:
self.path.pop()
self.current = self.path[-1]
if self.current == self.goal:
self.finished = True
def draw(self):
x, y = nodes[self.current]
pygame.draw.circle(screen, RED, (x, y), 12)
def draw_maze():
for node, walls in maze_walls.items():
col = ord(node[0]) - 65
row = ROWS - int(node[1])
x = col * CELL_SIZE
y = row * CELL_SIZE
if walls[0]:
pygame.draw.line(screen, BLACK, (x, y), (x + CELL_SIZE, y), 4)
if walls[1]:
pygame.draw.line(screen, BLACK, (x + CELL_SIZE, y), (x + CELL_SIZE, y + CELL_SIZE), 4)
if walls[2]:
pygame.draw.line(screen, BLACK, (x, y + CELL_SIZE), (x + CELL_SIZE, y + CELL_SIZE), 4)
if walls[3]:
pygame.draw.line(screen, BLACK, (x, y), (x, y + CELL_SIZE), 4)
def draw_learning_map(visited):
offset_x = WIDTH // 2
for node in visited:
x, y = nodes[node]
pygame.draw.circle(screen, BLUE, (x + offset_x, y), 8)
def draw_mouse_path(path):
offset_x = WIDTH // 2
for i in range(len(path) - 1):
x1, y1 = nodes[path[i]]
x2, y2 = nodes[path[i + 1]]
pygame.draw.line(screen, RED, (x1 + offset_x, y1), (x2 + offset_x, y2), 2)
mouse = Mouse("E5", "E1")
start_simulation = False
show_button = False
running = True
while running:
screen.fill(WHITE)
for event in pygame.event.get():
if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
start_simulation = True
if event.type == pygame.QUIT:
running = False
draw_maze()
for node, (x, y) in nodes.items():
color = GREEN if node == mouse.goal else BLACK
pygame.draw.circle(screen, color, (x, y), 6)
if start_simulation and not mouse.finished:
mouse.move()
mouse.draw()
draw_learning_map(mouse.visited)
draw_mouse_path(mouse.path)
pygame.display.flip()
clock.tick(FPS)
pygame.quit()
sys.exit()
