##############################################################
# layout_loops
#
# This creates a series of interlocking looped paths.
#
# First we have a whole lot of support functions.  Then we have
#  the layouts themselves.
#  -> layout_loops_misc
#  -> layout_loops_cross
#  -> layout_loops_corners
#
# All of these layouts feature hallways that frequently change
#  direction and irregularly-shaped rooms.  Although it is
#  rarely obvious from the maps, the hallways are normally
#  ragged, interlocking loops.  The rooms are generated by
#  functions and do not clutter up the maps with subvaults.
#

#
# randomLow
# randomHigh
#
# Generates a random number in the range, weighted to low/high
#  values.
#
{{
function randomLow (excluded_max)
  local v1 = crawl.random2(excluded_max)
  local v2 = crawl.random2(excluded_max)

  if v1 < v2 then
    return v1
  else
    return v2
  end
end
}}

{{
function randomHigh (excluded_max)
  local v1 = crawl.random2(excluded_max)
  local v2 = crawl.random2(excluded_max)

  if v1 > v2 then
    return v1
  else
    return v2
  end
end
}}

#
# randomNearValue
#
# Generates a random number within the specified range of the
#  specified value.  The possible values are weighted equally.
#
{{
function randomNearValue (center, range)
  local range2 = range * 2 + 1
  return center - range + crawl.random2(range2)
end
}}

#
# getRandomWallMaterial
#
# This is like random_wall_material function, except that it
#  returns the material type instead of changing the map.
# Note this originally applied to Lair as well as D.
#
{{
function getRandomWallMaterial ()
  if you.in_branch("D")
     and you.absdepth() >= 4 and crawl.one_chance_in(20) then
    if crawl.one_chance_in(3) then
      return 'v'
    else
      return 'c'
    end
  else
    return 'x'
  end
end
}}

#
# These functions below pass around an array/table (named
#  corners) of all the "corners" that have been generated.
#  These corners are then used as the centers for other loops.
#  A corner is represented as:
#
# c = y * 1000 + x
#
# The array starts at 1.  Special position count (corners.count)
#  is reserved for the number of elements in the array.  This
#  array is basically serving as a vector with data in the range
#  [1, count].
#
# There a few cases where a corner might get in the array more
#  than once.  The real corners of the original loop-square are
#  an example.  This will not do any harm; it will just make
#  that position more likely to get a loop around it.  As the
#  main trick with this algorithm is getting the paths to spread
#  out enough, that is actually a good thing.
#

#
# loopsInsertDividedLineX
#
# Draws a "blocky" line of open cells connecting the two
#  specified points.  The line is divided into a number of
#  intervals that each of their own y-coordinate in the
#  specified range.  The y-coordinates can be weighted to low
#  or high values.
#
# Parameters:
#  <1> e: The global environment
#  <2> corners: The array of corners to update
#  <3> start_x
#  <4> start_y: The coordinates of the start point
#  <5> end_x
#  <6> end_y: The coordinates of the end point
#  <7> min_y: The minimum y-coordinate
#  <8> max_y: The maximum y-coordinate
#  <9> weight_type: How the y-coordinates should be weighted
#         < 0: to low values
#        == 0: evenly
#         > 0: to high values
#  <10>divisions: How many segments the line should be broken
#                 into
#
# Preconditions:
#  <1> start_x >= 0
#  <2> start_x < width()
#  <3> start_y >= 0
#  <4> start_y < height()
#  <5> end_x >= 0
#  <6> end_x < width()
#  <7> end_y >= 0
#  <8> end_y < height()
#  <9> start_y < end_y
#  <10>0 <= min_x
#  <11>min_x <= max_x
#  <12>max_x < width()
#  <13>start_x >= min_x
#  <14>start_x <= max_x
#  <15>end_x >= min_x
#  <16>end_x <= max_x
#  <17>weight_type == -1 || weight_type == 0 || weight_type == 1
#  <18>divisions >= 1
#  <19>divisions <= end_y - start_y
#
# Example divided line (10 divisions)
#
#         ........
#  .....  .      .         ......
#      .  .      ......    .    .     .....   .....
#      ....           .    .    .......   .....
#                     ......
#

{{
function loopsInsertDividedLineX (e, corners, start_x, start_y,
                                  end_x, end_y, min_y, max_y,
                                  weight_type, divisions)
  if max_y - min_y < 1 then
    weight_type = 0
  end

  -- LUA arrays start at 1, not 0 (by convention)
  local cut_xs = {}
  cut_xs[1] = start_x
  cut_xs[divisions + 1] = end_x

  local ys = {}
  ys[1] = start_y
  ys[divisions] = end_y

  local x_range = end_x - start_x

  -- end will be excluded by random functions so add 1
  local y_range = max_y - min_y + 1

  for d = 2, divisions do
    local min_cut_x = math.floor((d - 1.5) / divisions * x_range)
    local max_cut_x = math.floor((d - 0.5) / divisions * x_range)
    local cut_x_range = max_cut_x - min_cut_x
    cut_xs[d] = min_cut_x + crawl.random2(cut_x_range) + start_x

    if d < divisions then
      if weight_type < 0 then
        ys[d] = min_y + randomLow(y_range)
      elseif weight_type > 0 then
        ys[d] = min_y + randomHigh(y_range)
      else
        ys[d] = min_y + crawl.random2(y_range)
      end
    -- else ys[d] is already set
    end
  end

  for i = 1, divisions do
    local fixed_y = ys[i]

    for x = cut_xs[i], cut_xs[i + 1] do
      e.mapgrd[x][fixed_y] = "."
    end

    if i > 0 then
      corners[corners.count + 1] = fixed_y * 1000 + cut_xs[i]
      corners.count = corners.count + 1
    end

    if i < divisions then

      local fixed_x = cut_xs[i + 1]

      local y_low = ys[i]
      local y_high = ys[i + 1]
      if y_high < y_low then
        local temp = y_low
        y_low = y_high
        y_high = temp
      end

      for y = y_low, y_high do
        e.mapgrd[fixed_x][y] = "."
      end

      corners[corners.count + 1] = fixed_y * 1000 + cut_xs[i + 1]
      corners.count = corners.count + 1
    end
  end
end
}}

#
# loopsInsertDividedLineY
#
# The same as inserDividedLineX, but the other way.
#

{{
function loopsInsertDividedLineY (e, corners, start_x, start_y,
                                  end_x, end_y, min_x, max_x,
                                  weight_type, divisions)
  if max_x - min_x < 1 then
    weight_type = 0
  end

  -- LUA arrays start at 1, not 0 (by convention)
  local cut_ys = {}
  cut_ys[1] = start_y
  cut_ys[divisions + 1] = end_y

  local xs = {}
  xs[1] = start_x
  xs[divisions] = end_x

  local y_range = end_y - start_y

  -- end will be excluded by random functions so add 1
  local x_range = max_x - min_x + 1

  for d = 2, divisions do
    local min_cut_y = math.floor((d - 1.5) / divisions * y_range)
    local max_cut_y = math.floor((d - 0.5) / divisions * y_range)
    local cut_y_range = max_cut_y - min_cut_y
    cut_ys[d] = min_cut_y + crawl.random2(cut_y_range) + start_y

    if d < divisions then
      if weight_type < 0 then
        xs[d] = min_x + randomLow(x_range)
      elseif weight_type > 0 then
        xs[d] = min_x + randomHigh(x_range)
      else
        xs[d] = min_x + crawl.random2(x_range)
      end
    -- else xs[d] is already set
    end
  end

  for i = 1, divisions do
    local fixed_x = xs[i]

    for y = cut_ys[i], cut_ys[i + 1] do
      e.mapgrd[fixed_x][y] = "."
    end

    if i > 0 then
      corners[corners.count + 1] = cut_ys[i] * 1000 + fixed_x
      corners.count = corners.count + 1
    end

    if i < divisions then
      local fixed_y = cut_ys[i + 1]

      local x_low = xs[i]
      local x_high = xs[i + 1]
      if x_high < x_low then
        local temp = x_low
        x_low = x_high
        x_high = temp
      end

      for x = x_low, x_high do
        e.mapgrd[x][fixed_y] = "."
      end

      corners[corners.count + 1] = cut_ys[i + 1] * 1000 + fixed_x
      corners.count = corners.count + 1
    end
  end
end
}}


