UNPKG

pink-coin

Version:

A random bit (coin flip) that you can change the behavior of

105 lines (84 loc) 3.27 kB
// 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;