I built a genetics simulation in Phaser 3, React, and Redux. I’ve already written about how to get Phaser and React working together and how I got the isometric perspective in Phaser 3, so I wanted to explain the actual simulation part of the simulation. You can run the simulation on SpaceGarden.xyz, itch.io, or view the source code on Github.
Initially, two flowers are placed on the plane at the northern and most southern tiles. These have random genotypes, but you can change these two parent flowers genotype by adjusting the Punnett table below the game screen. There are two traits, color and shape. You can also go to the options menu and set the recessivity of these genes. Pressing the bee play button will start the bee flying to the first parent.
The bee will always fly to the first parent and then to the second parent. After that, all other flights will be random with the exception that the bee will not fly to the flower that it is currently resting on (a single flower cannot be both parents). The bee picks up pollen (genetic information) from the first flower then flies to the second flower where it “drops” the pollen. This causes a new flower to spawn randomly on an empty tile.
The new flowers genotype is determined by picking one allele from each parent with no preference to parent or position of the allele. With the new genotype, the phenotype is determined by checking if any recessive traits are present in the genotype. Phenotype determination follows simple Punnett Square rules. For a given trait, if no alleles are recessive or both alleles are recessive then the displayed trait is a 50/50 chance of either allele. If one allele is recessive then the other trait will always be displayed (it is the dominant allele).
Code Breakdown — For my future self, and anyone trying to read my spaghetti code
index.js kicks off the app by creating the store for redux using the initialState example if no state has been saved to localStorage. index.html holds the two divs being used for Phaser and the React section of the app with ids to reference.
initialState.js holds the initial state of the redux store. This is imported by the flowersReducer, beesReducer, configReducer, and tilesReducer, to set their respective initial states.
The initialState grabs two random colors and shapes per parent to populate their genotypes. The flower store has a genotype, position (x,y on the Phaser Canvas), phenotype, name, and tileIndex (to keep a reference to their tile position independent of the x,y position). The flowers are ID’d by the convention “flower1”, “flower2”, etc. An initial name is provided as well but the name can be changed, the ID cannot. IDs are held in an array for quick access to reference specific flowers.
The bee store holds the only bee, and the current pollen/genetic information of parent 1. The config store handles whether the bee can be flying, the recessive traits, and the tooltip information to be displayed. The tiles store keeps track of which tiles have been filled.
After the store is created the JSX is rendered as HTML, the Phaser app is injected to
<div id=”phaser” aria-label=”Game Canvas”></div> . The art is loaded in preload.js. Then create.js creates the background for the canvas, before init.js creates the tiles by referencing the store (init.js -> buildGarden.js -> addTiles.js). Once the tiles are in place, init.js checks the store to get the flowers and add3dFlower.js draws them on the screen according to their phenotype and position. init.js then adds the bee and sets the flower to fly to as the first flower. Create.js then sets a check every half second to compare the redux store (the source of truth) and the current state the phaser game knows about. This is sloppy. A better way would be to have a reducer for the game to fire whenever the store changes and kick off the respective rendering changes required in Phaser.
checkStore.js compares the state, if anything in the previous state is different from the current state then additional checks are made for seeing if any new flowers are in the store, checking if the punnett flowers have changed and if the bee should be flying. If the punnett flowers (the first two flowers) have been changed, then recreatePunnettFlowers.js destroys the old sprites and creates new ones.
update.js handles the bee movement, moving the bee to the target flowerToFlyTo, and handling the sprite “rotation”. When the bee overlaps with the flowerToFlyTo, overlapCb.js checks if the bee has already picked up pollen from a previous flower. If it hasn’t, then checkForPollen.js dispatches a pickupPollen action to the store. If it has, then pollinate.js grabs the bee pollen genetic info and the flower the bee is on’s info and sends that to the addFlowerToStore action (and also drops the pollen).
addFlowerToStore in flowerActions.js kicks off the process of determining the genotype and phenotype of the new flower being created. This is handled in the flowersReducer.
And thats the loop!
There’s unused code for setting certain tiles to be available or not in pattern, for controlling the bee directly as a player, for blending colors as a genetic feature, and for including stem type as a possible gene. “Finishing” something is relative.