view_level
Generate a visual representation of VibeTide 2D platformer levels from encoded strings to preview level layouts and designs.
Instructions
View a VibeTide level with visual representation.
Args:
encoded_level: An encoded level string from a URL or sharing link
Returns a visual representation of the level.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| encoded_level | Yes |
Implementation Reference
- vibe_tide_mcp_server.py:570-598 (handler)The @mcp.tool()-decorated async function implementing the core logic of the 'view_level' tool: decodes the input encoded_level using LevelEncoder, generates an ASCII visualization via visualize_level, constructs a play URL, and returns structured level data.@mcp.tool() async def view_level(encoded_level: str) -> Dict[str, Any]: """View a VibeTide level with visual representation. Args: encoded_level: An encoded level string from a URL or sharing link Returns a visual representation of the level. """ try: # Decode level from string level_data = level_encoder.decode(encoded_level) play_url = f"{VIBE_TIDE_CONFIG['web_player_url']}?level={encoded_level}" # Generate visualization visualization = visualize_level(level_data) return { "success": True, "level_data": level_data, "visualization": visualization, "play_url": play_url, "message": "Level visualized successfully", } except Exception as e: logger.error(f"Failed to view level: {e}") return {"success": False, "error": f"Failed to view level: {str(e)}"}
- vibe_tide_mcp_server.py:438-481 (helper)Helper function called by view_level to generate an ASCII art visualization of the level layout using tile symbols, including level info, parameters, layout grid, and legend.def visualize_level(level_data: Dict[str, Any]) -> str: """Create a visual representation of the level using symbols""" tiles = level_data.get("tiles", []) if not tiles: return "Empty level" visualization = [] visualization.append(f"Level: {level_data.get('name', 'Unnamed')}") visualization.append(f"Size: {len(tiles[0])}x{len(tiles)}") # Add game parameters if present params = [] if "maxEnemies" in level_data: params.append(f"Max Enemies: {level_data['maxEnemies']}") if "enemySpawnChance" in level_data: params.append(f"Enemy Spawn: {level_data['enemySpawnChance']}%") if "coinSpawnChance" in level_data: params.append(f"Coin Spawn: {level_data['coinSpawnChance']}%") if params: visualization.append("Parameters: " + ", ".join(params)) visualization.append("\nLevel Layout:") visualization.append("=" * (len(tiles[0]) + 2)) for row in tiles: row_str = "|" for tile in row: symbol = TILE_TYPES.get(tile, {"symbol": "?"})["symbol"] row_str += symbol row_str += "|" visualization.append(row_str) visualization.append("=" * (len(tiles[0]) + 2)) # Add legend visualization.append("\nTile Legend:") for tile_id, tile_info in TILE_TYPES.items(): visualization.append( f" {tile_info['symbol']} = {tile_info['name']} ({tile_info['description']})" ) return "\n".join(visualization)
- vibe_tide_mcp_server.py:81-323 (helper)The LevelEncoder class provides the decode method used in view_level to parse the encoded_level string into structured level data (tiles, dimensions, etc.). Global instance: level_encoder.class LevelEncoder: """Fast level encoding/decoding without agent tools""" def __init__(self): # Character mapping for tiles (URL-safe) self.tile_chars = { 0: ".", # Empty - most common 1: "G", # Grass 2: "R", # Rock 3: "Y", # Yellow 4: "I", # Ice 5: "F", # Fire/Red 6: "S", # Spikes 7: "W", # Water } self.char_tiles = {char: tile for tile, char in self.tile_chars.items()} def encode(self, level_data: Dict[str, Any]) -> str: """Encode level data to URL-safe string""" try: tiles = level_data.get("tiles", []) if not tiles: raise ValueError("No tiles found") height = len(tiles) width = len(tiles[0]) if tiles else 0 # Convert 2D array to character string tile_string = "" for row in tiles: for tile in row: tile_string += self.tile_chars.get(tile, ".") # Apply run-length encoding for empty tiles tile_string = self._run_length_encode(tile_string) # Create base format encoded = f"{width}x{height}:{tile_string}" # Add game parameters if provided params = {} for param in ["maxEnemies", "enemySpawnChance", "coinSpawnChance"]: if param in level_data and level_data[param] is not None: params[param] = level_data[param] if params: params_json = json.dumps(params) encoded += f"|{self._base64url_encode(params_json)}" return self._base64url_encode(encoded) except Exception as e: logger.error(f"Failed to encode level: {e}") raise ValueError(f"Level encoding failed: {str(e)}") def decode(self, encoded_string: str) -> Dict[str, Any]: """Decode URL-safe string back to level data""" try: encoded_string = encoded_string.strip() if not encoded_string: raise ValueError("Empty encoded string") # Decode from base64url decoded = self._base64url_decode(encoded_string) # Parse parameters if present main_data = decoded game_params = {} if "|" in decoded: parts = decoded.split("|", 1) if len(parts) == 2: main_data, params_data = parts try: params_json = self._base64url_decode(params_data) game_params = json.loads(params_json) except Exception as e: logger.warning(f"Failed to parse parameters: {e}") # Parse format: widthxheight:encoded_data if ":" not in main_data: raise ValueError("Invalid format: missing colon") dimensions, tile_data = main_data.split(":", 1) if "x" not in dimensions: raise ValueError("Invalid format: missing dimensions") width, height = map(int, dimensions.split("x")) if width < 1 or height < 1: raise ValueError(f"Invalid dimensions: {width}x{height}") # Decode run-length encoding tile_string = self._run_length_decode(tile_data) # Convert back to 2D array tiles = [] index = 0 for y in range(height): tiles.append([]) for x in range(width): char = tile_string[index] if index < len(tile_string) else "." tiles[y].append(self.char_tiles.get(char, 0)) index += 1 result = { "tiles": tiles, "width": width, "height": height, "name": self._generate_level_name(tiles), } # Add game parameters for param in ["maxEnemies", "enemySpawnChance", "coinSpawnChance"]: if param in game_params: result[param] = game_params[param] return result except Exception as e: logger.error(f"Failed to decode level: {e}") raise ValueError(f"Level decoding failed: {str(e)}") def _run_length_encode(self, s: str) -> str: """Run-length encoding for repeated empty tiles""" if not s: return s result = "" count = 1 current = s[0] for i in range(1, len(s)): if s[i] == current and current == ".": count += 1 else: if current == "." and count > 2: result += f".{count}" else: result += current * count current = s[i] count = 1 # Handle final sequence if current == "." and count > 2: result += f".{count}" else: result += current * count return result def _run_length_decode(self, s: str) -> str: """Decode run-length encoding""" result = "" i = 0 while i < len(s): char = s[i] if char == "." and i + 1 < len(s) and s[i + 1].isdigit(): # Extract number num_str = "" j = i + 1 while j < len(s) and s[j].isdigit(): num_str += s[j] j += 1 count = int(num_str) result += "." * count i = j else: result += char i += 1 return result def _base64url_encode(self, s: str) -> str: """URL-safe base64 encoding""" encoded = base64.b64encode(s.encode("utf-8")).decode("ascii") return encoded.replace("+", "-").replace("/", "_").rstrip("=") def _base64url_decode(self, s: str) -> str: """URL-safe base64 decoding""" try: # Add padding if necessary padding = (4 - len(s) % 4) % 4 s += "=" * padding s = s.replace("-", "+").replace("_", "/") decoded_bytes = base64.b64decode(s) return decoded_bytes.decode("utf-8") except Exception as e: logger.error(f"Base64 decode error: {e}") raise ValueError(f"Invalid base64 encoding: {str(e)}") def _generate_level_name(self, tiles: List[List[int]]) -> str: """Generate a name based on level content""" stats = self._analyze_level_content(tiles) name = "" if stats["water"] > 0.3: name += "Aquatic " if stats["spikes"] > 0.1: name += "Dangerous " if stats["ice"] > 0.2: name += "Icy " if stats["platforms"] < 0.1: name += "Minimal " if stats["platforms"] > 0.4: name += "Dense " name += "Adventure" return name.strip() def _analyze_level_content(self, tiles: List[List[int]]) -> Dict[str, float]: """Analyze level content for statistics""" if not tiles: return { "empty": 1.0, "platforms": 0.0, "ice": 0.0, "dangerous": 0.0, "spikes": 0.0, "water": 0.0, } total = len(tiles) * len(tiles[0]) counts = {i: 0 for i in range(8)} for row in tiles: for tile in row: counts[tile] = counts.get(tile, 0) + 1 return { "empty": counts[0] / total, "platforms": (counts[1] + counts[2] + counts[3]) / total, "ice": counts[4] / total, "dangerous": counts[5] / total, "spikes": counts[6] / total, "water": counts[7] / total, }