← Back to Writeups
CUSTOMMediumMisc

Deep Blue Sea

XESXOR9/2/20266 min read
#misc#custom#medium#bfs_pathfinding#image_to_grid_conversion#obstacle_avoidance#two_phase_navigation

Deep Blue Sea

Platform: undutmaning | Category: Misc | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-03-21 | Status: Solved Techniques: bfs_pathfinding, image_to_grid_conversion, obstacle_avoidance, two_phase_navigation

Summary

English summary: Navigate through a flooded underwater lab maze to retrieve a battery and return to the exit. The lab is dark and filled with sharks. You receive shark positions via radar every 6 seconds and must plan your entire path before moving (hitting a wall alerts the sharks). The challenge r

Deep Blue Sea — Undutmaning

Description

Att använda CASCADAs räddningskappslar för att ta sig till ytan verkar vara en bra plan. Tyvärr är underhållet lite eftersatt och man har inte bytt batterierna till de livsuppehållande systemen enligt det rekommenderade schemat. För att göra saken etter värre så ligger förrådet med batterier i en för närvarande oåtkomlig del av anläggningen.

Lyckligtvis kommer forskaren Perseus Plankton ihåg att han har ett passande batteri i sitt labb. Vilken lättnad! Nu är det bara det lilla abret att labbet ligger i den del av CASCADA som delvis fyllts med vatten och hajar!

Perseus ger dig en karta över labbet där han ritat in var batteriet finns. Han kan också, med hjälp av labbets mikrovågsradar, hålla lite koll på var hajarna befinner sig. Tyvärr är utrustningen stor, tung och långsam så det dröjer lite mellan varje radarsvep. Han kan därför bara ge dig informationen om hajarna med 6 sekunders mellanrum. Inte nog med det, på grund av vattnet fungerar inte belysningen i labbet. Det är bara vid ingången och hörnet där batteriet ligger, där det är ljust nog att titta på kartan. Går du in i en vägg så kommer hajarna uppfatta var du är och kasta sig över dig, så du måste redan innan du springer in i labbet bestämma hur du ska röra dig hela vägen fram till batteriet (respektive utgången)!

Du måste ta dig till batteriet och ta med det tillbaka till utgången. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

English summary: Navigate through a flooded underwater lab maze to retrieve a battery and return to the exit. The lab is dark and filled with sharks. You receive shark positions via radar every 6 seconds and must plan your entire path before moving (hitting a wall alerts the sharks). The challenge requires two-phase navigation: to the battery and back.

  • Service: undutmaning-deep.chals.io (SSL on port 443)
  • Map: lab-map.png (868x868 PNG maze)
  • Flag format: undut{...}

Analysis

Map Structure

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The maze image is 868x868 pixels representing a 31x31 grid (28 pixels per cell):

  • Start position: Cell (1,1) - top-left area (marked with running person icon)
  • Battery location: Cell (29,29) - bottom-right area (marked with green battery icon)
  • White cells: Passable paths
  • Black cells: Walls

Server Protocol

  1. Connect via SSL to port 443
  2. Receive instructions in Swedish
  3. Receive JSON with shark positions: {"sharks": [[row, col], ...]}
  4. Send direction string using Swedish notation: N (North/up), S (South/down), Ö (East/right), V (West/left)
  5. After reaching battery, receive new shark positions
  6. Send return path direction string
  7. Receive flag on success ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key Constraints

  • Must plan entire path before moving (no mid-path corrections)
  • Hitting a wall = death (sharks detect you)
  • Sharks block cells - must avoid them
  • Two-phase journey: start → battery → start

Solution

Step 1: Parse the Maze Image

Convert the PNG into a binary grid representation:

python
from PIL import Image

def parse_maze(image_path):
    img = Image.open(image_path).convert('RGB')
    cell_size = 28  # 868 / 31 = 28 pixels per cell
    grid_size = 31
    
    maze = []
    for row in range(grid_size):
        maze_row = []
        for col in range(grid_size):
            # Sample center of each cell
            x = col * cell_size + cell_size // 2
            y = row * cell_size + cell_size // 2
            
            # Count white pixels in cell
            white_count = 0
            total = 0
            for dy in range(-5, 6):
                for dx in range(-5, 6):
                    px, py = x + dx, y + dy
                    if 0 <= px < img.width and 0 <= py < img.height:
                        r, g, b = img.getpixel((px, py))
                        if r > 200 and g > 200 and b > 200:
                            white_count += 1
                        total += 1
            
            # Cell is passable if >50% white
            maze_row.append(0 if white_count > total * 0.5 else 1)
        maze.append(maze_row)
    
    return maze

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Step 2: BFS Pathfinding with Shark Avoidance

