Game of Life build with Flutter.

Game of Life build with Flutter.

I would like to show you a version of Game of Life build with Flutter. My intention was to get some insights on the following topics:* Custom drawing — create your own UI-elements Very basic “game” approach — game loop

I would like to show you a version of Game of Life build with Flutter. My intention was to get some insights on the following topics:

  • Custom drawing — create your own UI-elements
  • Very basic “game” approach — game loop

About the game

Let’s first start with a brief intro explaining what “Game of Life” is actually about. In case you know the board game called “Game of Life” I have to disappoint you — the game we are building here is a bit different. Below is the summary in my words, feel free to skip this and check the great Wikipedia article directly.

The rules

The game shows a board of cells which can either be alive or dead. Every turn, each cell gets checked and a new state (dead, alive) is calculated based on the current game board. The rules are quite simple — a cell will only change the state based on the following rules:

  1. Any living cell with < 2 live neighbors will be changed to dead in the next round
  2. If a dead cell has 3 living neighbors it will get alive in the next round
  3. Any living cell with either two or three live neighbors (in all directions) will not change the state in the next round
  4. Any living cell with more than three neighbors will die in the next round

The game will run infinitely and every round will introduce a new generation of cells until all cells are dead or spread across the board in a way that they can't interact with other cells according to the described rules above.

Code, just show me the code

This is image title

Photo by Heather McKean on Unsplash

The complete project is hosted on GitHub. Please note, I’m new to Flutter so please excuse any code smells you might spot:

The start

To start, either create a new flutter project or simply clone my repository. I will skip the project creation, in case you need a helping hand check out the great Flutter starting guide.

Project structure

We can divide the code into three main areas of tasks:

  1. The main widget — gets all parts of the game together
  2. The game logic class — all game rules are applied in here
  3. The drawing department — drawing of the cells is handled in here

If you’re wondering why the code is split up like this, at first I like to separate code by its actual job and secondly I might want to reuse the game logic code (which is pure dart code) for other projects. For example, we could set up a Game of Life web version using dart and the logic class from the Flutter project…

The main widget

This is image title

To keep it short, the job of the main widget is to bring everything together and provide sizing information to our drawing department. Most of the code inside the main.dart file is nothing special, nevertheless I would like to point out the more interesting places to get you started:

 final _game = GameOfLife();

  Size _cellSize;
  @override
  void initState() {
    super.initState();
    //Fullscreen display (still including appbar)
    SystemChrome.setEnabledSystemUIOverlays([]);
    _game.resetWorld();
  }

  void _toggleGame() {
    setState(() {
      _game.toggleGame();
    });
  }

main_part1.dart

The first line creates our logic class, it will be used to interact with our game. To get a new board and ensure the app is running in fullscreen during startup we use the initState method. Thanks to the game_ object we can simply call resetWorld() and get a fresh new start. The last method in the code allows the app to toggle the game between running or paused mode, the call to setState ensures that Flutter will update the UI accordingly. To have something we can start/pause we need a so called game loop. This loop is handled by our logic object (game_) but somehow we have to update our UI after each iteration to make sure our App shows changes. I decided to use a StreamBuilder for this job — with every loop the StreamBuilder gets a notification to update the UI.

 @override
  Widget build(BuildContext contGameOfLifeext) {
    final screenSize = MediaQuery.of(context).size;
    _cellSize = Size(
        screenSize.width / GameOfLife.rowLength,
        (screenSize.height - kToolbarHeight) /
            (GameOfLife.worldSize / GameOfLife.rowLength));

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        actions: <Widget>[
          FlatButton(
            child: Icon(Icons.restore),
            onPressed:() {
              if(_game.running)
                _game.toggleGame();
                setState(() {
                  _game.resetWorld();    
                });
            } ,
          )

        ],
      ),
      body: StreamBuilder<double>(
        stream: _game.stateController.stream,
        builder: (context, snapshot) {
          return GameBoard(
            GameOfLife.cellMargin,
            _game.world,
            GameOfLife.rowLength,
            cellSize: _cellSize,
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _toggleGame,
        tooltip: 'Start/Stop',
        child: _game.running ? Icon(Icons.pause) : Icon(Icons.play_arrow),
      ),
    );
  }

main_part2.dart

You might spot a bit more in the above code. It includes the callback to our toggle methods to start/pause the game and also some size calculation for the cells. I will explain most of it in the following parts.

Game logic

