| | 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) => void; |
| | 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 | this.cells = |
| | 57 | this.cells.map((row, r) => row.map((_, k) => f({r: r, k: k}))); |
| | 58 | }; |
| | 59 | |
| | 60 | private print_border(): void { |
| | 61 | process.stdout.write("+"); |
| | 62 | for (let k = 0; k < this.columns; k++) { |
| | 63 | process.stdout.write("-"); |
| | 64 | }; |
| | 65 | process.stdout.write("+"); |
| | 66 | process.stdout.write("\n"); |
| | 67 | }; |
| | 68 | |
| | 69 | print(to_string) : void { |
| | 70 | this.print_border(); |
| | 71 | for (let r = 0; r < this.rows; r++) { |
| | 72 | process.stdout.write("|"); |
| | 73 | for (let k = 0; k < this.columns; k++) { |
| | 74 | const element = this.cells[r][k]; |
| | 75 | const element_string = to_string(element); |
| | 76 | process.stdout.write(element_string); |
| | 77 | }; |
| | 78 | process.stdout.write("|"); |
| | 79 | process.stdout.write("\n"); |
| | 80 | }; |
| | 81 | this.print_border(); |
| | 82 | }; |
| | 83 | }; |
| | 84 | |
| | 85 | namespace Terminal { |
| | 86 | export const clear = () : void => { |
| | 87 | process.stdout.write("\x1B[2J"); |
| | 88 | }; |
| | 89 | |
| | 90 | export const reset = () : void => { |
| | 91 | process.stdout.write("\x1B[1;1H"); |
| | 92 | }; |
| | 93 | }; |
| | 94 | |
| | 95 | namespace Life { |
| | 96 | namespace State { |
| | 97 | export type T = "Dead" | "Alive"; |
| | 98 | |
| | 99 | export const of_integer = (i : number) : T => { |
| | 100 | switch (i) |
| | 101 | { case 0 : return "Dead" |
| | 102 | ; case 1 : return "Alive" |
| | 103 | ; default: throw new RangeError("No known State for integer: " + i) |
| | 104 | } |
| | 105 | }; |
| | 106 | |
| | 107 | export const to_string = (t : T) : string => { |
| | 108 | switch (t) |
| | 109 | { case "Dead" : return " " |
| | 110 | ; case "Alive": return "o" |
| | 111 | ; default : throw new TypeError("Illegal member of Life.State.T: " + t) |
| | 112 | } |
| | 113 | }; |
| | 114 | |
| | 115 | export const is_alive = (t : T) : boolean => { |
| | 116 | switch (t) |
| | 117 | { case "Dead" : return false |
| | 118 | ; case "Alive": return true |
| | 119 | ; default : throw new TypeError("Illegal member of Life.State.T: " + t) |
| | 120 | } |
| | 121 | }; |
| | 122 | |
| | 123 | export const next = (t : T, neighbors_alive : number) : T => { |
| | 124 | const is_cell_alive = is_alive(t); |
| | 125 | if (is_cell_alive && neighbors_alive < 2) { |
| | 126 | return "Dead" |
| | 127 | } else if (is_cell_alive && neighbors_alive < 4) { |
| | 128 | return "Alive" |
| | 129 | } else if (is_cell_alive && neighbors_alive > 3) { |
| | 130 | return "Dead" |
| | 131 | } else if (!is_cell_alive && neighbors_alive === 3) { |
| | 132 | return "Alive" |
| | 133 | } else { |
| | 134 | return t |
| | 135 | } |
| | 136 | }; |
| | 137 | }; |
| | 138 | |
| | 139 | export class Board { |
| | 140 | private grid: Grid<State.T>; |
| | 141 | |
| | 142 | constructor( |
| | 143 | {rows, columns} : |
| | 144 | { rows : number |
| | 145 | , columns : number |
| | 146 | } |
| | 147 | ) |
| | 148 | { |
| | 149 | const init = (_) => State.of_integer(Math.round(Math.random())); |
| | 150 | this.grid = new Grid({rows: rows, columns: columns, init: init}); |
| | 151 | }; |
| | 152 | |
| | 153 | next() : void { |
| | 154 | this.grid.map( |
| | 155 | (location) => { |
| | 156 | const neighbor_locations = this.grid.moore_neighbors(location); |
| | 157 | const neighbor_states = neighbor_locations.map((l) => this.grid.get(l)); |
| | 158 | const state_0 = this.grid.get(location); |
| | 159 | const neighbors_alive = neighbor_states.filter(State.is_alive).length; |
| | 160 | const state_1 = State.next(state_0, neighbors_alive); |
| | 161 | return state_1 |
| | 162 | } |
| | 163 | ); |
| | 164 | }; |
| | 165 | |
| | 166 | print() : void { |
| | 167 | this.grid.print(State.to_string) |
| | 168 | }; |
| | 169 | }; |
| | 170 | }; |
| | 171 | |
| | 172 | const board_loop = (b : Life.Board) : void => { |
| | 173 | Terminal.reset(); |
| | 174 | b.print(); |
| | 175 | b.next(); |
| | 176 | setTimeout(() => board_loop(b), 250) |
| | 177 | }; |
| | 178 | |
| | 179 | const main = () : void => { |
| | 180 | const height = parseInt(process.argv[2]) |
| | 181 | const width = parseInt(process.argv[3]) |
| | 182 | const b = new Life.Board({rows: height - 3, columns: width - 2}); |
| | 183 | Terminal.clear(); |
| | 184 | board_loop(b); |
| | 185 | }; |
| | 186 | |
| | 187 | main(); |