15110 SUMMER SESSION ONE - 2013

Programming Assignment 10 - due Monday, June 24 at 10:30AM by ELECTRONIC HANDIN

This assignment cannot be dropped. However, you may hand this assignment in late by Tuesday, June 25 at 10:30AM with a 2-point penalty.

For this assignment, you will create a Ruby source file containing a Ruby function(s) implementing each of the problems described below. If you find it useful for any of the problems in this assignment, you may define additional helper functions that your primary function for that problem calls to solve a sub-problem. Definitions for these helper functions should be included in the same Ruby source file as the primary function they help. You should store a source file for each problem in a folder named pa10.

Note: You are responsible for protecting your solutions to these problems from being seen by other students either physically (e.g., by looking over your shoulder) or electronically. (More information can be found in the instructions for Programming Assignment 2.)

Overview

In this assignment, you will develop a simple graphical simulation that analyzes how forest fires spread and how forests regrow after fires. Each time step of the simulation represents one day.

On each day, if a tree is on fire, it may turn to ashes. If a tree is ashes, it may turn into a shoot (i.e. baby tree). If a tree is a shoot, it may starting growing. If a tree is growing, it may reach maturity. If a tree is mature and at least one of its neighbors is on fire, it may ignite (i.e. catch on fire).

This simulation will show how the forest evolves over time, represented by a square 15 × 15 matrix.

Here is a sample of what the first eight "days" of the simulation might look like:



Top row: Days 1-4 of simulation. Bottom row: Days 5-8 of simulation.

Each snapshot above is a time step of the simulation shown in a 300 × 300 window (in pixels). Notice that the window is "divided" up into 15 × 15 squares. Each of these 225 squares represents one tree in the simulated forest.

To perform the simulation, you will need to use the Canvas object in RubyLabs. When you initialize a Canvas, you need to supply the size of the window and a title for the window. For example:

Canvas.init(400, 400, "My_Simulation")

creates a window of size 400 × 400 (in pixels) with a title of "My_Simulation". The origin of this window is at the top left corner (0,0), with the x coordinate increasing as you go from left to right, and the y coordinate increasing as you go from top to bottom.

To draw a square of size 10 × 10 with a fill color of yellow and an outline (border) of blue and its top left corner at coordinate (100,100) in the Canvas, we can execute:

Canvas::Rectangle.new(100, 100, 110, 110, :fill=>"yellow", :outline=>"blue")

The first two parameters are the top left coordinates (x,y) of the rectangle and the next two parameters are the bottom right coordinates (x,y) of the rectangle. The final two parameters specify the fill color and the outline (border) color as strings.

In addition to the display, you will also need to use random number generators in this simulation. Suppose an event occurs with a chance of 20 percent. We can write a function that returns true if the event occurs and false otherwise given that the chance of the event happening is 20%:

def event?()
   if (rand(100) < 20) then
      return true
   else
      return false
   end
end

Assuming the rand generator is uniform, then this returns true if rand(100) is 0 through 19, and false if rand(100) is 20 through 99. So 20% of the values will cause the function to return true.

More generally for a chance of p percent, where p is between 0 and 100, inclusive:

def event?(p)
   if (rand(100) < p) then
      return true
   else
      return false
   end
end
Complete the problems below to build this forest fire simulation in Ruby.

Problems

  1. [3 points] Write a function display(forest) (in the file display.rb). The parameter forest is a 15 X 15 matrix representing the 225 trees of the forest for the simuation. Each tree is a cell of the matrix. Each cell has an integer in the range 0 through 4 (inclusive), which encodes the tree's state as follows:

    0    on fire
    1    in ashes
    2    a shoot (i.e. baby tree)
    3    growing tree
    4    mature tree
    

    Your function should go through the entire matrix and display each "tree" as a square of size 20 pixels X 20 pixels in one of the following colors:

    red          on fire
    black        in ashes
    lightgreen   a shoot
    green        growing tree
    darkgreen    mature tree
    

    General algorithm:

    1. For each row and column of the grid, do the following:
      1. Set color equal to "red" if that cell of the grid represents a tree on fire.
      2. Set color equal to "black" if that cell of the grid represents a tree in ashes.
      3. Set color equal to "lightgreen" if that cell of the grid represents a tree starting to emerge (a shoot).
      4. Set color equal to "green" if that cell of the grid represents a growing tree.
      5. Set color equal to "darkgreen" if that cell of the grid represents a mature tree.
      6. Draw a 20 X 20 square on the canvas based on the current row and column with a fill color as specified above and an outline of "black".

    Test your function using this Ruby function that creates a matrix of size 15 × 15 and fills each cell with a random integer between 0 and 4:

    def test_display()
            # seed the random number generator
            srand(15110)                            
            # create a canvas of size 300 X 300
            Canvas.init(300, 300, "Testing_Display")
            # initialize forest matrix randomly
            forest = Array.new(15)         # 15 rows
            for i in 0..14 do
                forest[i] = Array.new(15)  # create 15 columns for each row
                for j in 0..14 do
                    forest[i][j] = rand(5) # pick a random tree state (0-4) for each tree in forest
                end
            end
    	# display the forest using your display function
    	display(forest)
    end
    

    Sample Usage:

    >> load "display.rb"
    => true
    >> load "test_display.rb"
    => true
    >> test_display()
    

    If you seed the random number generator with a different integer, your image will differ.

  2. [4 points] Next, you will write 3 functions to test a specific tree in the forest to see if it changes its state. Complete all of these functions in the file tests.rb.

    1. A cell in the forest at position i, j turns into ashes (i.e. cools) with a chance of p percent if it is currently on fire. Write a function cool?(forest, i, j, p) that returns true approximately p% of the time if the cell at row i, column j in the forest is currently on fire, or false otherwise.

      General algorithm: Test to see if the cell forest[i][j] represents a tree on fire and use the event function above to see if it returns true given the percentage p. If both of these conditions are true, then return true. Otherwise, return false.

    2. A cell in the forest at position i, j grows with a chance of p percent if it is not on fire and it is not mature. Write a function grow?(forest, i, j, p) that returns true approximately p% of the time if the cell at row i, column j in the forest is not on fire and is not mature, or false otherwise.

      General algorithm: Test to see if the cell forest[i][j] represents a tree that is not on fire and is not mature, and then use the event function above to see if it returns true given the percentage p. If both of these conditions are true, then return true. Otherwise, return false.

    3. A cell in the forest at position i, j ignites (i.e. catches on fire) with a chance of p percent if it is mature and at least one of its neighbors is on fire. A neighbor of a tree is immediately above, below, to the left or to the right of the tree. Write a function ignite?(forest, i, j, p) that returns true approximately p% of the time if the cell at row i, column j in the forest is mature and at least one of its neighbors is on fire, or false otherwise.

      General algorithm: Test to see if the cell forest[i][j] represents a tree that is mature, then test to see if at least one of its neighbors is on fire, and then use the event function above to see if it returns true given the percentage p. If both of these conditions are true, then return true. Otherwise, return false.

      NOTE: Be careful when you test the neighbors. You might be on the edge of the forest. If you test for a neighbor and you go off of the matrix, you might cause your program to crash. It will help here to look at each neighbor separately rather than trying to evaluate all four neighbors in one statement.

    Test your cool? function using the following function which creates a simple small forest of size 3 X 5 and calls the function directly on each cell, printing the results.

    def test_cool()
       forest = [ [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [1, 2, 3, 4, 4] ]
       for i in 0..forest.length-1 do
          for j in 0..forest[i].length-1 do
             print cool?(forest, i, j, 20), " "
          end
          print "\n"
       end
    end
    

    Sample usage:

    >> load "tests.rb" 
    => true
    >> load "test_cool.rb"
    => true
    >> test_cool()
    false false true false false 
    false false false false true 
    false false false false false 
    => 0..2
    
    In general, for the first two rows, a total of two outputs on average should be true. For the last row, all outputs should be false.

    Write two additional test functions to test grow? and ignite?.

  3. [3 points] Write a function update(forest) (in the file update.rb). This function takes a 15 × 15 matrix representing our forest, as described above, for the current time step of the simulation, and returns a new 15 × 15 matrix representing our forest during the next time step (i.e. after one day has passed).

    The basic idea here is that we start with the current forest in the array forest and create a new snapshot of the forest after one time step in an array new_forest. Each cell new_forest[row][column] represents the new status of the cell in the forest from forest[row][column].

    General Algorithm:

    1. Set pc (the chance that a tree on fire will cool) equal to 10. (The value 10 used here represents 10%.)
      Set pg (the chance that a tree will grow one stage) equal to 10.
      Set pi (the chance that a tree will ignite) equal to 20.
      (NOTE: Once you have the simulation working, you can change these values to explore the simulated forest further.)
    2. Create a new 15 × 15 matrix, called new_forest, with each cell initialized to its respective value in forest. (HINT: Look at the test_display function above to see how to create and initialize a matrix. Do NOT use the clone function here. It won't work.)
    3. For each row and column of the forest, do the following:
      1. If the cell is on fire, then set the respective cell in new_forest to ashes pc percent of the time. Call the function cool? that you wrote earlier to help you perform this step.
      2. If the cell is not on fire and is not mature, then set the respective cell in new_forest one level higher pg percent of the time. (That is, set the cell in new_forest to be a shoot if it was ashes in forest, set the cell in new_forest to growing if it was a shoot in forest, and set the cell in new_forest to mature if it was growing in forest.) Call the function grow? that you wrote earlier to help you perform this step.
      3. If the cell is on mature and at a least one of its neighbors is on fire, then set the respective cell in new_forest to be on fire pi percent of the time. Call the function ignite? that you wrote earlier to help you perform this step.
    4. Return the new_forest as the final result.

    Test your update function using the following function which creates a random forest and runs the simulation for 100 "days":

    def run_simulation()
            # seed the random number generator
            srand(15110)                            
            # create a canvas of size 300 X 300
            Canvas.init(300, 300, "Testing_Display")
            # initialize forest matrix randomly
            forest = Array.new(15)         # 15 rows
            for i in 0..14 do
                forest[i] = Array.new(15)  # create 15 columns for each row
                for j in 0..14 do
                    forest[i][j] = rand(5) # pick a random tree state (0-4) 
                    end
            end
            display(forest)
            99.times {
                sleep(1)
                forest = update(forest)
                display(forest)
            }
    end
    

    Sample usage:

    >> load "display.rb"
    => true
    >> load "tests.rb"
    => true
    >> load "update.rb"
    => true
    >> load "simulation.rb"
    => true
    >> run_simulation()
    

    Alternatively, you might use the following function instead of run_simulation which steps through the simulation day by day, waiting for you to hit enter to advance to the next day, rather than sleeping for a set amount of time. Notice the change in the final loop.

    def step_simulation()
            # seed the random number generator
            srand(15110)                            
            # create a canvas of size 300 X 300
            Canvas.init(300, 300, "Testing_Display")
            # initialize forest matrix randomly
            forest = Array.new(15)         # 15 rows
            for i in 0..14 do
                forest[i] = Array.new(15)  # create 15 columns for each row
                for j in 0..14 do
                    forest[i][j] = rand(5) # pick a random tree state (0-4) 
                    end
            end
            display(forest)
            99.times {
                Readline::readline("Press Enter to Continue")
                forest = update(forest)
                display(forest)
            }
    end
    

    If you do this part correctly, the first 8 days of your simulation should match the pictures at the beginning of this assignment. You can change the seed for the random number generator later to try out other simulations, and you can change the chances for cool, grow and ignite to see how these effect the simulation once you're done.

Submission

You should now have a pa10 directory that contains the required files, display.rb, tests.rb, and update.rb, each—in turn—containing the corresponding function(s). You do not have to hand in the testing functions since we will have copies of these. Zip up your directory and upload it using the handin system. (The handin system will accept submissions beginning on Friday until the deadline on Monday. Submissions after the deadline but before the late deadline will be marked on our system as "late".)