To get the game started we have to fill the world, this job is done by the code below:

 static const worldSize = 1024;
  static const rowLength = 32;

  final _world = List<bool>(worldSize);

  void resetWorld() {
    final random = Random();
    _world.fillRange(0, worldSize, false);
    final totalSets = random.nextInt(((worldSize / 100) * 50).round());
    for (var i = 0; i < totalSets; ++i) {
      _world[random.nextInt(worldSize)] = true;
    }
  }

gameOfLive.dart

The board itself is a simple List of boolean values. The resetWorld function performs the following steps:

  1. Fill the world with dead cells, from zero to worldSize (right now 1024 cells)
  2. Getting a random number of cells up to 50% of the world size, see documentation for nextInt
  3. Mark cells as living until we reached the limit generated in our second step

You might have noticed that the above logic could end up with less active cells as our second step decided. This is due to the fact that the third step doesn’t check if the randomly selected cell is already active, it could happen that we set the same cell active twice.

Ready to go

Our game is ready to start, now we need to take care of the game loop that will update the cells based according to the game rules.

Future<void> _runTheGame() async {
    while (_running) {
      await Future.delayed(Duration(milliseconds: 350));
      var newWorld = List<bool>.from(_world);
      for (var i = 0; i < worldSize; ++i) {
        //In case x cell alive do this
        var livingNeighbors = _countLivingCellsNearby(i);
        if (livingNeighbors < 2 || livingNeighbors > 3) {
          //die
          newWorld[i] = false;
        } else if (_world[i] &&
            (livingNeighbors == 2 || livingNeighbors == 3)) {
          //life
          newWorld[i] = true;
        } else if (!_world[i] && livingNeighbors == 3) {
          //life
          newWorld[i] = true;
        }
        _world[i] = newWorld[i];
      }
      _stateController.add(0);
    }
  }

  int _countLivingCellsNearby(int cellIndexToCheck) {
    final left = cellIndexToCheck % rowLength == 0
        ? cellIndexToCheck + (rowLength - 1)
        : cellIndexToCheck - 1;
    final right = (cellIndexToCheck + 1) % rowLength == 0
        ? cellIndexToCheck - (rowLength - 1)
        : cellIndexToCheck + 1;
    final top = cellIndexToCheck <= (rowLength - 1)
        ? worldSize - (rowLength - cellIndexToCheck).abs()
        : cellIndexToCheck - rowLength;
    final bottom = cellIndexToCheck >= (worldSize - rowLength)
        ? ((worldSize - rowLength) - cellIndexToCheck).abs()
        : cellIndexToCheck + rowLength;
    final topLeft = top % rowLength == 0 ? top + (rowLength - 1) : top - 1;
    final topRight =
        (top + 1) % rowLength == 0 ? top - (rowLength - 1) : top + 1;
    final bottomLeft =
        bottom % rowLength == 0 ? bottom + (rowLength - 1) : bottom - 1;
    final bottomRight =
        (bottom + 1) % rowLength == 0 ? bottom - (rowLength - 1) : bottom + 1;
    return _boolToInt(_world[left]) +
        _boolToInt(_world[right]) +
        _boolToInt(_world[top]) +
        _boolToInt(_world[bottom]) +
        _boolToInt(_world[topLeft]) +
        _boolToInt(_world[topRight]) +
        _boolToInt(_world[bottomLeft]) +
        _boolToInt(_world[bottomRight]);
  }

  int _boolToInt(bool input) {
    return input ? 1 : 0;
  }

gameOfLive.dart

Game updates are handled by the __runTheGame_ Method. To ensure the loop is running “slow” enough it starts with sleep, our app will wait 350ms before it calculates the next round.

This part might look complicated but it solves a problem that all Game of life implementation face:

How do you handle cells at the edge of the board?

In my app the board has no real edges, if you are on the far right side and go one step to the right you will end up on the first left position. The same for the top or bottom end of the board, if you step over the edges you will end up on the opposite side. It might remind you of the old Pacman game. The complete calculation to apply the game rules is done by changing the index inside the array to check all surrounding cells, left, right, top, bottom and diagonal.

The drawing department

Getting the logic on the screen is the job of our drawing department, in this game we don’t require a lot of elements. Everything we need for custom drawing can be found in the code below.

import 'package:flutter/material.dart';

class CellPainter extends CustomPainter {
  bool cellIsAlive = false;
  double left;
  double top;
  final Paint paintSetting = Paint();

