Refactor with classes, assertions, const and more.
[cellular-automata.git] / life / 006 / life.ts
CommitLineData
b1310fe0 1"use strict";
e16b8b54 2
c942e34c
SK
3type GridLocation = {r: number, k: number};
4
b1310fe0
SK
5interface GridInterface<T> {
6 get : (location: GridLocation) => T;
7 map : (f : (location: GridLocation) => T) => Grid<T>;
8 moore_neighbors: (origin : GridLocation) => Array<GridLocation>;
9 print : (toString : (T: T) => string) => void;
10};
11
12class Grid<T> implements GridInterface<T> {
c942e34c
SK
13 private rows : number;
14 private columns : number;
15 private cells : Array<Array<T>>;
16
17 constructor(
18 {rows, columns, init} :
19 { rows : number
20 , columns : number
21 , init : (location: GridLocation) => T
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
b1310fe0
SK
37 get({r, k}: GridLocation) : T {
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
b1310fe0
SK
55 map(f : (location: GridLocation) => T) {
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 };
b1310fe0
SK
64 const init = ({r, k}) => cells[r][k];
65 const grid = new Grid({rows: this.rows, columns: this.columns, init: init});
c942e34c
SK
66 return grid
67 };
b1310fe0
SK
68
69 private print_border(): void {
70 process.stdout.write("+");
71 for (let k = 0; k < this.columns; k++) {
72 process.stdout.write("-");
73 };
74 process.stdout.write("+");
75 process.stdout.write("\n");
76 };
77
78 print(to_string) : void {
79 this.print_border();
80 for (let r = 0; r < this.rows; r++) {
81 process.stdout.write("|");
82 for (let k = 0; k < this.columns; k++) {
83 const element = this.cells[r][k];
84 const element_string = to_string(element);
85 process.stdout.write(element_string);
86 };
87 process.stdout.write("|");
88 process.stdout.write("\n");
89 };
90 this.print_border();
91 };
c942e34c
SK
92};
93
b1310fe0
SK
94type State = "Dead" | "Alive";
95
96type States = Array<State>;
97
98type Board = Grid<State>;
99
100const state_of_integer = (i : number) : State => {
e16b8b54
SK
101 switch (i)
102 { case 0 : return "Dead"
103 ; case 1 : return "Alive"
b1310fe0 104 ; default: throw("No known State for integer: " + i)
e16b8b54
SK
105 }
106};
107
b1310fe0 108const state_to_string = (state : State) : string => {
e16b8b54
SK
109 switch (state)
110 { case "Dead" : return " "
111 ; case "Alive": return "o"
b1310fe0 112 ; default : throw("Illegal member of State type: " + state)
e16b8b54
SK
113 }
114};
115
b1310fe0
SK
116const board_new = ({rows, columns} : {rows: number, columns: number}) : Board => {
117 const init = (_) => state_of_integer(Math.round(Math.random()));
118 return new Grid({rows: rows, columns: columns, init: init});
e16b8b54
SK
119};
120
b1310fe0
SK
121const state_is_alive = (s : State) : boolean => {
122 if (s === "Alive") {
123 return true
124 } else if (s === "Dead") {
125 return false
126 } else {
127 throw("Illegal member of State type: " + s)
128 }
e16b8b54
SK
129};
130
b1310fe0
SK
131const state_next = (state : State, neighbor_states : Array<State>) : State => {
132 const neighbors_alive = neighbor_states.filter(state_is_alive).length;
e16b8b54
SK
133 if (state === "Alive" && neighbors_alive < 2) {
134 return "Dead"
135 } else if (state === "Alive" && neighbors_alive < 4) {
136 return "Alive"
137 } else if (state === "Alive" && neighbors_alive > 3) {
138 return "Dead"
139 } else if (state === "Dead" && neighbors_alive === 3) {
140 return "Alive"
141 } else {
142 return state
143 }
144}
145
b1310fe0
SK
146const board_next = (b0 : Board) : Board => {
147 const b1 = b0.map(
148 (location) => {
149 const neighbor_locations = b0.moore_neighbors(location);
150 const neighbor_states = neighbor_locations.map((l) => b0.get(l));
151 const state_0 = b0.get(location);
152 const state_1 = state_next(state_0, neighbor_states);
153 return state_1
e16b8b54 154 }
b1310fe0 155 );
e16b8b54
SK
156 return b1
157};
158
b1310fe0
SK
159const console_clear = () : void => {
160 process.stdout.write("\x1B[2J");
e16b8b54
SK
161}
162
b1310fe0
SK
163const console_reset = () : void => {
164 process.stdout.write("\x1B[1;1H");
e16b8b54
SK
165}
166
b1310fe0 167const board_loop = (b0 : Board) : void => {
e16b8b54 168 console_reset();
b1310fe0
SK
169 b0.print(state_to_string);
170 const b1 = board_next(b0);
e16b8b54
SK
171 setTimeout(() => board_loop(b1), 250)
172};
173
b1310fe0
SK
174const main = () : void => {
175 const height = parseInt(process.argv[2])
176 const width = parseInt(process.argv[3])
177 const b = board_new({rows: height - 3, columns: width - 2});
e16b8b54
SK
178 console_clear();
179 board_loop(b);
180};
181
182main();
This page took 0.029868 seconds and 4 git commands to generate.