Make Grid object mutable.
[cellular-automata.git] / life / 006 / life.ts
CommitLineData
b1310fe0 1"use strict";
e16b8b54 2
c942e34c
SK
3type GridLocation = {r: number, k: number};
4
ecf4b00f
SK
5interface GridInterface<A> {
6 get : (location: GridLocation) => A;
6fe41dc7 7 map : (f : (location: GridLocation) => A) => void;
b1310fe0 8 moore_neighbors: (origin : GridLocation) => Array<GridLocation>;
ecf4b00f 9 print : (toString : (A: A) => string) => void;
b1310fe0
SK
10};
11
ecf4b00f 12class Grid<A> implements GridInterface<A> {
c942e34c
SK
13 private rows : number;
14 private columns : number;
ecf4b00f 15 private cells : Array<Array<A>>;
c942e34c
SK
16
17 constructor(
18 {rows, columns, init} :
19 { rows : number
20 , columns : number
ecf4b00f 21 , init : (location: GridLocation) => A
c942e34c
SK
22 }
23 )
24 {
25 this.rows = rows;
26 this.columns = columns;
b1310fe0 27 const cells = [];
c942e34c
SK
28 for (let r = 0; r < rows; r++) {
29 cells[r] = [];
30 for (let k = 0; k < columns; k++) {
31 cells[r][k] = init({r: r, k: k})
32 };
33 };
34 this.cells = cells
35 };
36
ecf4b00f 37 get({r, k}: GridLocation) : A {
b1310fe0
SK
38 return this.cells[r][k]
39 };
40
c942e34c 41 moore_neighbors(origin : GridLocation) : Array<GridLocation> {
b1310fe0 42 const offsets =
c942e34c
SK
43 [ {r: -1, k: -1}, {r: -1, k: 0}, {r: -1, k: 1}
44 , {r: 0, k: -1}, {r: 0, k: 1}
45 , {r: 1, k: -1}, {r: 1, k: 0}, {r: 1, k: 1}
46 ];
b1310fe0 47 const offset_to_location =
c942e34c 48 (offset) => {return {r: origin.r + offset.r, k: origin.k + offset.k}};
b1310fe0
SK
49 const locations = offsets.map(offset_to_location);
50 const is_location_within_bounds =
c942e34c
SK
51 ({r, k}) => r >= 0 && k >= 0 && r < this.rows && k < this.columns;
52 return locations.filter(is_location_within_bounds)
53 };
54
ecf4b00f 55 map(f : (location: GridLocation) => A) {
b1310fe0 56 const cells = [];
c942e34c
SK
57 for (let r = 0; r < this.rows; r++) {
58 cells[r] = [];
59 for (let k = 0; k < this.columns; k++) {
b1310fe0 60 const location = {r: r, k: k};
c942e34c
SK
61 cells[r][k] = f(location);
62 }
63 };
6fe41dc7 64 this.cells = cells;
c942e34c 65 };
b1310fe0
SK
66
67 private print_border(): void {
68 process.stdout.write("+");
69 for (let k = 0; k < this.columns; k++) {
70 process.stdout.write("-");
71 };
72 process.stdout.write("+");
73 process.stdout.write("\n");
74 };
75
76 print(to_string) : void {
77 this.print_border();
78 for (let r = 0; r < this.rows; r++) {
79 process.stdout.write("|");
80 for (let k = 0; k < this.columns; k++) {
81 const element = this.cells[r][k];
82 const element_string = to_string(element);
83 process.stdout.write(element_string);
84 };
85 process.stdout.write("|");
86 process.stdout.write("\n");
87 };
88 this.print_border();
89 };
c942e34c
SK
90};
91
ecf4b00f
SK
92namespace Terminal {
93 export const clear = () : void => {
94 process.stdout.write("\x1B[2J");
95 };
b1310fe0 96
ecf4b00f
SK
97 export const reset = () : void => {
98 process.stdout.write("\x1B[1;1H");
99 };
100};
b1310fe0 101
ecf4b00f
SK
102namespace Life {
103 namespace State {
104 export type T = "Dead" | "Alive";
b1310fe0 105
ecf4b00f
SK
106 export const of_integer = (i : number) : T => {
107 switch (i)
108 { case 0 : return "Dead"
109 ; case 1 : return "Alive"
54042df4 110 ; default: throw new RangeError("No known State for integer: " + i)
ecf4b00f
SK
111 }
112 };
e16b8b54 113
ecf4b00f
SK
114 export const to_string = (t : T) : string => {
115 switch (t)
116 { case "Dead" : return " "
117 ; case "Alive": return "o"
54042df4 118 ; default : throw new TypeError("Illegal member of Life.State.T: " + t)
ecf4b00f
SK
119 }
120 };
e16b8b54 121
ecf4b00f
SK
122 export const is_alive = (t : T) : boolean => {
123 switch (t)
124 { case "Dead" : return false
125 ; case "Alive": return true
54042df4 126 ; default : throw new TypeError("Illegal member of Life.State.T: " + t)
ecf4b00f
SK
127 }
128 };
e16b8b54 129
ecf4b00f
SK
130 export const next = (t : T, neighbors_alive : number) : T => {
131 const is_cell_alive = is_alive(t);
132 if (is_cell_alive && neighbors_alive < 2) {
133 return "Dead"
134 } else if (is_cell_alive && neighbors_alive < 4) {
135 return "Alive"
136 } else if (is_cell_alive && neighbors_alive > 3) {
137 return "Dead"
138 } else if (!is_cell_alive && neighbors_alive === 3) {
139 return "Alive"
140 } else {
141 return t
142 }
143 };
144 };
e16b8b54 145
ecf4b00f
SK
146 export class Board {
147 private grid: Grid<State.T>;
148
149 constructor(
150 {rows, columns} :
151 { rows : number
152 , columns : number
153 }
154 )
155 {
156 const init = (_) => State.of_integer(Math.round(Math.random()));
157 this.grid = new Grid({rows: rows, columns: columns, init: init});
158 };
e16b8b54 159
ecf4b00f 160 next() : void {
6fe41dc7 161 this.grid.map(
ecf4b00f
SK
162 (location) => {
163 const neighbor_locations = this.grid.moore_neighbors(location);
164 const neighbor_states = neighbor_locations.map((l) => this.grid.get(l));
165 const state_0 = this.grid.get(location);
166 const neighbors_alive = neighbor_states.filter(State.is_alive).length;
167 const state_1 = State.next(state_0, neighbors_alive);
168 return state_1
169 }
170 );
ecf4b00f 171 };
e16b8b54 172
ecf4b00f
SK
173 print() : void {
174 this.grid.print(State.to_string)
175 };
176 };
177};
e16b8b54 178
ecf4b00f
SK
179const board_loop = (b : Life.Board) : void => {
180 Terminal.reset();
181 b.print();
182 b.next();
183 setTimeout(() => board_loop(b), 250)
e16b8b54
SK
184};
185
b1310fe0
SK
186const main = () : void => {
187 const height = parseInt(process.argv[2])
188 const width = parseInt(process.argv[3])
ecf4b00f
SK
189 const b = new Life.Board({rows: height - 3, columns: width - 2});
190 Terminal.clear();
e16b8b54
SK
191 board_loop(b);
192};
193
194main();
This page took 0.03156 seconds and 4 git commands to generate.