Question

Looking for feedback on obvious logic errors on this, not optimizing. I keep getting weird tick counts on the end game message (ex: 1 tick turns into 11 ticks)

The largest error I can spot while running the code is on the 2nd tick, a very large amount of alive cells appear. I am too new to this to understand why, but it seems like the @alive_cells is not resetting back to 0 after each check.

Here is my entire code, its large but it should be child's play to anyone with experience.

class CellGame

def initialize

  puts "How big do you want this game?"
  @size = gets.chomp.to_i

  @cell_grid = Array.new(@size) { Array.new(@size) }
  @grid_storage = Array.new(@size) { Array.new(@size) }

  @tick_count = 0

  fill_grid_with_random_cells
end

def fill_grid_with_random_cells
  @cell_grid.each do |row|
    row.map! do |cell|
      roll = rand(10)
        if roll > 9
          "•"
        else
          " "
        end
    end
  end

  check_cells_for_future_state
end

def check_for_any_alive_cells

  @cell_grid.each do |row|
    if row.include?("•")
      check_cells_for_future_state
    break
    else
      end_game_print_result
    end
  end
end


def check_cells_for_future_state

  @cell_grid.each_with_index do |row, row_index|
    row.each_with_index do |cell, cell_index|
      @live_neighbors = 0

      add_row_shift = (row_index + 1)
      if add_row_shift == @size
        add_row_shift = 0
      end

      add_cell_shift = (cell_index + 1)
      if add_cell_shift == @size
        add_cell_shift = 0
      end

      def does_this_include_alive(cell)
        if cell.include?("•")
          @live_neighbors +=1
        end
      end

      top_left_cell = @cell_grid[(row_index - 1)][(cell_index - 1)] 
        does_this_include_alive(top_left_cell)

      top_cell = @cell_grid[(row_index - 1)][(cell_index)]
        does_this_include_alive(top_cell)

      top_right_cell = @cell_grid[(row_index - 1)][(add_cell_shift)]
         does_this_include_alive(top_right_cell)

      right_cell = @cell_grid[(row_index)][(add_cell_shift)]
        does_this_include_alive(right_cell)

      bottom_right_cell = @cell_grid[(add_row_shift)][(add_cell_shift)]
        does_this_include_alive(bottom_right_cell)

      bottom_cell = @cell_grid[(add_row_shift)][(cell_index)]
        does_this_include_alive(bottom_cell)

      bottom_left_cell = @cell_grid[(add_row_shift)][(cell_index - 1)] 
        does_this_include_alive(bottom_left_cell)

      left_cell = @cell_grid[(row_index)][(cell_index - 1)] 
        does_this_include_alive(left_cell)


      if @live_neighbors == 2 || @live_neighbors == 3
        @grid_storage[row_index][cell_index] = "•"
      else
        @grid_storage[row_index][cell_index] = " "
      end

    end
  end

  update_cell_grid
end

def update_cell_grid
  @cell_grid = @grid_storage

  print_cell_grid_and_counter
end


def print_cell_grid_and_counter

  system"clear"
  @cell_grid.each do |row|
    row.each do |cell|
      print cell + " "
    end
    print "\n"  
  end

  @tick_count += 1
  print "\n"
  print "Days passed: #{@tick_count}"
  sleep(0.25)
  check_for_any_alive_cells
end


def end_game_print_result
  print "#{@tick_count} ticks were played, end of game."
  exit
end


end
Was it helpful?

Solution

I couldn't see where your code went wrong. It does have a recursive call which can easily cause strange behavior. Here is what I came up with:

class CellGame
  def initialize(size)
    @size = size; @archive = []
    @grid = Array.new(size) { Array.new(size) { rand(3).zero? } }
  end

  def lives_on?(row, col)
    neighborhood = (-1..1).map { |r| (-1..1).map { |c| @grid[row + r] && @grid[row + r][col + c] } }
    its_alive = neighborhood[1].delete_at(1)
    neighbors = neighborhood.flatten.count(true)
    neighbors == 3 || neighbors == 2 && its_alive
  end

  def next_gen
    (0...@size).map { |row| (0...@size).map { |col| lives_on?(row, col) } }
  end

  def play
    tick = 0; incr = 1
    loop do
      @archive.include?(@grid) ? incr = 0 : @archive << @grid
      sleep(0.5); system "clear"; @grid = next_gen
      puts "tick - #{tick += incr}"
      puts @grid.map { |row| row.map { |cell| cell ? '*' : ' ' }.inspect }
    end
  end
end

cg = CellGame.new 10
cg.play

The tick count stops but the program keeps running through the oscillator at the end.

OTHER TIPS

I wanted to revisit this and confidently say I have figured it out! Here is my new solution - still super beginner focused. Hope it helps someone out.

class Game

  # Uses constants for values that won't change
  LIVE = "🦄"
  DEAD = " "
  WIDTH = 68
  HEIGHT = 34

  def initialize
    # Sets our grid to a new empty grid (set by method below)
    @grid = empty_grid

    # Randomly fills our grid with live cells
    @grid.each do |row|
      # Map will construct our new array, we use map! to edit the @grid
      row.map! do |cell|
        if rand(10) == 1
          LIVE # Place a live cell
        else
          DEAD # Place a dead cell
        end
      end
    end

    # Single line implimentation
    # @grid.each {|row|row.map! {|cell|rand(10) == 1 ? LIVE : DEAD}}

    loop_cells #start the cycle
  end

  def empty_grid
    Array.new(HEIGHT) do
      # Creates an array with HEIGHT number of empty arrays
      Array.new(WIDTH) do
        # Fills each array with a dead cell WIDTH number of times
        DEAD
      end
    end

    # Single line implimentation
    # Array.new(HEIGHT){ Array.new(WIDTH) { DEAD } }
  end

  def print_grid # Prints our grid to the terminal
    system "clear" # Clears the terminal window

    # Joins cells in each row with an empty space
    rows = @grid.map do |row|
      row.join(" ")
    end

    # Print rows joined by a new line
    print rows.join("\n")

    # Single line implimentation
    # print @grid.map{|row| row.join(" ")}.join("\n")
  end

  def loop_cells
    print_grid # Start by printing the current grid
    new_grid = empty_grid # Set an empty grid (this will be the next life cycle)

    # Loop through every cell in every row
    @grid.each_with_index do |row, row_index|
      row.each_with_index do |cell, cell_index|

        # Find the cells friends
        friends = find_friends(row_index, cell_index)

        # Apply life or death rules
        if cell == LIVE
          state = friends.size.between?(2,3)
        else
          state = friends.size == 3
        end

        # Set cell in new_grid for the next cycle
        new_grid[row_index][cell_index] = state ? LIVE : DEAD

      end
    end

    # Replace grid and start over
    @grid = new_grid
    start_over
  end

  def find_friends(row_index, cell_index)

    # Ruby can reach backwards through arrays and start over at the end - but it cannot reach forwards. If we're going off the grid, start over at 0
    row_fix = true if (row_index + 1) == HEIGHT
    cell_fix = true if (cell_index + 1) == WIDTH
    # You'll see below I will use 0 if one of these values is truthy when checking cells to the upper right, right, lower right, lower, and lower left.

    # Check each neighbor, use 0 if we're reaching too far
    friends = [
      @grid[(row_index - 1)][(cell_index - 1)],
      @grid[(row_index - 1)][(cell_index)],
      @grid[(row_index - 1)][(cell_fix ? 0 : cell_index + 1)],
      @grid[(row_index)][(cell_fix ? 0 : cell_index + 1)],
      @grid[(row_fix ? 0 : row_index + 1)][(cell_fix ? 0 : cell_index + 1)],
      @grid[(row_fix ? 0 : row_index + 1)][(cell_index)],
      @grid[(row_fix ? 0 : row_index + 1)][(cell_index - 1)],
      @grid[(row_index)][(cell_index - 1)]
    ]

    # Maps live neighbors into an array, removes nil values
    friends.map{|x| x if x == LIVE}.compact
  end

  def start_over
    sleep 0.1
    loop_cells
  end

end

# Start game when file is run
Game.new
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top