Skip to main content

Map File Format

Cub3D uses .cub files that contain:
  1. Texture paths for each wall direction (NO, SO, WE, EA)
  2. Floor and ceiling colors as RGB values (F, C)
  3. Map grid with walls (‘1’), empty spaces (‘0’), and player start (‘N’, ‘S’, ‘E’, ‘W’)

Example Map File

example.cub
NO ./textures/north_wall.xpm
SO ./textures/south_wall.xpm
WE ./textures/west_wall.xpm
EA ./textures/east_wall.xpm

F 220,100,0
C 225,230,255

111111
100001
101N01
100001
111111
Empty lines are ignored. Attribute order doesn’t matter, but all six attributes (NO, SO, WE, EA, F, C) must be present before the map grid.

Parsing Pipeline

The parsing process follows this sequence:

Phase 1: Filename Validation

ismap() - File Extension Check

utils.c (lines 15-36)
int	ismap(char *str, t_map *map)
{
	int		i;
	int		ii;
	char	*check;

	i = ft_strlen(str);
	ii = checkmap_name_folders(str);
	if (ii > 0)
		ii++;
	if (i - ii < 5)
		return (msg(2, FORMAT), exit(1), 0);
	check = &str[i - 4];
	check_printable_map_name(str, i, ii);
	if (check[0] == '.' && check[1] == 'c' && check[2] == 'u'
		&& check[3] == 'b' && check[4] == '\0')
	{
		map->name = ft_substr(str, ii, i -4 - ii);
		return (1);
	}
	return (0);
}
1

Check Minimum Length

Filename must be at least 5 characters (e.g., a.cub)
2

Extract Path Components

checkmap_name_folders() finds the last / to separate directory from filename
3

Validate Characters

check_printable_map_name() ensures all characters in the filename are printable
4

Check Extension

Last 4 characters must be exactly .cub
5

Extract Map Name

The filename without path or extension is stored as map->name (used for window title)
The validation exits the program on error rather than returning an error code. This is inconsistent with the rest of the parsing pipeline.

Phase 2: Attribute Parsing

check3atributtes() - Main Orchestrator

utils.c (lines 38-59)
int	check3atributtes(t_map *map)
{
	int	fd;
	int	i;

	fd = open(map->path, O_RDONLY);
	if (fd > 0)
	{
		i = 1;
		parsefloorceilingdirections(map, i, fd);
		i = 0;
		i = 0;
		mapcpy(map, fd);
		getfinalmap(map);
		if (map->final_map == NULL)
			return (msg(2, MAPERROR), freeme(map->final_map), 0);
		flood_staff(map);
	}
	else
		return (msg(2, MAPERROR2), 0);
	return (close(fd), 1);
}
This function:
  1. Opens the .cub file
  2. Parses texture/color attributes
  3. Parses the map grid
  4. Validates the map structure
  5. Returns 1 on success, 0 on failure

parsefloorceilingdirections() - Attribute Loop

parselineutils.c (lines 64-82)
void	parsefloorceilingdirections(t_map *map, int i, int fd)
{
	char	*line;

	while (i)
	{
		line = get_next_line(fd);
		if (i == 1 && line == NULL)
			return (msg(2, MAPERROR3), freetextures(map), exit(1));
		if (line && ft_strcmp(line, "\n"))
			checkatributes(map, line);
		if (all_atributes_asigned(map))
			return (ft_memdel(line));
		if (line == NULL)
			break ;
		ft_memdel(line);
		i++;
	}
}
1

Read Line

Uses get_next_line() to read one line at a time
2

Skip Empty Lines

Ignores lines that contain only \n
3

Parse Attribute

Calls checkatributes() for non-empty lines
4

Check Completion

Exits loop once all 6 attributes (NO, SO, WE, EA, F, C) are assigned
5

Handle EOF

Exits with error if file ends before all attributes are found

checkatributes() - Line Parser

parselineutils.c (lines 34-53)
static void	checkatributes(t_map *map, char *line)
{
	char	**arr;
	int		i;

	i = 0;
	arr = ft_split(line, ' ');
	if (!arr)
		return (msg(2, MAPERROR9), ft_memdel(line), freeall(map), exit(1));
	while (arr[i])
		i++;
	if (i > 2 && ft_strcmp(arr[2], "\n"))
		return (msg(2, MAPERROR9), printf("%s", arr[2]), ft_memdel(line),
			freeall(map), freeme(arr), exit(1));
	if (!ft_strcmp(arr[0], "\n"))
		return (freeme(arr));
	if (check_array_for_atributes(map, arr))
		return (freeme(arr), ft_memdel(line), freeall(map), exit(1));
	freeme(arr);
}
Validates line format:
  • Splits on spaces
  • Ensures at most 2 tokens (identifier + value)
  • Calls check_array_for_atributes() to process

