Skip to content

Instantly share code, notes, and snippets.

@andrejb-dev
Last active August 17, 2022 13:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save andrejb-dev/b573c9709ec0a82470080c929388727c to your computer and use it in GitHub Desktop.
Save andrejb-dev/b573c9709ec0a82470080c929388727c to your computer and use it in GitHub Desktop.
Simulacia online zapasu na www.immortalfighters.net - da sa spustit lokalne v node.js alebo na stranke https://www.tutorialspoint.com/execute_nodejs_online.php
// ------------------------- DEFINITIONS
var db = {
defaultValues: { A: 0.25, B: 0.6, C: 0.9 },
minimalValues: { A: 0.05, B: 0.1, C: 0.3 },
maximalValues: { A: 0.7, B: 0.95, C: 0.99 },
bonuses: [
{
key: "default",
name: "Bez bonusu",
attack: { levelA: 0, levelB: 0, levelC: 0 },
defend: { levelA: 0, levelB: 0, levelC: 0 },
},
{
key: "attack",
name: "Zameranie na útok",
attack: { levelA: -0.03, levelB: -0.02, levelC: -0.02 },
defend: { levelA: -0.02, levelB: -0.01, levelC: -0.01 },
},
{
key: "defend",
name: "Zameranie na obranu",
attack: { levelA: 0.02, levelB: 0.01, levelC: 0 },
defend: { levelA: 0.01, levelB: 0.01, levelC: 0.01 },
},
{
key: "berserk",
name: "Berserker",
attack: { levelA: -0.03, levelB: -0.03, levelC: -0.03 },
defend: { levelA: -0.02, levelB: -0.02, levelC: -0.01 },
},
{
key: "nature",
name: "Sily prírody",
attack: { levelA: -0.01, levelB: 0.02, levelC: 0 },
defend: { levelA: 0.02, levelB: 0, levelC: 0 },
},
{
key: "alchemy",
name: "Ohnivá pasta",
attack: { levelA: 0, levelB: -0.02, levelC: -0.01 },
defend: { levelA: 0, levelB: 0, levelC: 0 },
},
{
key: "sneak",
name: "Zakrádanie",
attack: { levelA: -0.02, levelB: -0.03, levelC: -0.03 },
defend: { levelA: 0, levelB: -0.03, levelC: 0 },
},
{
key: "magic",
name: "Magický štít",
attack: { levelA: 0.01, levelB: 0.01, levelC: 0.02 },
defend: { levelA: 0, levelB: 0, levelC: 0 },
},
],
weapons: [
{
key: "bstd",
name: "bastard",
levelA: 0.02,
levelB: 0.0,
levelC: 0.0,
dmg: 0.1,
},
{
key: "plct",
name: "palcát",
levelA: 0.02,
levelB: 0.0,
levelC: 0.0,
dmg: 0.1,
},
{
key: "kldv",
name: "kladivo",
levelA: 0.02,
levelB: 0.0,
levelC: 0.0,
dmg: 0.1,
},
{
key: "skra",
name: "sekera",
levelA: 0.02,
levelB: 0.0,
levelC: 0.0,
dmg: 0.1,
},
{
key: "kpia",
name: "kopia",
levelA: 0.02,
levelB: 0.0,
levelC: 0.0,
dmg: 0.05,
},
{
key: "plca",
name: "palica",
levelA: -0.05,
levelB: 0.0,
levelC: 0.0,
dmg: -0.1,
},
{
key: "trjz",
name: "trojzubec",
levelA: 0.02,
levelB: -0.02,
levelC: 0.0,
dmg: 0.05,
},
{
key: "krmc",
name: "krátky meč",
levelA: 0,
levelB: 0.02,
levelC: 0.0,
dmg: 0.05,
},
{
key: "dvdk",
name: "dve dýky",
levelA: -0.05,
levelB: 0.05,
levelC: 0.0,
dmg: 0.0,
},
{
key: "bic",
name: "bič",
levelA: -0.03,
levelB: 0.0,
levelC: 0.0,
dmg: -0.05,
},
],
};
// round decimal value to 2 places
function precisionRound(number) {
var factor = Math.pow(10, 2);
return Math.round(number * factor) / factor;
}
// remove random selected item from array and return it
function removeRandomItem(arr) {
return arr.splice(Math.floor(Math.random() * arr.length), 1)[0];
}
// remove random selected item from array and return it
function removeStrongestPlayer(arr) {
var hid = 0;
//pick player with max hp, not truly random
for(var i = arr.lengt - 1; i >=0; --i)
{
if (arr[i].health > arr[hid].health){hid = i;}
}
//var hitted = removeRandomItem(players); // random defender
return arr.splice(hid,1)[0]; // defender with max hp
}
// remove random selected item from array and return it
function removeWeakestPlayer(arr) {
var hid = 0;
//pick player with max hp, not truly random
for(var i = 1; i<arr.length; ++i)
{
if (arr[i].health < arr[hid].health){hid = i;}
}
//var hitted = removeRandomItem(players); // random defender
return arr.splice(hid,1)[0]; // defender with max hp
}
// remove specific player from array of players
function removePlayer(arr, toRem) {
for (var i = 0; i < arr.length; i++) {
if (arr[i].id === toRem.id) {
arr.splice(i, 1);
return;
}
}
}
// get random item from array
function randomItem(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
function createPlayers(non, att, def, ber, nat, mag, alch, snea) {
var players = []
var items = 0;
for (let index = 0; index < non; index++) {
players.push({
id: items++,
name: "Boromir",
health: 100,
baseHealth: 100,
bonus: db.bonuses.find((bonus) => bonus.key === "default"),
weapon: null,
})
}
for (let index = 0; index < att; index++) {
players.push({
id: items++,
name: "Azog",
health: 100,
baseHealth: 100,
bonus: db.bonuses.find((bonus) => bonus.key === "attack"),
weapon: null,
})
}
for (let index = 0; index < def; index++) {
players.push({
id: items++,
name: "Gimli",
health: 100,
baseHealth: 100,
bonus: db.bonuses.find((bonus) => bonus.key === "defend"),
weapon: null,
})
}
for (let index = 0; index < ber; index++) {
players.push({
id: items++,
name: "Ragnar",
health: 100,
baseHealth: 100,
bonus: db.bonuses.find((bonus) => bonus.key === "berserk"),
weapon: null,
})
}
for (let index = 0; index < nat; index++) {
players.push({
id: items++,
name: "Aragorn",
health: 100,
baseHealth: 100,
bonus: db.bonuses.find((bonus) => bonus.key === "nature"),
weapon: null,
})
}
for (let index = 0; index < alch; index++) {
players.push({
id: items++,
name: "MajsterN",
health: 100,
baseHealth: 100,
bonus: db.bonuses.find((bonus) => bonus.key === "alchemy"),
weapon: null,
})
}
for (let index = 0; index < mag; index++) {
players.push({
id: items++,
name: "Gandalf",
health: 100,
baseHealth: 100,
bonus: db.bonuses.find((bonus) => bonus.key === "magic"),
weapon: null,
})
}
for (let index = 0; index < snea; index++) {
players.push({
id: items++,
name: "Lupin",
health: 100,
baseHealth: 100,
bonus: db.bonuses.find((bonus) => bonus.key === "sneak"),
weapon: null,
})
}
return players.sort(() => (Math.random() > .5) ? 1 : -1);
}
// calculate border A.B.C values from defaults and both bonuses
function getBorders(bonusAtt, bonusDef) {
let border = {};
border.A = precisionRound(
db.defaultValues.A + bonusAtt.attack.levelA + bonusDef.defend.levelA
);
border.B = precisionRound(
db.defaultValues.B + bonusAtt.attack.levelB + bonusDef.defend.levelB
);
border.C = precisionRound(
db.defaultValues.C + bonusAtt.attack.levelC + bonusDef.defend.levelC
);
return border;
}
// check border for minimal and maximal values
function fixMinMaxValues(border) {
// fix overlaping values
border.B = Math.max(border.A + 0.02, border.B)
border.C = Math.max(border.B + 0.02, border.C)
// min max values check
border.A = Math.max(border.A, db.minimalValues.A)
border.B = Math.max(border.B, db.minimalValues.B)
border.C = Math.max(border.C, db.minimalValues.C)
border.A = Math.min(border.A, db.maximalValues.A)
border.B = Math.min(border.B, db.maximalValues.B)
border.C = Math.min(border.C, db.maximalValues.C)
if (border.A >= border.B || border.B >= border.C || border.C > 0.99 || border.A < 0.05) {
console.log("err: overlaping borders!!!")
}
}
// generate percentual damage value depending on dice value and borders
function generatePartialDamage(border, d100) {
if (d100 <= border.A) {
return precisionRound(0.0);
} else if (d100 <= border.B) {
return precisionRound(Math.random() * 0.15 + 0.1);
} else if (d100 <= border.C) {
return precisionRound(Math.random() * 0.15 + 0.25);
} else {
return precisionRound(1.0);
}
}
// returns true if at least one player is alive
function notLastStanding(hitted, others) {
return isAlive(hitted) || others.filter(isAlive).length > 0;
}
function isAlive(player) {
return player.health > 1;
}
// raise counter for specified key in map, if key not found, initialize new value for it. Default raise value = 1
function raiseMapEntryCount(map, key, count = 1) {
if (!map) {
return
}
map.has(key) ? map.set(key, map.get(key) + count) : map.set(key, count);
}
function visualMap(datamap) {
let sum = 0;
datamap.forEach((val) => {
sum = sum + val;
});
datamap.forEach((val, key) => {
datamap.set(key, val + " = " + precisionRound((val * 100) / sum) + "%");
});
return new Map([...datamap.entries()].sort());
}
// execute one hit
function makeHit(hitter, hitted, others) {
let border = getBorders(hitter.bonus, hitted.bonus); // currently ignoring weapons
fixMinMaxValues(border); // check min max values
// apply bonus
if (hitted.bonus.key === "berserk") {
//border.A = 0.05;
}
var d100 = precisionRound(Math.random()) + 0.01; // get dice throw value 0.01-1
var dmgPartial = generatePartialDamage(border, d100); // get dmg percentage value
if (dmgPartial > 0.0) {
hitted.health = hitted.health - Math.ceil(dmgPartial * hitted.baseHealth); // remove health from defender
// use alchemy bonus, but only when more than 1vs1 duel
if (hitter.bonus.key === "alchemy" && notLastStanding(hitted, others)) {
if (others.length > 0) {
var other1 = randomItem(others);
other1.health = other1.health - Math.ceil(0.1 * dmgPartial * other1.baseHealth);
if (others.length > 1) {
var other2 = randomItem(others);
while (other1.id == other2.id) {
other2 = randomItem(others);
}
other2.health = other2.health - Math.ceil(0.1 * dmgPartial * other2.baseHealth);
}
}
//if attacking alchymist is not alone alive, deal self damage, else he is winner
if (notLastStanding(hitted, others)) {
hitter.health =
Math.random() < 0.3
? hitter.health - Math.ceil(0.1 * dmgPartial * hitter.baseHealth)
: hitter.health;
}
}
// return magic damage only when more than hitter is alive
if (hitted.bonus.key === "magic" && notLastStanding(hitted, others)) {
hitter.health =
hitter.health - Math.ceil(0.15 * dmgPartial * hitter.baseHealth);
}
}
// return hit type
if (dmgPartial == 0) {
return "missed";
} else if (dmgPartial < 0.25) {
return "smallHit";
} else if (dmgPartial < 0.4) {
return "strongHit";
} else {
return "criticalHit";
}
}
// execute the one tournament.
function deadland(resultTable, players) {
resultTable.fights = resultTable.fights + 1
// Prepare array of players that still can make hit
var playersToHit = [...players];
var rounds = 0
while (players.length > 1) { // while there is anybody to fight with
while (playersToHit.length > 0 && players.length > 1) { // while there is anybody to make hit
var hitter = removeRandomItem(playersToHit); // random attacker from players who can hit
removePlayer(players, hitter); // temporal remove from group
var hitted = removeRandomItem(players); // random defender
// var hitted = removeStrongestPlayer(players)
// var hitted = removeWeakestPlayer(players)
var hitType = makeHit(hitter, hitted, players); // execute the hit and update healths
raiseMapEntryCount(resultTable.hits, hitter.bonus.key + "-" + hitType); // log hit type for that bonus
if (isAlive(hitter)) {
players.push(hitter); // return to group
} else {
raiseMapEntryCount(resultTable.kills, hitted.bonus.key + "->" + hitter.bonus.key); // log killing
}
if (isAlive(hitted)) {
players.push(hitted); // return to group
} else {
removePlayer(playersToHit, hitted); // if dead, remove if still had to hit
raiseMapEntryCount(resultTable.kills, hitter.bonus.key + "->" + hitted.bonus.key);
}
[...players].forEach((player) => {
if (!isAlive(player)) { // check if somebody else died in fight and remove if necessary
removePlayer(players, player);
removePlayer(playersToHit, player);
raiseMapEntryCount(resultTable.kills, hitter.bonus.key + "->" + player.bonus.key);
}
});
}
rounds = rounds + 1
// after all players executed hit, refresh array of new players to hit
var playersToHit = [...players];
}
// now, all players but one are dead, end of the tournament
if (players.length == 0) { // just checking, draw should always be 0
resultTable.draws = resultTable.draws + 1;
} else {
raiseMapEntryCount(resultTable.wins, players[0].bonus.key + "-" + players[0].name + players[0].id);
}
resultTable.rounds = (((resultTable.fights - 1) * resultTable.rounds) + rounds) / (resultTable.fights)
resultTable.minRounds = resultTable.minRounds > 0 ? Math.min(resultTable.minRounds, rounds) : rounds
resultTable.maxRounds = resultTable.maxRounds > 0 ? Math.max(resultTable.maxRounds, rounds) : rounds
return players[0].id
}
function printPlayers(players) {
var output = new Map()
players.forEach(pl => {
raiseMapEntryCount(output, pl.bonus.key)
})
console.log("players: " + [...output].join(" | "))
}
function simulateTournaments(n, players) {
console.log("================== start tournament");
printPlayers(players)
var tournamentsTable = {
wins: new Map(),
hits: new Map(),
kills: new Map(),
draws: 0,
rounds: 0,
fights: 0
};
// simulate n tournaments
for (let index = 0; index < n; index++) {
deadland(tournamentsTable, JSON.parse(JSON.stringify(players)));
}
tournamentsTable.hits = "disabled" //visualMap(tournamentsTable.hits);
tournamentsTable.wins = visualMap(tournamentsTable.wins);
tournamentsTable.kills = "disabled" //visualMap(tournamentsTable.kills);
console.log("tournaments table", tournamentsTable.wins);
console.log()
}
function simulateDuels(n) {
console.log("====================== start dueling");
console.log("Each bonus has duels: " + n * 14)
var duelsTable = {
duels: new Map(),
wins: new Map(),
draws: 0,
rounds: 0,
fights: 0
};
// simulate n tournaments
var players = createPlayers(1,1,1,1,1,1,1,1);
players.forEach(player1 => {
players.forEach(player2 => {
if (player1.id != player2.id) {
for (let index = 0; index < n; index++) {
const winnerId = deadland(duelsTable, [{...player1}, {...player2}])
if (winnerId === player1.id) {
raiseMapEntryCount(duelsTable.duels, player1.bonus.key + "->" + player2.bonus.key)
} else {
raiseMapEntryCount(duelsTable.duels, player2.bonus.key + "->" + player1.bonus.key)
}
}
}
});
});
duelsTable.duels = visualMap(duelsTable.duels);
duelsTable.wins = visualMap(duelsTable.wins);
console.log("duels table", duelsTable.wins);
}
//run in https://www.tutorialspoint.com/execute_nodejs_online.php
// tip to fill parameters: createPlayers(non, att, def, ber, nat, mag, alch, snea)
simulateTournaments(50000, createPlayers(2,2,2,2,2,2,2,2))
simulateDuels(10000)
//experiments
simulateTournaments(10000, createPlayers(0,4,4,0,0,0,0,0))
simulateTournaments(10000, createPlayers(0,1,1,0,0,0,0,0))
simulateTournaments(10000, createPlayers(0,0,0,0,1,0,0,1))
simulateTournaments(10000, createPlayers(0,1,0,1,0,0,0,0))
simulateTournaments(10000, createPlayers(0,0,0,1,0,0,0,1))
simulateTournaments(10000, createPlayers(0,0,0,1,0,0,1,0))
simulateTournaments(10000, createPlayers(0,0,0,1,0,1,0,0))
simulateTournaments(10000, createPlayers(0,3,0,1,2,2,0,5))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment