Created
September 16, 2024 11:59
-
-
Save viktorbezdek/99379d17a5bf252eda0cce933b62c8d2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type Cell = 0 | 1; | |
type Grid = Cell[][]; | |
// Utility to transpose a matrix (swap rows and columns) | |
const transpose = <T>(grid: T[][]): T[][] => | |
grid[0].map((_, i) => grid.map(row => row[i])); | |
// Create a function to get neighboring cells of a given coordinate | |
const neighbors = (grid: Grid) => ([x, y]: [number, number]): [number, number][] => | |
[-1, 0, 1] | |
.flatMap(dx => [-1, 0, 1].map(dy => [x + dx, y + dy])) | |
.filter(([nx, ny]) => (nx !== x || ny !== y) && inBounds(grid)([nx, ny])); | |
// Check if coordinates are within bounds of the grid | |
const inBounds = (grid: Grid) => ([x, y]: [number, number]): boolean => | |
x >= 0 && y >= 0 && x < grid.length && y < grid[0].length; | |
// Count live neighbors for a cell at (x, y) | |
const liveNeighborCount = (grid: Grid) => ([x, y]: [number, number]): number => | |
neighbors(grid)([x, y]) | |
.map(([nx, ny]) => grid[nx][ny]) | |
.reduce((sum, val) => sum + val, 0); | |
// Rule for determining the next cell state based on live neighbors | |
const nextCellState = (grid: Grid) => ([x, y]: [number, number]): Cell => { | |
const currentState = grid[x][y]; | |
const liveNeighbors = liveNeighborCount(grid)([x, y]); | |
return currentState === 1 | |
? (liveNeighbors === 2 || liveNeighbors === 3 ? 1 : 0) | |
: (liveNeighbors === 3 ? 1 : 0); | |
}; | |
// Function to compute next generation for a given grid | |
const nextGeneration = (grid: Grid): Grid => | |
grid.map((row, x) => | |
row.map((_, y) => nextCellState(grid)([x, y])) | |
); | |
// Utility to print grid (for debugging/testing) | |
const printGrid = (grid: Grid): string => | |
grid.map(row => row.join(' ')).join('\n'); | |
// Example usage: Initial grid configuration | |
const initialGrid: Grid = [ | |
[0, 1, 0, 0], | |
[0, 0, 1, 0], | |
[1, 1, 1, 0], | |
[0, 0, 0, 0] | |
]; | |
// Run Conway's Game of Life generations | |
const runGenerations = (grid: Grid, generations: number): void => { | |
let currentGrid = grid; | |
for (let i = 0; i < generations; i++) { | |
console.log(`Generation ${i}:\n${printGrid(currentGrid)}\n`); | |
currentGrid = nextGeneration(currentGrid); | |
} | |
}; | |
// Run the simulation for N generations | |
runGenerations(initialGrid, 5); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Conway's Game of Life - Functional Point-Free Implementation in TypeScript | |
type Grid = Set<string> | |
type Coordinate = string | |
// Utility Functions | |
const parseCell = ([x, y]: [number, number]): Coordinate => `${x},${y}` | |
const neighborDeltas: [number, number][] = [ | |
[-1, -1], [-1, 0], [-1, 1], | |
[ 0, -1], [ 0, 1], | |
[ 1, -1], [ 1, 0], [ 1, 1], | |
] | |
const getNeighbors = (cell: Coordinate): Coordinate[] => | |
neighborDeltas | |
.map(([dx, dy]) => cell.split(',').map(Number)) | |
.map(([x, y]) => [x + dx, y + dy] as [number, number]) | |
.map(parseCell) | |
const expandGrid = (grid: Grid): Grid => | |
new Set( | |
[...grid] | |
.flatMap(getNeighbors) | |
.concat([...grid]) | |
) | |
const countNeighbors = (grid: Grid) => (cell: Coordinate): number => | |
getNeighbors(cell).filter(grid.has).length | |
const shouldBeAlive = (grid: Grid) => (cell: Coordinate): boolean => | |
(grid.has(cell) && [2, 3].includes(countNeighbors(grid)(cell))) || | |
(!grid.has(cell) && countNeighbors(grid)(cell) === 3) | |
const nextGeneration = (grid: Grid): Grid => | |
Array.from(expandGrid(grid)) | |
.filter(shouldBeAlive(grid)) | |
.reduce((acc, cell) => acc.add(cell), new Set<string>()) | |
const renderGrid = (grid: Grid): string => { | |
const cells = Array.from(grid).map(cell => cell.split(',').map(Number) as [number, number]) | |
const xs = cells.map(([x]) => x) | |
const ys = cells.map(([, y]) => y) | |
const minX = Math.min(...xs) | |
const maxX = Math.max(...xs) | |
const minY = Math.min(...ys) | |
const maxY = Math.max(...ys) | |
const lines = [] | |
for (let y = minY; y <= maxY; y++) { | |
let line = '' | |
for (let x = minX; x <= maxX; x++) { | |
line += grid.has(`${x},${y}`) ? '█' : ' ' | |
} | |
lines.push(line) | |
} | |
return lines.join('\n') | |
} | |
// Initial Configuration (Glider) | |
const initialGrid: Grid = new Set([ | |
parseCell([1, 0]), | |
parseCell([2, 1]), | |
parseCell([0, 2]), | |
parseCell([1, 2]), | |
parseCell([2, 2]), | |
]) | |
// Run the Game | |
const run = (grid: Grid, iterations: number): void => { | |
console.clear() | |
console.log(renderGrid(grid)) | |
if (iterations > 0) { | |
setTimeout(() => run(nextGeneration(grid), iterations - 1), 500) | |
} | |
} | |
// Start the Game with 20 iterations | |
run(initialGrid, 20) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type Grid = ReadonlyArray<ReadonlyArray<number>>; | |
// Compose functions from right to left | |
const compose = (...fns: Function[]) => (x: any) => | |
fns.reduceRight((v, f) => f(v), x); | |
// Pipe functions from left to right | |
const pipe = (...fns: Function[]) => (x: any) => | |
fns.reduce((v, f) => f(v), x); | |
// Map function in point-free style | |
const map = (fn: Function) => (arr: any[]) => arr.map(fn); | |
// Reduce function in point-free style | |
const reduce = (fn: Function, init: any) => (arr: any[]) => | |
arr.reduce(fn, init); | |
// Sum function in point-free style | |
const add = (a: number) => (b: number) => a + b; | |
const sum = reduce((a: number, b: number) => a + b, 0); | |
// Get the value at position (x, y) in the grid, or 0 if out of bounds | |
const getCellValue = (grid: Grid) => (x: number) => (y: number): number => | |
grid[y] && grid[y][x] !== undefined ? grid[y][x] : 0; | |
// Get the coordinates of neighboring cells for position (x, y) | |
const getNeighborCoords = (x: number) => (y: number): [number, number][] => | |
[ | |
[x - 1, y - 1], | |
[x, y - 1], | |
[x + 1, y - 1], | |
[x - 1, y], | |
// [x, y], // Exclude the cell itself | |
[x + 1, y], | |
[x - 1, y + 1], | |
[x, y + 1], | |
[x + 1, y + 1], | |
]; | |
// Count the live neighbors for cell at position (x, y) | |
const countLiveNeighbors = (grid: Grid) => (x: number) => (y: number): number => | |
pipe( | |
getNeighborCoords(x), | |
map(([nx, ny]) => getCellValue(grid)(nx)(ny)), | |
sum | |
)(y); | |
// Determine the next state of a cell based on the Game of Life rules | |
const computeNextCellState = (grid: Grid) => (x: number) => (y: number): number => | |
((liveNeighbors: number) => { | |
const currentState = getCellValue(grid)(x)(y); | |
return (currentState === 1 && (liveNeighbors === 2 || liveNeighbors === 3)) || | |
(currentState === 0 && liveNeighbors === 3) | |
? 1 | |
: 0; | |
})(countLiveNeighbors(grid)(x)(y)); | |
// Compute the next state of the entire grid | |
const computeNextGrid = (grid: Grid): Grid => | |
grid.map((row, y) => | |
row.map((_, x) => computeNextCellState(grid)(x)(y)) | |
); | |
// Generate the Game of Life for N generations | |
const iterateLife = (grid: Grid) => (generations: number): Grid[] => | |
generations === 0 | |
? [grid] | |
: [grid, ...iterateLife(computeNextGrid(grid))(generations - 1)]; | |
// Example usage | |
const initialGrid: Grid = [ | |
[0, 1, 0], | |
[0, 1, 0], | |
[0, 1, 0], | |
]; | |
const generations = iterateLife(initialGrid)(5); | |
// Output the grid states for each generation | |
generations.forEach((gen, index) => { | |
console.log(`Generation ${index}:`); | |
gen.forEach(row => console.log(row.join(' '))); | |
console.log(''); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type Cell = 0 | 1; | |
type Grid = Cell[][]; | |
const size = 20; | |
const range = (n: number) => [...Array(n).keys()]; | |
const initial = () => | |
range(size).map(() => | |
range(size).map(() => Math.random() > 0.5 ? 1 : 0) | |
); | |
const neighbors = ([x, y]: [number, number]): [number, number][] => | |
[[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]] | |
.map(([dx, dy]) => [(x + dx + size) % size, (y + dy + size) % size]); | |
const countAlive = (grid: Grid) => ([x, y]: [number, number]): number => | |
neighbors([x, y]).reduce((sum, [nx, ny]) => sum + grid[nx][ny], 0); | |
const nextState = (grid: Grid) => (x: number) => (y: number): Cell => | |
(grid[x][y] === 1 && [2, 3].includes(countAlive(grid)([x, y]))) || | |
(grid[x][y] === 0 && countAlive(grid)([x, y]) === 3) | |
? 1 | |
: 0; | |
const evolve = (grid: Grid): Grid => | |
range(size).map(x => range(size).map(nextState(grid)(x))); | |
const render = (grid: Grid): string => | |
grid.map(row => row.map(cell => cell ? '◼' : '◻').join('')).join('\n'); | |
const game = (grid: Grid) => { | |
console.clear(); | |
console.log(render(grid)); | |
setTimeout(() => game(evolve(grid)), 100); | |
}; | |
game(initial()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment