-- Copyright 1993-1998, by the Cecil Project -- Department of Computer Science and Engineering, University of Washington -- See the LICENSE file for license information. -- the game of life include "prelude.small.cecil"; include "matrix.cecil"; include "stream.cecil"; include "randstream.cecil"; (-- Basic "life_board" ADT used as the world for life. Its basically a 2D matrix with the addition of some stuff to do wrap around the edges. --) template object life_board isa vector_matrix[cell]; -- makes the board "wrap around," for fetches at least method fetch(b@:life_board, row:int, col:int):cell { let real_row:int := if(row < 0, { row + b.num_rows }, { if(row >= b.num_rows, { row - b.num_rows }, { row }) }); let real_col:int := if(col < 0, { col + b.num_cols }, { if(col >= b.num_cols, { col - b.num_cols }, { col }) }); resend(b, real_row, real_col) } -- called to display the world. once we get graphics we could slick this up method display(b@:life_board, gen:int):void { print_line("\n\n----------"); b.print_line; print("Generation "); print_line(gen); } method compute_next_generation(b@:life_board):void { b.indices_do(&(row:int,col:int){ let t:int := b.fetch(row-1,col-1).birth_value + b.fetch(row-1,col).birth_value + b.fetch(row-1,col+1).birth_value + b.fetch(row,col-1).birth_value + b.fetch(row,col).birth_value + b.fetch(row,col+1).birth_value + b.fetch(row+1,col-1).birth_value + b.fetch(row+1,col).birth_value + b.fetch(row+1,col+1).birth_value; next_gen(b.fetch(row,col), t); }); } method new_board(num_rows:int, num_cols:int, live_frac:int):life_board { let s:random_stream := new_rand_stream(live_frac, num_rows * num_cols); concrete object isa life_board { rows := new_m_vector_init[m_vector[cell]](num_rows, &(row:int){ new_m_vector_init[cell](num_cols, &(col:int){ new_cell(s.next = 0) }) }) } } -- a cell is a unit of potential life. template object cell; var field live_now(@:cell):bool := false; -- current state of cell var field live_next(@:cell):bool := false; -- state of cell next generation method birth_value(c@:cell):int { if(c.live_now, { 1 }, { 0 }) } -- given an integer which represents the number of live neighbors in -- the current generation, this method determines if the cell will be alive -- or dead in the next generation. Note that the cell itself is counted as -- a neighbor in this calculation (see compute_next_generation(b@:life_board)) method next_gen(c@:cell, n:int):void { c.live_next := n = 3 | { n = 4 } | { n = 5 }; } method update(c@:cell):void { c.live_now := c.live_next; } method print_string(c@:cell):string { if(c.live_now, {"*"}, {" "}) } method new_cell(alive:bool):cell { concrete object isa cell { live_now := alive } } -- the "top level" method that whose evaluation causes life to go method game_of_life(num_gen:int, num_rows:int, num_cols:int, display:bool, fraction_live:int):void { let b:life_board := new_board(num_rows, num_cols, fraction_live); if(display, { b.display(0); }); do(num_gen, &(gen:int){ b.compute_next_generation; b.indices_do(&(row:int,col:int){ b.fetch(row,col).update; }); if(display, { b.display(gen+1); }); }); } -- this method adds some timing, etc. around the game of life. method test_life(num_gen:int, num_rows:int, num_cols:int, display:bool, fraction_live:int):void { "Starting life with ".print; num_gen.print; " generations, on a ".print; num_rows.print; " by ".print; num_cols.print; " world...".print; garbage_collect(); let time:int := time({ game_of_life(num_gen, num_rows, num_cols, display, fraction_live); }); "done. time: ".print; time.print; " ms.".print_line; }