Skip to content

Instantly share code, notes, and snippets.

@Levi-Lesches
Last active September 25, 2020 12:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Levi-Lesches/54bb017fb50cfa0dd265a38dfe653dbd to your computer and use it in GitHub Desktop.
Save Levi-Lesches/54bb017fb50cfa0dd265a38dfe653dbd to your computer and use it in GitHub Desktop.
Conway's Game of Life, demo in the comments
import "dart:io";
class Position {
static int rows = 13;
static int columns = 13;
static Set<Position> offsets = {
Position(-1, -1), Position(-1, 0), Position(-1, 1),
Position(0, -1), Position(0, 1),
Position(1, -1), Position(1, 0), Position(1, 1),
};
final int row, col;
const Position(this.row, this.col);
@override
String toString() => "($row, $col)";
operator + (Position other) =>
Position(row + other.row, col + other.col);
}
class Cell {
final Board board;
final Position position;
bool isAlive;
bool isAliveNextTick;
Cell(this.position, this.board) : isAlive = false;
@override
String toString() => isAlive ? "■" : "□";
int get aliveNeighborsCount {
int result = 0;
for (final Position offset in Position.offsets) {
final Position newPosition = offset + position;
if (!board.contains(newPosition)) continue;
final bool isOtherCellAlive = board [newPosition].isAlive;
if (isOtherCellAlive) result++;
}
return result;
}
void prepareNextTick() {
final neighborCount = aliveNeighborsCount;
isAliveNextTick = isAlive
? neighborCount == 2 || neighborCount == 3
: neighborCount == 3;
}
void update() {
isAlive = isAliveNextTick;
isAliveNextTick = null;
}
}
abstract class FileParser {
final List<String> lines;
FileParser(String filename) : lines = File(filename).readAsLinesSync() {
initDimensions();
}
void initDimensions();
Iterable<Iterable<bool>> get layout;
}
class RLEParser extends FileParser {
static final RegExp regex = RegExp(r"(?<count>\d*)(?<status>[b|o])");
RLEParser(String filename) : super(filename);
void initDimensions() {
for (final String line in lines) {
if (!line.startsWith("x")) continue;
List<String> parts = line.split(",");
final String x = parts [0].trim();
final String y = parts [1].trim();
final String x_value = x.split("=") [1].trim();
final String y_value = y.split("=") [1].trim();
Position.columns = int.parse(x_value);
Position.rows = int.parse(y_value);
}
}
Iterable<bool> parseLine(String line) sync* {
final Iterable<RegExpMatch> matches = regex.allMatches(line);
for (final RegExpMatch match in matches) {
final String countString = match.namedGroup("count");
final int count = int.parse(countString.isEmpty ? "1" : countString);
final String status = match.namedGroup("status");
for (int _ = 0; _ < count; _++) {
yield status == "o";
}
}
}
Iterable<Iterable<bool>> get layout sync* {
int index = 0;
for (;index < lines.length; index++) {
final String line = lines[index];
if (line.startsWith("x") || line.startsWith("#")) continue;
break;
}
final String contents = lines.sublist(index).join("");
for (final String line in contents.split("\$")) {
yield parseLine(line);
}
}
}
class Board {
List<List<Cell>> board;
Board(this.board);
Board.empty() {
board = emptyBoard;
}
Board.fromPositions(List<Position> positions) {
board = emptyBoard;
for (final Position position in positions) {
this [position].isAlive = true;
}
}
Board.fromList(List<List<int>> data) {
board = emptyBoard;
for (int rowIndex = 0; rowIndex < data.length; rowIndex++) {
for (final int index in data [rowIndex]) {
final int columnIndex = index % Position.columns;
final Position position = Position(rowIndex, columnIndex);
this [position].isAlive = true;
}
}
}
Board.fromRLE(String path) {
final RLEParser parser = RLEParser(path);
board = emptyBoard;
int rowIndex = 0;
for (final Iterable<bool> row in parser.layout) {
int columnIndex = 0;
for (final bool status in row) {
final Position position = Position(rowIndex, columnIndex);
this [position].isAlive = status;
columnIndex++;
}
rowIndex++;
}
}
List<List<Cell>> get emptyBoard => List.generate(
Position.rows,
(int row) => List.generate(
Position.columns,
(int column) => Cell(
Position(row, column),
this,
)
)
);
void forEach(void Function(Cell) func) {
for (int row = 0; row < Position.rows; row++) {
for (int column = 0; column < Position.columns; column++) {
final Position position = Position(row, column);
func(this [position]);
}
}
}
void update() {
forEach((Cell cell) => cell.prepareNextTick());
forEach((Cell cell) => cell.update());
}
bool contains(Position position) =>
position.row < Position.rows
&& position.row >= 0
&& position.col < Position.columns
&& position.col >= 0;
Cell operator [](Position position) =>
board [position.row] [position.col];
@override
String toString() {
String result = "";
for (final List<Cell> row in board) {
String rowString = "";
for (final Cell cell in row) {
rowString += cell.toString() + " ";
}
result += rowString + "\n";
}
return result;
}
void simulate(int period) async {
for (int t = 0; t < period; t++) {
clearScreen();
print(this);
print("t = $t");
await Future.delayed(Duration(milliseconds: 10));
update();
}
}
}
void clearScreen() => print("\x1B[2J\x1B[0;0H");
void main(List<String> args) async {
final Board gliderGun = Board.fromRLE("glider_gun.rle");
int period = int.parse(args [0]);
//print(gliderGun);
gliderGun.simulate(period);
}
#N Gosper glider gun
#C This was the first gun discovered.
#C As its name suggests, it was discovered by Bill Gosper.
#C I modified the x and y values to allow the gliders to travel a bit.
x = 40, y = 30, rule = B3/S23
24bo$22bobo$12b2o6b2o12b2o$11bo3bo4b2o12b2o$2o8bo5bo3b2o$2o8bo3bob2o4b
obo$10bo5bo7bo$11bo3bo$12b2o!
@Levi-Lesches
Copy link
Author

Make sure you include the glider_gun.rle file (or change the filename in the code)

Video demo:
Glider Gun

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment