# frozen_string_literal: true DEBUG = !ENV['DEBUG'].nil? require 'io/console' if DEBUG # BASIC arrays are 1-based, unlike Ruby 0-based arrays, # and this class simulates that. BASIC arrays are zero-filled, # which is also done here. While we could easily update the # algorithm to work with zero-based arrays, this class makes # the problem easier to reason about, row or col 1 are the # first row or column. class BasicArrayTwoD def initialize(rows, cols) @val = Array.new(rows) { Array.new(cols, 0) } end def [](row, col = nil) if col @val[row - 1][col - 1] else @val[row - 1] end end def []=(row, col, n) @val[row - 1][col - 1] = n end def to_s(width: max_width, row_hilite: nil, col_hilite: nil) @val.map.with_index do |row, row_index| row.map.with_index do |val, col_index| if row_hilite == row_index + 1 && col_hilite == col_index + 1 "[#{val.to_s.center(width)}]" else val.to_s.center(width + 2) end end.join end.join("\n") end def max_width @val.flat_map { |row| row.map { |val| val.to_s.length } }.sort.last end end class Maze EXIT_DOWN = 1 EXIT_RIGHT = 2 # Set up a constant hash for directions # The values represent the direction of the move as changes to row, col # and the type of exit when moving in that direction DIRECTIONS = { left: { row: 0, col: -1, exit: EXIT_RIGHT }, up: { row: -1, col: 0, exit: EXIT_DOWN }, right: { row: 0, col: 1, exit: EXIT_RIGHT }, down: { row: 1, col: 0, exit: EXIT_DOWN } }.freeze attr_reader :width, :height, :used, :walls, :entry def initialize(width, height) @width = width @height = height @used = BasicArrayTwoD.new(height, width) @walls = BasicArrayTwoD.new(height, width) create end def draw # Print the maze draw_top(entry, width) (1..height - 1).each do |row| draw_row(walls[row]) end draw_bottom(walls[height]) end private def create # entry represents the location of the opening @entry = (rand * width).round + 1 # Set up our current row and column, starting at the top and the locations of the opening row = 1 col = entry c = 1 used[row, col] = c # This marks the opening in the first row c += 1 while c != width * height + 1 do debug walls, row, col # remove possible directions that are blocked or # hit cells that we have already processed possible_dirs = DIRECTIONS.reject do |dir, change| nrow = row + change[:row] ncol = col + change[:col] nrow < 1 || nrow > height || ncol < 1 || ncol > width || used[nrow, ncol] != 0 end.keys # If we can move in a direction, move and make opening if possible_dirs.size != 0 direction = possible_dirs.sample change = DIRECTIONS[direction] # pick a random direction if %i[left up].include?(direction) row += change[:row] col += change[:col] walls[row, col] = change[:exit] else walls[row, col] += change[:exit] row += change[:row] col += change[:col] end used[row, col] = c c = c + 1 # otherwise, move to the next used cell, and try again else loop do if col != width col += 1 elsif row != height row += 1 col = 1 else row = col = 1 end break if used[row, col] != 0 debug walls, row, col end end end # Add a random exit walls[height, (rand * width).round] += 1 end def draw_top(entry, width) (1..width).each do |i| if i == entry print i == 1 ? '┏ ' : '┳ ' else print i == 1 ? '┏━━' : '┳━━' end end puts '┓' end def draw_row(row) print '┃' row.each.with_index do |val, col| print val < 2 ? ' ┃' : ' ' end puts row.each.with_index do |val, col| print val == 0 || val == 2 ? (col == 0 ? '┣━━' : '╋━━') : (col == 0 ? '┃ ' : '┫ ') end puts '┫' end def draw_bottom(row) print '┃' row.each.with_index do |val, col| print val < 2 ? ' ┃' : ' ' end puts row.each.with_index do |val, col| print val == 0 || val == 2 ? (col == 0 ? '┗━━' : '┻━━') : (col == 0 ? '┗ ' : '┻ ') end puts '┛' end def debug(walls, row, col) return unless DEBUG STDOUT.clear_screen puts walls.to_s(row_hilite: row, col_hilite: col) sleep 0.1 end end class Amazing def run draw_header width, height = ask_dimensions while width <= 1 || height <= 1 puts "MEANINGLESS DIMENSIONS. TRY AGAIN." width, height = ask_dimensions end maze = Maze.new(width, height) puts "\n" * 3 maze.draw end def draw_header puts ' ' * 28 + 'AMAZING PROGRAM' puts ' ' * 15 + 'CREATIVE COMPUTING MORRISTOWN, NEW JERSEY' puts "\n" * 3 end def ask_dimensions print 'WHAT ARE YOUR WIDTH AND HEIGHT? ' width = gets.to_i print '?? ' height = gets.to_i [width, height] end end Amazing.new.run