Advertisement
jeffdeon

Balanced Dice Algorithm

Sep 27th, 2020
2,692
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. interface IStandardDiceDeck {
  2.     totalDice: number
  3.     dicePairs: IDicePair[],
  4. }
  5.  
  6. interface IWeightedDiceDeck {
  7.     totalDice: number,
  8.     dicePairs: IDicePair[],
  9.     probabilityWeighting: number,
  10.     recentlyRolledCount: number,
  11. }
  12.  
  13. export interface IDicePair {
  14.     dice1: number
  15.     dice2: number
  16. }
  17.  
  18. export abstract class DiceController {
  19.     abstract throwDice(): IDicePair
  20. }
  21.  
  22. export class WeightedDiceDeckController extends DiceController {
  23.  
  24.     private readonly minimumCardsBeforeReshuffling: number
  25.     private readonly probabilityReductionForRecentlyRolled: number
  26.  
  27.     private weightedDiceDeck: IWeightedDiceDeck[]
  28.     private cardsLeftInDeck: number
  29.     private recentRolls: number[]
  30.     private maximumRecentRollMemory: number
  31.  
  32.     constructor() {
  33.         super()
  34.         this.initWeightedDiceDeck()
  35.         this.reshuffleWeightedDiceDeck()
  36.         this.updateWeightedDiceDeckProbabilities()
  37.  
  38.         this.minimumCardsBeforeReshuffling = 13
  39.         this.probabilityReductionForRecentlyRolled = 0.3
  40.  
  41.         this.recentRolls = []
  42.         this.maximumRecentRollMemory = 5
  43.     }
  44.  
  45.     throwDice(): IDicePair {
  46.         return this.drawWeightedCard()
  47.     }
  48.  
  49.     private drawWeightedCard(): IDicePair {
  50.         if(this.cardsLeftInDeck < this.minimumCardsBeforeReshuffling) this.reshuffleWeightedDiceDeck()
  51.         this.updateWeightedDiceDeckProbabilities()
  52.         this.adjustWeightedDiceDeckBasedOnRecentRolls()
  53.         return this.getWeightedDice()
  54.     }
  55.  
  56.     private initWeightedDiceDeck() {
  57.         this.weightedDiceDeck = []
  58.         this.weightedDiceDeck.push({totalDice: 2, dicePairs: [], probabilityWeighting: 0, recentlyRolledCount: 0})
  59.         this.weightedDiceDeck.push({totalDice: 3, dicePairs: [], probabilityWeighting: 0, recentlyRolledCount: 0})
  60.         this.weightedDiceDeck.push({totalDice: 4, dicePairs: [], probabilityWeighting: 0, recentlyRolledCount: 0})
  61.         this.weightedDiceDeck.push({totalDice: 5, dicePairs: [], probabilityWeighting: 0, recentlyRolledCount: 0})
  62.         this.weightedDiceDeck.push({totalDice: 6, dicePairs: [], probabilityWeighting: 0, recentlyRolledCount: 0})
  63.         this.weightedDiceDeck.push({totalDice: 7, dicePairs: [], probabilityWeighting: 0, recentlyRolledCount: 0})
  64.         this.weightedDiceDeck.push({totalDice: 8, dicePairs: [], probabilityWeighting: 0, recentlyRolledCount: 0})
  65.         this.weightedDiceDeck.push({totalDice: 9, dicePairs: [], probabilityWeighting: 0, recentlyRolledCount: 0})
  66.         this.weightedDiceDeck.push({totalDice: 10, dicePairs: [], probabilityWeighting: 0, recentlyRolledCount: 0})
  67.         this.weightedDiceDeck.push({totalDice: 11, dicePairs: [], probabilityWeighting: 0, recentlyRolledCount: 0})
  68.         this.weightedDiceDeck.push({totalDice: 12, dicePairs: [], probabilityWeighting: 0, recentlyRolledCount: 0})
  69.     }
  70.  
  71.     private reshuffleWeightedDiceDeck() {
  72.         const standardDiceDeck = this.getStandardDiceDeck()
  73.  
  74.         for(const [totalDiceIndex, dicePairsForTotalDice] of standardDiceDeck.entries()) {
  75.             this.weightedDiceDeck[totalDiceIndex].dicePairs = dicePairsForTotalDice.dicePairs
  76.         }
  77.  
  78.         const totalCombinations = 36
  79.         this.cardsLeftInDeck = totalCombinations
  80.     }
  81.  
  82.     private updateWeightedDiceDeckProbabilities() {
  83.         for(const diceDeckForTotalDice of this.weightedDiceDeck) {
  84.             diceDeckForTotalDice.probabilityWeighting = diceDeckForTotalDice.dicePairs.length / this.cardsLeftInDeck
  85.         }
  86.     }
  87.  
  88.     private getWeightedDice(): IDicePair {
  89.         const totalProbabilityWeight = this.getTotalProbabilityWeight()
  90.  
  91.         let targetRandomNumber = Math.random() * totalProbabilityWeight
  92.         for(const diceDeckForTotalDice of this.weightedDiceDeck) {
  93.             if(targetRandomNumber <= diceDeckForTotalDice.probabilityWeighting) {
  94.                 const drawnCard = randomElementFromArray(diceDeckForTotalDice.dicePairs)
  95.                 removeElementFromArray(diceDeckForTotalDice.dicePairs, drawnCard)
  96.  
  97.                 this.recentRolls.push(diceDeckForTotalDice.totalDice)
  98.                 diceDeckForTotalDice.recentlyRolledCount ++
  99.                 this.cardsLeftInDeck --
  100.  
  101.                 if(this.recentRolls.length > this.maximumRecentRollMemory) this.updateRecentlyRolled()
  102.                 return drawnCard
  103.             }
  104.             targetRandomNumber -= diceDeckForTotalDice.probabilityWeighting
  105.         }
  106.  
  107.         JL4('Something seriously wrong with weighted dice deck')
  108.         const defaultRollIfError = {dice1: 3, dice2: 4}
  109.         return defaultRollIfError
  110.     }
  111.  
  112.     private getTotalProbabilityWeight(): number {
  113.         let totalProbabilityWeight = 0
  114.         for(const dicePairs of this.weightedDiceDeck) {
  115.             totalProbabilityWeight += dicePairs.probabilityWeighting
  116.         }
  117.  
  118.         return totalProbabilityWeight
  119.     }
  120.  
  121.     private updateRecentlyRolled() {
  122.         const ignore0and1 = 2
  123.         const totalDiceFiveRollsAgo = this.recentRolls[0]
  124.         this.weightedDiceDeck[totalDiceFiveRollsAgo - ignore0and1].recentlyRolledCount --
  125.         this.recentRolls.shift()
  126.     }
  127.  
  128.     private adjustWeightedDiceDeckBasedOnRecentRolls() {
  129.         for(const diceDeckForTotalDice of this.weightedDiceDeck) {
  130.             const probabilityReduction = (diceDeckForTotalDice.recentlyRolledCount * this.probabilityReductionForRecentlyRolled)
  131.             const probabilityMultiplier = 1 - probabilityReduction
  132.             diceDeckForTotalDice.probabilityWeighting *= probabilityMultiplier
  133.             if(diceDeckForTotalDice.probabilityWeighting < 0) diceDeckForTotalDice.probabilityWeighting = 0
  134.         }
  135.     }
  136.  
  137.     private getStandardDiceDeck(): IStandardDiceDeck[] {
  138.         const standardDiceDeck: IStandardDiceDeck[] = []
  139.         standardDiceDeck.push({totalDice: 2, dicePairs: [{dice1: 1, dice2: 1}]})
  140.         standardDiceDeck.push({totalDice: 3, dicePairs: [{dice1: 1, dice2: 2}, {dice1: 2, dice2: 1}]})
  141.         standardDiceDeck.push({totalDice: 4, dicePairs: [{dice1: 1, dice2: 3}, {dice1: 2, dice2: 2}, {dice1: 3, dice2: 1}]})
  142.         standardDiceDeck.push({totalDice: 5, dicePairs: [{dice1: 1, dice2: 4}, {dice1: 2, dice2: 3}, {dice1: 3, dice2: 2}, {dice1: 4, dice2: 1}]})
  143.         standardDiceDeck.push({totalDice: 6, dicePairs: [{dice1: 1, dice2: 5}, {dice1: 2, dice2: 4}, {dice1: 3, dice2: 3}, {dice1: 4, dice2: 2}, {dice1: 5, dice2: 1}]})
  144.         standardDiceDeck.push({totalDice: 7, dicePairs: [{dice1: 1, dice2: 6}, {dice1: 2, dice2: 5}, {dice1: 3, dice2: 4}, {dice1: 4, dice2: 3}, {dice1: 5, dice2: 2}, {dice1: 6, dice2: 1}]})
  145.         standardDiceDeck.push({totalDice: 8, dicePairs: [{dice1: 2, dice2: 6}, {dice1: 3, dice2: 5}, {dice1: 4, dice2: 4}, {dice1: 5, dice2: 3}, {dice1: 6, dice2: 2}]})
  146.         standardDiceDeck.push({totalDice: 9, dicePairs: [{dice1: 3, dice2: 6}, {dice1: 4, dice2: 5}, {dice1: 5, dice2: 4}, {dice1: 6, dice2: 3}]})
  147.         standardDiceDeck.push({totalDice: 10, dicePairs: [{dice1: 4, dice2: 6}, {dice1: 5, dice2: 5}, {dice1: 6, dice2: 4}]})
  148.         standardDiceDeck.push({totalDice: 11, dicePairs: [{dice1: 5, dice2: 6}, {dice1: 6, dice2: 5}]})
  149.         standardDiceDeck.push({totalDice: 12, dicePairs: [{dice1: 6, dice2: 6}]})
  150.  
  151.         return standardDiceDeck
  152.     }
  153. }
  154.  
  155. export function randomElementFromArray<T>(array: T[]): T {
  156.     return array[Math.floor(Math.random() * array.length)]
  157. }
  158.  
  159. export function removeElementFromArray<T>(array: T[], element: T): boolean {
  160.     const index = array.indexOf(element)
  161.     if(index > -1) {
  162.         array.splice(index, 1)
  163.         return true
  164.     }
  165.     return false
  166. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement