Awesome
GoL2
Game of L2
Cellular automata on replicated state machine. An implementation of Conway's Game of Life as a contract on StarkNet, written in Cairo, with an interactive element.
Players can alter the state of the game, affecting the future of the simulation. People may create interesting states or coordinate with others to achieve some outcome of interest.
This implementation is novel in that the game state shared (agreed by all) and permissionless (anyone may participate). The game rules are enforced by a validity proof, which means that no one can evolve the game using different rules.
The main rules of the game are:
- The normal rules of Conways' Game of Life (3 to revive, 2 or 3 to stay alive).
- The boundaries wrap - a glider may travel infinitely within the confines of the grid.
Acorn generation 0 | Acorn generation 1 |
---|---|
Game modes
There are two modes: Inifinite and Creator
- Usage and Mechanics
- Frontend Considerations
- Example deployments
Infinite
A single game with an ability for participants to evolve the game to its next state. By doing so they also gain a special power - to revive a single cell at their discretion.
The game may flourish and produce a myriad of diverse game states, or it may fall to ruin and become a barren wasteland. It will be up to the participants to decide if and when to use their life-giving power.
The purpose of this game mode is to encourage collaboration.
Creator
An open-ended collection of starting states that anyone can create. A player can specify the alive/dead state for all the cells in the game they spawn. The game can be evolved from that point, but individual cells cannot be altered.
Anyone can progress a game, and in return they get creator credits. Ten creator credits can be redeemed to spawn their own game.
The purpose of this game is to allow players to explore interesting starting patterns in the the game. E.g., inventing new starting positions that last a many generations before dying out or create a unique pattern.
Architecture
Each game mode is a separate contract and holds the state of Conway's Game of Life.
- The game board is a square with side length
dim
, (E.g., dim = 16) containingdim**2
cells. Cells wrap around edges. - Every cell is in storage as alive or dead.
- A row of cells will be stored as a binary number of length
dim
(maxdim
is limited to 250 bit due to field size). - Each row is stored as a felt in storage. (
dim
storage updates per interaction)
- A row of cells will be stored as a binary number of length
- When the contract is called by a player to progress a game it:
- Runs the simulation for one generation..
- Saves the new state to storage.
- Saves the generation.
- Issues a credit.
- Anyone can call the contract to view the game which returns a set of 32 integers representing rows of the game board. The columns are binary encoded with 0 or 1 for dead/alive states in each row.
- Anyone with a
Warden
token may callgive_life_to_cell
once per token to revive a chosen cell.
Inner Contract Operations
- A player calls the external function to have a turn.
- Initialization: A
cell_states
array is created to hold the state of all the cells (lengthdim**2
):- Iterate over
dim
to access rows by index indices) - Call
saved_cells.read(row)
to get binary representation of state (for each row). - For each bit, save it as a felt to the array.
- Use a mask for each column:
state[dim*row + column] = bitwise_and(row, 2**column)
) - At the end of the contract call the state will be converted back to binary representation for storage.
- Use a mask for each column:
- Iterate over
- Simulation: A
next_state
array is made to hold the values during evaluation.- Iterate over all cells (
dim**2
). - For each cell check and sum all 8 neighbours, apply life rule and save alive/dead (
0/1
). - Save result to
next_state
array. - After final cell is read, change the array to point at the new neighbour array
cell_states=next_state
.
- Iterate over all cells (
- Repeat simulation for
number_of_generations
. - Recreate the binary representation of each row and save with
saved_cells.write(row)
.
In Infinite
mode, a give life action is processed as follows:
- A player calls
give_life(row_index, col_index)
. - The current row is read from storage.
- The column is applied with a bitwise AND mask to the row.
- The row is saved to storage.
- The player loses one give life credit.
In Creator
mode, a player submits an array of 32 integers, representing the rows
of the game. The game then stores these, ensuring that no two starting points are the same.
Anyone can then evolve that game to earn a creator credit.
Token contract ownership model
The games currently have tokens as an internal representation. There is no ERC20/ERC721 connected to the game at the moment. Given that each game state is stored on chain, another contract could be written to interact with this game (e.g. to score games by some metric).
Example storage
Storage: store DIM
rows as binary alive/dead
row[0] = 01001100101001010100110010100101 (as a felt: 1285901477).
row[1]
...
row[dim] = 10001100010101001001010100110010 (as a felt: 2354353458).
Calling view_game()
will produce dim
numbers in decimal representation, which
can be rendered as binary (e.g., in the console).
Parameters
dim = 32 (max 250)
cell_count = dim**2 (e.g., 1024 cells if DIM=32)
Dev
Activate virtual environment with python >=3.7. Install cairo-lang either with nile or directly.
Nile:
pip install cairo-nile
https://github.com/OpenZeppelin/nile
Directly:
pip install cairo-lang
Data structure
Both game modes use a binary encoded game state. Calling for a game state will return 32 numbers, the starting state for the Infinite game is the Acorn (as shown in the image above), situated mid-right.
0 0 0 0 0 0 0 0 0 0 0 0 32 8 103 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Which decoded as bin(32), bin(8) and bin(103) take the acorn form:
100000
1000
1100111
After evolving one generation, and calling for the state again, the returned state is:
0 0 0 0 0 0 0 0 0 0 0 0 0 118 6 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Or:
1110110
110
10
If for example a user gave life to a particular cell
(row index 9
and column index 9
). The state would then be:
0 0 0 0 0 0 0 0 0 4194304 0 0 0 118 6 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Where 4194304 is the binary number: 00000000010000000000000000000000
Column index 9 is '1', or 'alive' now.
This single cell would die out in the next generation, and so would not be a wise placement, unless other cells are placed in adjacent locations.