boardgame.io
Version:
library for turn-based games
230 lines (225 loc) • 7.06 kB
JavaScript
'use strict';
// Inlined version of Alea from https://github.com/davidbau/seedrandom.
// Converted to Typescript October 2020.
class Alea {
constructor(seed) {
const mash = Mash();
// Apply the seeding algorithm from Baagoe.
this.c = 1;
this.s0 = mash(' ');
this.s1 = mash(' ');
this.s2 = mash(' ');
this.s0 -= mash(seed);
if (this.s0 < 0) {
this.s0 += 1;
}
this.s1 -= mash(seed);
if (this.s1 < 0) {
this.s1 += 1;
}
this.s2 -= mash(seed);
if (this.s2 < 0) {
this.s2 += 1;
}
}
next() {
const t = 2091639 * this.s0 + this.c * 2.3283064365386963e-10; // 2^-32
this.s0 = this.s1;
this.s1 = this.s2;
return (this.s2 = t - (this.c = Math.trunc(t)));
}
}
function Mash() {
let n = 0xefc8249d;
const mash = function (data) {
const str = data.toString();
for (let i = 0; i < str.length; i++) {
n += str.charCodeAt(i);
let h = 0.02519603282416938 * n;
n = h >>> 0;
h -= n;
h *= n;
n = h >>> 0;
h -= n;
n += h * 0x100000000; // 2^32
}
return (n >>> 0) * 2.3283064365386963e-10; // 2^-32
};
return mash;
}
function copy(f, t) {
t.c = f.c;
t.s0 = f.s0;
t.s1 = f.s1;
t.s2 = f.s2;
return t;
}
function alea(seed, state) {
const xg = new Alea(seed);
const prng = xg.next.bind(xg);
if (state)
copy(state, xg);
prng.state = () => copy(xg, {});
return prng;
}
/*
* Copyright 2017 The boardgame.io Authors
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
/**
* Random
*
* Calls that require a pseudorandom number generator.
* Uses a seed from ctx, and also persists the PRNG
* state in ctx so that moves can stay pure.
*/
class Random {
/**
* constructor
* @param {object} ctx - The ctx object to initialize from.
*/
constructor(state) {
// If we are on the client, the seed is not present.
// Just use a temporary seed to execute the move without
// crashing it. The move state itself is discarded,
// so the actual value doesn't matter.
this.state = state || { seed: '0' };
this.used = false;
}
/**
* Generates a new seed from the current date / time.
*/
static seed() {
return Date.now().toString(36).slice(-10);
}
isUsed() {
return this.used;
}
getState() {
return this.state;
}
/**
* Generate a random number.
*/
_random() {
this.used = true;
const R = this.state;
const seed = R.prngstate ? '' : R.seed;
const rand = alea(seed, R.prngstate);
const number = rand();
this.state = {
...R,
prngstate: rand.state(),
};
return number;
}
api() {
const random = this._random.bind(this);
const SpotValue = {
D4: 4,
D6: 6,
D8: 8,
D10: 10,
D12: 12,
D20: 20,
};
// Generate functions for predefined dice values D4 - D20.
const predefined = {};
for (const key in SpotValue) {
const spotvalue = SpotValue[key];
predefined[key] = (diceCount) => {
return diceCount === undefined
? Math.floor(random() * spotvalue) + 1
: Array.from({ length: diceCount }).map(() => Math.floor(random() * spotvalue) + 1);
};
}
function Die(spotvalue = 6, diceCount) {
return diceCount === undefined
? Math.floor(random() * spotvalue) + 1
: Array.from({ length: diceCount }).map(() => Math.floor(random() * spotvalue) + 1);
}
return {
/**
* Similar to Die below, but with fixed spot values.
* Supports passing a diceCount
* if not defined, defaults to 1 and returns the value directly.
* if defined, returns an array containing the random dice values.
*
* D4: (diceCount) => value
* D6: (diceCount) => value
* D8: (diceCount) => value
* D10: (diceCount) => value
* D12: (diceCount) => value
* D20: (diceCount) => value
*/
...predefined,
/**
* Roll a die of specified spot value.
*
* @param {number} spotvalue - The die dimension (default: 6).
* @param {number} diceCount - number of dice to throw.
* if not defined, defaults to 1 and returns the value directly.
* if defined, returns an array containing the random dice values.
*/
Die,
/**
* Generate a random number between 0 and 1.
*/
Number: () => {
return random();
},
/**
* Shuffle an array.
*
* @param {Array} deck - The array to shuffle. Does not mutate
* the input, but returns the shuffled array.
*/
Shuffle: (deck) => {
const clone = [...deck];
let sourceIndex = deck.length;
let destinationIndex = 0;
const shuffled = Array.from({ length: sourceIndex });
while (sourceIndex) {
const randomIndex = Math.trunc(sourceIndex * random());
shuffled[destinationIndex++] = clone[randomIndex];
clone[randomIndex] = clone[--sourceIndex];
}
return shuffled;
},
_private: this,
};
}
}
/*
* Copyright 2018 The boardgame.io Authors
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
const RandomPlugin = {
name: 'random',
noClient: ({ api }) => {
return api._private.isUsed();
},
flush: ({ api }) => {
return api._private.getState();
},
api: ({ data }) => {
const random = new Random(data);
return random.api();
},
setup: ({ game }) => {
let { seed } = game;
if (seed === undefined) {
seed = Random.seed();
}
return { seed };
},
playerView: () => undefined,
};
exports.RandomPlugin = RandomPlugin;
exports.alea = alea;