seeded random

all runs are now seeded
seed defaults to the last 8 digits of UTC time in milliseconds
set a custom randomization seed in settings
seed controls:
    at start - boss list, mob type list, level list, horizontal flip
    during run - tech, gun, field choices, some custom level randomization
doesn't control: mob spawns, mob size, some minor level differences, specific level boss choices, ammo reward values, specific tech effects

bug fix with ground state
This commit is contained in:
landgreen
2022-02-06 08:52:39 -08:00
parent 496cc83878
commit 775f45b863
10 changed files with 105 additions and 48 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -90,6 +90,11 @@
<details>
<summary>settings</summary>
<div style="line-height: 150%;" class="details-div">
<label for="seed">randomization seed:</label>
<input type="text" id="seed" name="seed" autocomplete="off" spellcheck="false" minlength="1" size="20" style="width: 120px;">
<br>
<label for="difficulty-select" title="effects: number of mobs, damage done by mobs, damage done to mobs, mob speed, heal effects">combat difficulty:</label>
<select name="difficulty-select" id="difficulty-select" style="background-color: #fff">
<option value="1">easy</option>

View File

@@ -1,4 +1,39 @@
"use strict";
//convert text into numbers for seed
Math.hash = s => { for (var i = 0, h = 9; i < s.length;) h = Math.imul(h ^ s.charCodeAt(i++), 9 ** 9); return h ^ h >>> 9 }
// const date1 = new Date()
// Math.seed = date1.getUTCDate() * date1.getUTCFullYear(); // daily seed, day + year
// Math.seed = Date.now() //random every time: just the time in seconds UTC
Math.seed = Math.floor(Date.now() % 100000000) //random every time: just the time in seconds UTC
Math.seededRandom = function(min = 0, max = 1) { // in order to work 'Math.seed' must NOT be undefined
Math.seed = (Math.seed * 9301 + 49297) % 233280;
return min + Math.seed / 233280 * (max - min);
}
document.getElementById("seed").placeholder = Math.seed //display seed in settings
//Math.seed is set to document.getElementById("seed").value in level.populate level at the start of runs
// console.log(Math.seed)
function shuffle(array) {
var currentIndex = array.length,
temporaryValue,
randomIndex;
// While there remain elements to shuffle...
while (0 !== currentIndex) {
// Pick a remaining element...
// randomIndex = Math.floor(Math.random() * currentIndex);
randomIndex = Math.floor(Math.seededRandom(0, currentIndex)) //Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
// And swap it with the current element.
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
//collision groups
// cat.player | cat.map | cat.body | cat.bullet | cat.powerUp | cat.mob | cat.mobBullet | cat.mobShield | cat.phased
const cat = {
@@ -36,23 +71,6 @@ const color = { //light
// map: "#444",
// }
function shuffle(array) {
var currentIndex = array.length,
temporaryValue,
randomIndex;
// While there remain elements to shuffle...
while (0 !== currentIndex) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
// And swap it with the current element.
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
// shrink power up selection menu
// if (screen.height < 800) {
// document.getElementById("choose-grid").style.fontSize = "1em"; //1.3em is normal

View File

@@ -16,7 +16,6 @@ const level = {
if (level.levelsCleared === 0) { //this code only runs on the first level
// m.immuneCycle = Infinity //you can't take damage
// localSettings.levelsClearedLastGame = 10
// spawn.setSpawnList();spawn.setSpawnList();
// level.difficultyIncrease(30) //30 is near max on hard //60 is near max on why
// simulation.isHorizontalFlipped = true
// m.setField("standing wave")
@@ -222,10 +221,12 @@ const level = {
}
},
populateLevels() {
if (document.getElementById("seed").value) Math.seed = Math.hash(document.getElementById("seed").value) //update randomizer seed in case the player changed it
if (simulation.isTraining) {
level.levels = level.trainingLevels.slice(0) //copy array, not by just by assignment
} else {
simulation.isHorizontalFlipped = (Math.random() < 0.5) ? true : false //if true, some maps are flipped horizontally
simulation.isHorizontalFlipped = (Math.seededRandom() < 0.5) ? true : false //if true, some maps are flipped horizontally
level.levels = level.playableLevels.slice(0) //copy array, not by just by assignment
if (simulation.isCommunityMaps) {
// level.levels.push(level.communityLevels)
@@ -235,8 +236,9 @@ const level = {
} else {
level.levels = shuffle(level.levels); //shuffles order of maps
}
level.levels.splice(Math.floor(level.levels.length * (0.4 + 0.6 * Math.random())), 0, "reservoir"); //add level to the back half of the randomized levels list
level.levels.splice(Math.floor(level.levels.length * (0.4 + 0.6 * Math.random())), 0, "reactor"); //add level to the back half of the randomized levels list
// level.levels.splice(Math.floor(level.levels.length * (0.4 + 0.6 * Math.random())), 0, "reservoir"); //add level to the back half of the randomized levels list
level.levels.splice(Math.floor(Math.seededRandom((level.levels.length) * 0.4, level.levels.length)), 0, "reservoir"); //add level to the back half of the randomized levels list
level.levels.splice(Math.floor(Math.seededRandom((level.levels.length) * 0.4, level.levels.length)), 0, "reactor"); //add level to the back half of the randomized levels list
level.levels.splice(0, 2); //remove 2 levels from the start of the array
if (!build.isExperimentSelection || (build.hasExperimentalMode && !simulation.isCheating)) { //experimental mode is endless, unless you only have an experiment Tech
level.levels.unshift("intro"); //add level to the start of the randomized levels list
@@ -244,6 +246,9 @@ const level = {
level.levels.push("final"); //add level to the end of the randomized levels list
}
}
//set seeded random lists of mobs and bosses
for (let i = 0; i < level.levels.length; i++) spawn.mobTypeSpawnOrder.push(spawn.fullPickList[Math.floor(Math.seededRandom(0, spawn.fullPickList.length))])
for (let i = 0; i < level.levels.length * 2; i++) spawn.bossTypeSpawnOrder.push(spawn.randomBossList[Math.floor(Math.seededRandom(0, spawn.randomBossList.length))])
},
flipHorizontal() {
const flipX = (who) => {
@@ -2508,8 +2513,6 @@ const level = {
level.exit.y = -430;
// level.difficultyIncrease(14); //hard mode level 7
spawn.setSpawnList();
spawn.setSpawnList();
level.defaultZoom = 1500
simulation.zoomTransition(level.defaultZoom)
document.body.style.backgroundColor = color.background //"#ddd";
@@ -3232,8 +3235,6 @@ const level = {
// level.difficultyIncrease(30) //30 is near max on hard //60 is near max on why
// m.immuneCycle = Infinity //you can't take damage
// spawn.setSpawnList(); //picks a couple mobs types for a themed random mob spawns
// spawn.setSpawnList(); //picks a couple mobs types for a themed random mob spawns
simulation.isHorizontalFlipped = true
if (simulation.isHorizontalFlipped) { //flip the map horizontally

View File

@@ -961,7 +961,7 @@ const m = {
}
},
setMaxEnergy() {
m.maxEnergy = (tech.isMaxEnergyTech ? 0.5 : 1) + tech.bonusEnergy + tech.healMaxEnergyBonus + tech.harmonicEnergy
m.maxEnergy = (tech.isMaxEnergyTech ? 0.5 : 1) + tech.bonusEnergy + tech.healMaxEnergyBonus + tech.harmonicEnergy + 2 * tech.isGroundState
simulation.makeTextLog(`<span class='color-var'>m</span>.<span class='color-f'>maxEnergy</span> <span class='color-symbol'>=</span> ${(m.maxEnergy.toFixed(2))}`)
},
fieldMeterColor: "#0cf",

View File

@@ -566,7 +566,8 @@ const powerUps = {
}
}
if (options.length > 0) {
return options[Math.floor(Math.random() * options.length)]
// return options[Math.floor(Math.random() * options.length)]
return options[Math.floor(Math.seededRandom(0, options.length))]
}
},
choiceLog: [], //records all previous choice options
@@ -650,7 +651,8 @@ const powerUps = {
}
if (options.length > 0) {
const choose = options[Math.floor(Math.random() * options.length)]
// const choose = options[Math.floor(Math.random() * options.length)]
const choose = options[Math.floor(Math.seededRandom(0, options.length))]
const isCount = tech.tech[choose].count > 0 ? `(${tech.tech[choose].count+1}x)` : "";
if (tech.tech[choose].isFieldTech) {
@@ -813,7 +815,10 @@ const powerUps = {
}
}
if (options.length > 0) {
return options[Math.floor(Math.random() * options.length)]
// console.log(`random: ${Math.seededRandom(0, options.length)}`)
return options[Math.floor(Math.seededRandom(0, options.length))]
// return options[Math.floor(Math.random() * options.length)]
}
},
choiceLog: [], //records all previous choice options

View File

@@ -658,7 +658,9 @@ const simulation = {
b.setFireMethod()
b.setFireCD();
// simulation.updateTechHUD();
powerUps.tech.choiceLog = []
powerUps.tech.choiceLog = [];
powerUps.gun.choiceLog = [];
powerUps.field.choiceLog = [];
powerUps.totalPowerUps = 0;
powerUps.research.count = 0;
m.setFillColors();

View File

@@ -2,12 +2,19 @@
const spawn = {
nonCollideBossList: ["cellBossCulture", "bomberBoss", "powerUpBoss", "orbitalBoss", "spawnerBossCulture", "growBossCulture"],
// other bosses: suckerBoss, laserBoss, tetherBoss, //these need a particular level to work so they are not included in the random pool
randomLevelBoss(x, y, options = [
"shieldingBoss", "orbitalBoss", "historyBoss", "shooterBoss", "cellBossCulture", "bomberBoss", "spiderBoss", "launcherBoss", "laserTargetingBoss",
randomBossList: ["shieldingBoss", "orbitalBoss", "historyBoss", "shooterBoss", "cellBossCulture", "bomberBoss", "spiderBoss", "launcherBoss", "laserTargetingBoss",
"powerUpBoss", "powerUpBossBaby", "snakeBoss", "streamBoss", "pulsarBoss", "spawnerBossCulture", "grenadierBoss", "growBossCulture", "blinkBoss",
"snakeSpitBoss", "laserBombingBoss", "blockBoss", "revolutionBoss", "mantisBoss", "slashBoss"
]) {
spawn[options[Math.floor(Math.random() * options.length)]](x, y)
],
bossTypeSpawnOrder: [], //preset list of boss names calculated at the start of a run by the randomSeed
bossTypeSpawnIndex: 0, //increases as the boss type cycles
randomLevelBoss(x, y, options = []) {
if (options.length === 0) {
const boss = spawn.bossTypeSpawnOrder[spawn.bossTypeSpawnIndex++ % spawn.bossTypeSpawnOrder.length]
spawn[boss](x, y)
} else {
spawn[options[Math.floor(Math.random() * options.length)]](x, y)
}
},
pickList: ["starter", "starter"],
fullPickList: [
@@ -33,11 +40,17 @@ const spawn = {
"spawner",
"ghoster",
],
mobTypeSpawnOrder: [], //preset list of mob names calculated at the start of a run by the randomSeed
mobTypeSpawnIndex: 0, //increases as the mob type cycles
allowedGroupList: ["spinner", "striker", "springer", "laser", "focuser", "beamer", "exploder", "spawner", "shooter", "launcher", "launcherOne", "stabber", "sniper", "pulsar", "grenadier", "slasher"],
setSpawnList() { //this is run at the start of each new level to determine the possible mobs for the level
//each level has 2 mobs: one new mob and one from the last level
spawn.pickList.splice(0, 1);
spawn.pickList.push(spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)]);
const push = spawn.mobTypeSpawnOrder[spawn.mobTypeSpawnIndex++ % spawn.mobTypeSpawnOrder.length]
spawn.pickList.push(push);
// if (spawn.mobTypeSpawnIndex > spawn.mobTypeSpawnOrder.length) spawn.mobTypeSpawnIndex = 0
//each level has 2 mobs: one new mob and one from the last level
// spawn.pickList.splice(0, 1);
// spawn.pickList.push(spawn.fullPickList[Math.floor(Math.random() * spawn.fullPickList.length)]);
},
spawnChance(chance) {
return Math.random() < chance + 0.07 * simulation.difficulty && mob.length < -1 + 16 * Math.log10(simulation.difficulty + 1)

View File

@@ -3067,7 +3067,7 @@ const tech = {
},
{
name: "particle collider",
description: `<strong>clicking</strong> <strong class='color-m'>tech</strong> while <strong>paused</strong> <strong>ejects</strong> them<br><em><strong>3%</strong> chance to convert your tech into <strong class='color-f'>energy</strong></em>`,
description: `<strong>clicking</strong> <strong class='color-m'>tech</strong> while <strong>paused</strong> <strong>ejects</strong> them<br><em><strong>3%</strong> chance to convert that tech into <strong class='color-f'>energy</strong></em>`,
maxCount: 1,
count: 0,
frequency: 1,

View File

@@ -1,20 +1,33 @@
******************************************************** NEXT PATCH **************************************************
tech: particle collider - in pause menu clicking a tech ejects it (5% chance to lose the power up and convert into energy)
(also works on testing without the tech)
all runs are now seeded
seed defaults to the last 8 digits of UTC time in milliseconds
set a custom randomization seed in settings
seed controls:
at start - boss list, mob type list, level list, horizontal flip
during run - tech, gun, field choices, some custom level randomization
doesn't control: mob spawns, mob size, some minor level differences, specific level boss choices, ammo reward values, specific tech effects
growBoss no longer goes invulnerable
bounceBoss bullets (on reactor map)
do 33% less damage
move 50% slower, so they don't fill the entire map
ground state reworked: reduce passive energy regen by 66%, increase max energy by 200
electronegativity: increase damage by 1% for every 9 -> 8 energy
acetone peroxide does 300 -> 200% more self harm from explosions
predator renamed parasitism
bug fix with ground state
******************************************************** TODO ********************************************************
make a seed/hash system that controls only the tech/guns/fields shown
seed will control:
seeded random at start done - random level boss, mob types list, level order, horizontal flip
seeded random during run - tech, gun, field choices
not seeded random - mob spawns, mob size, minor level differences, custom level boss choices, ammo rewards, tech effect randomness
better to only seed things at the start of the run so it doesn't mess with power up choices
put a seed display in top right corner of splash menu, or settings?
normal seed = full UTC time
make option for a daily seed: seed = day+year
give 1 extra tech for doing the daily seeded run
make the option for the daily run, a secret exit in the intro level?
figure out how to convert text into seed
display seed in pause menu
tech upgrade to anthropic to make it trigger at 50% life and 0%
JUNK tech: https://bindingofisaacrebirth.fandom.com/wiki/Damocles
cloaking field doesn't show energy over max