Open main menu

Level generation is the process the game uses to create each floor. Each floor has its own requirements of size, special rooms, dead ends that must be met.

ProcessEdit

First, the game determines how many rooms will generate on the floor. Later floors have more rooms, and Curse of the Labyrinth makes the floor much larger.

  • If the floor is not XL or   The Void:
    • NumberOfRooms = 3.33 × FloorDepth + 5-6 (maximum of 20)
  • If the floor is an XL floor:
    • NumberOfRooms = 1.8 × (3.33 × FloorDepth + 5-6) (maximum of 45)
  • If the floor is The Void:
    • NumberOfRooms = 50-59
  • 2-3 more rooms are added if playing on Hard Mode
  • 4 more rooms are added if the floor has Curse of the Lost.

Then the minimum number of dead ends the floor must generate is set.

  • All floors have at least 5 dead ends
    • 1 dead end is added to all floors except the first floor.
    • 1 dead end is added to XL floors.
    • 2 dead ends are added to The Void.

Next, special rooms are generated in dead ends.

Special rooms generate in the following order:

  • Boss Room (always generates)
    • If it is an XL floor, two boss rooms generate back to back.
    • Multiple Boss Rooms are generated in The Void.
  • Super Secret Room (always generates)
  • Shop (always generates in the first 3 chapters, or 4 if Isaac has    Silver Dollar)
  • Treasure Room (always generates in the first 3 chapters, or 4 if Isaac has    Bloody Crown)
    • If it is an XL floor, two Treasure Rooms generate.
  • Dice or Sacrifice Room (1/7 (~14%) base chance, plus an independent 1/4 chance for a total of 5/14 (~36%) if Isaac’s total health is equal to or greater than his total Heart Containers)
    • Has a 1/50 base chance to be a Dice Room, plus an independent 1/5 chance for a total of 27/125 (~22%) if Isaac has 2 or more keys; otherwise a Sacrifice Room is chosen.
  • Library (1/20 chance, plus an independent 1/4 chance for a total of 23/80 (~29%) if Isaac touched a book this run)
  • Curse Room (1/2 chance, plus an independent 1/4 chance for a total of 5/8 (~63%) if Isaac has entered a Devil Room this run)
  • Miniboss (1/4 chance, plus an independent 1/4 chance for a total of 7/16 (~44%) if it isn’t the first floor)
  • Challenge Room (1/2 chance if Isaac’s total health is equal to or greater than his total heart containers, can't spawn otherwise)
    • Cannot spawn on the first floor.
    • Will always be a Boss Challenge Room on the second floor of a chapter, and a normal Challenge Room otherwise.
  • Vault or Arcade (guaranteed if Isaac has 5 or more coins and it’s the second floor of a chapter, can’t spawn otherwise)
    • Has a 1/10 chance to be a Vault, plus an independent 1/3 chance for a total of 2/5 (40%) if Isaac has 2 or more keys; otherwise an Arcade is chosen.
  • Bedroom (1/50 chance, plus an independent 1/5 chance for a total of 27/125 (~22%) if Isaac has less than 2 Red/Bone Hearts and no Soul Hearts, or 2 or fewer Soul Hearts and no Red/Bone Hearts)
    • Equal chance to be a clean or dirty bedroom.
    • Can only spawn in the first 3 chapters
  • Secret Room (guaranteed)
  • Finally, a Grave Room is placed if the floor is Dark Room.

Pseudo-CodeEdit

Some parts of the level generation are based on the following pseudo-code:

--Pseudo lua code for getting the number of rooms for a floor.

NumberOfRooms = Min(20, Rand(0, 1) + 5 + Floor(StageId * 10 / 3))
if CurseOfTheLabyrinth then
 NumberOfRooms = Min(45, Floor(NumberOfRooms * 1.8))
elseif CurseOfTheLost then
 NumberOfRooms += 4
end
if StageId == 12 then --The Void
 NumberOfRooms = 50 + (Rand() % 10)
end
if IsHardDifficulty then
 NumberOfRooms += 2 + Rand(0, 1)
end

--Pseudo lua code for getting the number of deadends a floor must have.

MinDeadEnds = 5
if StageId ~= 1 then
 MinDeadEnds += 1
end
if CurseOfTheLabyrinth then
 MinDeadEnds += 1
end
if StageId == 12 then
	MinDeadEnds += 2
end


--Deadends are sorted by distance from the starting room descending.
--e.g. Boss room is placed in the farthest deadend, super secret is placed in the next farthest deadend

--DequeueDeadend also resizes the deadend. This is important to note because that means most deadends get changed to a 1x1 by the end of this.

PlaceRoom(ROOM_BOSS, DequeueDeadend())
--Super Secret
PlaceRoom(ROOM_SUPERSECRET, DequeueDeadend())
--Shop
if StageId < 7 or (StageId < 9 and HasTrinket(SilverDollar)) and VictoryLap < 3 then
	PlaceRoom(ROOM_SHOP, DequeueDeadend())
end
--Treasure
if StageId < 7 or (StageId < 9 and HasTrinket(BloodyCrown)) then
	PlaceRoom(ROOM_TREASURE, DequeueDeadend())
end
--Dice and Sacrifice
if StageId < 12 then
	local deadend = DequeueDeadend() --resizes the room :(
	if deadend ~= nil then
		local roomType = nil
		if Rng() % 50 == 0 or (Rng() % 5 == 0 and GetNumKeys() > 1) then 
			roomType = ROOM_DICE
		else
			roomType = ROOM_SACRIFICE
		end
		if Rng() % 7 == 0 or (Rng() & 3 == 0 and GetHearts() + GetSoulHearts() >= GetMaxHearts()) then
			PlaceRoom(roomType)
		else
			QueueDeadend(deadend)
		end
	end
end
--Library
local deadend = DequeueDeadend()
if deadend ~= nil then
	if Rng() % 20 == 0 or (Rng() & 3 == 0 and GetStateFlag(STATE_BOOK_PICKED_UP)) then
		PlaceRoom(ROOM_LIBRARY, deadend)
	else
		QueueDeadend(deadend)
	end
end
--Curse
local deadend = DequeueDeadend()
if deadend ~= nil then
	if Rng() & 1 == 0 or (Rng() & 3 == 0 and GetStateFlag(STATE_DEVILROOM_VISITED)) then
		PlaceRoom(ROOM_CURSE, deadend)
	else
		QueueDeadend(deadend)
	end
end
--MiniBoss
local boss = GetMiniBossRoom() --mini boss is picked based on floor, STATE_ULTRAPRIDE_SPAWNED and rng
local deadend = DequeueDeadend()
if deadend ~= nil then
	if Rng() & 3 == 0 or (Rng() & 3 == 0 and StageId ~= 1) then
		PlaceRoom(boss, deadend)
	else
		QueueDeadend(deadend)
	end
end
--Challenge
local deadend = DequeueDeadend()
if deadend ~= nil then
	if (Rng() & 1 == 0 or StageId >= 2) and GetHearts() + GetSoulHearts() >= GetMaxHearts() and StageId > 1 then
		PlaceRoom(boss, deadend)
	else
		QueueDeadend(deadend)
	end
end
--Chest and Arcade
local deadend = DequeueDeadend()
if deadend ~= nil then
	local roomType = nil
	if Rng() % 10 == 0 or (Rng() % 3 == 0 and GetNumKeys() > 1) then 
		roomType = ROOM_CHEST
	else
		roomType = ROOM_ARCADE
	end
	if GetNumCoins() >= 5 and (StageId == 2 or StageId == 4 or StageId == 6 or StageId == 8) then
		PlaceRoom(roomType)
	else
		QueueDeadend(deadend)
	end
end
--Bedroom
if StageId < 7 then
	local deadend = DequeueDeadend()
	if deadend ~= nil then
		local roomType = nil
		if Rng() & 1 == 0 then
			roomType = ROOM_ISAACS
		else
			roomType = ROOM_BARREN
		end
		
		local maxHearts = nil
		if GetPlayerType() == PLAYER_THELOST or GetPlayerType() == PLAYER_XXX or GetPlayerType() == PLAYER_THESOUL then
			maxHearts = GetMaxHearts()
		else
			maxHearts = GetMaxHearts() + GetBoneHearts() * 2
		end
		
		local second = false
		if (GetHearts() < 2 and GetSoulHearts() <= 0) or (maxHearts <= 0 and GetSoulHearts() <= 2) then
			second = true
		end

		if Rng() % 50 == 0 or (Rng() % 5 == 0 and second) then
			PlaceRoom(roomType)
		else
			QueueDeadend(deadend)
		end
	end
end
--Secret
TryPlacingSecret()
if HasTrinket(TRINKET_FRAGMENTED_CARD) then
	TryPlacingSecret()
end
-- Grave Room
if StageId == 11 and StageType == 0 then --Dark Room
	PlaceGrave(DequeueDeadend())
end

NotesEdit

This is taken from Blade's GitHub Gist. (Blade is also known as blcd / Will.) He reverse engineered the game using a disassembler in order to create this pseudo-code.