-- Copyright 1993-1998, by the Cecil Project -- Department of Computer Science and Engineering, University of Washington -- See the LICENSE file for license information. -- this version attempts to be more efficient by implementing 2D arrays -- as a map onto 1 D arrays. -- the game of life (-- A not very general 2D matrix ADT --) template object life_grid; var field data(@:life_grid):m_vector[cell]; var field num_rows(@:life_grid):int; var field num_cols(@:life_grid):int; method fetch(g@:life_grid,i:int,j:int):cell { g.data!((i*g.num_cols) + j) } method set(g@:life_grid,i:int,j:int,c:cell):void { g.data!((i*g.num_cols) + j) := c; } method allocate_grid(nr:int, nc:int):m_vector[cell] { let var temp:m_vector[cell] := new_m_vector[cell](nr * nc); do ((nr * nc), &(i:int) { temp!i := object isa cell; }); temp } method new_grid(nr:int,nc:int):life_grid { concrete object isa life_grid {num_rows := nr, num_cols := nc, data := allocate_grid(nr,nc)} } (-- 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; var field world(@:life_board):life_grid; var field num_rows(@:life_board):int; var field num_cols(@:life_board):int; method print_string(b@:life_board):string { let var s:string := ""; do (b.num_rows, &(i:int) { do (b.num_cols, &(j:int) { s := s || print_string(fetch(b.world,i,j)); }); s := s || "\n"; }); s } -- 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); } -- update the board by calling update on every cell method update(b@:life_board):void { do (b.num_rows, &(i:int) { do (b.num_cols, &(j:int) { update(fetch(b.world,i,j)); }); }); } -- makes the board "wrap around" method access(b@:life_board, i:int, j:int):cell { let var real_i:int := i; let var real_j:int := j; if (i < 0, {real_i := b.num_rows-1;}); if (i = b.num_rows, {real_i := 0;}); if (j < 0, {real_j := b.num_cols-1;}); if (j = b.num_cols, {real_j := 0;}); fetch(b.world,real_i,real_j) } method compute_next_generation(b@:life_board):void { do (b.num_rows, &(i:int) { do (b.num_cols, &(j:int) { let var t:int := access(b,i-1,j-1).birth_value + access(b,i-1,j).birth_value + access(b,i-1,j+1).birth_value + access(b,i,j-1).birth_value + access(b,i,j).birth_value + access(b,i,j+1).birth_value + access(b,i+1,j-1).birth_value + access(b,i+1,j).birth_value + access(b,i+1,j+1).birth_value; next_gen(fetch(b.world,i,j),t); }); }); } -- "randomly" get live cells in the world method initialize(b@:life_board,live_frac:int):void { let var s:random_stream := new_rand_stream(live_frac,(b.num_rows * b.num_cols)); do (b.num_rows, &(i:int) { do (b.num_cols, &(j:int) { if (next(s) = 0, {fetch(b.world,i,j).live_now := true;}); }); }); } method new_board(nr:int, nc:int):life_board { concrete object isa life_board {num_rows := nr, num_cols := nc, world := new_grid(nr,nc) } } (-- 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_fut(@:cell):bool := false; -- state of cell next generation method update(c@:cell):void { c.live_now := c.live_fut; } -- 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 { if ((n=3)|(n=4)|(n=5),{c.live_fut := true;},{c.live_fut := false;}); } -- some assertions for the type checker (when it actually cares about this) divide cell into live_cell, dead_cell; -- Predicate object that expresses behavior specific to dead cells predicate dead_cell isa cell when not (cell.live_now); method print_string(c@:dead_cell):string { " " } method birth_value(c@:dead_cell):int { 0 } -- Predicate object that expresses behavior specific to live cells predicate live_cell isa cell when cell.live_now; method print_string(c@:live_cell):string { "*" } method birth_value(c@live_cell):int { 1 } -- 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 var b: life_board := new_board(num_rows,num_cols); initialize(b,fraction_live); if (display, {display(b,0);}); do (num_gen, &(i:int) { compute_next_generation(b); b.update; if (display, {display(b,i+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_line; let var time:int := time({game_of_life(num_gen,num_rows,num_cols,display, fraction_live);}); "Done. time: ".print; time.print; " ms.".print_line; }