import random from collections import deque import gradio as gr import gymnasium as gym import numpy as np from gymnasium import spaces class WarehouseGridWorldEnv(gym.Env): metadata = {"render_modes": ["ansi"]} def __init__(self, width=10, height=10, obstacle_density=0.20, max_steps=150): super().__init__() self.width = width self.height = height self.obstacle_density = obstacle_density self.max_steps = max_steps self.action_space = spaces.Discrete(4) # 0=UP, 1=RIGHT, 2=DOWN, 3=LEFT self.observation_space = spaces.Box(low=0.0, high=1.0, shape=(4,), dtype=np.float32) self.reset() def _obs(self): ax, ay = self.agent_pos gx, gy = self.goal_pos return np.array([ ax / (self.width - 1), ay / (self.height - 1), gx / (self.width - 1), gy / (self.height - 1), ], dtype=np.float32) def _manhattan(self, a=None, b=None): a = self.agent_pos if a is None else a b = self.goal_pos if b is None else b return abs(a[0] - b[0]) + abs(a[1] - b[1]) def _neighbors(self, cell): x, y = cell for dx, dy in [(0, -1), (1, 0), (0, 1), (-1, 0)]: nx, ny = x + dx, y + dy if 0 <= nx < self.width and 0 <= ny < self.height and (nx, ny) not in self.obstacles: yield (nx, ny) def _solvable(self, start, goal): q = deque([start]) seen = {start} while q: cell = q.popleft() if cell == goal: return True for n in self._neighbors(cell): if n not in seen: seen.add(n) q.append(n) return False def _generate_solvable_maze(self): cells = [(x, y) for y in range(self.height) for x in range(self.width)] obstacle_count = int(len(cells) * self.obstacle_density) for _ in range(500): self.start_pos, self.goal_pos = random.sample(cells, 2) available = [c for c in cells if c not in {self.start_pos, self.goal_pos}] self.obstacles = set(random.sample(available, obstacle_count)) if self._solvable(self.start_pos, self.goal_pos): return # Safe fallback: mostly empty maze if random attempts fail. self.start_pos, self.goal_pos = (0, 0), (self.width - 1, self.height - 1) self.obstacles = set() def reset(self, seed=None, options=None): super().reset(seed=seed) self._generate_solvable_maze() self.agent_pos = self.start_pos self.visited = {self.agent_pos} self.steps = 0 self.total_score = 0.0 self.last_reward = 0.0 self.reached_goal = False return self._obs(), self._info() def step(self, action): if self.reached_goal: return self._obs(), 0.0, True, False, self._info() moves = { 0: (0, -1), # UP 1: (1, 0), # RIGHT 2: (0, 1), # DOWN 3: (-1, 0), # LEFT } dx, dy = moves[int(action)] old_pos = self.agent_pos old_dist = self._manhattan() new_pos = (old_pos[0] + dx, old_pos[1] + dy) reward = -0.02 # small step cost terminated = False truncated = False outside = not (0 <= new_pos[0] < self.width and 0 <= new_pos[1] < self.height) blocked = new_pos in self.obstacles if outside or blocked: reward -= 1.0 new_pos = old_pos else: self.agent_pos = new_pos new_dist = self._manhattan() if new_dist < old_dist: reward += 0.30 elif new_dist > old_dist: reward -= 0.20 else: reward -= 0.05 if new_pos not in self.visited: reward += 0.10 self.visited.add(new_pos) if new_pos == self.goal_pos: reward += 10.0 self.reached_goal = True terminated = True self.steps += 1 if self.steps >= self.max_steps and not terminated: reward -= 3.0 truncated = True self.last_reward = float(reward) self.total_score += float(reward) return self._obs(), float(reward), terminated, truncated, self._info() def _info(self): return { "total_score": round(self.total_score, 2), "last_reward": round(self.last_reward, 2), "steps": self.steps, "agent_position": self.agent_pos, "goal_position": self.goal_pos, "manhattan_distance": self._manhattan(), "goal_reached": self.reached_goal, } def render_html(self): rows = [] for y in range(self.height): cells = [] for x in range(self.width): pos = (x, y) label = "." cls = "empty" inner = "." if pos in self.obstacles: label, cls, inner = "X", "obstacle", "X" if pos == self.start_pos: label, cls, inner = "S", "start", "S" if pos == self.goal_pos: label, cls, inner = "G", "goal", "G" if pos == self.agent_pos: cls += " agent-cell" inner = f"A" cells.append(f"{inner}") rows.append("" + "".join(cells) + "") return "" + "".join(rows) + "
" env = WarehouseGridWorldEnv() def scoreboard(): info = env._info() return f""" ### Scoreboard - **Total score:** {info['total_score']} - **Last reward:** {info['last_reward']} - **Steps:** {info['steps']} / {env.max_steps} - **Agent position:** {info['agent_position']} - **Goal position:** {info['goal_position']} - **Manhattan distance:** {info['manhattan_distance']} - **Goal reached:** {info['goal_reached']} """ def update(action=None): if action is not None: env.step(action) return env.render_html(), scoreboard() def reset_game(): env.reset() return env.render_html(), scoreboard() CSS = """ .grid { border-collapse: collapse; margin: 8px 0; } .grid td { width: 42px; height: 42px; text-align: center; vertical-align: middle; border: 1px solid #888; font-family: monospace; font-size: 20px; font-weight: 700; } .empty { background: #f7f7f7; color: #999; } .start { background: #1976d2; color: white; } .goal { background: #2e7d32; color: white; } .obstacle { background: #222; color: white; } .agent-cell { position: relative; } .agent { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; border-radius: 50%; background: #d32f2f; color: white; font-size: 16px; font-family: Arial, sans-serif; } #hidden_action { display: none; } """ JS = """ """ with gr.Blocks(css=CSS, title="Warehouse GridWorld") as demo: gr.Markdown("# Warehouse GridWorld Navigation") gr.HTML(JS) with gr.Row(): grid = gr.HTML(env.render_html()) board = gr.Markdown(scoreboard()) with gr.Row(): up = gr.Button("⬆ Up", elem_id="up_btn") right = gr.Button("➡ Right", elem_id="right_btn") down = gr.Button("⬇ Down", elem_id="down_btn") left = gr.Button("⬅ Left", elem_id="left_btn") reset = gr.Button("Reset / Randomize") up.click(lambda: update(0), outputs=[grid, board]) right.click(lambda: update(1), outputs=[grid, board]) down.click(lambda: update(2), outputs=[grid, board]) left.click(lambda: update(3), outputs=[grid, board]) reset.click(reset_game, outputs=[grid, board]) if __name__ == "__main__": demo.launch()