| 1 | "use strict"; |
| 2 | |
| 3 | type GridLocation = {r: number, k: number}; |
| 4 | |
| 5 | interface GridInterface<A> { |
| 6 | get : (location: GridLocation) => A; |
| 7 | map : (f : (location: GridLocation) => A) => Grid<A>; |
| 8 | moore_neighbors: (origin : GridLocation) => Array<GridLocation>; |
| 9 | print : (toString : (A: A) => string) => void; |
| 10 | }; |
| 11 | |
| 12 | class Grid<A> implements GridInterface<A> { |
| 13 | private rows : number; |
| 14 | private columns : number; |
| 15 | private cells : Array<Array<A>>; |
| 16 | |
| 17 | constructor( |
| 18 | {rows, columns, init} : |
| 19 | { rows : number |
| 20 | , columns : number |
| 21 | , init : (location: GridLocation) => A |
| 22 | } |
| 23 | ) |
| 24 | { |
| 25 | this.rows = rows; |
| 26 | this.columns = columns; |
| 27 | const cells = []; |
| 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 | |
| 37 | get({r, k}: GridLocation) : A { |
| 38 | return this.cells[r][k] |
| 39 | }; |
| 40 | |
| 41 | moore_neighbors(origin : GridLocation) : Array<GridLocation> { |
| 42 | const offsets = |
| 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 | ]; |
| 47 | const offset_to_location = |
| 48 | (offset) => {return {r: origin.r + offset.r, k: origin.k + offset.k}}; |
| 49 | const locations = offsets.map(offset_to_location); |
| 50 | const is_location_within_bounds = |
| 51 | ({r, k}) => r >= 0 && k >= 0 && r < this.rows && k < this.columns; |
| 52 | return locations.filter(is_location_within_bounds) |
| 53 | }; |
| 54 | |
| 55 | map(f : (location: GridLocation) => A) { |
| 56 | const cells = []; |
| 57 | for (let r = 0; r < this.rows; r++) { |
| 58 | cells[r] = []; |
| 59 | for (let k = 0; k < this.columns; k++) { |
| 60 | const location = {r: r, k: k}; |
| 61 | cells[r][k] = f(location); |
| 62 | } |
| 63 | }; |
| 64 | const init = ({r, k}) => cells[r][k]; |
| 65 | const grid = new Grid({rows: this.rows, columns: this.columns, init: init}); |
| 66 | return grid |
| 67 | }; |
| 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 | }; |
| 92 | }; |
| 93 | |
| 94 | namespace Terminal { |
| 95 | export const clear = () : void => { |
| 96 | process.stdout.write("\x1B[2J"); |
| 97 | }; |
| 98 | |
| 99 | export const reset = () : void => { |
| 100 | process.stdout.write("\x1B[1;1H"); |
| 101 | }; |
| 102 | }; |
| 103 | |
| 104 | namespace Life { |
| 105 | namespace State { |
| 106 | export type T = "Dead" | "Alive"; |
| 107 | |
| 108 | export const of_integer = (i : number) : T => { |
| 109 | switch (i) |
| 110 | { case 0 : return "Dead" |
| 111 | ; case 1 : return "Alive" |
| 112 | ; default: throw new RangeError("No known State for integer: " + i) |
| 113 | } |
| 114 | }; |
| 115 | |
| 116 | export const to_string = (t : T) : string => { |
| 117 | switch (t) |
| 118 | { case "Dead" : return " " |
| 119 | ; case "Alive": return "o" |
| 120 | ; default : throw new TypeError("Illegal member of Life.State.T: " + t) |
| 121 | } |
| 122 | }; |
| 123 | |
| 124 | export const is_alive = (t : T) : boolean => { |
| 125 | switch (t) |
| 126 | { case "Dead" : return false |
| 127 | ; case "Alive": return true |
| 128 | ; default : throw new TypeError("Illegal member of Life.State.T: " + t) |
| 129 | } |
| 130 | }; |
| 131 | |
| 132 | export const next = (t : T, neighbors_alive : number) : T => { |
| 133 | const is_cell_alive = is_alive(t); |
| 134 | if (is_cell_alive && neighbors_alive < 2) { |
| 135 | return "Dead" |
| 136 | } else if (is_cell_alive && neighbors_alive < 4) { |
| 137 | return "Alive" |
| 138 | } else if (is_cell_alive && neighbors_alive > 3) { |
| 139 | return "Dead" |
| 140 | } else if (!is_cell_alive && neighbors_alive === 3) { |
| 141 | return "Alive" |
| 142 | } else { |
| 143 | return t |
| 144 | } |
| 145 | }; |
| 146 | }; |
| 147 | |
| 148 | export class Board { |
| 149 | private grid: Grid<State.T>; |
| 150 | |
| 151 | constructor( |
| 152 | {rows, columns} : |
| 153 | { rows : number |
| 154 | , columns : number |
| 155 | } |
| 156 | ) |
| 157 | { |
| 158 | const init = (_) => State.of_integer(Math.round(Math.random())); |
| 159 | this.grid = new Grid({rows: rows, columns: columns, init: init}); |
| 160 | }; |
| 161 | |
| 162 | next() : void { |
| 163 | const grid = this.grid.map( |
| 164 | (location) => { |
| 165 | const neighbor_locations = this.grid.moore_neighbors(location); |
| 166 | const neighbor_states = neighbor_locations.map((l) => this.grid.get(l)); |
| 167 | const state_0 = this.grid.get(location); |
| 168 | const neighbors_alive = neighbor_states.filter(State.is_alive).length; |
| 169 | const state_1 = State.next(state_0, neighbors_alive); |
| 170 | return state_1 |
| 171 | } |
| 172 | ); |
| 173 | this.grid = grid |
| 174 | }; |
| 175 | |
| 176 | print() : void { |
| 177 | this.grid.print(State.to_string) |
| 178 | }; |
| 179 | }; |
| 180 | }; |
| 181 | |
| 182 | const board_loop = (b : Life.Board) : void => { |
| 183 | Terminal.reset(); |
| 184 | b.print(); |
| 185 | b.next(); |
| 186 | setTimeout(() => board_loop(b), 250) |
| 187 | }; |
| 188 | |
| 189 | const main = () : void => { |
| 190 | const height = parseInt(process.argv[2]) |
| 191 | const width = parseInt(process.argv[3]) |
| 192 | const b = new Life.Board({rows: height - 3, columns: width - 2}); |
| 193 | Terminal.clear(); |
| 194 | board_loop(b); |
| 195 | }; |
| 196 | |
| 197 | main(); |