| 1 |
import { random, weightedRandom } from "jga-algorithms-random"; |
| 2 |
import { DifficultyEnum, Encounter, Hero, ModularSet, ResultEnum, Scenario } from "../@models"; |
| 3 |
import { createSelectElement, createTitle } from "../helper"; |
| 4 |
import { EncountersService, HeroesService, ModularSetsService } from "../services"; |
| 5 |
|
| 6 |
export const loadProgressionEncounters = ( |
| 7 |
body: HTMLBodyElement, |
| 8 |
encounters: Encounter[], |
| 9 |
heroes: Hero[], |
| 10 |
scenarios: Scenario[], |
| 11 |
modules: ModularSet[], |
| 12 |
): void => { |
| 13 |
// order scenarios by play count |
| 14 |
const gamesScenarios = encounters |
| 15 |
.map((encounter) => encounter.scenario.name) |
| 16 |
.reduce((acc, e) => acc.set(e, (acc.get(e) || 0) + 1), new Map()); |
| 17 |
const unorderedScenarios = scenarios.reduce((acc, e) => { |
| 18 |
acc.set(e, (acc.get(e) || 0) + (gamesScenarios.get(e.name) || 0)); |
| 19 |
|
| 20 |
return acc; |
| 21 |
}, new Map()); |
| 22 |
|
| 23 |
const orderedScenarios = new Map([...unorderedScenarios.entries()].sort((a, b) => a[1] - b[1])); |
| 24 |
|
| 25 |
const scenariosWinEncounters = encounters |
| 26 |
.filter((encounter) => encounter.result === ResultEnum.WON) |
| 27 |
.reduce((acc, e) => { |
| 28 |
if (!acc.has(e.scenario.name)) { |
| 29 |
acc.set(e.scenario.name, []); |
| 30 |
} |
| 31 |
|
| 32 |
const newHeroes = acc.get(e.scenario.name); |
| 33 |
[e] |
| 34 |
.flatMap((encounter) => encounter.heroes) |
| 35 |
.filter((hero) => null != hero) |
| 36 |
.map((hero) => hero.name) |
| 37 |
.filter((hero, i, array) => array.indexOf(hero) === i) |
| 38 |
.forEach((hero) => newHeroes.push(hero)); |
| 39 |
|
| 40 |
acc.set(e.scenario.name, newHeroes.sort()); |
| 41 |
|
| 42 |
return acc; |
| 43 |
}, new Map()); |
| 44 |
|
| 45 |
const progressionsEncounters: Map<Scenario, Partial<Encounter>[]> = new Map(); |
| 46 |
Array.from(orderedScenarios.keys()).forEach((scenario) => { |
| 47 |
heroes.forEach((hero) => { |
| 48 |
if ( |
| 49 |
!scenariosWinEncounters.has(scenario.name) || |
| 50 |
!scenariosWinEncounters.get(scenario.name).includes(hero.name) |
| 51 |
) { |
| 52 |
if (!progressionsEncounters.has(scenario)) { |
| 53 |
progressionsEncounters.set(scenario, []); |
| 54 |
} |
| 55 |
|
| 56 |
const tryHeroes = progressionsEncounters.get(scenario) as Partial<Encounter>[]; |
| 57 |
tryHeroes.push({ |
| 58 |
scenario: scenario, |
| 59 |
modularSets: scenario.modularSets, |
| 60 |
difficulty: DifficultyEnum.STANDARD, |
| 61 |
heroes: [hero], |
| 62 |
}); |
| 63 |
|
| 64 |
progressionsEncounters.set(scenario, tryHeroes); |
| 65 |
} |
| 66 |
}); |
| 67 |
}); |
| 68 |
|
| 69 |
// console.table(scenariosLostEncounters); |
| 70 |
// eslint-disable-next-line compat/compat |
| 71 |
// console.table(scenariosWinEncounters); |
| 72 |
// eslint-disable-next-line compat/compat |
| 73 |
console.table(progressionsEncounters); |
| 74 |
|
| 75 |
createTitle("Suggested encounters", body); |
| 76 |
|
| 77 |
const table = document.createElement("table"); |
| 78 |
const firstRow = document.createElement("tr"); |
| 79 |
const scenario = document.createElement("th"); |
| 80 |
scenario.innerText = "Scenario"; |
| 81 |
const modularsets = document.createElement("th"); |
| 82 |
modularsets.innerText = "Modular Sets"; |
| 83 |
const hero1 = document.createElement("th"); |
| 84 |
hero1.innerText = "Hero 1"; |
| 85 |
// const aspects1 = document.createElement("th"); |
| 86 |
// aspects1.innerText = "Aspects Hero 1"; |
| 87 |
const hero2 = document.createElement("th"); |
| 88 |
hero2.innerText = "Hero 2"; |
| 89 |
// const aspects2 = document.createElement("th"); |
| 90 |
// aspects2.innerText = "Aspects Hero 2"; |
| 91 |
const difficulty = document.createElement("th"); |
| 92 |
difficulty.innerText = "Difficulty"; |
| 93 |
const result = document.createElement("th"); |
| 94 |
result.innerText = "Result"; |
| 95 |
const add = document.createElement("th"); |
| 96 |
|
| 97 |
firstRow.appendChild(scenario); |
| 98 |
firstRow.appendChild(modularsets); |
| 99 |
firstRow.appendChild(hero1); |
| 100 |
// firstRow.appendChild(aspects1); |
| 101 |
firstRow.appendChild(hero2); |
| 102 |
// firstRow.appendChild(aspects2); |
| 103 |
firstRow.appendChild(difficulty); |
| 104 |
firstRow.appendChild(result); |
| 105 |
firstRow.appendChild(add); |
| 106 |
|
| 107 |
table.appendChild(firstRow); |
| 108 |
|
| 109 |
// Add random encounters |
| 110 |
table.appendChild(addRandomEncounter(encounters, scenarios, modules, heroes)); |
| 111 |
table.appendChild(addRandomEncounter(encounters, scenarios, modules, heroes)); |
| 112 |
table.appendChild(addRandomEncounter(encounters, scenarios, modules, heroes)); |
| 113 |
table.appendChild(addRandomEncounter(encounters, scenarios, modules, heroes)); |
| 114 |
table.appendChild(addRandomEncounter(encounters, scenarios, modules, heroes)); |
| 115 |
|
| 116 |
// load encounters |
| 117 |
progressionsEncounters.forEach((scenario) => { |
| 118 |
// pick a random one |
| 119 |
const encounters = Array.from(scenario); |
| 120 |
const encounter = encounters[random(0, encounters.length - 1)]; |
| 121 |
|
| 122 |
// scenario.forEach((encounter) => { |
| 123 |
const row = document.createElement("tr"); |
| 124 |
|
| 125 |
const scenarioElement = document.createElement("td"); |
| 126 |
scenarioElement.innerText = encounter.scenario?.name as string; |
| 127 |
|
| 128 |
const modularsets = document.createElement("td"); |
| 129 |
modularsets.innerText = |
| 130 |
null != encounter.modularSets ? encounter.modularSets.map((modularSet) => modularSet.name).join(", ") : ""; |
| 131 |
|
| 132 |
const allreadyWonHeroes = scenariosWinEncounters.get(encounter.scenario?.name) || []; |
| 133 |
const excludedHeroes = heroes.filter((hero) => !allreadyWonHeroes.includes(hero.name)); |
| 134 |
|
| 135 |
const hero1 = document.createElement("td"); |
| 136 |
const hero1Element = createSelectElement( |
| 137 |
null != encounter.heroes ? encounter.heroes[0] : null, |
| 138 |
excludedHeroes.map((hero) => hero.name), |
| 139 |
); |
| 140 |
hero1.appendChild(hero1Element); |
| 141 |
|
| 142 |
// const aspects1 = document.createElement("td"); |
| 143 |
// const aspects1Element = createSelectElement(encounter.heroes[0]?.aspects, Object.values(AspectEnum), true); |
| 144 |
// aspects1.appendChild(aspects1Element); |
| 145 |
|
| 146 |
const hero2 = document.createElement("td"); |
| 147 |
const hero2Element = createSelectElement( |
| 148 |
null, |
| 149 |
heroes.map((hero) => hero.name), |
| 150 |
); |
| 151 |
hero2.appendChild(hero2Element); |
| 152 |
|
| 153 |
// const aspects2 = document.createElement("td"); |
| 154 |
// const aspects2Element = createSelectElement(null, Object.values(AspectEnum), true); |
| 155 |
// aspects2.appendChild(aspects2Element); |
| 156 |
|
| 157 |
const difficulty = document.createElement("td"); |
| 158 |
const difficultyElement = createSelectElement(DifficultyEnum.STANDARD, Object.values(DifficultyEnum)); |
| 159 |
difficulty.appendChild(difficultyElement); |
| 160 |
|
| 161 |
const result = document.createElement("td"); |
| 162 |
const resultElement = createSelectElement(null, Object.values(ResultEnum)); |
| 163 |
result.appendChild(resultElement); |
| 164 |
|
| 165 |
const add = document.createElement("td"); |
| 166 |
const button = document.createElement("button"); |
| 167 |
button.type = "button"; |
| 168 |
button.innerText = "Add !"; |
| 169 |
button.onclick = async () => { |
| 170 |
const heroesService = new HeroesService(); |
| 171 |
encounter.id = null; |
| 172 |
|
| 173 |
if ("" !== hero2Element.value) { |
| 174 |
const secondHero = await heroesService.find(hero2Element.value); |
| 175 |
if (null == encounter.heroes) { |
| 176 |
encounter.heroes = []; |
| 177 |
} |
| 178 |
encounter.heroes.push(secondHero); |
| 179 |
} |
| 180 |
|
| 181 |
encounter.result = Object.values(ResultEnum).find((x) => x === resultElement.value) || undefined; |
| 182 |
console.log(encounter); |
| 183 |
|
| 184 |
const encountersService = new EncountersService(); |
| 185 |
encountersService.save(encounter).then(() => window.location.reload()); |
| 186 |
}; |
| 187 |
add.appendChild(button); |
| 188 |
|
| 189 |
row.appendChild(scenarioElement); |
| 190 |
row.appendChild(modularsets); |
| 191 |
row.appendChild(hero1); |
| 192 |
// row.appendChild(aspects1); |
| 193 |
row.appendChild(hero2); |
| 194 |
// row.appendChild(aspects2); |
| 195 |
row.appendChild(difficulty); |
| 196 |
row.appendChild(result); |
| 197 |
row.appendChild(add); |
| 198 |
|
| 199 |
table.appendChild(row); |
| 200 |
// }); |
| 201 |
}); |
| 202 |
|
| 203 |
body.appendChild(table); |
| 204 |
}; |
| 205 |
|
| 206 |
const addRandomEncounter = ( |
| 207 |
encounters: Encounter[], |
| 208 |
scenarios: Scenario[], |
| 209 |
modularsets: ModularSet[], |
| 210 |
heroes: Hero[], |
| 211 |
): HTMLTableRowElement => { |
| 212 |
const row = document.createElement("tr"); |
| 213 |
|
| 214 |
const [weightedScenarios, weightedModularSets, weightedHeroes] = getWeightedData( |
| 215 |
encounters, |
| 216 |
scenarios, |
| 217 |
modularsets, |
| 218 |
heroes, |
| 219 |
); |
| 220 |
|
| 221 |
const randomScenario = weightedRandom(scenarios, weightedScenarios); |
| 222 |
const randomModularSet = weightedRandom(modularsets, weightedModularSets); |
| 223 |
const randomHero = weightedRandom(heroes, weightedHeroes); |
| 224 |
|
| 225 |
console.log(randomHero); |
| 226 |
|
| 227 |
const scenario = document.createElement("td"); |
| 228 |
const secnarioElement = createSelectElement( |
| 229 |
null != randomScenario ? randomScenario : null, |
| 230 |
scenarios.map((scenario) => scenario.name), |
| 231 |
); |
| 232 |
scenario.appendChild(secnarioElement); |
| 233 |
|
| 234 |
const modularSet = document.createElement("td"); |
| 235 |
const modularSetElement = createSelectElement( |
| 236 |
null != randomModularSet ? randomModularSet : null, |
| 237 |
modularsets.map((modularSet) => modularSet.name), |
| 238 |
true, |
| 239 |
); |
| 240 |
modularSet.appendChild(modularSetElement); |
| 241 |
|
| 242 |
const hero1 = document.createElement("td"); |
| 243 |
const hero1Element = createSelectElement( |
| 244 |
null != randomHero ? randomHero : null, |
| 245 |
heroes.map((hero) => hero.name), |
| 246 |
); |
| 247 |
hero1.appendChild(hero1Element); |
| 248 |
|
| 249 |
// const aspects1 = document.createElement("td"); |
| 250 |
// const aspects1Element = createSelectElement(randomAspect, Object.values(AspectEnum), true); |
| 251 |
// aspects1.appendChild(aspects1Element); |
| 252 |
|
| 253 |
const hero2 = document.createElement("td"); |
| 254 |
const hero2Element = createSelectElement( |
| 255 |
null, |
| 256 |
heroes.map((hero) => hero.name), |
| 257 |
); |
| 258 |
hero2.appendChild(hero2Element); |
| 259 |
|
| 260 |
// const aspects2 = document.createElement("td"); |
| 261 |
// const aspects2Element = createSelectElement(null, Object.values(AspectEnum), true); |
| 262 |
// aspects2.appendChild(aspects2Element); |
| 263 |
|
| 264 |
const difficulty = document.createElement("td"); |
| 265 |
const difficultyElement = createSelectElement(DifficultyEnum.STANDARD, Object.values(DifficultyEnum)); |
| 266 |
difficulty.appendChild(difficultyElement); |
| 267 |
|
| 268 |
const result = document.createElement("td"); |
| 269 |
const resultElement = createSelectElement(null, Object.values(ResultEnum)); |
| 270 |
result.appendChild(resultElement); |
| 271 |
|
| 272 |
const add = document.createElement("td"); |
| 273 |
const button = document.createElement("button"); |
| 274 |
button.type = "button"; |
| 275 |
button.innerText = "Add !"; |
| 276 |
button.onclick = async () => { |
| 277 |
const heroesService = new HeroesService(); |
| 278 |
const modularSetsService = new ModularSetsService(); |
| 279 |
|
| 280 |
// Retrieve modular sets |
| 281 |
const encounterModularSetsValues = Array.from(modularSetElement.selectedOptions).map((option) => option.value); |
| 282 |
|
| 283 |
const encounterModularSets = await Promise.all( |
| 284 |
encounterModularSetsValues.map(async (modularSet) => await modularSetsService.find(modularSet)), |
| 285 |
); |
| 286 |
|
| 287 |
const heroes = null != randomHero ? [randomHero] : []; |
| 288 |
|
| 289 |
if ("" !== hero2Element.value) { |
| 290 |
const secondHero = await heroesService.find(hero2Element.value); |
| 291 |
heroes.push(secondHero); |
| 292 |
} |
| 293 |
|
| 294 |
const result = Object.values(ResultEnum).find((x) => x === resultElement.value) || undefined; |
| 295 |
|
| 296 |
if (null != randomScenario && null != heroes && null != result) { |
| 297 |
const encounter: Encounter = new Encounter({ |
| 298 |
id: null, |
| 299 |
difficulty: DifficultyEnum.STANDARD, |
| 300 |
scenario: randomScenario, |
| 301 |
modularSets: encounterModularSets, |
| 302 |
heroes, |
| 303 |
result, |
| 304 |
}); |
| 305 |
|
| 306 |
const encountersService = new EncountersService(); |
| 307 |
encountersService.save(encounter).then(() => window.location.reload()); |
| 308 |
} |
| Error |
Row 309, Column 1: "Delete `⏎`"
prettier/prettier
|
| 309 |
|
| 310 |
}; |
| 311 |
add.appendChild(button); |
| 312 |
|
| 313 |
row.appendChild(scenario); |
| 314 |
row.appendChild(modularSet); |
| 315 |
row.appendChild(hero1); |
| 316 |
// row.appendChild(aspects1); |
| 317 |
row.appendChild(hero2); |
| 318 |
// row.appendChild(aspects2); |
| 319 |
row.appendChild(difficulty); |
| 320 |
row.appendChild(result); |
| 321 |
row.appendChild(add); |
| 322 |
|
| 323 |
return row; |
| 324 |
}; |
| 325 |
|
| 326 |
const getWeightedData = ( |
| 327 |
encounters: Encounter[], |
| 328 |
scenarios: Scenario[], |
| 329 |
modularsets: ModularSet[], |
| 330 |
heroes: Hero[], |
| 331 |
): [number[], number[], number[]] => { |
| 332 |
const base = encounters.length * 5; |
| 333 |
const weightedScenarios: number[] = []; |
| 334 |
const weightedModularSets: number[] = []; |
| 335 |
const weightedHeroes: number[] = []; |
| 336 |
|
| 337 |
const gamesHeroes = encounters |
| 338 |
.flatMap((encounter) => encounter.heroes) |
| 339 |
.reduce((acc, e) => acc.set(e, (acc.get(e) || 0) + 1), new Map()); |
| 340 |
|
| 341 |
const gamesModularSets = encounters |
| 342 |
.flatMap((encounter) => encounter.modularSets) |
| 343 |
.map((module) => module) |
| 344 |
.reduce((acc, e) => acc.set(e, (acc.get(e) || 0) + 1), new Map()); |
| 345 |
|
| 346 |
const gamesScenarios = encounters |
| 347 |
.map((encounter) => encounter.scenario) |
| 348 |
.reduce((acc, e) => acc.set(e, (acc.get(e) || 0) + 1), new Map()); |
| 349 |
|
| 350 |
modularsets.forEach((module) => { |
| 351 |
const pastOccurrences = gamesModularSets.has(module.name) ? gamesModularSets.get(module.name) : 0; |
| 352 |
|
| 353 |
weightedModularSets.push(base - pastOccurrences); |
| 354 |
}); |
| 355 |
|
| 356 |
scenarios.forEach((scenario) => { |
| 357 |
const pastOccurrences = gamesScenarios.has(scenario.name) ? gamesScenarios.get(scenario.name) : 0; |
| 358 |
|
| 359 |
weightedScenarios.push(base - pastOccurrences); |
| 360 |
}); |
| 361 |
|
| 362 |
heroes.forEach((hero) => { |
| 363 |
const pastOccurrences = gamesHeroes.has(hero.name) ? gamesHeroes.get(hero.name) : 0; |
| 364 |
|
| 365 |
weightedHeroes.push(base - pastOccurrences); |
| 366 |
}); |
| 367 |
|
| 368 |
return [weightedScenarios, weightedModularSets, weightedHeroes]; |
| 369 |
}; |
| 370 |
|