Skip to main content
Glama

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
NameRequiredDescriptionDefault
encoded_levelYes

Implementation Reference

  • 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)}"}
  • 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)
  • 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,
            }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/banjtheman/vibe_tide_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server