Skip to content

Instantly share code, notes, and snippets.

@smitch88
Last active September 18, 2023 21:12
Show Gist options
  • Save smitch88/3ab7a82fbc9b0a767ad8889e6058a4d3 to your computer and use it in GitHub Desktop.
Save smitch88/3ab7a82fbc9b0a767ad8889e6058a4d3 to your computer and use it in GitHub Desktop.
Bearz battle algo v1.0
export const COEFFICIENT = 50;
export const randomUint256 = () =>
ethers.BigNumber.from(ethers.utils.randomBytes(32));
export const calculateDamage = (stats, oppEndurance, chance) => {
return (
(stats.str / oppEndurance + 2 + (stats.int * 0.5) / 40) *
chance *
(stats.lck * 0.8) +
150
).toFixed(2);
};
export const calculateHitPoints = (stats, level) => {
return (
Math.floor(0.01 * (2 * stats.end + (stats.int + stats.lck) * level) + 15) *
COEFFICIENT
);
};
export const runBattle = async (battle, battleConfig) => {
const {
host,
opponent,
hostAddress,
hostTokenId,
opponentAddress,
opponentTokenId,
} = battle;
const { final: hostStats } = host;
const { final: opponentStats } = opponent;
const {
enduranceFactor = 76,
winsNeeded = 2,
chanceFloor = 65,
chanceDynamic = 50,
critMod = 100,
critMultiplier = 3,
hpBoost = 500,
} = battleConfig;
// Determine who gets first attack
const hostHigherEndurance = hostStats.end > opponentStats.end;
let events = [];
let winnerDetermined = null;
let round = 1;
let hostRoundsWon = 0;
let opponentRoundsWon = 0;
// Start Round
events.push(
createEvent("BATTLE_BEGIN", {
startedAt: toSqlDatetime(new Date()),
host: {
hp: host.hp,
stats: host.final,
},
opponent: {
hp: opponent.hp,
stats: opponent.final,
},
config: battleConfig,
})
);
// Run each round until a winner is determined
while (winnerDetermined !== "HOST" && winnerDetermined != "OPPONENT") {
// Start Round
events.push(
createEvent("ROUND_START", {
round,
})
);
const randomBattleSeed = randomUint256().mod(100);
let p1;
let p2;
// Determine who attacks first in the round
const hostClone = { ...host };
// opponent.final = host.final;
const opponentClone = { ...opponent };
const [first, second] =
randomBattleSeed?.toNumber() < enduranceFactor
? [hostClone, opponentClone]
: [opponentClone, hostClone];
const playerProps = [
"name",
"image",
"tokenId",
"hp",
"seed",
"final",
"attributeLookup.Level",
];
p1 = pick(first, playerProps);
p2 = pick(second, playerProps);
events.push(
createEvent("DETERMINE_FIRST_ATTACK", {
round,
randomBattleSeed: randomBattleSeed.toString(),
hostHigherEndurance,
p1: p1.tokenId,
p2: p2.tokenId,
})
);
// Play out the battle scenario
p1.isDead = false;
p2.isDead = false;
let turn = 0;
while (!p1.isDead && !p2.isDead) {
const [attacker, defender] = turn % 2 === 0 ? [p1, p2] : [p2, p1];
const chanceSeed = randomUint256();
const critSeed = randomUint256();
const onHitKillSeed = randomUint256();
const evasiveSeed = randomUint256();
const attackerLuck = attacker.final.lck;
const chance = ethers.BigNumber.from(chanceSeed)
.mod(chanceDynamic)
.add(Math.floor(chanceFloor));
const crit = ethers.BigNumber.from(critSeed).mod(critMod);
const oneHitKill = ethers.BigNumber.from(onHitKillSeed).mod(1000);
const evasive = ethers.BigNumber.from(critSeed).mod(1000);
const chancePercent = chance?.toNumber() / 100;
const isOneHitKill = oneHitKill?.toNumber() <= attackerLuck;
const isCritHit = crit?.toNumber() <= attackerLuck;
const wasEvaded = evasive?.toNumber() <= defender.final.int;
events.push(
createEvent("TURN_ATTACK", {
round,
turn,
attacker: attacker.tokenId,
defender: defender.tokenId,
attackerLuck,
chance: chance.toString(),
oneHitKill: oneHitKill.toNumber(),
wasEvaded,
crit: crit.toNumber(),
isOneHitKill,
isCritHit,
chancePercent,
seeds: {
chanceSeed: chanceSeed.toString(),
critSeed: critSeed.toString(),
onHitKillSeed: onHitKillSeed.toString(),
evasiveSeed: evasiveSeed.toString(),
},
})
);
// Attack
let dmg = parseFloat(
calculateDamage(attacker.final, defender.final.end, chancePercent)
);
if (isOneHitKill) {
dmg = 999999999;
} else if (isCritHit) {
dmg *= critMultiplier;
}
const boostSeed = randomUint256();
const boost = ethers.BigNumber.from(boostSeed).mod(100);
const wasBoosted = boost?.toNumber() <= 50;
// Increase defender health by 20%
if (wasEvaded) {
dmg = 0;
if (wasBoosted) {
defender.hp += hpBoost;
}
} else {
// Reduce defender health
defender.hp -= dmg;
}
// Check win condition
const isDefenderDead = defender.hp < 0;
defender.isDead = isDefenderDead;
events.push(
createEvent("TURN_OUTCOME", {
round,
turn,
attacker: attacker.tokenId,
defender: defender.tokenId,
attackerDamage: dmg,
defenderHealthReamining: defender.hp,
defenderDied: isDefenderDead,
wasEvaded,
defenderHPBoosted: wasBoosted ? hpBoost : 0,
})
);
if (defender.isDead) {
if (attacker.tokenId === hostTokenId) {
hostRoundsWon += 1;
} else {
opponentRoundsWon += 1;
}
events.push(
createEvent("ROUND_END", {
round,
turn,
winner: attacker.tokenId,
loser: defender.tokenId,
hostRoundsWon,
opponentRoundsWon,
})
);
}
turn++;
}
turn = 0;
p1 = null;
p2 = null;
winnerDetermined =
hostRoundsWon === winsNeeded
? "HOST"
: opponentRoundsWon === winsNeeded
? "OPPONENT"
: null;
round++;
}
// Battle End
const isHostWinner = winnerDetermined === "HOST";
const [winnerTokenId, loserTokenId] = isHostWinner
? [hostTokenId, opponentTokenId]
: [opponentTokenId, hostTokenId];
const [winnerAddress, loserAddress] = isHostWinner
? [hostAddress, opponentAddress]
: [opponentAddress, hostAddress];
events.push(
createEvent("BATTLE_END", {
endedAt: toSqlDatetime(new Date()),
hostRoundsWon,
opponentRoundsWon,
winnerDetermined,
winnerAddress,
winnerTokenId,
loserTokenId,
loserAddress,
})
);
return events;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment