[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Conway's Game of Life (cellular automata)



   From: Michael Bogomolny <bogo@cs.utexas.edu>

   | Ian Stewart's book "The Collapse of Chaos" describes Conway's
   | Game of Life and claims that a "glider gun" is periodic with
   | period 30, with the additional feature that the "glider gun"
   | shoots out a stream of pulses diagonally (down & to the right).

   Generation 29 is the same as generation 89, 30 as 90, etc.
   Generation 29 is *not* the same as generation 59, due to the
   "offshoot".  So why wouldn't you say that after 28 generations it
   stabilizes to a cycle with period 60?

No, Michael!  But you found an interesting effect of my bug I
explained at the end of my code:

   ;; Still need to implement error checking: if we ever get 3
   ;; consecutive black cells on the bounding frame (an outer row or
   ;; column), then the grid is too small, and must be enlarged.

What you noticed is that as a glider heads off to the bottom edge, it
(as a result of my bug) turns into a 2x2 square, which never changes,
but annihilates the next glider coming in!  That's nice.  The true
fact, which I didn't know, is that this figure

                x           
              x x           
               xx           
                  xx        
                  xx        

disappears in 4 iterations.  (i.e. This disappearance isn't caused by
my bug, although the 2x2 square is caused by my bug.)
                            

So I crudely fixed your bug, but introduced another, with this code
below, which now pushes the problem onto the right edge rather than
the bottow:

(define (three-in-row? row)
  ;; returns #t if three consecutive black squares on the bottom row
  (letrec ((three-in-row-real? 
            (lambda (n row)
              (cond ((= n 3) #t)
                    ((null? row) #f)
                    (else (three-in-row-real? (if (char=? (car row) #\x)
                                                  (+ n 1)
                                                  0)
                                              (cdr row)))))))
  (three-in-row-real? 0 row)))

(define (make-padded-game game)
  ;; We also first error-check to see if there are 3 consecutive black squares on the 
  ;; bottom row.  If so, we add an extra row
  (letrec ((make-blank-list 
            (lambda (list) 
              ;; returns a list full of spaces of same length as list 
              (if (null? list) 
                  `()
                  (cons #\space 
                        (make-blank-list (cdr list)))))))
    (let* ((make-padded-row (lambda (row) 
                              (cons #\space 
                                    (append row 
                                            (list #\space)))))
           (game-padded-by-rows 
            ;; the game with each row padded by a SPC on both ends
            (map make-padded-row game))
           (padded-blank-row (make-blank-list (car game-padded-by-rows)))
           (padded-game 
            ;; game padded all around now
            (cons padded-blank-row 
                  (append game-padded-by-rows 
                          (list padded-blank-row)))))
      ;; Check to see if there's 3 consecutive black squares in the last row 
      ;; of game, which is the 2nd to last row of padded-game
      (if (three-in-row? (cadr (reverse padded-game)))
          (append padded-game  (list padded-blank-row))
          padded-game))))

I.e. replace `make-padded-game' with this version, and add in
`three-in-row?'.   

Now it's fine through 146, and it's now "bug-periodic" with period 60
starting with 146.   That's interesting!

So we need to error-check on all 4 sides.  `make-padded-game' isn't
the right place to do the error-checking and row-adding anyway.

BTW I goofed, it's not TLS coding I was trying to emulate, but TSS,
especially the 11th commandment:  

"Use additional arguments when the function needs to know previous
arguments to the function."

Except I consed the previous arguments into the game rather than
adding them as separate arguments.  

I.e. my function consumes a list (rectangular list of lists) in the
usual way, throwing out the car.  But I need at each stage the row I
just consumed, so I just keep it, and throw it out next turn.