visual_memory / server /renderer.py
kdemon1011's picture
Upload folder using huggingface_hub
816634a verified
"""SVG board renderer for Visual Memory Gym.
Produces deterministic board views as inline SVG text.
Each cell state (hidden, revealed, flagged, faded, fog) has a
distinct visual theme so the agent must interpret spatial layout,
colors, and icons to reason about the board.
"""
from __future__ import annotations
from typing import Any
import svgwrite
CELL_SIZE = 48
PADDING = 24
COORD_FONT_SIZE = 11
CELL_FONT_SIZE = 13
ICON_FONT_SIZE = 18
COLORS = {
"background": "#1a1a2e",
"grid_line": "#2d2d4a",
"coord_text": "#8888aa",
"hidden_fill": "#2d2d4a",
"hidden_stroke": "#3d3d5a",
"revealed_empty_fill": "#e8e8f0",
"revealed_signal_fill": "#d0e8ff",
"revealed_hazard_fill": "#ff4d4d",
"revealed_key_fill": "#ffd700",
"revealed_decoy_fill": "#c8b8e8",
"revealed_goal_fill": "#50fa7b",
"flagged_fill": "#ff6b35",
"flagged_stroke": "#ff8c5a",
"faded_fill": "#3a3a55",
"faded_stroke": "#4a4a65",
"fog_fill": "#111122",
"cell_text": "#1a1a2e",
"hazard_text": "#ffffff",
"flag_text": "#ffffff",
}
CELL_ICONS = {
"hazard": "\u2620",
"key": "\u26bf",
"decoy": "\u2662",
"goal": "\u2605",
"flag": "\u2691",
"faded": "?",
}
class Renderer:
"""Renders a visible board grid to deterministic SVG text."""
def __init__(self, cell_size: int = CELL_SIZE, padding: int = PADDING):
self.cell_size = cell_size
self.padding = padding
def render_board(
self,
visible_cells: list[list[dict]],
board_width: int,
board_height: int,
*,
scenario_type: str = "hidden_grid",
step_count: int = 0,
) -> str:
svg_w = self.padding + board_width * self.cell_size + self.padding
svg_h = self.padding + board_height * self.cell_size + self.padding
dwg = svgwrite.Drawing(size=(f"{svg_w}px", f"{svg_h}px"))
dwg.add(dwg.rect(
insert=(0, 0),
size=(svg_w, svg_h),
fill=COLORS["background"],
))
self._draw_coords(dwg, board_width, board_height)
for r in range(board_height):
for c in range(board_width):
cell = visible_cells[r][c]
x = self.padding + c * self.cell_size
y = self.padding + r * self.cell_size
self._draw_cell(dwg, x, y, cell, scenario_type)
self._draw_grid_lines(dwg, board_width, board_height)
return dwg.tostring()
def get_board_view(
self,
visible_cells: list[list[dict]],
board_width: int,
board_height: int,
*,
scenario_type: str = "hidden_grid",
step_count: int = 0,
) -> dict[str, Any]:
svg_text = self.render_board(
visible_cells,
board_width,
board_height,
scenario_type=scenario_type,
step_count=step_count,
)
hidden_count = 0
revealed_count = 0
flagged_count = 0
faded_count = 0
fog_count = 0
for row in visible_cells:
for cell in row:
state = cell.get("state", "hidden")
if state == "hidden":
hidden_count += 1
elif state == "revealed":
revealed_count += 1
elif state == "flagged":
flagged_count += 1
elif state == "faded":
faded_count += 1
elif state == "fog":
fog_count += 1
return {
"svg": svg_text,
"metadata": {
"board_width": board_width,
"board_height": board_height,
"step_count": step_count,
"scenario_type": scenario_type,
"cell_counts": {
"hidden": hidden_count,
"revealed": revealed_count,
"flagged": flagged_count,
"faded": faded_count,
"fog": fog_count,
"total": board_width * board_height,
},
},
}
# ─── Internal Drawing Methods ───────────────────────────────────
def _draw_coords(
self,
dwg: svgwrite.Drawing,
board_width: int,
board_height: int,
) -> None:
for c in range(board_width):
x = self.padding + c * self.cell_size + self.cell_size // 2
dwg.add(dwg.text(
str(c),
insert=(x, self.padding - 6),
text_anchor="middle",
font_size=COORD_FONT_SIZE,
fill=COLORS["coord_text"],
font_family="monospace",
))
for r in range(board_height):
y = self.padding + r * self.cell_size + self.cell_size // 2 + 4
dwg.add(dwg.text(
str(r),
insert=(self.padding - 8, y),
text_anchor="middle",
font_size=COORD_FONT_SIZE,
fill=COLORS["coord_text"],
font_family="monospace",
))
def _draw_grid_lines(
self,
dwg: svgwrite.Drawing,
board_width: int,
board_height: int,
) -> None:
x0 = self.padding
y0 = self.padding
x1 = self.padding + board_width * self.cell_size
y1 = self.padding + board_height * self.cell_size
for r in range(board_height + 1):
y = y0 + r * self.cell_size
dwg.add(dwg.line(
start=(x0, y),
end=(x1, y),
stroke=COLORS["grid_line"],
stroke_width=1,
))
for c in range(board_width + 1):
x = x0 + c * self.cell_size
dwg.add(dwg.line(
start=(x, y0),
end=(x, y1),
stroke=COLORS["grid_line"],
stroke_width=1,
))
def _draw_cell(
self,
dwg: svgwrite.Drawing,
x: int,
y: int,
cell: dict,
scenario_type: str,
) -> None:
state = cell.get("state", "hidden")
content = cell.get("content")
if state == "fog":
self._draw_fog_cell(dwg, x, y)
elif state == "hidden":
self._draw_hidden_cell(dwg, x, y)
elif state == "flagged":
self._draw_flagged_cell(dwg, x, y)
elif state == "faded":
self._draw_faded_cell(dwg, x, y)
elif state == "revealed" and content:
self._draw_revealed_cell(dwg, x, y, content)
else:
self._draw_hidden_cell(dwg, x, y)
def _draw_hidden_cell(self, dwg: svgwrite.Drawing, x: int, y: int) -> None:
dwg.add(dwg.rect(
insert=(x + 1, y + 1),
size=(self.cell_size - 2, self.cell_size - 2),
fill=COLORS["hidden_fill"],
stroke=COLORS["hidden_stroke"],
stroke_width=1,
rx=3,
ry=3,
))
def _draw_fog_cell(self, dwg: svgwrite.Drawing, x: int, y: int) -> None:
dwg.add(dwg.rect(
insert=(x + 1, y + 1),
size=(self.cell_size - 2, self.cell_size - 2),
fill=COLORS["fog_fill"],
rx=3,
ry=3,
))
def _draw_flagged_cell(self, dwg: svgwrite.Drawing, x: int, y: int) -> None:
dwg.add(dwg.rect(
insert=(x + 1, y + 1),
size=(self.cell_size - 2, self.cell_size - 2),
fill=COLORS["flagged_fill"],
stroke=COLORS["flagged_stroke"],
stroke_width=1,
rx=3,
ry=3,
))
cx = x + self.cell_size // 2
cy = y + self.cell_size // 2 + 5
dwg.add(dwg.text(
CELL_ICONS["flag"],
insert=(cx, cy),
text_anchor="middle",
font_size=ICON_FONT_SIZE,
fill=COLORS["flag_text"],
))
def _draw_faded_cell(self, dwg: svgwrite.Drawing, x: int, y: int) -> None:
dwg.add(dwg.rect(
insert=(x + 1, y + 1),
size=(self.cell_size - 2, self.cell_size - 2),
fill=COLORS["faded_fill"],
stroke=COLORS["faded_stroke"],
stroke_width=1,
rx=3,
ry=3,
))
cx = x + self.cell_size // 2
cy = y + self.cell_size // 2 + 5
dwg.add(dwg.text(
CELL_ICONS["faded"],
insert=(cx, cy),
text_anchor="middle",
font_size=CELL_FONT_SIZE,
fill=COLORS["coord_text"],
font_family="monospace",
))
def _draw_revealed_cell(
self,
dwg: svgwrite.Drawing,
x: int,
y: int,
content: dict,
) -> None:
cell_type = content.get("type", "empty")
value = content.get("value")
fill = COLORS["revealed_empty_fill"]
text_color = COLORS["cell_text"]
label = ""
if cell_type == "empty":
fill = COLORS["revealed_empty_fill"]
elif cell_type == "signal":
fill = COLORS["revealed_signal_fill"]
label = self._format_signal_value(value)
elif cell_type == "hazard":
fill = COLORS["revealed_hazard_fill"]
text_color = COLORS["hazard_text"]
label = CELL_ICONS["hazard"]
elif cell_type == "key":
fill = COLORS["revealed_key_fill"]
label = CELL_ICONS["key"]
elif cell_type == "decoy":
fill = COLORS["revealed_decoy_fill"]
label = CELL_ICONS["decoy"]
elif cell_type == "goal":
fill = COLORS["revealed_goal_fill"]
label = CELL_ICONS["goal"]
dwg.add(dwg.rect(
insert=(x + 1, y + 1),
size=(self.cell_size - 2, self.cell_size - 2),
fill=fill,
rx=3,
ry=3,
))
if label:
cx = x + self.cell_size // 2
cy = y + self.cell_size // 2 + 5
font_size = ICON_FONT_SIZE if len(label) <= 2 else CELL_FONT_SIZE
dwg.add(dwg.text(
label,
insert=(cx, cy),
text_anchor="middle",
font_size=font_size,
fill=text_color,
font_family="monospace",
))
def _format_signal_value(self, value: Any) -> str:
if value is None:
return ""
if isinstance(value, int):
return str(value)
if isinstance(value, dict):
lo = value.get("min", "?")
hi = value.get("max", "?")
return f"{lo}-{hi}"
if isinstance(value, list):
return ",".join(str(v) for v in value)
return str(value)