Commit | Line | Data |
---|---|---|
b1310fe0 | 1 | "use strict"; |
e16b8b54 | 2 | |
c942e34c SK |
3 | type GridLocation = {r: number, k: number}; |
4 | ||
ecf4b00f SK |
5 | interface 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 | 12 | class 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 |
92 | namespace 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 |
102 | namespace 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 |
179 | const 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 |
186 | const 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 | ||
194 | main(); |