mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-22 23:26:40 -08:00
Spaces tend to cause annoyances in a Unix-style shell environment. This change fixes that.
161 lines
4.5 KiB
Ruby
Executable File
161 lines
4.5 KiB
Ruby
Executable File
#!ruby
|
|
|
|
# The Pattern class encapsulates everything we would want to know about a
|
|
# pattern in our Game of Life: its size, its current pattern of alive and
|
|
# dead cells, which generation it's on and how long it can run, and how
|
|
# to accept input for itself, print itself, and most importantly iterate
|
|
# from one generation to the next.
|
|
|
|
PATTERN_WIDTH = 80
|
|
PATTERN_HEIGHT = 24
|
|
|
|
class Pattern
|
|
|
|
# Begin with a totally empty/dead pattern of fixed size at generation 0.
|
|
|
|
def initialize(max_generations: 10)
|
|
@max_generations = max_generations
|
|
@generation = 0
|
|
@population = nil
|
|
@counter = Array.new(PATTERN_HEIGHT) { Array.new(PATTERN_WIDTH, 0) }
|
|
@invalid = false
|
|
end
|
|
|
|
# Take input from the console and enter it into the pattern.
|
|
|
|
def get_input
|
|
input = []
|
|
done = false
|
|
|
|
# Accept the input from the user on the console
|
|
|
|
loop do
|
|
print "? "
|
|
line = gets.chomp
|
|
break if line == 'DONE'
|
|
line.gsub!(/^\./, ' ')
|
|
input << line
|
|
end
|
|
|
|
# Emit some blank space
|
|
(1..10).each { puts }
|
|
|
|
# Center the input in the two-dimensional array
|
|
input_width = input.map { |line| line.length }.max
|
|
width_offset = ((PATTERN_WIDTH - input_width) / 2).floor
|
|
height_offset = ((PATTERN_HEIGHT - input.count) / 2).floor
|
|
# TODO emit error if width > PATTERN_WIDTH or line count > PATTERN_HEIGHT
|
|
|
|
# Start by setting each element to 0 if dead or a 1 if alive
|
|
|
|
input.each_index do |y|
|
|
line = input[y].ljust(input_width)
|
|
y_offset = y + height_offset
|
|
@counter[y_offset] = [
|
|
[0] * width_offset,
|
|
line.split("").map { |char| char == ' ' ? 0 : 1 },
|
|
[0] * PATTERN_WIDTH
|
|
].flatten.take(PATTERN_WIDTH)
|
|
end
|
|
|
|
@population = @counter.flatten.sum
|
|
end
|
|
|
|
# Emit the pattern to the console.
|
|
|
|
def display
|
|
puts "GENERATION:#{@generation}\tPOPULATION:#{@population}"
|
|
puts "INVALID!"if @invalid
|
|
|
|
@counter.each do |row|
|
|
puts row.map { |cell| ((cell == 0 || cell == 2) ? ' ' : 'X') }.join('')
|
|
end
|
|
end
|
|
|
|
# Iterate from one generation to the next, returning true if the
|
|
# game of life should continue, and false if it should terminate.
|
|
|
|
def iterate
|
|
@generation = @generation + 1
|
|
return false if @generation > @max_generations
|
|
|
|
# Update the counter array with new values.
|
|
# First, change each 2 (dying) to a 0 (dead)
|
|
# and each 3 (born) to a 1 (alive)
|
|
@counter.map! { |row| row.map! { |cell| cell >= 2 ? cell-2 : cell } }
|
|
|
|
# Now for each cell, count its neighbors and update it
|
|
|
|
@population = 0
|
|
@counter.each_index do |rownum|
|
|
@counter[rownum].each_index do |colnum|
|
|
cell_value = @counter[rownum][colnum]
|
|
|
|
# If any cell on the border is set, our small algorithm is not
|
|
# smart enough to correctly check its neighbors, so sadly our
|
|
# pattern becomes invalid. We keep going though
|
|
|
|
@invalid = true if cell_value > 0 && (
|
|
rownum == 0 || rownum == PATTERN_HEIGHT-1 || colnum == 0 || colnum == PATTERN_WIDTH-1
|
|
)
|
|
|
|
# Count the cell's neighbors (not including itself)
|
|
|
|
neighbors = @counter[rownum-1..rownum+1].map { |row| row[colnum-1..colnum+1] }.flatten
|
|
neighbor_count = neighbors.inject(0) do |sum, value|
|
|
sum += (value == 1 || value == 2) ? 1 : 0
|
|
end
|
|
neighbor_count = neighbor_count - cell_value
|
|
|
|
# Update this cell based on its neighbor count, either leaving it
|
|
# as a 0 or 1, or setting it to 2 (dying) or 3 (being born)
|
|
|
|
if cell_value == 0
|
|
if neighbor_count == 3
|
|
cell_value = 3
|
|
@population = @population + 1
|
|
end
|
|
elsif neighbor_count < 2 || neighbor_count > 3
|
|
cell_value = 2
|
|
else
|
|
@population = @population + 1
|
|
end
|
|
@counter[rownum][colnum] = cell_value
|
|
|
|
end
|
|
end
|
|
|
|
# If every cell is dead, we are done. Otherwise, keep going up to the
|
|
# maximum number of generations
|
|
|
|
@population > 0
|
|
end
|
|
|
|
end
|
|
|
|
# The following program code makes use of the Pattern class to create,
|
|
# iterate on, and display the Game of Life.
|
|
|
|
def display_banner
|
|
puts " " * 34 + "LIFE"
|
|
puts " " * 15 + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"
|
|
puts
|
|
puts "PLEASE ENTER YOUR STARTING PATTERN, USING SPACE FOR AN EMPTY CELL"
|
|
puts "AND AN 'X' FOR A FILLED CELL. YOUR PATTERN MAY BE UP TO #{PATTERN_HEIGHT} ROWS"
|
|
puts "OF UP TO #{PATTERN_WIDTH} COLUMNS EACH. TYPE 'DONE' WHEN DONE."
|
|
puts "ENTER YOUR PATTERN:"
|
|
end
|
|
|
|
def main
|
|
|
|
display_banner
|
|
|
|
pattern = Pattern.new
|
|
pattern.get_input
|
|
pattern.display
|
|
pattern.display while pattern.iterate
|
|
|
|
end
|
|
|
|
main
|