check_array_for_atributes() - Attribute Dispatcher

parselineutils.c (lines 15-32)
int	check_array_for_atributes(t_map *map, char **arr)
{
	if (ft_strcmp(arr[0], "NO") && ft_strcmp(arr[0], "SO")
		&& ft_strcmp(arr[0], "WE") && ft_strcmp(arr[0], "EA")
		&& ft_strcmp(arr[0], "F") && ft_strcmp(arr[0], "C"))
		return (msg(2, arr[0]), msg(2, MAPERROR10), 1);
	if (!ft_strcmp(arr[0], "F") || !ft_strcmp(arr[0], "C"))
	{
		if (asign_colors(map, arr))
			return (1);
	}
	else
	{
		if (asign_directions(map, arr))
			return (1);
	}
	return (0);
}
Routes to appropriate handler:
  • F or Casign_colors()
  • NO, SO, WE, EAasign_directions()
  • Anything else → Error
The function uses ft_strcmp() which returns 0 for equal strings. The logic may appear inverted due to this convention.

Phase 3: Texture Path Assignment

asign_directions() - Texture Path Validation

asignatributes.c (lines 37-56)
int	asign_directions(t_map *map, char **arr)
{
	char	*noendline;
	int		fd;

	if (arr[1])
		noendline = noendl_dup(arr[1]);
	else
		return (msg(2, NOPATH), 1);
	if (noendline)
		if (fill_directions(map, noendline, arr[0]))
			return (ft_memdel(noendline), 1);
	fd = open(noendline, O_RDONLY);
	ft_memdel(noendline);
	if (fd > 0)
		close(fd);
	else
		return (msg(2, IMG), 1);
	return (0);
}
1

Strip Newline

noendl_dup() creates a copy without the trailing \n
2

Assign to Structure

fill_directions() stores the path in the appropriate field (northtexture, southtexture, etc.)
3

Verify File Exists

Attempts to open the file to ensure it exists and is readable
4

Check for Duplicates

fill_directions() returns error if the attribute was already set

fill_directions() - Field Assignment

asignatributes.c (lines 24-35)
static int	fill_directions(t_map *map, char *noendline, char *arr0)
{
	if (!ft_strcmp(arr0, "NO") && !map->northtexture)
		return (map->northtexture = ft_strdup(noendline), 0);
	if (!ft_strcmp(arr0, "SO") && !map->southtexture)
		return (map->southtexture = ft_strdup(noendline), 0);
	if (!ft_strcmp(arr0, "WE") && !map->westtexture)
		return (map->westtexture = ft_strdup(noendline), 0);
	if (!ft_strcmp(arr0, "EA") && !map->easttexture)
		return (map->easttexture = ft_strdup(noendline), 0);
	return (msg(2, MAP_ARG_DUP), msg(2, arr0), msg(2, "\n"), 1);
}
Ensures each attribute is set exactly once.

Phase 4: Color Parsing

asign_colors() - RGB Validation

asignatributes.c (lines 58-78)
int	asign_colors(t_map *map, char **arr)
{
	char	*noendline;
	char	**arr2;

	if (arr[1])
		noendline = noendl_dup(arr[1]);
	else
		return (msg(2, NOPATH), 1);
	if (noendline)
		if (fill_colors(map, noendline, arr[0]))
			return (ft_memdel(noendline), 1);
	arr2 = ft_split(noendline, ',');
	if (!arr2)
		return (ft_memdel(noendline), 1);
	ft_memdel(noendline);
	if (isvalidnum(arr2))
		return (freeme(arr2), 1);
	freeme(arr2);
	return (0);
}
1

Strip Newline

Creates a clean copy of the RGB string
2

Store String

fill_colors() saves the string to floorcolor or ceilingcolor
3

Split on Comma

Separates R, G, B components
4

Validate Numbers

isvalidnum() checks format and range

isvalidnum() - RGB Range Validation