  CellPainter({this.cellIsAlive,this.left,this.top}){
    paintSetting.strokeWidth = 1;
  }

  @override
  void paint(Canvas canvas, Size size) {
  paintSetting.color = Colors.teal;

    //canvas.drawRect(Rect.fromLTWH(left, top, size.width, size.height), paintSetting);
    paintSetting.color =  cellIsAlive ? Colors.green : Colors.amber;
    canvas.drawRRect(RRect.fromRectAndRadius(Rect.fromLTWH(left, top, size.width, size.height), Radius.circular(4)),paintSetting);
    //canvas.drawCircle(Rect.fromLTWH(left, top, size.width, size.height).center, size.height/2, paintSetting);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    var cellPainter = (oldDelegate as CellPainter);
    return cellPainter.cellIsAlive != this.cellIsAlive
            || cellPainter.left != this.left 
            || cellPainter.top != this.top;
  }
}

cell.dart

Flutter contains the great and easy to use CustomPainter, the main idea is quite easy, it has two jobs:

  1. Draw the element with the provided size on the given canvas
  2. Decide if you need to draw your element again

Our cells will be represented by a rectangle with rounded edges, the color depends on the cell state. Living cells will be green, dead cells will be orange. Drawing the rectangle is done by the build in function drawRRectyou need to pass a rectangle with size, position and the radius for the edges_._

A cell has to be painted again only if:

  • The position or size of the cell has changed, for example, because the user rotated the phone
  • The cell state changed, a living cell died or the other way around

Last puzzle piece is to arrange all cells in a way that our app renders the board, this is done by the code below:

import 'package:flutter/material.dart';
import 'package:gameoflife/cell.dart';

class GameBoard extends StatelessWidget {
  final List<bool> _world;
  final Size cellSize;
  final int _rowLength;
  final int _cellMargin;

  const GameBoard(this._cellMargin,this._world, this._rowLength, {Key key, this.cellSize})
      : super(key: key);

  List<Widget> _buildCells() {
    final cells = List<Widget>();
    var rowCounter = 0;
    for (var i = 0; i < _world.length; ++i) {
      if (i != 0 && i  % _rowLength == 0) rowCounter++;

      final left = ((i - (rowCounter * _rowLength)) * cellSize.width)+_cellMargin;
      final top  = (cellSize.height * rowCounter)+_cellMargin;
      final marginedSize = Size(cellSize.width-2*_cellMargin,cellSize.height-2*_cellMargin);
      cells.add(
        CustomPaint(
          size: marginedSize,
          painter: CellPainter(
            cellIsAlive: _world[i],
            left: left,
            top: top,
          ),
        ),
      );
    }
    return cells;
  }

  @override
  Widget build(BuildContext context) {
    return Stack(children: _buildCells());
  }
}

board.dart

The big idea behind the code is to draw the board row by row based on the calculated sizes.

Conclusion

First, Flutter is awesome, I tried a lot of cross-platform development tools but I have to admit that Dart and Flutter are easy to start and the provided tooling just works ( I’m using Linux, tested it also on Windows).

Creating custom UI elements is made in a logical two-step implementation, decide what you want to draw and when to redraw.

Game of life itself can be implemented in many ways, for myself it’s always a good way to test out a new platform/language. Besides the technical point of view, I like to watch and see how a randomly created board develops into different patterns or simple fades out or gets stuck in a repeating motion.

Flutter game

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

Google's Flutter 1.20 stable announced with new features - Navoki

Google has announced new flutter 1.20 stable with many improvements, and features, enabling flutter for Desktop and Web

What is Flutter and why you should learn it?

Flutter is an open-source UI toolkit for mobile developers, so they can use it to build native-looking Android and iOS applications from the same code base for both platforms. Flutter is also working to make Flutter apps for Web, PWA (progressive Web-App) and Desktop platform (Windows,macOS,Linux).

Complete SQLite CRUD Operations in Flutter

Now a days almost all application have to have some kind of data storage. Application without collaboration with other users will make use of local storage db – SQLite. In this tutorial, we are going to cover all CRUD operations in Flutter with SQLite.

Adobe XD plugin for Flutter with CodePen Tutorial

Recently Adobe XD releases a new version of the plugin that you can use to export designs directly into flutter widgets or screens.

Flutter - How to Add AdMob Real Ads in Flutter App | Flutter AdMob Tutorial

Hello Whats is up Everyone So, Today I am going to show u How to Add Admob Real ads in Flutter apps which are very Easy Implement After watching this video u...