Provably Fair
A way to ensure all battles are fair and unriggable.
Context
We define a function getHash
that computes the SHA256 hash of an input string and returns the bytes as a hex string:
import * as crypto from "crypto";
export function getHash(input: string) {
return crypto.createHash("sha256").update(input).digest("hex");
}
When a game is created, the server_seed
and battle_id
are generated. battle_id
is publicly shared, and the hash of the server_seed
is displayed on the battle. The server_seed
is revealed only after a battle has finished. You may verify that the server_seed
and hash match here.
player_number
is assigned 1 for the creator, and 2 for the joiner.
Picking Moves
Moves can be randomly chosen in two scenarios:
For a user who runs out of time in a round
By the bot (if called by the battle creator)
The move is chosen with the following procedure:
Generate the input string.
Compute the SHA256 hash of the input.
Convert the first 4 bytes of the hex string to an integer and apply modulo 3.
Choose fire for result 0, grass for result 1, and water for result 2.
const input: string = "move:{server_seed}:{battle_id}:{player_address}:{round_num}:{player_number}";
const hash: string = getHash(input);
const moveNumber: number = parseInt(hash.substring(0, 8), 16) % 3;
if (moveNumber == 0) {
return AttackTypeEnum.FIRE;
} else if (moveNumber == 1) {
return AttackTypeEnum.GRASS;
} else {
return AttackTypeEnum.WATER;
}
Calculating Miss, Hit, Super Active Chances
After every round that isn't a draw, the winning player assigned a chance to either miss, hit a normal attack, or land a super active attack. The attack type is chosen with the following procedure:
Generate the input string.
Compute the SHA256 hash of the input.
Convert the first 4 bytes of the hex string to an integer and apply modulo 100.
Ticket ranges for the result (inclusive) and respective attack types:
0 - 19: Miss
20 - 51: Super Active
52 - 99: Regular
const input: string = "attack_probability:{server_seed}:{battle_id}:{player_address}:{round_num}:{player_number}";
const hash: string = getHash(input);
const ticket: number = parseInt(hash.substring(0, 8), 16) % 100;
if (ticket <= 19) {
return "Miss";
} else if (ticket >= 20 && ticket <= 51) {
return "Super Active";
} else {
return "Hit";
}
Calculating Damage
This is a bit more nuanced due to the slightly more complicated rules of battling and attack types. The damage ticket is generated with the following procedure:
Generate the input string.
Compute the SHA256 hash of the input.
Convert the first 4 bytes of the hex string to an integer.
const input: string = "damage:{server_seed}:{battle_id}:{player_address}:{round_num}:{player_number}";
const hash: string = getHash(input);
const ticket: number = parseInt(hash.substring(0, 8), 16);
The ticket must now be confined within a range between the minimum and maximum possible attack damage values for the move type.
Misses
They do 0 damage regardless.
Regular Attacks
Take the ticket modulo 21 and add 25 to confine the normal attack damage possibilities between 25 and 45 inclusive.
return (ticket % 21) + 25;
Super Active Attacks
Take the ticket modulo 15 and add 46 to confine the super active attack damage possibilities between 46 and 60 inclusive.
return (ticket % 15) + 46;
Last updated