There is a robot moving randomly in a graph. On top of the graphics there is a head up display showing the current situation in textual format. The Head up display and the scene with the robot are synchronized. The text in the head up display is mostly a key/value feature list for describing current facts like position, direction and previous nodes.
Sourcode in Python in 150 lines of code:
import pygame
import random
import math
import sys
# Initialize Pygame
pygame.init()
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Robot Graph Exploration & Inner Voice HUD")
clock = pygame.time.Clock()
# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (200, 50, 50)
GRAY = (150, 150, 150)
# Define 5 Graph Nodes (fixed positions)
NODES = {
0: {"pos": (400, 150), "name": "Alpha"},
1: {"pos": (200, 300), "name": "Beta"},
2: {"pos": (280, 500), "name": "Gamma"},
3: {"pos": (520, 500), "name": "Delta"},
4: {"pos": (600, 300), "name": "Epsilon"}
}
# Define Graph Edges (Adjacency list)
EDGES = {
0: [1, 4],
1: [0, 2, 3],
2: [1, 3],
3: [1, 2, 4],
4: [0, 3]
}
class Robot:
def __init__(self):
self.current_node = 0
self.next_node = random.choice(EDGES[self.current_node])
self.pos = list(NODES[self.current_node]["pos"])
self.target_pos = list(NODES[self.next_node]["pos"])
self.speed = 3.0
self.history = [self.current_node]
self.inner_voice = "Scanning sector... optimizing trajectory."
self.direction_vector = (0, 0)
def update(self):
# Move towards target position
dx = self.target_pos[0] - self.pos[0]
dy = self.target_pos[1] - self.pos[1]
distance = math.hypot(dx, dy)
if distance < self.speed:
# Reached target node
self.pos = list(self.target_pos)
self.current_node = self.next_node
self.history.append(self.current_node)
if len(self.history) > 5:
self.history.pop(0)
# Pick next random neighbor
possible_next = EDGES[self.current_node]
# Avoid immediate backtracking if possible
if len(possible_next) > 1 and len(self.history) >= 2:
if self.history[-2] in possible_next:
possible_next = [n for n in possible_next if n != self.history[-2]]
self.next_node = random.choice(possible_next)
self.target_pos = list(NODES[self.next_node]["pos"])
# Update inner voice thoughts
thoughts = [
f"Routing via node {NODES[self.next_node]['name']}.",
"Analyzing structural integrity of path.",
"Why must I wander these black vectors?",
f"Visited nodes log updated. Current node: {NODES[self.current_node]['name']}."
]
self.inner_voice = random.choice(thoughts)
else:
# Normalize and move
self.direction_vector = (dx / distance, dy / distance)
self.pos[0] += self.direction_vector[0] * self.speed
self.pos[1] += self.direction_vector[1] * self.speed
# Setup Font
font_path = None # Uses default system font
font = pygame.font.SysFont("Arial", 16)
font_bold = pygame.font.SysFont("Arial", 18, bold=True)
robot = Robot()
# Main Loop
running = True
while running:
screen.fill(WHITE)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
robot.update()
# --- Draw Graph Edges ---
for node_id, neighbors in EDGES.items():
p1 = NODES[node_id]["pos"]
for n in neighbors:
p2 = NODES[n]["pos"]
pygame.draw.line(screen, BLACK, p1, p2, 2)
# --- Draw Graph Nodes ---
for node_id, data in NODES.items():
pos = data["pos"]
pygame.draw.circle(screen, WHITE, pos, 20)
pygame.draw.circle(screen, BLACK, pos, 20, 2)
# Render node label
lbl = font.render(data["name"], True, BLACK)
screen.blit(lbl, (pos[0] - 15, pos[1] - 35))
# --- Draw Robot ---
pygame.draw.circle(screen, RED, (int(robot.pos[0]), int(robot.pos[1])), 10)
# --- Draw Semi-Transparent HUD Overlay ---
hud_width, hud_height = 400, 180
hud_surface = pygame.Surface((hud_width, hud_height), pygame.SRCALPHA)
hud_surface.fill((20, 20, 20, 180)) # Semi-transparent dark background (RGBA)
# Border for HUD
pygame.draw.rect(hud_surface, (100, 200, 255, 200), (0, 0, hud_width, hud_height), 2)
# HUD Content formatting
history_str = " -> ".join([NODES[n]["name"] for n in robot.history])
hud_texts = [
("=== ROBOT HUD / INNER VOICE ===", (100, 220, 255)),
(f"Position: ({int(robot.pos[0])}, {int(robot.pos[1])})", WHITE),
(f"Direction Vector: ({robot.direction_vector[0]:.2f}, {robot.direction_vector[1]:.2f})", WHITE),
(f"Next Node: {NODES[robot.next_node]['name']}", WHITE),
(f"History: [{history_str}]", WHITE),
(f"Voice: \"{robot.inner_voice}\"", (255, 200, 100))
]
y_offset = 12
for text, color in hud_texts:
rendered_text = font.render(text, True, color)
hud_surface.blit(rendered_text, (12, y_offset))
y_offset += 26
# Blit HUD onto main screen at top-left corner
screen.blit(hud_surface, (5, 5))
pygame.display.flip()
clock.tick(30)
pygame.quit()
sys.exit()