#
# loopsInsertDividedLoop
#
# Draws a square "loop" with blocky, irregular edges around a
#  point.  The edges are in a number of segments with a
#  distance from the center in a specified range.  The segments
#  are weighted towards the outside of the square because there
#  is more are farther from the center.  The loop is forced to
#  lie within the minimum and maximum bounds given.
#
# Parameters:
#  <1> e: The global environment
#  <2> corners: The array of corners to update
#  <3> center_x
#  <4> center_y: The coordinates of the center point
#  <5> radius_min: The minimum radius of the square
#  <6> radius_max: The maximum radius of the square
#  <7> divisions: How many segments each side of the square
#                 should be broken into
#
# Preconditions:
#  <1> radius_min >= 1
#  <2> radius_max >= radius_min
#  <3> divisions >= 1
#  <4> divisions <= end_y - start_y
#
# Example divided loop (4 divisions)
#
#         .....
#  .....  .   ....
#  .   .  .      .
#  .   ....      .
#  .....       ...
#      .       .
#      .       ....
#      .          .
#   ....      .....
#   .         .
#   ..        .
#    .      ...
#    . .... .
#    ...  . .
#         ...
#

{{
function loopsInsertDividedLoop (e, corners, center_x, center_y,
                                 radius_min, radius_max, divisions)

  -- this is the "doughnut" of locations the loop may be in
  local min_xm = center_x - radius_max
  local max_xm = center_x - radius_min + 1
  local min_xp = center_x + radius_min
  local max_xp = center_x + radius_max + 1

  local min_ym = center_y - radius_max
  local max_ym = center_y - radius_min + 1
  local min_yp = center_y + radius_min
  local max_yp = center_y + radius_max + 1

  local diff_xm = max_xm - min_xm
  local diff_xp = max_xp - min_xp
  local diff_ym = max_ym - min_ym
  local diff_yp = max_yp - min_yp

  if divisions == 1 then
    -- just draw a rectangle
    local xm = min_xm + randomLow (diff_xm)
    local xp = min_xp + randomHigh(diff_xp)
    local ym = min_ym + randomLow (diff_ym)
    local yp = min_yp + randomHigh(diff_yp)

    for x = xm + 1, (xp - 1) do
      e.mapgrd[x][ym] = '.'
      e.mapgrd[x][yp] = '.'
    end

    for y = ym, yp do
      e.mapgrd[xm][y] = '.'
      e.mapgrd[xp][y] = '.'
    end
  else
    -- corner X- Y-
    local xm_ym_x = min_xm + randomLow (diff_xm)
    local xm_ym_y = min_ym + randomLow (diff_ym)

    -- corner X+ Y-
    local xp_ym_x = min_xp + randomHigh(diff_xp)
    local xp_ym_y = min_ym + randomLow (diff_ym)

    -- corner X- Y+
    local xm_yp_x = min_xm + randomLow (diff_xm)
    local xm_yp_y = min_yp + randomHigh(diff_yp)

    -- corner X+ Y+
    local xp_yp_x = min_xp + randomHigh(diff_xp)
    local xp_yp_y = min_yp + randomHigh(diff_yp)

    -- X-Y- to X+Y-
    loopsInsertDividedLineX(e, corners, xm_ym_x, xm_ym_y, xp_ym_x, xp_ym_y,
                            min_ym, max_ym, -1, divisions)
    -- X-Y+ to X+Y+
    loopsInsertDividedLineX(e, corners, xm_yp_x, xm_yp_y, xp_yp_x, xp_yp_y,
                            min_yp, max_yp,  1, divisions)

    -- X-Y- to X-Y+
    loopsInsertDividedLineY(e, corners, xm_ym_x, xm_ym_y, xm_yp_x, xm_yp_y,
                            min_xm, max_xm, -1, divisions)
    -- X+Y- to X+Y+
    loopsInsertDividedLineY(e, corners, xp_ym_x, xp_ym_y, xp_yp_x, xp_yp_y,
                            min_xp, max_xp,  1, divisions)
  end
end
}}

#
# loopsInsertLoopGroup
#
# Draws many loops at the specified number of the specified
#  positions.  The positions are chosen randomly.
#
# Parameters:
#  <1> e: The global environment
#  <2> corners: The array of corners to update
#  <3> positions: The possible positions for loops
#  <4> count: How many loops
#  <5> radius_min: The minimum radius of the loop
#  <6> radius_max: The maximum radius of the loop
#  <7> border: The minimum distance of a loop from the edge
#  <8> divisions: How many segments each side of the square
#                 should be broken into
#
# Preconditions:
#  <1> radius_min >= 1
#  <2> radius_max >= radius_min
#  <3> divisions >= 1
#  <4> divisions <= end_y - start_y
#

{{
function loopsInsertLoopGroup (e, corners, positions, count,
                               radius_min, radius_max, border, divisions)
  local min_x = border + radius_max
  local max_x = e.width() - (border + radius_max)
  local min_y = border + radius_max
  local max_y = e.height() - (border + radius_max)

  for i = 1, count do
    if positions.count == 0 then
      break
    end

    local index = 1 + crawl.random2(positions.count)
    local value = positions[index]
    -- if value == nil then
    --   crawl.mpr("Error: value == nil in loopsInsertLoopGroup")
    -- end
    local x = value % 1000
    local y = math.floor(value / 1000)

    if x < min_x then
      x = min_x
    end
    if x >= max_x then
      x = max_x - 1
    end
    if y < min_y then
      y = min_y
    end
    if y >= max_y then
      y = max_y - 1
    end

    loopsInsertDividedLoop(e, corners, x, y,
                           radius_min, radius_max, divisions)
    positions[index] = positions[positions.count]
    positions.count = positions.count - 1
  end

  for j = 1, positions.count do
    corners.count = corners.count + 1
    corners[corners.count] = positions[j]
  end
end
}}

