def parse_single_layout(layout_str, num_bots=4, allow_enemy_chars=False):
    """Parse a single layout from a string

    See parse_layout for details about valid layout strings.
    """
    width = None
    rows = []
    start = False
    for i, line in enumerate(layout_str.splitlines()):
        row = line.strip()
        if not row:
            continue
        if not start:
            if row.count('#') != len(row):
                raise ValueError(
                    f'Layout must be enclosed by walls (line: {i})!')
            else:
                start = True
                width = len(row)
                if width % 2:
                    raise ValueError(
                        f'Layout width must be even (found {width})!')
                rows.append(row)
                continue
        if len(row) != width:
            raise ValueError(f'Layout rows have differing widths (line: {i})!')
        if row[0] != '#' or row[-1] != '#':
            raise ValueError(f'Layout must be enclosed by walls (line:{i})!')
        rows.append(row)
        if row.count('#') == len(row):
            start = False
            break
    if start:
        raise ValueError(f'Layout must be enclosed by walls (line:{i})!')
    height = len(rows)
    walls = []
    food = []
    bots = [None] * num_bots
    enemy = []
    noisy_enemy = []
    for y, row in enumerate(rows):
        for x, char in enumerate(row):
            coord = x, y
            if char == '#':
                walls.append(coord)
            elif char == '.':
                food.append(coord)
            elif char == ' ':
                continue
            elif char == 'E':
                if allow_enemy_chars:
                    enemy.append(coord)
                else:
                    raise ValueError(f'Enemy character not allowed.')
            elif char == '?':
                if allow_enemy_chars:
                    noisy_enemy.append(coord)
                else:
                    raise ValueError(f'Enemy character not allowed.')
            else:
                try:
                    bot_idx = int(char)
                    if bot_idx >= len(bots):
                        raise ValueError
                except ValueError:
                    raise ValueError(f'Unknown character {char} in maze!')
                if bots[bot_idx]:
                    raise ValueError(
                        f'Cannot set bot {bot_idx} to position {coord} (already at {bots[bot_idx]}).'
                        )
                bots[bot_idx] = coord
    walls.sort()
    food.sort()
    out = {'walls': walls, 'food': food, 'bots': bots}
    if allow_enemy_chars:
        out['enemy'] = sorted(enemy)
        out['noisy_enemy'] = sorted(noisy_enemy)
    return out
