Skip to content

Instantly share code, notes, and snippets.

@danielmewes
Last active August 23, 2016 16:39
Show Gist options
  • Save danielmewes/196f4e2d85fc7bb02522678bc6082a88 to your computer and use it in GitHub Desktop.
Save danielmewes/196f4e2d85fc7bb02522678bc6082a88 to your computer and use it in GitHub Desktop.
Conway's Game of Life in ReQL
// This version precomputes neighbors in order to be more efficient
var initial = [
"........................O...........",
"......................O.O...........",
"............OO......OO............OO",
"...........O...O....OO............OO",
"OO........O.....O...OO..............",
"OO........O...O.OO....O.O...........",
"..........O.....O.......O...........",
"...........O...O....................",
"............OO......................",
"....................................",
"...................................."
];
var importGrid = (grid) => {
return grid.map( (line) => line.split("").map( (cell) => cell.eq(".").branch(0, 1) ) );
};
var printGrid = (grid) => {
return grid.fold("", (out, line) => out.add(line.fold("", (prefix, cell) => prefix.add(cell.eq(1).branch("#", " ")) ).add("\n")) );
};
var computeNeighbors = (grid, i, j) => {
var left = r.max([0, j.sub(1)]);
var right = r.min([grid(0).count().sub(1), j.add(1)]);
var up = r.max([0, i.sub(1)]);
var down = r.min([grid.count().sub(1), i.add(1)]);
// Generate all neighbors as pairs of [ni, nj] != [i, j]
return
r.range(up, down.add(1))
.concatMap((ni) =>
r.range(left, right.add(1))
.map( (nj) => [ni, nj] )
).filter( (pair) => pair.ne([i, j]) )
.coerceTo('array');
};
// Precomputes an auxiliary grid where each cell has a list of
// neighbor coordinates. This is for better performance.
var precomputeNeighbors = (grid) => {
return grid.do( (grid) =>
r.range(grid.count())
.fold([], (out, i) => {
return out.append( r.range(grid(i).count()).fold([], (prefix, j) => {
return prefix.append(computeNeighbors(grid, i, j));
}));
})
);
};
var evalCell = (state, i, j) => {
var neighbors = state('neighborGrid')(i)(j);
var liveNeighbors = neighbors.map( (pair) => state('grid')(pair(0))(pair(1)) ).sum();
return liveNeighbors.do( (n) =>
// A dead cell becomes alive if 3 neighbors are alive,
// A life cell stays alive if it has 2-3 neighbors
state('grid')(i)(j).eq(0).branch(
n.eq(3),
n.gt(1).and(n.lt(4))
)
).branch(1, 0);
};
// The changefeed on the table `trigger` acts as a trigger to generate
// the next generation. This is so that you can step through the generations
// in single steps by changing something in `trigger` (the actual change
// doesn't matter). Or you can run a small script that performs a write every
// n milliseconds to get a nice animation.
r.table('trigger').changes().fold(
{ grid: importGrid(r.expr(initial)), neighborGrid: precomputeNeighbors(importGrid(r.expr(initial))) },
(state, _ignored) => {
return {
grid: r.range(state('grid').count())
.map( (i) => {
return r.range(state('grid')(i).count()).map( (j) => evalCell(state, i, j) ).coerceTo('array');
})
.coerceTo('array'),
neighborGrid: state('neighborGrid')
};
},
{
emit: (oldState, _ignored, newState) => [printGrid(oldState('grid'))]
}
)
var initial = [
" * ",
" * * ",
" ** ",
" ",
" ",
" ",
" "
];
var importGrid = (grid) => {
return grid.map( (line) => line.split("").map( (cell) => cell.eq(" ").branch(0, 1) ) );
};
var printGrid = (grid) => {
return grid.fold("", (out, line) => out.add(line.fold("", (prefix, cell) => prefix.add(cell.eq(1).branch("#", " ")) ).add("\n")) );
};
var evalCell = (grid, i, j) => {
var left = r.max([0, j.sub(1)]);
var right = r.min([grid(0).count().sub(1), j.add(1)]);
var up = r.max([0, i.sub(1)]);
var down = r.min([grid.count().sub(1), i.add(1)]);
// Generate all neighbors as pairs of [ni, nj] != [i, j]
var neighbors =
r.range(up, down.add(1))
.concatMap((ni) =>
r.range(left, right.add(1))
.map( (nj) => [ni, nj] )
).filter( (pair) => pair.ne([i, j]) );
var liveNeighbors = neighbors.map( (pair) => grid(pair(0))(pair(1)) ).sum();
return liveNeighbors.do( (n) =>
// A dead cell becomes alive if 3 neighbors are alive,
// A life cell stays alive if it has 2-3 neighbors
grid(i)(j).eq(0).branch(
n.eq(3),
n.gt(1).and(n.lt(4))
)
).branch(1, 0);
};
/*
The changefeed on the table `trigger` acts as a trigger to generate
the next generation. This is so that you can step through the generations
in single steps by changing something in `trigger` (the actual change
doesn't matter). Or you can run a small script that performs a write every
n milliseconds to get a nice animation.
*/
r.table('trigger').changes().fold(
importGrid(r.expr(initial)),
(grid, _ignored) => {
return r.range(grid.count())
.map( (i) => {
return r.range(grid(i).count()).map( (j) => evalCell(grid, i, j) ).coerceTo('array');
})
.coerceTo('array');
},
{
emit: (oldGrid, _ignored, newGrid) => [printGrid(oldGrid)]
}
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment