Newbie: Case statement

Graham,

    You can save yourself an enormous number of headaches by simply
adding two more rows and columns to your array. For example, if you
wanted the world to be 80x24, make your array 82x26 and then make your
loops from 1..80 and 1..24. That way you can safely reference
cell[x-1][y-1] and cell[x+1][y+1] and you don't need all of your special
cases.

    - Warren Brown

I'm aware this is a common approach, but I'm personally not found of it.

First of all, it confuses readers of the code. What size board are we really dealing with here? Why do these loops keep starting at 1 instead of 0?

Second, it almost always still requires special cases. For example, in the mentioned Game of Life, we must be sure organisms are not born into these border rows, or they'll change the results.

Finally, it's wasteful. I realize the memory is insignificant here, but it might not be in a bigger application.

My opinion is that it's better to address the real issue than just hide it away.

James Edward Gray II

···

On Sep 23, 2004, at 4:34 PM, Warren Brown wrote:

Graham,

    You can save yourself an enormous number of headaches by simply
adding two more rows and columns to your array. For example, if you
wanted the world to be 80x24, make your array 82x26 and then make your
loops from 1..80 and 1..24. That way you can safely reference
cell[x-1][y-1] and cell[x+1][y+1] and you don't need all of your special
cases.

The 1 dimensional version of what you just described is how sqlite stores each row header. An array of num_fields+1 offsets where the last offset is the record length.
Pretty handy concept.
-Charlie

···

On Sep 23, 2004, at 2:34 PM, Warren Brown wrote:

Graham,

    You can save yourself an enormous number of headaches by simply
adding two more rows and columns to your array. For example, if you
wanted the world to be 80x24, make your array 82x26 and then make your
loops from 1..80 and 1..24. That way you can safely reference
cell[x-1][y-1] and cell[x+1][y+1] and you don't need all of your special
cases.

    - Warren Brown

I agree with the spirit, but I would take it even farther. Why
bound the board at all? It should be a lot easier to just have an
infinite board (or at least, limited to Bignum#"max", whatever that
is). Easier, that is, after you reach in one ear, grab your brain, and
give it a sharp twist (about 0.76 radians to the NW should do it if
you've been coding in C).

     A sketch:

WARNING: THIS CODE WAS TYPE IN MAIL AND HAS NEVER BEEN RUN

     class A_cell
         attr_accessor(
            :state, # :filled or :empty
            :neighbors, # 0..8
            :loc # [x,y]
            )
         def initialize(x,y)
             @state = :empty
             @neighbors = 0
             @loc = [x,y]
             end
         def fate
            #depending on @state & @neighbors, return :filled or :empty
            end
         end

     class A_board
         attr_reader :cells # a hash of [x,y] => A_cell
         def initialize
              @cells = Hash.new
              end
         def step
             update_neighbor_count
             update_states
             end
         def update_neighbor_counts
             @cells.keys.each { |x,y|
                 if @cells[[x,y]].state.filled
                     ((x-1)..(x+1)).each { |x0|
                         ((y-1)..(y+1)).each { |y0|
                             @cells[[x0,y0]] ||= A_cell.new(x0,y0)
                             @cells[[x0,y0]].neighbors += 1 unless
                                x0 == x and y0 == y
                             }
                          }
                     end
                 }
             end
         def update_states
             @cells.keys.each { |x,y|
                 case @cells[[x,y]].fate
                   when :filled
                     @cells[[x,y]].state = :filled
                     @cells[[x,y]].neighbors = 0
                   when :empty
                     @cells.delete_at [x,y]
                   end
             end
         end

This has some fat & may be missing some details, but at least it should
give you the idea.

-- Markus

···

On Thu, 2004-09-23 at 14:34, Warren Brown wrote:

Graham,

    You can save yourself an enormous number of headaches by simply
adding two more rows and columns to your array. For example, if you
wanted the world to be 80x24, make your array 82x26 and then make your
loops from 1..80 and 1..24. That way you can safely reference
cell[x-1][y-1] and cell[x+1][y+1] and you don't need all of your special
cases.

[snip]

     class A_cell
         attr_accessor(

            :state, # :filled or :empty
            :neighbors, # 0..8
            :loc # [x,y]

            )
         def initialize(x,y)
             @state = :empty
             @neighbors = 0
             @loc = [x,y]
             end
         def fate
            #depending on @state & @neighbors, return :filled or :empty
            end
         end

     class A_board
         attr_reader :cells # a hash of [x,y] => A_cell
         def initialize
              @cells = Hash.new
              end
         def step
             update_neighbor_count
             update_states
             end
         def update_neighbor_counts
             @cells.keys.each { |x,y|
                 if @cells[[x,y]].state.filled
                     ((x-1)..(x+1)).each { |x0|
                         ((y-1)..(y+1)).each { |y0|
                             @cells[[x0,y0]] ||= A_cell.new(x0,y0)
                             @cells[[x0,y0]].neighbors += 1 unless
                                x0 == x and y0 == y
                             }
                          }
                     end
                 }
             end
         def update_states
             @cells.keys.each { |x,y|
                 case @cells[[x,y]].fate
                   when :filled
                     @cells[[x,y]].state = :filled
                     @cells[[x,y]].neighbors = 0
                   when :empty
                     @cells.delete_at [x,y]
                   end
             end
         end

This has some fat & may be missing some details, but at least it should
give you the idea.

-- Markus

I have also made a gameoflife implementation.. it looks like this

bash-2.05b$ expand -t2 gameoflife.rb
module GameOfLife
  def determine_destiny(alive, count)
    unless alive
      return (count == 3)
    end
    (count == 2) or (count == 3)
  end
  def get(cells, y, x)
    return 0 if x < 0 or y < 0
    return 0 if y >= cells.size
    row = cells[y]
    return 0 if x >= row.size
    row
  end
  def count_neighbours(cells, x, y)
    n = 0
    n += get(cells, y-1, x-1)
    n += get(cells, y-1, x)
    n += get(cells, y-1, x+1)
    n += get(cells, y, x-1)
    n += get(cells, y, x+1)
    n += get(cells, y+1, x-1)
    n += get(cells, y+1, x)
    n += get(cells, y+1, x+1)
    n
  end
  def lifecycle(cells)
    y = 0
    next_cells = cells.map do |row|
      x = 0
      next_row = row.map do |cell|
        n = count_neighbours(cells, x, y)
        x += 1
        determine_destiny((cell != 0), n) ? 1 : 0
      end
      y += 1
      next_row
    end
    next_cells
  end
end

if $0 == __FILE__
  puts "lets play a game"
  class Game
    include GameOfLife
    def initialize
      @cells = [
        [0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0],
        [0, 0, 1, 1, 1, 0],
        [0, 1, 1, 1, 0, 0],
        [0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0]
      ]
    end
    def next
      @cells = lifecycle(@cells)
    end
    def inspect
      rows = @cells.map do |row|
        row.join(" ")
      end
      rows.join("\n")
    end
  end
  game = Game.new
  loop do
    p game
    gets
    game.next
  end
end
bash-2.05b$

···

On Friday 24 September 2004 06:25, Markus wrote:

--
Simon Strandgaard

This has some fat & may be missing some details, but at least it

should

give you the idea.

It doesn't seem to have much fat from my position. I appreciate the
time you (and others) have spent on this query. Thanks for the help.
I'll get on and remodel my version ASAP.
Thx to all for their assistance.
Regards
Graham