utils4.c (lines 29-54)
int	isvalidnum(char **arr2)
{
	int	i;
	int	ii;

	i = 0;
	while (arr2[i])
		i++;
	if (i != 3)
		return (msg(2, RGBERROR), 1);
	i = 0;
	while (arr2[i])
	{
		ii = 0;
		while (arr2[i][ii])
		{
			if (!ft_isdigit(arr2[i][ii]))
				return (msg(2, FORMAT3), 1);
			ii++;
		}
		i++;
	}
	if (isrgbable(arr2))
		return (1);
	return (0);
}
Validates:
  1. Exactly 3 values (R, G, B)
  2. All characters are digits
  3. All values in range [0, 255]

isrgbable() - Range Check

utils4.c (lines 15-27)
int	isrgbable(char **arr2)
{
	int	i;

	i = 0;
	while (arr2[i])
	{
		if (ft_atoi(arr2[i]) < 0 || ft_atoi(arr2[i]) > 255)
			return (msg(2, RGBERROR2), 1);
		i++;
	}
	return (0);
}

parse_color() - String to Integer Conversion

Colors are converted to integer format during initialization:
utils7.c (lines 15-29)
int	parse_color(const char *line)
{
	int	r;
	int	g;
	int	b;

	while (*line && (*line == 'F' || *line == ' '))
		line++;
	r = ft_atoi(line);
	line = ft_strchr(line, ',') + 1;
	g = ft_atoi(line);
	line = ft_strchr(line, ',') + 1;
	b = ft_atoi(line);
	return ((r << 16) | (g << 8) | b);
}
Packs RGB into format: 0x00RRGGBB
The function assumes ‘F’ prefix for floor but is also used for ceiling colors. The prefix check is not robust.

Phase 5: Map Grid Parsing

mapcpy() - Copy Map Lines

utils2.c (lines 35-61)
void	mapcpy(t_map *map, int fd)
{
	int		ii;
	char	*line;
	int		x;

	x = 0;
	while (1)
	{
		line = get_next_line(fd);
		if (line == NULL)
			break ;
		if (ft_strcmp(line, "\n"))
		{
			ii = 0;
			while (line[ii])
			{
				map->map[x][ii] = line[ii];
				ii++;
			}
			map->map[x][ii] = '\0';
			(x)++;
		}
		ft_memdel(line);
	}
	map->row = x;
}
Reads remaining lines into map->map[500][500], skipping empty lines.

getfinalmap() - Allocate Dynamic Array

utils2.c (lines 63-76)
void	getfinalmap(t_map *map)
{
	int	i;

	i = 0;
	map->final_map = ft_calloc(map->row + 1, sizeof(char *));
	if (!map->final_map)
		return (msg(2, MEMERROR), exit(1));
	while (i < map->row)
	{
		map->final_map[i] = ft_strdup(map->map[i]);
		i++;
	}
}
Copies static buffer to heap-allocated array for gameplay.

Phase 6: Map Validation

flood_staff() - Orchestrate Validation

utils2.c (lines 78-89)
void	flood_staff(t_map *map)
{
	int	i;

	i = 0;
	map4flood(map);
	if (map->toflood_map == NULL)
		return (msg(2, "Error\n"), freeall(map), exit(1));
	if (checkns(map, 0, 0, 0) != 1)
		return (msg(2, SPOTERROR), freeall(map), exit(1));
	flood_fill(map, map->toflood_map, map->x, map->y);
}
1

Create Flood Map Copy

map4flood() duplicates final_map to toflood_map
2

Find Player Position

checkns() finds exactly one N/S/E/W character and stores position
3

Flood Fill Validation

flood_fill() recursively marks all reachable spaces, exits with error if map edge is reached

checkns() - Find Player

utils3.c (lines 30-53)
int	checkns(t_map *map, int count, int i, int ii)
{
	while (map->toflood_map[i])
	{
		ii = 0;
		while (map->toflood_map[i][ii])
		{
			if (ft_is_nswe(map->toflood_map[i][ii]) != 1)
				return (0);
			if (map->toflood_map[i][ii] == 'N' || map->toflood_map[i][ii] == 'S'
				|| map->toflood_map[i][ii] == 'E'
				|| map->toflood_map[i][ii] == 'W')
			{
				if (count == 0)
					add_values(map, i, ii);
				know_starting_angle(map, i, ii);
				count++;
			}
			ii++;
		}
		i++;
	}
	return (count);
}
Returns the count of player characters found (must be exactly 1).

ft_is_nswe() - Valid Character Check

