diff --git a/02 Amazing/ruby/amazing.rb b/02 Amazing/ruby/amazing.rb index c14148b1..d9a4f9d0 100644 --- a/02 Amazing/ruby/amazing.rb +++ b/02 Amazing/ruby/amazing.rb @@ -1,19 +1,15 @@ # frozen_string_literal: true -# Set up a constant hash for directions -DIRECTIONS = { - left: 0, - up: 1, - right: 2, - down: 3 -}.freeze +DEBUG = !ENV['DEBUG'].nil? -EXIT_DOWN = 1 -EXIT_RIGHT = 2 +require 'io/console' if DEBUG # BASIC arrays are 1-based, unlike Ruby 0-based arrays, -# and this class simulates that. BASIC arrays are also zero-filled, -# which is also done here. +# 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) } @@ -31,132 +27,190 @@ class BasicArrayTwoD @val[row - 1][col - 1] = n end - def to_s - @val.map { |row| row.join(' ') }.join("\n") + 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 -def draw_top(entry, width) - (1..width).each do |i| - if i == entry - print i == 1 ? '┏ ' : '┳ ' - else - print i == 1 ? '┏━━' : '┳━━' +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 - puts '┓' -end + private -def draw_row(row) - print '┃' - row.each { |val| print val < 2 ? ' ┃' : ' ' } - puts - row.each { |val| print val == 0 || val == 2 ? '┣━━' : '┃ ' } - puts '┫' -end + 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 -# 10 PRINT TAB(28);"AMAZING PROGRAM" -# 20 PRINT TAB(15);"CREATIVE COMPUTING MORRISTOWN, NEW JERSEY" -# 30 PRINT:PRINT:PRINT:PRINT -puts ' ' * 28 + 'AMAZING PROGRAM' -puts ' ' * 15 + 'CREATIVE COMPUTING MORRISTOWN, NEW JERSEY' -puts "\n" * 3 + 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 -# 100 INPUT "WHAT ARE YOUR WIDTH AND LENGTH";H,V -# 102 IF H<>1 AND V<>1 THEN 110 -# 104 PRINT "MEANINGLESS DIMENSIONS. TRY AGAIN.":GOTO 100 -def ask_dimensions - print 'WHAT ARE YOUR WIDTH AND HEIGHT? ' - width = gets.to_i - print '?? ' - height = gets.to_i - [width, height] -end - -width, height = ask_dimensions -while width <= 1 || height <= 1 - puts "MEANINGLESS DIMENSIONS. TRY AGAIN." - width, height = ask_dimensions -end - -# 110 DIM W(H,V),V(H,V) -# BASIC programs can have the same variable names for different types, -# so the v array is not the same as the v int. Here we're renaming the arrays -# to have more friendly names -used = BasicArrayTwoD.new(height, width) -walls = BasicArrayTwoD.new(height, width) - -puts "\n" * 3 - - -# 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 - # remove possible directions that are blocked or - # hit cells that we have already processed - possible_dirs = DIRECTIONS.keys - if col == 0 || used[row, col - 1] != 0 - possible_dirs.delete(:left) - end - if row == 0 || used[row - 1, col] != 0 - possible_dirs.delete(:up) - end - if col == width || used[row, col + 1] != 0 - possible_dirs.delete(:right) - end - if row == height || used[row + 1, col] != 0 - possible_dirs.delete(:down) - end - - # If we can move in a direction, move and make opening - if possible_dirs.size != 0 - direction = possible_dirs.sample # pick a random direction - if direction == :left - col = col - 1 - walls[row, col] = EXIT_RIGHT - elsif direction == :up - row = row - 1 - walls[row, col] = EXIT_DOWN - elsif direction == :right - walls[row, col] += EXIT_RIGHT - col = col + 1 - elsif direction == :down - walls[row, col] += EXIT_DOWN - row = row + 1 - end - used[row, col] = c - c = c + 1 - # otherwise, move to the next used cell, and try again - else - while true do - if col != width - col += 1 - elsif row != height - row += 1 - col = 1 + # 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 - row = col = 1 + 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 - break if used[row, col] != 0 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 -# Add a random exit -walls[height, (rand * width).round + 1] += 1 +class Amazing + def run + draw_header -# Print the maze -draw_top(entry, width) -(1..height).each do |row| - draw_row(walls[row]) + 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 \ No newline at end of file