#
# loopsInsertRoom
#
# loopsInsertRoomBoxOpen
# loopsInsertRoomDiamondOpen
# loopsInsertRoomOctagonOpen
# loopsInsertRoomPlusOpen
# loopsInsertRoomHexagonNSOpen
# loopsInsertRoomHexagonEWOpen
# loopsInsertRoomBoxSolid
# loopsInsertRoomDiamondSolid
# loopsInsertRoomOctagonSolid
# loopsInsertRoomPlusSolid
# loopsInsertRoomHexagonNSSolid
# loopsInsertRoomHexagonEWSolid
# loopsInsertRoomBoxOutline
# loopsInsertRoomDiamondOutline
# loopsInsertRoomOctagonOutline
# loopsInsertRoomBoxGlyph
# loopsInsertRoomDiamondGlyph
# loopsInsertRoomOctagonGlyph
# loopsInsertRoomPlusGlyph
#
# Draws a "room" of the specified radius at the specified
#  position.  Octagon-shaped and outline-styled rooms are only
#  generated for radius >= 2.  Plus-shaped rooms replace box
#  rooms with increasing frequency at larger radius values.
#
# Room shape:
#  -> box: A simple square
#  -> diamond: A square, rotated 45 degrees
#  -> octagon: A diamond minus the corners
#  -> plus: A 3-wide X-junction
#  -> hexagonNS: A hexagon with sides facing north and south
#  -> hexagonEW: A hexagon with sides facing east and west
#
# Room styles:
#  -> open: Just open space
#  -> solid: All but the outer ring of the room is filled with
#            the specified glyph
#  -> outline: The outer ring of the room is cut; the rest is
#              left with whatever glyphs it had
#
# Parameters:
#  <1> e: The global environment
#  <3> center_x
#  <4> center_y: The coordinates of the center of the room
#  <5> radius: The radius of the room
#  <6> wall_glyph: The glyph to fill the center of the room
#                  with, if applicable
#
# Preconditions:
#  <1> radius > 0
#  <2> 0 < center_x - radius
#  <3> center_x + radius < width()
#  <4> 0 < center_y - radius
#  <5> center_y + radius < height()
#
# Example rooms (radius: 3, wall_glyph: 'x', center_glyph: '?'):
#
#  .......   .......   .......   .......
#  .......   .xxxxx.   .     .   .xx+xx.
#  .......   .xxxxx.   .     .   .x...x.
#  .......   .xxxxx.   .     .   .+.?.x.
#  .......   .xxxxx.   .     .   .x...x.
#  .......   .xxxxx.   .     .   .xx+xx.
#  .......   .......   .......   .......
#
#                                   .
#     .         .         .        .x.
#    ...       .x.       . .      .xxx.
#   .....     .xxx.     .   .    .x...x.
#  .......   .xxxxx.   .     .  .xx.?.xx.
#   .....     .xxx.     .   .    .x...x.
#    ...       .x.       . .      .xxx.
#     .         .         .        .x.
#                                   .
#
#    ...       ...       ...       ...
#   .....     .xxx.     .   .     .xxx.
#  .......   .xxxxx.   .     .   .x...x.
#  .......   .xxxxx.   .     .   .x.?.x.
#  .......   .xxxxx.   .     .   .x...x.
#   .....     .xxx.     .   .     .xxx.
#    ...       ...       ...       ...
#
#    ...       ...                  .
#    ...       .x.        NO        .
#  .......   ...x...               x.x
#  .......   .xxxxx.   OUTLINE   ...?...
#  .......   ...x...               x.x
#    ...       .x.       FORM       .
#    ...       ...                  .
#
#    ...       ...                  NO
#  .......   ...x...      NO
#  .......   .xxxxx.              CENTER
#  .......   .xxxxx.   OUTLINE
#  .......   .xxxxx.              GLYPH
#  .......   ...x...     FORM
#    ...       ...                 FORM
#
#   .....     .....                 NO
#   .....     .xxx.       NO
#  .......   ..xxx..              CENTER
#  .......   .xxxxx.   OUTLINE
#  .......   ..xxx..              GLYPH
#   .....     .xxx.      FORM
#   .....     .....                FORM
#

{{
function loopsInsertRoom (e, center_x, center_y, radius, wall_glyph)
  if radius < 2 then
    local shape  = crawl.random2(3)
    local center = crawl.random2(3)

    if shape < 2 then
      if center < 2 then
        loopsInsertRoomBoxOpen(e, center_x, center_y, radius)
      else
        loopsInsertRoomBoxSolid(e, center_x, center_y,
                                radius, wall_glyph)
      end
    else
      if center < 2 then
        loopsInsertRoomDiamondOpen(e, center_x, center_y, radius)
      else
        loopsInsertRoomDiamondSolid(e, center_x, center_y,
                                    radius, wall_glyph)
      end
    end
  else
    local shape  = util.random_choose_weighted { {0, 3}, {1, 2}, {2, 1} }
    local center = crawl.random2(6)
    if you.in_branch("Elf") then
      shape  = crawl.random2(4)
      center = crawl.random_range(1, 3)  -- 2/3 are open
    end

    if shape == 0 then
      local plus_chance = (radius - 1) * 25

      if center < 3 then
        if crawl.random2(100) < plus_chance then
          loopsInsertRoomPlusOpen(e, center_x, center_y, radius)
        else
          loopsInsertRoomBoxOpen(e, center_x, center_y, radius)
        end
      elseif center < 5 then
        if crawl.random2(200) < plus_chance then
          loopsInsertRoomPlusSolid(e, center_x, center_y,
                                  radius, wall_glyph)
        else
          loopsInsertRoomBoxSolid(e, center_x, center_y,
                                  radius, wall_glyph)
        end
      else
        loopsInsertRoomBoxOutline(e, center_x, center_y, radius)
      end
    elseif shape == 1 then
      if center < 2 then
        loopsInsertRoomDiamondOpen(e, center_x, center_y, radius)
      elseif center < 5 then
        loopsInsertRoomDiamondSolid(e, center_x, center_y,
                                    radius, wall_glyph)
      else
        loopsInsertRoomDiamondOutline(e, center_x, center_y, radius)
      end
    elseif shape == 2 then
      if center < 3 then
        loopsInsertRoomOctagonOpen(e, center_x, center_y, radius)
      elseif center < 5 then
        loopsInsertRoomOctagonSolid(e, center_x, center_y,
                                    radius, wall_glyph)
      else
        loopsInsertRoomOctagonOutline(e, center_x, center_y, radius)
      end
    elseif shape == 3 then
      if crawl.coinflip() then
        if center < 3 then
          loopsInsertRoomHexagonNSOpen(e, center_x, center_y, radius)
        else
          loopsInsertRoomHexagonNSSolid(e, center_x, center_y,
                                        radius, wall_glyph)
        end
      else
        if center < 3 then
          loopsInsertRoomHexagonEWOpen(e, center_x, center_y, radius)
        else
          loopsInsertRoomHexagonEWSolid(e, center_x, center_y,
                                        radius, wall_glyph)
        end
      end
    end
  end
end
}}

{{
function loopsInsertRoomBoxOpen (e, center_x, center_y, radius)
  for y = -radius, radius do
    for x = -radius, radius do
      e.mapgrd[center_x + x][center_y + y] = '.'
    end
  end
end
}}

{{
function loopsInsertRoomDiamondOpen (e, center_x, center_y, radius)
  for y = -radius, radius do
    for x = -radius, radius do
      if math.abs(x) + math.abs(y) <= radius then
        e.mapgrd[center_x + x][center_y + y] = '.'
      end
    end
  end
end
}}

{{
function loopsInsertRoomOctagonOpen (e, center_x, center_y, radius)
  for y = -radius, radius do
    for x = -radius, radius do
      if math.abs(x) + math.abs(y) <= radius + 1 then
        e.mapgrd[center_x + x][center_y + y] = '.'
      end
    end
  end
end
}}

{{
function loopsInsertRoomPlusOpen (e, center_x, center_y, radius)
  for y = -radius, radius do
    for x = -radius, radius do
      if math.abs(x) <= 1 or math.abs(y) <= 1 then
        e.mapgrd[center_x + x][center_y + y] = '.'
      end
    end
  end
end
}}

{{
function loopsInsertRoomHexagonNSOpen (e, center_x, center_y, radius)
  local y_addend = radius % 2
  for y = -radius, radius do
    local abs_y = math.abs(y)
    local max_x = radius - math.ceil((abs_y - y_addend) / 2)
    for x = -radius, radius do
        if (math.abs(x) <= max_x) then
            e.mapgrd[center_x + x][center_y + y] = '.'
        end
    end
  end
end
}}

{{
function loopsInsertRoomHexagonEWOpen (e, center_x, center_y, radius)
  local x_addend = radius % 2
  for x = -radius, radius do
    local abs_x = math.abs(x)
    local max_y = radius - math.ceil((abs_x - x_addend) / 2)
    for y = -radius, radius do
        if (math.abs(y) <= max_y) then
            e.mapgrd[center_x + x][center_y + y] = '.'
        end
    end
  end
end
}}

{{
function loopsInsertRoomBoxSolid (e, center_x, center_y,
                                  radius, wall_glyph)
  for x_off = -radius, radius do
    local x = center_x + x_off
    e.mapgrd[x][center_y - radius] = '.'
    e.mapgrd[x][center_y + radius] = '.'
  end

  for y_off = -radius + 1, radius - 1 do
    local y = center_y + y_off
    e.mapgrd[center_x - radius][y] = '.'
    e.mapgrd[center_x + radius][y] = '.'

    for x = -radius + 1, radius - 1 do
      e.mapgrd[center_x + x][y] = wall_glyph
    end
  end
end
}}

{{
function loopsInsertRoomDiamondSolid (e, center_x, center_y,
                                      radius, wall_glyph)
  for y = -radius, radius do
    for x = -radius, radius do
      local distance = math.abs(x) + math.abs(y)

      if distance == radius then
        e.mapgrd[center_x + x][center_y + y] = '.'
      elseif distance < radius then
        e.mapgrd[center_x + x][center_y + y] = wall_glyph
      end
    end
  end
end
}}

{{
function loopsInsertRoomOctagonSolid (e, center_x, center_y,
                                      radius, wall_glyph)
  for y = -radius, radius do
    for x = -radius, radius do
      local distance = math.abs(x) + math.abs(y)

      if distance == radius + 1 then
        e.mapgrd[center_x + x][center_y + y] = '.'
      elseif distance < radius + 1 then
        e.mapgrd[center_x + x][center_y + y] = wall_glyph
      end
    end
  end

  e.mapgrd[center_x + radius][center_y] = '.'
  e.mapgrd[center_x - radius][center_y] = '.'
  e.mapgrd[center_x][center_y + radius] = '.'
  e.mapgrd[center_x][center_y - radius] = '.'
end
}}

{{
function loopsInsertRoomPlusSolid (e, center_x, center_y,
                                   radius, wall_glyph)
  for y = 1, radius do
    e.mapgrd[center_x - 1][center_y - y] = '.'
    e.mapgrd[center_x]    [center_y - y] = wall_glyph
    e.mapgrd[center_x + 1][center_y - y] = '.'

    e.mapgrd[center_x - 1][center_y + y] = '.'
    e.mapgrd[center_x]    [center_y + y] = wall_glyph
    e.mapgrd[center_x + 1][center_y + y] = '.'
  end

  for x = 1, radius do
    e.mapgrd[center_x - x][center_y - 1] = '.'
    e.mapgrd[center_x - x][center_y]     = wall_glyph
    e.mapgrd[center_x - x][center_y + 1] = '.'

    e.mapgrd[center_x + x][center_y - 1] = '.'
    e.mapgrd[center_x + x][center_y]     = wall_glyph
    e.mapgrd[center_x + x][center_y + 1] = '.'
  end

  e.mapgrd[center_x][center_y] = wall_glyph
  e.mapgrd[center_x + radius][center_y] = '.'
  e.mapgrd[center_x - radius][center_y] = '.'
  e.mapgrd[center_x][center_y + radius] = '.'
  e.mapgrd[center_x][center_y - radius] = '.'
end
}}

{{
function loopsInsertRoomHexagonNSSolid (e, center_x, center_y,
                                        radius, wall_glyph)
  local y_addend = radius % 2
  for y = -radius, radius do
    local abs_y = math.abs(y)
    local open_x  = radius - math.ceil((abs_y - y_addend)     / 2)
    local solid_x = radius - math.ceil((abs_y - y_addend + 2) / 2)
    for x = -radius, radius do
      local abs_x = math.abs(x)
      if (abs_x <= solid_x and abs_y < radius) then
        e.mapgrd[center_x + x][center_y + y] = wall_glyph
      elseif (abs_x <= open_x) then
        e.mapgrd[center_x + x][center_y + y] = '.'
      end
    end
  end
end
}}

{{
function loopsInsertRoomHexagonEWSolid (e, center_x, center_y,
                                        radius, wall_glyph)
  local x_addend = radius % 2
  for x = -radius, radius do
    local abs_x = math.abs(x)
    local open_y  = radius - math.ceil((abs_x - x_addend)     / 2)
    local solid_y = radius - math.ceil((abs_x - x_addend + 2) / 2)
    for y = -radius, radius do
      local abs_y = math.abs(y)
      if (abs_y <= solid_y and abs_x < radius) then
        e.mapgrd[center_x + x][center_y + y] = wall_glyph
      elseif (abs_y <= open_y) then
        e.mapgrd[center_x + x][center_y + y] = '.'
      end
    end
  end
end
}}

{{
function loopsInsertRoomBoxOutline (e, center_x, center_y, radius)
  for x_off = -radius, radius do
    local x = center_x + x_off
    e.mapgrd[x][center_y - radius] = '.'
    e.mapgrd[x][center_y + radius] = '.'
  end

  for y_off = -radius + 1, radius - 1 do
    local y = center_y + y_off
    e.mapgrd[center_x - radius][y] = '.'
    e.mapgrd[center_x + radius][y] = '.'
  end
end
}}

{{
function loopsInsertRoomDiamondOutline (e, center_x, center_y, radius)
  for y = -radius, radius do
    for x = -radius, radius do
      if math.abs(x) + math.abs(y) == radius then
        e.mapgrd[center_x + x][center_y + y] = '.'
      end
    end
  end
end
}}

{{
function loopsInsertRoomOctagonOutline (e, center_x, center_y, radius)
  for y = -radius, radius do
    for x = -radius, radius do
      if math.abs(x) + math.abs(y) == radius + 1 then
        e.mapgrd[center_x + x][center_y + y] = '.'
      end
    end
  end

  e.mapgrd[center_x + radius][center_y] = '.'
  e.mapgrd[center_x - radius][center_y] = '.'
  e.mapgrd[center_x][center_y + radius] = '.'
  e.mapgrd[center_x][center_y - radius] = '.'
end
}}

{{
function loopsInsertRoomBoxGlyph (e, center_x, center_y, wall_glyph, center_glyph)
  loopsInsertRoomBoxSolid(e, center_x, center_y, 3, wall_glyph);
  e.mapgrd[center_x + 2][center_y]     = '+'
  e.mapgrd[center_x - 2][center_y]     = '+'
  e.mapgrd[center_x]    [center_y + 2] = '+'
  e.mapgrd[center_x]    [center_y - 2] = '+'
  loopsInsertRoomBoxOpen(e, center_x, center_y, 1);
  e.mapgrd[center_x][center_y] = center_glyph
end
}}

{{
function loopsInsertRoomDiamondGlyph (e, center_x, center_y, wall_glyph, center_glyph)
  loopsInsertRoomDiamondSolid(e, center_x, center_y, 4, wall_glyph);
  loopsInsertRoomBoxOpen(e, center_x, center_y, 1);
  e.mapgrd[center_x][center_y] = center_glyph
end
}}

{{
function loopsInsertRoomOctagonGlyph (e, center_x, center_y, wall_glyph, center_glyph)
  loopsInsertRoomOctagonSolid(e, center_x, center_y, 3, wall_glyph);
  loopsInsertRoomBoxOpen(e, center_x, center_y, 1);
  e.mapgrd[center_x][center_y] = center_glyph
end
}}

{{
function loopsInsertRoomPlusGlyph (e, center_x, center_y, radius, wall_glyph, center_glyph)
  for x = -radius, radius do
    e.mapgrd[center_x + x][center_y] = '.'
  end
  for y = -radius, radius do
    e.mapgrd[center_x][center_y + y] = '.'
  end

  e.mapgrd[center_x + 1][center_y + 1] = wall_glyph
  e.mapgrd[center_x + 1][center_y - 1] = wall_glyph
  e.mapgrd[center_x - 1][center_y + 1] = wall_glyph
  e.mapgrd[center_x - 1][center_y - 1] = wall_glyph

  e.mapgrd[center_x][center_y] = center_glyph
end
}}

#
# loopsInsertRoomsAtPositions
#
# Has a chance at drawing a room at each unused corner.
#
# Parameters:
#  <1> e: The global environment
#  <2> corners: The array of corners to draw rooms at
#  <3> percent: The chance (out of 100) of drawing a room
#  <4> radius_min: The minimum radius of a room
#  <5> radius_max: The maximum radius of a room
#  <6> border: The minimum distance of a room from the edge of the map
#  <7> wall_glyph: The glyph to fill the center of the rooms
#                  with, if applicable
#
# Preconditions:
#  <1> percent >= 0
#  <2> percent <= 100
#  <3> radius_min >= 0
#  <4> radius_min <= radius_max
#

{{
function loopsInsertRoomsAtPositions (e, positions, percent,
                                      radius_min, radius_max,
                                      border, wall_glyph)
  local radius_diff = radius_max - radius_min

  while positions.count > 0 do
    local index = 1 + crawl.random2(positions.count)

    if crawl.random2(100) < percent then
      local min_x = border + radius_max
      local max_x = e.width() - (border + radius_max)
      local min_y = border + radius_max
      local max_y = e.height() - (border + radius_max)

      local value = positions[index]
      local x = value % 1000
      local y = math.floor(value / 1000)
      local radius = math.floor(radius_min + randomLow(radius_diff + 1))
      if you.in_branch("Elf") then
        radius = crawl.random_range(radius_min, radius_max)
      end

      if x < min_x then
        x = min_x
      end
      if x >= max_x then
        x = max_x - 1
      end
      if y < min_y then
        y = min_y
      end
      if y >= max_y then
        y = max_y - 1
      end

      loopsInsertRoom(e, x, y, radius, wall_glyph)
    end

    positions[index] = positions[positions.count]
    positions.count = positions.count - 1
  end
end
}}

#
# loopsInsertRoomsAtIntersections
#
# Has a chance at drawing a room at each X-junction in the map.
#  The rooms are drawn in a random order to avoid directional
#  bias.
#
# Parameters:
#  <1> e: The global environment
#  <2> percent: The chance (out of 100) of drawing a room
#  <3> radius_min: The minimum radius of a room
#  <4> radius_max: The maximum radius of a room
#  <5> border: The minimum distance of a room from the edge of
#              the map
#  <6> wall_glyph: The glyph to fill the center of the rooms
#
# Preconditions:
#  <1> percent >= 0
#  <2> percent <= 100
#  <3> radius_min >= 0
#  <4> radius_min <= radius_max
#
# The map pattern required for a chance of a room is:
#
#  x.x
#  ...
#  x.x
#

{{
function loopsInsertRoomsAtIntersections (e, percent,
                                          radius_min, radius_max,
                                          border, wall_glyph)
  local positions = {}
  positions.count = 0

  for y = radius_max, e.height() - 1 - radius_max do
    for x = radius_max, e.width() - 1 - radius_max do
      if e.mapgrd[x]    [y]     == '.' and
         e.mapgrd[x - 1][y]     == '.' and
         e.mapgrd[x + 1][y]     == '.' and
         e.mapgrd[x]    [y - 1] == '.' and
         e.mapgrd[x]    [y + 1] == '.' and
         e.mapgrd[x - 1][y - 1] ~= '.' and
         e.mapgrd[x - 1][y + 1] ~= '.' and
         e.mapgrd[x + 1][y - 1] ~= '.' and
         e.mapgrd[x + 1][y + 1] ~= '.' then

        local value = y * 1000 + x
        positions[positions.count + 1] = value
        positions.count = positions.count + 1
      end
    end
  end

  loopsInsertRoomsAtPositions(e, positions, percent,
                              radius_min, radius_max,
                              border, wall_glyph)
end
}}

#
#  loopsInsertCornerDiamond
#
# Builds one quarter of the layout_loops_corners map
#
# Parameters:
#  <1> e: The global environment
#  <2> center_x
#  <3> center_y: The center of the corner
#  <4> wall_glyph: The glyph to fill the center of the rooms
#
# Preconditions:
#  <1> center_x >= 0
#  <2> center_x < e.width()
#  <3> center_x >= 0
#  <4> center_x < e.height()
#

{{
function loopsInsertCornerDiamond (e, center_x, center_y,
                                   wall_glyph, room_corner_percent,
                                   room_radius_min, room_radius_max)
  local positions1 = {}
  local positions2 = {}
  positions1.count = 0
  positions2.count = 0

  local subroom_xm = center_x - 10
  local subroom_xp = center_x + 10
  local subroom_ym = center_y - 10
  local subroom_yp = center_y + 10

  loopsInsertRoomDiamondOpen(e, center_x, center_y, 5)

  loopsInsertRoomDiamondOpen(e, subroom_xm, center_y,   2)
  loopsInsertRoomDiamondOpen(e, subroom_xp, center_y,   2)
  loopsInsertRoomDiamondOpen(e, center_x,   subroom_ym, 2)
  loopsInsertRoomDiamondOpen(e, center_x,   subroom_yp, 2)

  for x = subroom_xm, subroom_xp - 1 do
    e.mapgrd[x][center_y] = '.'
  end
  for y = subroom_ym, subroom_yp do
    e.mapgrd[center_x][y] = '.'
  end

  -- add doors around main corner room
  e.mapgrd[center_x + 5][center_y] = '+'
  e.mapgrd[center_x - 5][center_y] = '+'
  e.mapgrd[center_x][center_y + 5] = '+'
  e.mapgrd[center_x][center_y - 5] = '+'

  loopsInsertDividedLoop(e, positions1, center_x, center_y, 7, 13, 3)
  loopsInsertLoopGroup(e, positions2, positions1, 5, 1, 4, 1, 1)
  loopsInsertRoomsAtPositions(e, positions1, room_corner_percent,
                              room_radius_min, room_radius_max,
                              1, wall_glyph)
end
}}



#
# Finally, we have the real layouts.
#

##############################################################
# layout_loops_ring
#
# Previously named "layout_loops_misc".
#
# This layout has a lot of hallways with many, usually-small
#  rooms.  The overall structure is a doughnut, although this
#  can be difficult to see in the final map.
#
NAME:   layout_loops_ring
DEPTH:  D, Elf, Lair
WEIGHT: 15 (D), 5 (Elf), 8 (Lair)
ORIENT: encompass
TAGS:   overwritable layout allow_dup unrand layout_type_corridors
TAGS:   no_rotate no_vmirror no_hmirror
{{
  local IS_ELF = you.in_branch("Elf")
  local ROOM_RADIUS_MIN = IS_ELF and 2 or 1
  local ROOM_RADIUS_MAX = IS_ELF and 5 or 4

  local ROOM_BORDER = 1
  local LOOP_SMALL_BORDER  = ROOM_BORDER + ROOM_RADIUS_MAX
  local LOOP_MEDIUM_BORDER = LOOP_SMALL_BORDER  + 2
  local LOOP_LARGE_BORDER  = LOOP_MEDIUM_BORDER + 1
  local LOOP_MASTER_BORDER = LOOP_LARGE_BORDER  + 3

  local ROOM_INTERSECTION_PERCENT = 50
  local ROOM_CORNER_PERCENT = IS_ELF and 20 or 10

  local wall_glyph = getRandomWallMaterial()

  local gxm, gym = dgn.max_bounds()
  extend_map{width = gxm, height = gym, fill = wall_glyph}

  local SMALLER_DIMENSION = math.min(width(), height())
  local USABLE_SIZE = SMALLER_DIMENSION - LOOP_SMALL_BORDER * 2
  local USABLE_CELLS = USABLE_SIZE * USABLE_SIZE

  local LOOP_MASTER_DIVISIONS  = math.floor(USABLE_SIZE / 12)
  local LOOP_MASTER_RADIUS_MAX = SMALLER_DIMENSION / 2 - LOOP_MASTER_BORDER
  local LOOP_MASTER_RADIUS_MIN = math.floor(USABLE_SIZE / 10)

  local LOOP_LARGE_DIVISIONS  = 3
  local LOOP_LARGE_COUNT      = math.floor(USABLE_CELLS / 1600)
  local LOOP_LARGE_RADIUS_MAX = 20
  local LOOP_LARGE_RADIUS_MIN = 8

  local LOOP_MEDIUM_DIVISIONS  = 2
  local LOOP_MEDIUM_COUNT      = math.floor(USABLE_CELLS / 1200)
  local LOOP_MEDIUM_RADIUS_MAX = 10
  local LOOP_MEDIUM_RADIUS_MIN = 4

  local LOOP_SMALL_DIVISIONS  = 1
  local LOOP_SMALL_COUNT      = math.floor(USABLE_CELLS / 800)
  local LOOP_SMALL_RADIUS_MAX = 6
  local LOOP_SMALL_RADIUS_MIN = 2

  local positions1 = {}
  local positions2 = {}
  local positions3 = {}
  local positions4 = {}

  positions1.count = 0
  positions2.count = 0
  positions3.count = 0
  positions4.count = 0

  loopsInsertDividedLoop(_G, positions1, width() / 2, height() / 2,
                         LOOP_MASTER_RADIUS_MIN,
                         LOOP_MASTER_RADIUS_MAX,
                         LOOP_MASTER_DIVISIONS)

  loopsInsertLoopGroup(_G, positions2, positions1,
                       LOOP_LARGE_COUNT,
                       LOOP_LARGE_RADIUS_MIN,
                       LOOP_LARGE_RADIUS_MAX,
                       LOOP_LARGE_BORDER,
                       LOOP_LARGE_DIVISIONS)
  loopsInsertLoopGroup(_G, positions3, positions2,
                       LOOP_MEDIUM_COUNT,
                       LOOP_MEDIUM_RADIUS_MIN,
                       LOOP_MEDIUM_RADIUS_MAX,
                       LOOP_MEDIUM_BORDER,
                       LOOP_MEDIUM_DIVISIONS)
  loopsInsertLoopGroup(_G, positions4, positions3,
                       LOOP_SMALL_COUNT,
                       LOOP_SMALL_RADIUS_MIN,
                       LOOP_SMALL_RADIUS_MAX,
                       LOOP_SMALL_BORDER,
                       LOOP_SMALL_DIVISIONS)

  loopsInsertRoomsAtIntersections(_G, ROOM_INTERSECTION_PERCENT,
                                  ROOM_RADIUS_MIN,
                                  ROOM_RADIUS_MAX,
                                  ROOM_BORDER,
                                  wall_glyph)

  -- yes, positions3: no rooms for smallest loops
  loopsInsertRoomsAtPositions(_G, positions3,
                              ROOM_CORNER_PERCENT,
                              ROOM_RADIUS_MIN,
                              ROOM_RADIUS_MAX,
                              ROOM_BORDER,
                              wall_glyph)

  if IS_ELF then
    add_windows {wall = wall_glyph .. "m", open = ".", window = "m" }
  end
}}
MAP
ENDMAP

##############################################################
# layout_loops_cross
#
# This layout is in the rough shape of a cross with a box
#  around the end of each arm.  There are also other hallways
#  and rooms generated.  The arms of the cross are always
#  connected through the center and they may be connected to
#  adjacent arms.  Normally, at least two arms are directly
#  connected but there is one that is not.
#
# There is always a (open) plus-shaped room placed at the
#  center of this map where the four arms are guaranteed to be
#  connected.
#
# There is a chance that this layout places all 6 staircases.
#  If so, the three up stairs will be placed around one arm of
#  the cross and the three down stairs will be placed around
#  another (random) arm of the cross.
#
NAME:   layout_loops_cross
DEPTH:  D, Elf
WEIGHT: 5 (D), 10 (Elf)
ORIENT: encompass
TAGS:   overwritable layout allow_dup unrand layout_type_corridors
TAGS:   no_rotate no_vmirror no_hmirror
{{
  local IS_ELF = you.in_branch("Elf")
  local ROOM_RADIUS_MIN = IS_ELF and 2 or 1
  local ROOM_RADIUS_MAX = IS_ELF and 5 or 4
  local ROOM_BORDER = 1

  local CONNECTION_PLUS_OFFSET = 4
  local CONNECTION_VARIATION_BIG = 8
  local CONNECTION_VARIATION_SMALL = 3

  local PLACE_STAIRS_PERCENT = IS_ELF and 50 or 0
  local ROOM_CORNER_PERCENT  = IS_ELF and 25 or 20

  -- Elf + length 20 arms can result in floor on map edge
  local CROSS_ARM_LENGTH      = IS_ELF and 19 or 20
  local STAIR_ROOM_DISTANCE   = IS_ELF and 10 or 8
  local STAIR_ROOM_SIZE_MIN   = IS_ELF and 3 or 2
  local STAIR_ROOM_SIZE_RANGE = 2
  local STAIR_COUNT = 12

  local wall_glyph = getRandomWallMaterial()

  local gxm, gym = dgn.max_bounds()
  extend_map{width = gxm, height = gym, fill = wall_glyph}

  local positions_use = {}
  local positions_dummy = {}
  positions_use.count = 0
  positions_dummy.count = 0

  local center_x = width() / 2
  local center_y = height() / 2

  local center_xm = center_x - CROSS_ARM_LENGTH
  local center_xp = center_x + CROSS_ARM_LENGTH
  local center_ym = center_y - CROSS_ARM_LENGTH
  local center_yp = center_y + CROSS_ARM_LENGTH

  local stairs_xm = center_x - STAIR_ROOM_DISTANCE
  local stairs_xp = center_x + STAIR_ROOM_DISTANCE
  local stairs_ym = center_y - STAIR_ROOM_DISTANCE
  local stairs_yp = center_y + STAIR_ROOM_DISTANCE

  local outmost_xm = center_xm - STAIR_ROOM_DISTANCE
  local outmost_xp = center_xp + STAIR_ROOM_DISTANCE
  local outmost_ym = center_ym - STAIR_ROOM_DISTANCE
  local outmost_yp = center_yp + STAIR_ROOM_DISTANCE

  -- we have a center room and one on each arm
  local center_room_size = 5 + crawl.random2(4)
  loopsInsertRoomPlusOpen(_G, center_x, center_y, center_room_size)

  local size1 = STAIR_ROOM_SIZE_MIN + randomLow(STAIR_ROOM_SIZE_RANGE)
  local size2 = STAIR_ROOM_SIZE_MIN + randomLow(STAIR_ROOM_SIZE_RANGE)
  local size3 = STAIR_ROOM_SIZE_MIN + randomLow(STAIR_ROOM_SIZE_RANGE)
  local size4 = STAIR_ROOM_SIZE_MIN + randomLow(STAIR_ROOM_SIZE_RANGE)
  loopsInsertRoomBoxOpen(_G, center_xm, center_y,  size1)
  loopsInsertRoomBoxOpen(_G, center_xp, center_y,  size2)
  loopsInsertRoomBoxOpen(_G, center_x,  center_ym, size3)
  loopsInsertRoomBoxOpen(_G, center_x,  center_yp, size4)

  -- we connect these rooms
  loopsInsertDividedLineX(_G, positions_use,
                          center_xm, center_y,
                          center_x - CONNECTION_PLUS_OFFSET, center_y,
                          center_y - CONNECTION_VARIATION_BIG,
                          center_y + CONNECTION_VARIATION_BIG, 0, 3)
  loopsInsertDividedLineX(_G, positions_use,
                          center_x + CONNECTION_PLUS_OFFSET, center_y,
                          center_xp, center_y,
                          center_y - CONNECTION_VARIATION_BIG,
                          center_y + CONNECTION_VARIATION_BIG, 0, 3)
  loopsInsertDividedLineY(_G, positions_use,
                          center_x, center_ym,
                          center_x, center_y - CONNECTION_PLUS_OFFSET,
                          center_x - CONNECTION_VARIATION_BIG,
                          center_x + CONNECTION_VARIATION_BIG, 0, 3)
  loopsInsertDividedLineY(_G, positions_use,
                          center_x, center_y + CONNECTION_PLUS_OFFSET,
                          center_x, center_yp,
                          center_x - CONNECTION_VARIATION_BIG,
                          center_x + CONNECTION_VARIATION_BIG, 0, 3)

  -- add in some other rooms and passages
  loopsInsertDividedLoop(_G, positions_use, center_xm, center_y,  5, 10, 3)
  loopsInsertDividedLoop(_G, positions_use, center_xp, center_y,  5, 10, 3)
  loopsInsertDividedLoop(_G, positions_use, center_x,  center_ym, 5, 10, 3)
  loopsInsertDividedLoop(_G, positions_use, center_x,  center_yp, 5, 10, 3)

  loopsInsertLoopGroup(_G, positions_dummy, positions_use, 25, 2, 3, 1, 1)
  loopsInsertRoomsAtPositions(_G, positions_use,
                              ROOM_CORNER_PERCENT,
                              ROOM_RADIUS_MIN,
                              ROOM_RADIUS_MAX,
                              ROOM_BORDER,
                              wall_glyph)

  -- we have a chance of placing the stairs
  if crawl.random2(100) < PLACE_STAIRS_PERCENT then
    -- In general, we add 3 rooms on each arm to hold the stairs
    --  and pick two arms at random to have the stairs.  We
    --  also connect these stairs rooms to the central room for
    --  the arm.  The connections are added first so that they
    --  do not overwrite the stairs rooms (which may be fancy).

    local end_x_1 = 0
    local end_x_2 = 0
    local end_y_1 = 0
    local end_y_2 = 0

    -- connect stairs rooms on Y- end
    end_y_1 = randomNearValue(center_ym, CONNECTION_VARIATION_SMALL)
    end_y_2 = randomNearValue(center_ym, CONNECTION_VARIATION_SMALL)
    loopsInsertDividedLineX(_G, positions_dummy,
                            stairs_xm, end_y_1,
                            stairs_xp, end_y_2,
                            center_ym - CONNECTION_VARIATION_SMALL,
                            center_ym + CONNECTION_VARIATION_SMALL, 0, 3)
    end_x_1 = randomNearValue(center_x,  CONNECTION_VARIATION_SMALL)
    end_x_2 = randomNearValue(center_x,  CONNECTION_VARIATION_SMALL)
    loopsInsertDividedLineY(_G, positions_dummy,
                            end_x_1, outmost_ym,
                            end_x_2, center_ym,
                            center_x - CONNECTION_VARIATION_SMALL,
                            center_x + CONNECTION_VARIATION_SMALL, 0, 2)

    -- connect stairs roomss on Y+ end
    end_y_1 = randomNearValue(center_yp, CONNECTION_VARIATION_SMALL)
    end_y_2 = randomNearValue(center_yp, CONNECTION_VARIATION_SMALL)
    loopsInsertDividedLineX(_G, positions_dummy,
                            stairs_xm, end_y_1,
                            stairs_xp, end_y_2,
                            center_yp - CONNECTION_VARIATION_SMALL,
                            center_yp + CONNECTION_VARIATION_SMALL, 0, 3)
    end_x_1 = randomNearValue(center_x,  CONNECTION_VARIATION_SMALL)
    end_x_2 = randomNearValue(center_x,  CONNECTION_VARIATION_SMALL)
    loopsInsertDividedLineY(_G, positions_dummy,
                            end_x_1, center_yp,
                            end_x_1, outmost_yp,
                            center_x  - CONNECTION_VARIATION_SMALL,
                            center_x  + CONNECTION_VARIATION_SMALL, 0, 2)

    -- connect stairs rooms on X- end
    end_x_1 = randomNearValue(center_xm, CONNECTION_VARIATION_SMALL)
    end_x_2 = randomNearValue(center_xm, CONNECTION_VARIATION_SMALL)
    loopsInsertDividedLineY(_G, positions_dummy,
                            end_x_1, stairs_ym,
                            end_x_2, stairs_yp,
                            center_xm - CONNECTION_VARIATION_SMALL,
                            center_xm + CONNECTION_VARIATION_SMALL, 0, 3)
    end_y_1 = randomNearValue(center_y,  CONNECTION_VARIATION_SMALL)
    end_y_2 = randomNearValue(center_y,  CONNECTION_VARIATION_SMALL)
    loopsInsertDividedLineX(_G, positions_dummy,
                            outmost_xm, end_y_1,
                            center_xm,  end_y_2,
                            center_y - CONNECTION_VARIATION_SMALL,
                            center_y + CONNECTION_VARIATION_SMALL, 0, 2)

    -- connect stairs rooms on X+ end
    end_x_1 = randomNearValue(center_xp, CONNECTION_VARIATION_SMALL)
    end_x_2 = randomNearValue(center_xp, CONNECTION_VARIATION_SMALL)
    loopsInsertDividedLineY(_G, positions_dummy,
                            end_x_1, stairs_ym,
                            end_x_2, stairs_yp,
                            center_xp - CONNECTION_VARIATION_SMALL,
                            center_xp + CONNECTION_VARIATION_SMALL, 0, 3)
    end_y_1 = randomNearValue(center_y,  CONNECTION_VARIATION_SMALL)
    end_y_2 = randomNearValue(center_y,  CONNECTION_VARIATION_SMALL)
    loopsInsertDividedLineX(_G, positions_dummy,
                            center_xp,  end_y_1,
                            outmost_xp, end_y_2,
                            center_y - CONNECTION_VARIATION_SMALL,
                            center_y + CONNECTION_VARIATION_SMALL, 0, 2)

    local stairs_x = {}
    local stairs_y = {}
    local stairs_glyph =
      { '[', '{', '(', ']', '}', ')', 'A', 'B', 'C', 'D', 'E', 'F' }

    stairs_x[1]  = outmost_xm
    stairs_y[1]  = center_y
    stairs_x[2]  = center_xm
    stairs_y[2]  = stairs_ym
    stairs_x[3]  = center_xm
    stairs_y[3]  = stairs_yp
    stairs_x[4]  = outmost_xp
    stairs_y[4]  = center_y
    stairs_x[5]  = center_xp
    stairs_y[5]  = stairs_ym
    stairs_x[6]  = center_xp
    stairs_y[6]  = stairs_yp
    stairs_x[7]  = center_x
    stairs_y[7]  = outmost_ym
    stairs_x[8]  = stairs_xm
    stairs_y[8]  = center_ym
    stairs_x[9]  = stairs_xp
    stairs_y[9]  = center_ym
    stairs_x[10] = center_x
    stairs_y[10] = outmost_yp
    stairs_x[11] = stairs_xm
    stairs_y[11] = center_yp
    stairs_x[12] = stairs_xp
    stairs_y[12] = center_yp

    local room_type = IS_ELF and crawl.random_range(6, 11) or crawl.random2(12)
    if room_type < 2 then
      for i = 1, STAIR_COUNT do
        loopsInsertRoomBoxOpen(_G, stairs_x[i], stairs_y[i], 2)
        mapgrd[stairs_x[i]][stairs_y[i]] = stairs_glyph[i]
      end
    elseif room_type < 4 then
      for i = 1, STAIR_COUNT do
        loopsInsertRoomDiamondOpen(_G, stairs_x[i], stairs_y[i], 3)
        mapgrd[stairs_x[i]][stairs_y[i]] = stairs_glyph[i]
      end
    elseif room_type < 6 then
      for i = 1, STAIR_COUNT do
        loopsInsertRoomOctagonOpen(_G, stairs_x[i], stairs_y[i], 3)
        mapgrd[stairs_x[i]][stairs_y[i]] = stairs_glyph[i]
      end
    -- Note that a plus-shaped room of radius 3 is an octagon
    elseif room_type < 7 then
      for i = 1, STAIR_COUNT do
        loopsInsertRoomBoxGlyph(_G, stairs_x[i], stairs_y[i],
                                wall_glyph, stairs_glyph[i])
      end
    elseif room_type < 9 then
      for i = 1, STAIR_COUNT do
        loopsInsertRoomDiamondGlyph(_G, stairs_x[i], stairs_y[i],
                                    wall_glyph, stairs_glyph[i])
      end
    elseif room_type < 11 then
      for i = 1, STAIR_COUNT do
        loopsInsertRoomOctagonGlyph(_G, stairs_x[i], stairs_y[i],
                                    wall_glyph, stairs_glyph[i])
      end
    else
      for i = 1, STAIR_COUNT do
        loopsInsertRoomPlusGlyph(_G, stairs_x[i], stairs_y[i], 4,
                                 wall_glyph, stairs_glyph[i])
      end
    end

    shuffle('[{( / ]}) / ABC / DEF')
    shuffle('[{(')
    shuffle(']})')
    subst('A : .')
    subst('B : .')
    subst('C : .')
    subst('D : .')
    subst('E : .')
    subst('F : .')
  end
  -- else place stairs randomly

  if IS_ELF then
    add_windows {wall = wall_glyph .. "m", open = ".", window = "m" }
  end
}}
MAP
ENDMAP

##############################################################
# layout_loops_corners
#
# This builds a big-ish central room with a hallway around it
#  in each corner.  There are small hallway loops and rooms
#  generated around the central rooms.
#
NAME:   layout_loops_corners
DEPTH:  D, Elf
WEIGHT: 5 (D), 15 (Elf)
ORIENT: encompass
TAGS:   overwritable layout allow_dup unrand layout_type_corridors
TAGS:   no_rotate no_vmirror no_hmirror
{{
  local IS_ELF = you.in_branch("Elf")
  local ROOM_RADIUS_MIN = IS_ELF and 2 or 1
  local ROOM_RADIUS_MAX = IS_ELF and 5 or 4
  local ROOM_CORNER_PERCENT = IS_ELF and 40 or 25

  local wall_glyph = getRandomWallMaterial()

  local gxm, gym = dgn.max_bounds()
  extend_map{width = gxm, height = gym, fill = wall_glyph}

  local center_xm = width() / 2 - 17
  local center_xp = width() / 2 + 17
  local center_ym = height() / 2 - 17
  local center_yp = height() / 2 + 17

  for x = center_xm, center_xp - 1 do
    mapgrd[x][center_ym] = '.'
    mapgrd[x][center_yp] = '.'
  end
  for y = center_ym, center_yp - 1 do
    mapgrd[center_xm][y] = '.'
    mapgrd[center_xp][y] = '.'
  end

  loopsInsertCornerDiamond(_G, center_xm, center_ym, wall_glyph,
                           ROOM_CORNER_PERCENT,
                           ROOM_RADIUS_MIN, ROOM_RADIUS_MAX)
  loopsInsertCornerDiamond(_G, center_xm, center_yp, wall_glyph,
                           ROOM_CORNER_PERCENT,
                           ROOM_RADIUS_MIN, ROOM_RADIUS_MAX)
  loopsInsertCornerDiamond(_G, center_xp, center_ym, wall_glyph,
                           ROOM_CORNER_PERCENT,
                           ROOM_RADIUS_MIN, ROOM_RADIUS_MAX)
  loopsInsertCornerDiamond(_G, center_xp, center_yp, wall_glyph,
                           ROOM_CORNER_PERCENT,
                           ROOM_RADIUS_MIN, ROOM_RADIUS_MAX)

  if you.in_branch("Elf") then
    add_windows {wall = wall_glyph .. "m", open = ".", window = "m" }
  end
}}
MAP
ENDMAP
