class MazeData def initialize(width, height) @width = width @height = height @grid = Array.new(height) { Array.new(width, 0) } @boss_rooms = [] crate_rooms! carve_passages_from(0, 0) fix_room_entrances! end def width @width * 2 + 1 end def height @height * 2 + 1 end def wall_type(gx, gy) mask = 0 return mask unless wall_at?(gx, gy) mask |= N if gy > 0 && wall_at?(gx, gy - 1) mask |= S if gy < height - 1 && wall_at?(gx, gy + 1) mask |= E if gx < width - 1 && wall_at?(gx + 1, gy ) mask |= W if gx > 0 && wall_at?(gx - 1, gy ) return mask end def wall_at?(gx, gy) return true if gx == 0 || gy == 0 || gx == width - 1 || gy == height - 1 @boss_rooms.each do |rx, ry| display_x1 = rx * 2 + 1 display_y1 = ry * 2 + 1 display_x2 = display_x1 + (BOSS_ROOM_SIZE - 1) * 2 display_y2 = display_y1 + (BOSS_ROOM_SIZE - 1) * 2 return false if gx.between?(display_x1, display_x2) && gy.between?(display_y1, display_y2) end return false if gx.odd? && gy.odd? if gx.odd? && gy.even? cx = (gx - 1) / 2 cy = (gy - 1) / 2 return (@grid[cy][cx] & S) == 0 end if gx.even? && gy.odd? cx = (gx - 1) / 2 cy = (gy - 1) / 2 return (@grid[cy][cx] & E) == 0 end true end private BOSS_ROOM_SIZE = 5 BOSS_ROOM = 16 N, S, W, E = 1, 2, 4, 8 DX = { E => 1, W => -1, N => 0, S => 0 } DY = { E => 0, W => 0, N => -1, S => 1 } OPPOSITE = { E => W, W => E, N => S, S => N } def room_free?(x, y) (y - 2...(y + BOSS_ROOM_SIZE + 2)).each do |j| (x - 2...(x + BOSS_ROOM_SIZE + 2)).each do |i| return false if @grid[j][i] & BOSS_ROOM != 0 end end true end def fix_room_entrances! @boss_rooms.each do |x, y| @grid[y][x + 2] |= N @grid[y - 1][x + 2] |= S end end def crate_rooms! base = Math.sqrt(@width * @height / 200.0).round num_rooms = rand((base - 1)..(base)) num_rooms = 1 if num_rooms < 1 attempts = 0 while @boss_rooms.size < num_rooms && attempts < 10 attempts += 1 x = rand(2..(@width - BOSS_ROOM_SIZE - 2)) y = rand(2..(@height - BOSS_ROOM_SIZE - 2)) next unless room_free?(x, y) (y...(y + BOSS_ROOM_SIZE)).each do |j| (x...(x + BOSS_ROOM_SIZE)).each do |i| cell = N | S | E | W cell |= BOSS_ROOM cell &= ~N if j == y cell &= ~S if j == y + BOSS_ROOM_SIZE - 1 cell &= ~W if i == x cell &= ~E if i == x + BOSS_ROOM_SIZE - 1 @grid[j][i] = cell end end @boss_rooms << [x, y] end end def carve_passages_from(cx, cy) directions = [N, S, E, W].shuffle directions.each do |direction| nx, ny = cx + DX[direction], cy + DY[direction] if ny.between?(0, @height - 1) && nx.between?(0, @width - 1) && @grid[ny][nx] == 0 @grid[cy][cx] |= direction @grid[ny][nx] |= OPPOSITE[direction] carve_passages_from(nx, ny) end end end end if __FILE__ == $0 WALL_CHARS = { 0 => " ", # NONE 1 => "╵", # N 2 => "╷", # S 3 => "│", # NS 4 => "╴", # W 5 => "┘", # NW 6 => "┐", # WS 7 => "┤", # WNS 8 => "╶", # E 9 => "└", # NE 10 => "┌", # ES 11 => "├", # ENS 12 => "─", # EW 13 => "┴", # EWN 14 => "┬", # EWS 15 => "┼" # EWNS } maze = MazeData.new(80, 80) (0...maze.height).each do |y| (0...maze.width).each do |x| print WALL_CHARS[maze.wall_type(x, y)] end puts end end