pink-coin
Version:
A random bit (coin flip) that you can change the behavior of
105 lines (84 loc) • 3.27 kB
JavaScript
// Defaults allow PinkCoin to act as a normal coin
const defaults = {
// max number of consecutive heads or tails. Positive infinity, perhaps obviously, means we are not imposing a maximum.
max: Number.POSITIVE_INFINITY,
// This is the initial probability that a flip event will occur.
initial: 0.5,
// This defines the random function used by the coin. Range should be between 0 to <1
random: Math.random,
// "adjust" changes the behavior of the probability as flip events do not happen
// For "(x) => Math.pow(x, Number.MAX_SAFE_INTEGER)" If only a max number is defined, the probability will remain at effectively 0 until the maxConsecutive is reached in which it will be effectively 1
// For "(x) => x" the probability will grow linearly as the "no-event history" grows to the "maxConservative"
adjust: (x) => Math.pow(x, Number.MAX_SAFE_INTEGER),
};
class PinkCoin {
constructor(
options = {
maxConsecutive: defaults.max,
initialProbability: defaults.initial,
randomFunction: defaults.random,
adjustmentScale: defaults.adjust,
}
) {
// Options
this.maxConsecutive = options.maxConsecutive ?? defaults.max;
this.initialProbability = options.initialProbability ?? defaults.initial;
this.randomFunction = options.randomFunction ?? defaults.random;
this.adjustmentScale = options.adjustmentScale ?? defaults.adjust;
// Handling range offenses
if (this.maxConsecutive <= 1) {
this.initialProbability = 1;
this.maxConsecutive = 1;
}
this.stateChanged = false;
this.history = 0;
this.state = this.randomFunction() < 0.5;
}
get probability() {
// Diving by 0 can break stuff, so let's not.
if (this.maxConsecutive - 1 === 0) return 1;
// Remove one from maxConsecutive (instead of adding one to history) so that the first slip after a change isn't altered by the adjustment
const linearAdjustment = this.history / (this.maxConsecutive - 1);
// Only adjust the probability not included in the initial
const scaledAdjustment = this.adjustmentScale(linearAdjustment);
const adjustRemainder = 1 - this.initialProbability;
const adjustTotal = adjustRemainder * scaledAdjustment;
const total = adjustTotal + this.initialProbability;
return total;
}
get isHeads() {
return this.state;
}
get didChange() {
return this.stateChanged;
}
next() {
// Save data for result
const prevHistory = this.history;
const prevProbability = this.probability;
// Check if state changed
if (this.randomFunction() < this.probability) {
this.history = 0;
this.state = !this.state;
this.stateChanged = true;
} else {
this.history += 1;
this.stateChanged = false;
}
// Return results
return {
isHeads: this.state,
didChange: this.stateChanged,
// It's more useful to know the history and probability that went into the state update
history: prevHistory,
probability: prevProbability,
};
}
flip() {
return this.next();
}
toss() {
return this.next();
}
}
module.exports = PinkCoin;