Skip to content

Instantly share code, notes, and snippets.

@KJLJon
Created April 28, 2018 04:09
Show Gist options
  • Save KJLJon/75b44a0e60e69da4c43b2bcbefa60f17 to your computer and use it in GitHub Desktop.
Save KJLJon/75b44a0e60e69da4c43b2bcbefa60f17 to your computer and use it in GitHub Desktop.
Xincrol algorithm example
import Xincrol from './Xincrol';
class Deck {
constructor(...rules) {
this.setRules(rules);
this.size = rules.reduce((a, b) => {
const base = typeof a === 'string' ? a.length : a;
return base * b.length;
});
this.shuffle();
}
setRules(rules) {
let possibilities = 1;
this.rules = rules.map((value) => {
const results = {
range: possibilities,
value,
};
possibilities *= value.length;
return results;
});
}
shuffle() {
// http://openpatent.blogspot.co.il/2013/04/xincrol-unique-and-random-number.html
this.seq = new Xincrol(this.size);
}
getNextCardIndex() {
return this.seq.next();
// return Math.floor(Math.random() * this.size);
}
pickCard() {
let cardNumber = this.getNextCardIndex();
let card = '';
for (let j = this.rules.length - 1; j >= 0; j -= 1) {
const rule = this.rules[j];
const index = Math.floor(cardNumber / rule.range);
cardNumber -= index * rule.range;
card = rule.value[index] + card;
}
return card;
}
/**
* Exception for cards
* @param string message
* @param string[] cards
*/
createCardException = (message, cards) => ({
message,
cards,
});
/**
* Deals from the deck
* @param integer numberOfCards
*/
deal(numberOfCards) {
const cards = [];
for (let i = 0; i < numberOfCards; i += 1) {
const card = this.pickCard();
if (typeof card === 'undefined') {
throw this.createCardException('Not enough cards in the deck', cards);
}
cards.push(card);
}
return cards;
}
}
import Deck from './Deck';
const deck = new Deck('dsch', '23456789TJQKA');
//deal will repeat cards if numberOfCards > total number of available cards
const numberOfCards = 20;
const cards = deck.deal(numberOfCards);
console.log(cards);
//use deck.shuffle() if you want to shuffle the deck
//quick and dirty port of http://openpatent.blogspot.com/2013/04/xincrol-unique-and-random-number.html
/**
* Xincrol - Unique and Random Number Generator
* Xincrol.java
* Purpose: Generating unique random numbers in specified range.
*
* @author Tofig Kareemov
* @version 1.7 2013.05.03
*
* v1.7 notes: improved randomness for big numbers
*
* v1.6 notes: made ROL and ROR functions to support big masks
*
* v1.5 notes: Fixed bug in ROL and ROR functions
*
* v1.4 notes: added REFLECT and NOT methods
*/
export default class Xincrol {
iUniqueSeed = 0;
iKey = new Array(31).fill(null);
iBaseRange = 0;
iBase = 0;
iBaseMask = 0;
iBaseBitSize = 0;
constructor(iRange) {
this.randomize(iRange);
}
// Private Methods...
reset = (bNew) => {
this.iUniqueSeed = 0;
// todo find other way to generate
this.hashKey(navigator.userAgent, bNew);
this.hashKey(`${Date.now()}`, false);
for (let i = 0; i < this.iKey.length; ++i) {
const date = new Date();
this.hashKey(`${date.getMilliseconds()}`, false);
}
}
hashKey = (sKey, bNew) => {
if ((sKey == null) || (sKey === '')) {
return;
}
if (bNew) {
for (let i = 0; i < this.iKey.length; ++i) {
this.iKeyValue = 0;
for (let i1 = 0; i1 < 4; ++i1) {
const iBit = (i >>> i1) & 1;
if (iBit == 1) {
this.iKeyValue ^= 0x5a;
} else {
this.iKeyValue ^= 0xa5;
}
if (i1 < 3) {
this.iKeyValue <<= 8;
}
}
this.iKey[i] = this.iKeyValue;
}
}
let iGlue = this.iKey[this.iKey.length - 1];
this.iKeyIndex = 0;
for (let i1 = 0; i1 < this.iKey.length; ++i1) {
for (let i = 0; i < sKey.length; ++i) {
this.iKey[this.iKeyIndex] ^= sKey[i];
this.iKey[this.iKeyIndex] = (this.iKey[this.iKeyIndex] << 1) | (this.iKey[this.iKeyIndex] >>> 31);
this.iKey[this.iKeyIndex] ^= iGlue;
this.iKey[this.iKeyIndex] = (this.iKey[this.iKeyIndex] << 1) | (this.iKey[this.iKeyIndex] >>> 31);
iGlue = this.iKey[this.iKeyIndex];
this.iKeyIndex = (++this.iKeyIndex) % this.iKey.length;
}
}
}
// Main Function
XOR = (iA, iB) => ((iA ^ iB) & this.iBaseMask);
// INC(var iA) {
// return ((++iA) & this.iBaseMask);
// }
ADD = (iA, iB) => ((iA + iB) & this.iBaseMask);
ROL = (iA, iPowerDistance) => {
if (this.iBaseBitSize <= 0) {
return iA;
}
iPowerDistance %= this.iBaseBitSize;
this.iBaseBit = ((this.iBaseMask >>> 1) + 1);
for (let i = 0; i < iPowerDistance; ++i) {
if ((iA & this.iBaseBit) != 0) {
iA = ((iA << 1) & this.iBaseMask) | 1;
} else {
iA = ((iA << 1) & this.iBaseMask);
}
}
return (iA);
}
REFLECT(iA) {
let iB = 0;
this.iBaseBit = ((this.iBaseMask >>> 1) + 1);
for (; (iA > 0) && (this.iBaseBit > 0);) {
if ((iA & 1) != 0) {
iB |= this.iBaseBit;
}
this.iBaseBit >>>= 1;
iA >>>= 1;
}
return iB;
}
generate(bUp) {
let iResult = this.iBaseRange;
for (let i = 0; (i < this.iBase) && (iResult >= this.iBaseRange); ++i) {
if (bUp) {
this.iUniqueSeed = (++this.iUniqueSeed) % this.iBase;
} else {
this.iUniqueSeed = (--this.iUniqueSeed) % this.iBase;
}
iResult = this.iUniqueSeed;
for (let i1 = 0; i1 < this.iKey.length; ++i1) {
iResult = this.ROL(iResult, 1);
let iCommand = this.iKey[i1];
for (let i2 = 0; i2 < this.iBaseBitSize; ++i2) {
const iOperand = (iCommand + i1 + i2);
switch (iCommand & 3) {
case 0:
iResult = this.REFLECT(iResult);
break;
case 1:
iResult = this.ROL(iResult, iOperand);
break;
case 2:
iResult = this.ADD(iResult, iOperand);
break;
case 3:
iResult = this.XOR(iResult, iOperand);
break;
}
iCommand >>>= 1;
}
}
}
return iResult;
}
// Public Methods
getRange = () => this.iBaseRange;
randomize = (iRange) => {
this.iBaseRange = iRange;
this.iBase = 1;
if (this.iBaseRange <= 0) {
this.iBaseRange = 0;
return;
}
if (this.iBaseRange > Number.MAX_SAFE_INTEGER / 2) {
this.iBaseRange = Number.MAX_SAFE_INTEGER / 2;
}
for (this.iBaseBitSize = 0; this.iBase < this.iBaseRange; this.iBase <<= 1) {
++this.iBaseBitSize;
}
if (this.iBaseBitSize > 1) {
--this.iBaseBitSize;
}
this.iBaseMask = this.iBase - 1;
if ((this.iKey == null) || (this.iUniqueSeed >= this.iBase)) {
this.reset(false);
}
this.reset(false);
}
setKey = (sKey, iRange) => {
this.iBaseRange = iRange;
this.setKey(sKey);
}
setKey = (sKey) => {
this.iBase = 1;
if (this.iBaseRange <= 0) {
this.iBaseRange = 0;
return;
}
if (this.iBaseRange > Number.MAX_SAFE_INTEGER / 2) {
this.iBaseRange = Number.MAX_SAFE_INTEGER / 2;
}
for (this.iBaseBitSize = 0; this.iBase < this.iBaseRange; this.iBase <<= 1) {
++this.iBaseBitSize;
}
if (this.iBaseBitSize > 0) {
--this.iBaseBitSize;
}
this.iBaseMask = this.iBase - 1;
if ((this.iKey == null) || (this.iUniqueSeed >= this.iBase)) {
this.reset(false);
}
this.iUniqueSeed = 0;
this.hashKey(sKey, true);
}
next = () => this.generate(true);
prev = () => this.generate(false);
encode = (iInput) => {
let iResult = (iInput + this.generate(true));
if (iResult >= this.iBaseRange) {
iResult -= this.iBaseRange;
}
this.hashKey(`${iInput}`, false);
return iResult;
}
decode = (iInput) => {
let iResult = (iInput - this.generate(true));
if (iResult < 0) {
iResult += this.iBaseRange;
}
this.hashKey(`${iResult}`, false);
return iResult;
}
random = (iRange) => {
this.iBase = 1;
if (iRange <= 0) {
return 0;
}
for (; (this.iBase < iRange) && (this.iBase < Number.MAX_SAFE_INTEGER / 2); this.iBase <<= 1) {
}
this.randomize(iRange);
return (this.iKey[this.iKey.length - 1] & (this.iBase - 1));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment