EvoPolka project-specific patterns for the on-chain genetic algorithm evolution arena
EvoPolka is an on-chain artificial life evolution arena on Polkadot Hub. This skill defines the project-specific patterns, data structures, and conventions.
struct Creature {
uint256 id;
address owner;
uint8 speed; // 0-255 — tiles moved per round
uint8 strength; // 0-255 — attack power
uint8 intelligence; // 0-255 — foraging efficiency
uint8 aggression; // 0-255 — combat initiation chance
uint8 reproRate; // 0-255 — breeding probability
uint8 defense; // 0-255 — damage reduction
uint16 energy; // current energy (food consumed)
uint16 hp; // hit points; 0 = dead
uint8 x; // grid position X
uint8 y; // grid position Y
uint32 generation; // evolution generation counter
bool alive;
bytes32 genome; // packed 32-byte genetic data
}
enum ArenaState { LOBBY, ACTIVE, EVOLVING, FINISHED }
struct Arena {
uint256 id;
ArenaState state;
uint256 stakePerPlayer;
uint256 totalPot;
uint256 roundNumber;
uint256 maxRounds;
uint256 gridSize; // NxN grid
uint256 creaturesPerPlayer;
uint256 mutationRate; // basis points 0-10000
uint256 lastRoundBlock;
uint256 roundInterval; // blocks between rounds
}
mutationRate (basis points)keccak256(abi.encodePacked(block.prevrandao, block.number, nonce++)) for entropyblock.timestamp for randomnessenergy > breedingThreshold and be in top 25% fitnessfitness = (speed + strength + intelligence + defense) * energy * (generation + 1) / 1000
Higher is better. Used for breeding selection and culling decisions.
speed/10 tiles toward nearest food or weaker creatureattacker.strength vs defender.defenseintelligenceEmit events for EVERY state change visible to the frontend:
event ArenaCreated(uint256 indexed arenaId, address creator);
event PlayerJoined(uint256 indexed arenaId, address player);
event ArenaStarted(uint256 indexed arenaId);
event RoundExecuted(uint256 indexed arenaId, uint256 round, uint256 survivors);
event RoundPartial(uint256 indexed arenaId, uint256 round, uint256 processed);
event CreatureBorn(uint256 indexed arenaId, uint256 creatureId, address owner, bytes32 genome);
event CreatureDied(uint256 indexed arenaId, uint256 creatureId);
event CombatOccurred(uint256 indexed arenaId, uint256 attacker, uint256 defender, bool attackerWon);
event BreedingOccurred(uint256 indexed arenaId, uint256 parent1, uint256 parent2, uint256 child);
event DisasterTriggered(uint256 indexed arenaId, uint8 disasterType);
event ArenaFinished(uint256 indexed arenaId);
event RewardClaimed(uint256 indexed arenaId, address player, uint256 amount);
gasleft() > 50_000 before each iterationRoundPartial and let the caller continue with continueRound()protocolFee = totalPot * 2 / 100
playerPool = totalPot - protocolFee
playerReward = playerPool * playerSurvivors[player] / totalSurvivors
Use pull pattern: store rewards in a mapping(address => uint256), let players claim via claimReward().
| Type | Directory | Naming |
|---|---|---|
| Contracts | src/ | PascalCase.sol |
| Libraries | src/libraries/ | PascalCase.sol |
| Interfaces | src/interfaces/ | IPascalCase.sol |
| Tests | test/ | PascalCase.t.sol |
| Deploy scripts | script/ | PascalCase.s.sol |
test_FunctionName_Scenario (e.g., test_JoinArena_RevertsWhenFull)test_FunctionName_RevertsWhen_Conditionvm.deal(address, amount) to fund test accountsvm.prank(address) to impersonate callersvm.expectEmit() to verify eventsvm.expectRevert() to verify revert conditions