Last active
August 17, 2022 13:42
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ------------------------- 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