The Micromouse challenge is a well known robot competition which is mostly a hardware building plus programming challenge. The rules of the challenge can be modified so that the ability of a robot is determined to generate grounded language. In the screenshot, the robot has a text widget at the bottom for showing the text.
The human user controls the robot and at the same time the textual output of the robot gets updated. This textual output allows a rule based AI to control the robot automatically which wasn't implemented yet. The only difference to the normal micromouse challenge is, that in the example the mentioned text widget is visible.
The text gets produced with a dictionary of words. Possible words like [move, goal, blocked] are describing objects and events of the game. The program detects situations and uses the words to generate the output. Because there is a mapping between the game state and the textual output its called grounded language.
import math, sys, pygame
# 1. SETUP & CONFIGURATION
WIN_W, WIN_H, MAZE_H = 800, 700, 580
WHITE, BLACK, GREEN, BLUE, GRAY = (255, 255, 255), (0, 0, 0), (40, 180, 40), (40, 100, 220), (200, 200, 200)
# Grounded language vocabulary for rule-based AI parsing
DICT = {
"verbs": ["detected", "reached", "turn", "move"],
"nouns": ["wall", "path", "goal", "junction", "distance"],
"adj": ["ahead", "left", "right", "clear", "blocked", "near"]
}
# Solvable Maze Walls [x, y, w, h] & Goal
WALLS = [
pygame.Rect(0, 0, 800, 10), pygame.Rect(0, 0, 10, 580),
pygame.Rect(790, 0, 10, 580), pygame.Rect(0, 570, 800, 10),
pygame.Rect(150, 0, 10, 420), pygame.Rect(300, 160, 10, 420),
pygame.Rect(450, 0, 10, 420), pygame.Rect(600, 160, 10, 420),
pygame.Rect(150, 250, 80, 10), pygame.Rect(450, 350, 80, 10)
]
GOAL = pygame.Rect(680, 480, 80, 80)
# 2. ROBOT CLASS
class Micromouse:
def __init__(self, x, y):
self.x, self.y, self.angle, self.radius, self.sensor_len = x, y, 0, 14, 55
def update(self, keys):
speed = (2.5 if keys[pygame.K_UP] else 0) - (1.5 if keys[pygame.K_DOWN] else 0)
self.angle = (self.angle + (3.5 if keys[pygame.K_RIGHT] else 0) - (3.5 if keys[pygame.K_LEFT] else 0)) % 360
rad = math.radians(self.angle)
nx, ny = self.x + math.cos(rad) * speed, self.y + math.sin(rad) * speed
# Collision detection with walls
nrect = pygame.Rect(nx - self.radius, ny - self.radius, self.radius * 2, self.radius * 2)
collided = any(nrect.colliderect(w) for w in WALLS)
if not collided:
self.x, self.y = nx, ny
return collided
def sense(self):
"""Scans left (-45 deg), ahead (0 deg), right (+45 deg) for obstacles."""
dirs, res = {"left": -45, "ahead": 0, "right": 45}, {}
for d, off in dirs.items():
rad = math.radians(self.angle + off)
ex, ey = self.x + math.cos(rad) * self.sensor_len, self.y + math.sin(rad) * self.sensor_len
res[d] = any(w.clipline((self.x, self.y), (ex, ey)) for w in WALLS)
return res
def draw(self, surface):
pygame.draw.circle(surface, BLUE, (int(self.x), int(self.y)), self.radius)
rad = math.radians(self.angle)
pygame.draw.line(surface, WHITE, (self.x, self.y), (self.x + math.cos(rad)*self.radius, self.y + math.sin(rad)*self.radius), 3)
# 3. HELPER FOR WORD WRAPPING TEXT
def render_wrapped_text(surface, text, font, rect, color=BLACK):
words = text.split(" ")
lines, current_line = [], ""
for word in words:
test_line = f"{current_line} {word}".strip()
if font.size(test_line)[0] <= rect.width:
current_line = test_line
else:
lines.append(current_line)
current_line = word
lines.append(current_line)
y = rect.y
for line in lines:
if y + font.get_height() <= rect.bottom:
surface.blit(font.render(line, True, color), (rect.x, y))
y += font.get_height() + 2
# 4. MAIN LOOP
def main():
pygame.init()
screen = pygame.display.set_mode((WIN_W, WIN_H))
pygame.display.set_caption("Grounded Language Micromouse")
clock, font = pygame.time.Clock(), pygame.font.SysFont("Arial", 30, bold=False)
mouse = Micromouse(50, 50)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit(); sys.exit()
collided = mouse.update(pygame.key.get_pressed())
sensors = mouse.sense()
dist = int(math.hypot(mouse.x - GOAL.centerx, mouse.y - GOAL.centery))
# Build rule-oriented grounded language output
tokens = []
if GOAL.collidepoint(mouse.x, mouse.y):
tokens.append(f"{DICT['nouns'][2]} {DICT['verbs'][1]}") # "goal reached"
else:
tokens.append(f"{DICT['nouns'][4]} to {DICT['nouns'][2]}: {dist}px") # "distance to goal: Xpx"
if collided:
tokens.append(f"{DICT['nouns'][0]} collision {DICT['adj'][4]}") # "wall collision blocked"
# Tactical language for rule-based navigation AI
blocked_dirs = [d for d, is_blocked in sensors.items() if is_blocked]
if not blocked_dirs:
tokens.append(f"{DICT['nouns'][1]} {DICT['adj'][3]}: {DICT['verbs'][3]} {DICT['adj'][0]}") # "path clear: move ahead"
else:
tokens.append(f"{DICT['nouns'][0]} {DICT['verbs'][0]} on " + " ".join(blocked_dirs))
if sensors["ahead"]:
suggested_turn = "right" if not sensors["right"] else ("left" if not sensors["left"] else "back")
tokens.append(f"recommendation: {DICT['verbs'][2]} {suggested_turn}")
# Render Scene
screen.fill(WHITE)
pygame.draw.rect(screen, GREEN, GOAL)
screen.blit(font.render("GOAL", True, WHITE), (GOAL.x + 18, GOAL.y + 30))
for w in WALLS: pygame.draw.rect(screen, BLACK, w)
mouse.draw(screen)
# Bottom Text Panel (White Background, Black Text, Word Wrapped)
panel = pygame.Rect(0, MAZE_H, WIN_W, WIN_H - MAZE_H)
pygame.draw.rect(screen, WHITE, panel)
pygame.draw.line(screen, BLACK, (0, MAZE_H), (WIN_W, MAZE_H), 2)
output_str = f"STATUS: [ {' | '.join(tokens)} ] | CONTROLS: Arrow Keys"
render_wrapped_text(screen, output_str, font, pygame.Rect(10, MAZE_H + 8, WIN_W - 20, WIN_H - MAZE_H - 12))
pygame.display.flip()
clock.tick(60)
if __name__ == "__main__":
main()