python
from collections import deque

def bfs_avoid_sharks(maze, start, end, sharks):
    """Find shortest path avoiding walls and sharks"""
    shark_set = set((s[0], s[1]) for s in sharks)
    queue = deque([(start, [start])])
    visited = {start}
    
    # Swedish direction notation
    directions = [
        (-1, 0, 'N'),  # North (up)
        (1, 0, 'S'),   # South (down)
        (0, 1, 'Ö'),   # East (right) - Swedish Ö
        (0, -1, 'V')   # West (left)
    ]
    
    while queue:
        (row, col), path = queue.popleft()
        
        if (row, col) == end:
            return path
        
        for dr, dc, _ in directions:
            new_row, new_col = row + dr, col + dc
            
            if (0 <= new_row < 31 and 0 <= new_col < 31 and
                maze[new_row][new_col] == 0 and
                (new_row, new_col) not in visited and
                (new_row, new_col) not in shark_set):
                
                visited.add((new_row, new_col))
                queue.append(((new_row, new_col), path + [(new_row, new_col)]))
    
    return None  # No path found
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

def path_to_directions(path):
    """Convert path coordinates to direction string"""
    direction_map = {
        (-1, 0): 'N',
        (1, 0): 'S',
        (0, 1): 'Ö',
        (0, -1): 'V'
    }
    
    directions = []
    for i in range(1, len(path)):
        dr = path[i][0] - path[i-1][0]
        dc = path[i][1] - path[i-1][1]
        directions.append(direction_map[(dr, dc)])
    
    return ''.join(directions)

Step 3: Two-Phase Navigation

python
import ssl
import socket
import json
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

def solve():
    maze = parse_maze('lab-map.png')
    start = (1, 1)
    battery = (29, 29)
    
    # SSL connection
    context = ssl.create_default_context()
    sock = socket.create_connection(('undutmaning-deep.chals.io', 443))
    ssl_sock = context.wrap_socket(sock, server_hostname='undutmaning-deep.chals.io')
    
    # Read initial instructions
    data = ssl_sock.recv(4096).decode()
    print(data)
    
    # Phase 1: Start to Battery
    # Parse shark positions from JSON
    shark_data = json.loads(ssl_sock.recv(4096).decode())
    sharks = shark_data['sharks']
    
    path1 = bfs_avoid_sharks(maze, start, battery, sharks)
    directions1 = path_to_directions(path1)
    ssl_sock.send((directions1 + '\n').encode())
    
    # Server response: "Med andan i halsen hittar du fram och plockar upp batteriet."
    response = ssl_sock.recv(4096).decode()
    print(response)
    
    # Phase 2: Battery back to Start
    shark_data = json.loads(ssl_sock.recv(4096).decode())
    sharks = shark_data['sharks']
    
    path2 = bfs_avoid_sharks(maze, battery, start, sharks)
    directions2 = path_to_directions(path2)
    ssl_sock.send((directions2 + '\n').encode())
    
    # Receive flag
    flag = ssl_sock.recv(4096).decode()
    print(flag)
    
    ssl_sock.close()
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

if __name__ == '__main__':
    solve()

Server Responses

Phase 1 success:

"Med andan i halsen hittar du fram och plockar upp batteriet." (Translation: "Breathlessly, you find your way and pick up the battery.")

Phase 2 success:

Returns the flag

Notes

  • The flag quote "REDACTED" is from Herman Melville's Moby-Dick, fitting the underwater shark theme
  • Swedish direction Ö (Öst) = East, V (Väst) = West
  • The 6-second radar delay mentioned in the description is flavor text - in practice, you receive shark positions immediately when needed
  • BFS guarantees shortest path, which is important when sharks might block longer routes
  • Image parsing must handle the start/battery icons (not just pure white/black cells) ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