mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-28 13:46:06 -08:00
Refactor
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user