utils4.c (lines 56-62)
int	ft_is_nswe(char c)
{
	if (c == 'N' || c == 'S' || c == 'W' || c == 'E' || c == '1'
		|| c == '0' || c == ' ' || c == '	' || c == '\n')
		return (1);
	return (0);
}
Allowed characters:
  • N, S, E, W: Player starting positions
  • 1: Wall
  • 0: Empty space
  • Space, tab, newline: Whitespace

know_starting_angle() - Set Initial Camera Direction

utils5.c (lines 75-85)
void	know_starting_angle(t_map *map, int i, int ii)
{
	if (map->toflood_map[i][ii] == 'S')
		map->starting_angle = PI / 2;
	else if (map->toflood_map[i][ii] == 'N')
		map->starting_angle = 3 * PI / 2;
	else if (map->toflood_map[i][ii] == 'E')
		map->starting_angle = 2 * PI;
	else if (map->toflood_map[i][ii] == 'W')
		map->starting_angle = PI;
}
CharacterAngleDirection
N3π/2North (up)
Sπ/2South (down)
E2π (0)East (right)
WπWest (left)

flood_fill() - Recursive Map Closure Check

utils4.c (lines 64-84)
void	flood_fill(t_map *mapa, char **map, int x, int y)
{
	if (x < 0)
		return (msg(2, MAPERROR4), freeall(mapa), exit(1));
	if (x == mapa->row)
		return (msg(2, MAPERROR4), freeall(mapa), exit(1));
	if (map[x][y] == ' ' || map[x][y] == '\n' || map[x][y] == '	'
		|| map[x][y] == '\0')
		return (msg(2, MAPERROR4), freeall(mapa), exit(1));
	if (!map[x][y])
		return (msg(2, MAPERROR4), freeall(mapa), exit(1));
	if (map[x][y] && map[x][y] != '1')
	{
		if (map[x][y] != 1)
			map[x][y] = '1';
		flood_fill(mapa, map, x + 1, y);
		flood_fill(mapa, map, x - 1, y);
		flood_fill(mapa, map, x, y + 1);
		flood_fill(mapa, map, x, y - 1);
	}
}
1

Boundary Check

If coordinates are out of bounds (x < 0 or x == row), the map is not closed → Error
2

Whitespace Check

If a space/tab/newline is reached, the player can “escape” → Error
3

Mark Cell

Change the cell to ‘1’ to mark it as visited
4

Recurse

Flood fill in all 4 directions (up, down, left, right)
5

Stop at Walls

If cell is already ‘1’, stop recursion (wall or already visited)
The flood fill algorithm exits the program immediately on error rather than returning an error code. This makes it impossible to recover or provide detailed error locations.

Error Handling Summary

Fatal Errors (exit)

  • Invalid filename format
  • Cannot open file
  • Empty file
  • Missing attributes
  • Duplicate attributes
  • Texture file not found
  • Invalid RGB values
  • Multiple or no player positions
  • Map not closed
  • Invalid map characters
  • Memory allocation failure

Recoverable Errors (return 0)

  • None - all errors are fatal
All error messages are printed to stderr (fd 2) using the msg() function.

Common Validation Failures

Problem: Player can reach a space, tab, or map edge
111111
100001    ← Open at top
1N0001
111111
Detection: Flood fill reaches whitespace or boundary
Problem: More than one N/S/E/W in the map
111111
1N0E01  ← Two players
111111
Detection: checkns() returns count > 1
Problem: Map contains characters other than 0, 1, N, S, E, W, space, tab, newline
111111
1N0#01  ← Invalid '#'
111111
Detection: ft_is_nswe() returns 0
Problem: Texture file path is invalid or file doesn’t exist
NO ./textures/missing.xpm  ← File not found
Detection: open() returns -1 in asign_directions()
Problem: Color values out of range or wrong format
F 255,100          ← Only 2 values
C 255,256,0        ← 256 > 255
F 255,100,abc      ← Non-numeric
Detection: isvalidnum() and isrgbable() checks

Memory Management

Parsing allocates memory that must be freed:
AllocationFreed By
northtexture, southtexture, easttexture, westtexturefreetextures()
floorcolor, ceilingcolorfreetextures()
pathfreeall()
namefreeall()
final_mapfreeme() called by freeall()
toflood_mapfreeme() called by freeall()
spaceless_mapfreeme() called by freeall() (if allocated)
The freeall() function (not shown in provided code) is responsible for complete cleanup.
The static map[500][500] buffer does not need explicit freeing as it’s stack-allocated within the t_map structure.