Home

Awesome

Lifelike

Online at https://lifelike.psychedelicio.us

Lifelike is a cellular automata toy that can model the late John Conway's famous Game of Life, as well as any "Life-like" cellular automaton.

The app is built using React with Chakra UI and uses HTML Canvas to render the grid. It's the CA browser toy I've always wanted.

Features

Guide

Main controls

Use the main row of controls to start, stop, tick, speed up and slow down the automaton, create automaton state bookmarks, change app settings, translate the grid and draw on the grid.

Translate mode

Click and drag the grid to translate (shift) it.

If edge wrapping is enabled, the grid wraps as you'd expect. If it is disabled, anything that goes out of view while translating the grid is cropped!

Use the arrow keys to nudge the grid by one cell, or hold shift and use arrows to move 10 cells at a time.

Translate mode overrides drawing mode.

Draw & Fill

From the draw & fill menu you can change drawing settings and clear or randomize the entire grid.

While drawing, use the mouse or your finger to draw on the canvas. Hold alt/option to erase. Eraser mode reverses this, so that erasing is the default action and alt/option-mousing draws.

Hold shift while clicking to draw or erase a line. Uses the current brush.

Rule & Neighborhood

These settings change the rules by which the automaton's next state is calculated. You can change these while the automaton is running.

Born rule: if a cell is dead and has X neighbors, it will be born.

Survive rule: if a cell is alive and doesn't have X neighbors, it will die.

In all other cases, cell state does not change.

Neighborhood: determines which cells are considered as possible neighbors.

Edge wrapping means opposite edges of the grid behave as if they are connected, like a torus.

Grid & Cell Size

Changes to the grid width and height (measured in cells) occur from the top left corner. So, if you increase the width or height, it will expand toward the right or downwards.

Changes to cell size (measured in pixels) do not change the width and height.

When you fit the grid to window...

Bookmarks

Add, rename, delete, and load bookmarks. Loading a bookmark will overwrite the current state.

No hard limit on bookmark count. Everything that affects the evolution of the CA is included in a bookmark (cell state, width, height, rule, etc), but most other things are not included in the bookmark (like if the HUD is enabled or not).

Settings

Pause on stable state: If no cells changed in the last generation, stop the CA.

Draw only changed cells: Alternate algorithm and drawing method. Is faster in when there is a particular ratio of cells changing per generation to grid size. This depends on your machine. Worth trying if the app is running slow.

Image & Video Recording

You can save an image of the current grid at any time by clicking the image icon at the top of the page. Images include the gridlines if they are enabled.

Video recording can be started and stopped by clicking the video camera icon at the top of the page. It will turn into a red circle while recording. Whatever you do or see will be recorded in real time, but you won't get the gridlines or see the drawing/HUD overlays.

When you click the red circle to stop, a recording will be immediately downloaded in webm format.

You will not be able to seek in the webm file but can convert it to mp4 using ffmpeg to fix this:

ffmpeg -i lifelike_ti2cn9wiz.webm -c:v libx264 lifelike_ti2cn9wiz.mp4

Note that if your grid has an odd width or height in pixels, the encoding to mp4 will fail. You can use a different pixel format to get around this:

ffmpeg -i lifelike_vrmf5aach.webm -c:v libx264 -pix_fmt yuv444p lifelike_vrmf5aach.mp4

However, the resultant file will not be as compatible with media players as the default pixel format (which is yuv420p). Best to ensure you set up the grid to result in even pixel dimensions for best compatibility.

You can start and stop recording at any time.

You'll get the best quality on Chrome as it supports the VP9 codec (maybe Brave and new Edge also support it?). Firefox only supports the older VP8 codec and quality is not quite as great, but it still works. Video recording is not available on Safari at all.

Keyboard Shortcuts

keyaction
spacebarrun/stop the automaton
righttick automaton once
cclear all cells
rrandomize all cells
ffit cells to window
up, downspeed up/down
mset neighborhood to moore
nset neighborhood to von neumann
bset neighborhood to hexagonal
wtoggle edge wrapping
gtoggle gridlines
htoggle HUD
dtoggle draw mode
ttoggle translate mode
up, down, left, righttranslate grid by 1 cell (translate mode only, hold shift to translate by 10 cells)

Tips

Some interesting rules

Roadmap

Design Decisions

This is my first venture outside the React tutorial world, and my second non-trivial javascript app. If you take the time to review the code, I would love to hear critical feedback.

React, Chakra UI and Canvas

I want to learn React and JSX makes sense to me. I tried Vue, but couldn't really wrap my head around it. I hope to eventually be employed as a front-end developer, and React is a good place to start for that.

Chakra UI's components are aesthetically pleasing and accessible out of the box. Really nice styling via styled-system, kinda like TailwindCSS utility classes.

Canvas (without a library) because I wanted the best performance possible without coding everything in a WebGL shader - that's still black magic to me.

Design

State management

The automaton's state is a 2D array of 1's and 0's - one int for each cell. The grid can be fairly large, containing 1 million+ cells! The algorithm in use currently is naive and checks each cell's 8 neighbors. This means 8 array reads per cell per iteration. The numbers get big really quickly.

There are other algorithms that I plan to explore that need fewer array reads, like Tony Finch's ListLife. I will explore those in the future.

I explored simple React state with useState(), useReducer(), useReducer() bundled up with Context, the new Redux Toolkit, and finally plain Redux.

useState() and useReducer() were performant but made handling multiple user inputs from several components affecting the automaton's cells very tricky. I couldn't figure out a way to do it without having performance issues and incredibly ugly code.

Redux Toolkit was really nice to use but its use of immer behind the scenes made it too slow to read and write potentially 1 million+ cells each iteration. Performance was unacceptable.

Plain Redux solved all of these issues. It is fast and allowed for the various user inputs to modify state far more simply.

Drawing to canvas

Immediately, I ran into some difficulties making React work well with Canvas, perhaps due to my inexperience. I played with PIXI.js and Fabric but ultimately they were too slow to handle the hundreds of thousands of calls to fillRect() 60 times per second. Using canvas directly was noticeably faster. The extra microseconds related to the abstraction of these libraries added up.

The cells are iterated using requestAnimationFrame() and then useEffect() handles the drawing. Amazingly, this works quite well.

I briefly explored using typed arrays, Canvas direct drawing methods and scaling to draw the grid, but didn't get any signficant improvements, and it made my brain hurt, so I reverted to using fillRect().

To improve performance, the gridlines are drawn to a separate transparent Canvas element, as is the drawing overlay.

Reducing re-renders

Because every re-render's performance hit is multiplied by the framerate during animation, I memoized all components and used useCallback() very often.

Other challenges

The drawing features needed aliased shapes. I had trouble creating my own algorithms to generate circles and arbitrary lines that were a single pixel thick. I ended up porting some algorithms from stackoverflow to javascript to draw things prettily. Geometry becomes more difficult when you are working in the pixel-y world and not the ideal math world.

I wasn't sure how to deploy the site, but had heard that Netlify was a joy to use so I gave it a whirl. Wow. Closest thing to real magic I have experience in some time. Needless to say, the app is deployed on Netlify now.

As nice as Chakra UI is, I had some challenges with theming things due to the lack of an included way to change themes and colors. I ended up with a hacky way to provide the Chakra ThemeProvider component with my own colors and such on the fly. It works fine but feels wrong.

Redux made implementing bookmarks a total breeze, and redux-persist made implementing persistance literally a 15 minute job.