All files / src/tokens TokenEffects.ts

98.61% Statements 71/72
92.31% Branches 24/26
100% Functions 21/21
98.44% Lines 63/64

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 2291x 1x 1x   1x             1x       50x 35x   15x                       1x 434x                           1x 3x 3x   3x 3x   3x                     1x 3x 5x 5x     3x 3x   3x                       1x 8x 8x 58x   8x 12x   8x                     1x 373x                     1x 379x                       1x 349x 1x   348x                     40x 40x 98x 33x   65x 3x   62x     62x 3x   59x 4x   55x                                 155x   249x 5x   241x 4x   146x 237x                               41x     41x 129x 129x     1x   1x                                
import { Autofail } from "./Autofail";
import { Modifier } from "./Modifier";
import { Token } from "./Token";
import { TokenEffect } from "./TokenEffect";
import { TokenEffectType } from "./TokenEffectType";
 
type TokenEffectMapping = [Token, TokenEffect];
 
/**
 * A immutable mapping of token effects.
 */
export class TokenEffects {
  private _map: Map<Token, TokenEffect>;
 
  constructor(mappings?: TokenEffectMapping[]) {
    if (mappings) {
      this._map = new Map(mappings);
    } else {
      this._map = new Map<Token, TokenEffect>();
    }
  }
 
  /**
   * Get the effect of the specified token.
   *
   * @param {Token} token
   *   The token from which we want the effect.
   * @return {TokenEffect}
   *   The effect of the specified token.
   */
  public getEffect(token: Token): TokenEffect {
    return this._map.get(token);
  }
 
  /**
   * Sets the effect for the specified token and returns a new mapping (the
   * original mapping is untouched).
   *
   * @param {Token} token
   *   The token to set the effect for.
   * @param {TokenEffect} effect
   *   The effect.
   * @return {TokenEffects}
   *   The whole mapping.
   */
  public setEffect(token: Token, effect: TokenEffect): TokenEffects {
    const newMap = new Map(this._map);
    newMap.set(token, effect);
 
    const newEffects = new TokenEffects();
    newEffects._map = newMap;
 
    return newEffects;
  }
 
  /**
   * Sets multiple effects at once a returns a new mapping.
   *
   * @param {TokenEffectMapping[]} mappings
   *   An array of token/effect pairs.
   * @return {TokenEffects}
   *   A new mapping.
   */
  public setEffects(mappings: TokenEffectMapping[]): TokenEffects {
    const newMap = new Map(this._map);
    mappings.forEach(([token, effect]) => {
      newMap.set(token, effect);
    });
 
    const newEffects = new TokenEffects();
    newEffects._map = newMap;
 
    return newEffects;
  }
 
  /**
   * Constructs a new token effects map by adding or overriding effects from
   * another token effects map.
   *
   * @param {TokenEffects}
   *   The token effects map to add.
   * @return {TokenEffects}
   *   A new token effects map.
   */
  public merge(otherEffects: TokenEffects): TokenEffects {
    const newTokenEffects = new TokenEffects();
    this._map.forEach((effect, token) => {
      newTokenEffects._map.set(token, effect);
    });
    otherEffects._map.forEach((effect, token) => {
      newTokenEffects._map.set(token, effect);
    });
    return newTokenEffects;
  }
 
  /**
   * Indicate of the specified token is autosuccess for this bag.
   *
   * @method isTokenAutoSuccess
   * @param {Token} token
   *   The token.
   * @return {boolean} `true` if the token is autosuccess, `false` otherwise.
   */
  public isTokenAutoSuccess(token: Token): boolean {
    return this._map.get(token).getOutcome() === TokenEffectType.AUTOSUCCESS;
  }
 
  /**
   * Indicate of the specified token is autofail for this bag.
   *
   * @method isTokenAutoFail
   * @param {Token} token
   *   The token.
   * @return {boolean} `true` if the token is autofail, `false` otherwise.
   */
  public isTokenAutoFail(token: Token): boolean {
    return this._map.get(token).getOutcome() === TokenEffectType.AUTOFAIL;
  }
 
  /**
   * Get the modifier value of the specified token for this bag.
   *
   * @method getTokenModifier
   * @param {Token} token
   *   The token.
   * @return {number} The modifier value.
   * @throws {TypeError} if the specified token is an autosuccess or an autofail.
   */
  public getTokenModifier(token: Token): number {
    if (this._map.get(token).getOutcome() !== TokenEffectType.MODIFIER) {
      throw new TypeError(`token ${token} is not a modifier`);
    }
    return (this._map.get(token) as Modifier).getValue();
  }
 
  /**
   * Sort provided tokens from best outcome to worse.
   *
   * @method sortByBestOutcomeDesc
   * @param {Token[]} tokens
   *   The tokens to sort.
   * @return {Token[]} Sorted tokens.
   */
  public sortByBestOutcomeDesc(tokens: Token[]): Token[] {
    return tokens.sort((tokenA: Token, tokenB: Token) => {
      if (tokenA === tokenB) {
        return 0;
      }
      if (this.isTokenAutoSuccess(tokenA) && !this.isTokenAutoSuccess(tokenB)) {
        return -1;
      }
      Iif (this.isTokenAutoSuccess(tokenB) && !this.isTokenAutoSuccess(tokenA)) {
        return 1;
      }
      if (this.isTokenAutoFail(tokenA) && !this.isTokenAutoFail(tokenB)) {
        return 1;
      }
      if (this.isTokenAutoFail(tokenB) && !this.isTokenAutoFail(tokenA)) {
        return -1;
      }
      return this.getTokenModifier(tokenB) - this.getTokenModifier(tokenA);
    });
  }
 
  /**
   * Return wether or not pulling these tokens result in a success given the
   * difference between to total skill value and the test difficulty. If
   * several tokens are provided and none of them are autosuccess or autofails,
   * their modifier values are added.
   *
   * @method isSuccess
   * @param {Token[]} tokens
   *   The tokens pulled from the bag.
   * @param {number} skillMinusDifficulty
   *   The difference between the total skill value and the difficulty fo the
   *   test.
   */
  public isSuccess(tokens: Token[], skillMinusDifficulty: number): boolean {
    // Autofail prevails
    if (tokens.some(t => this.isTokenAutoFail(t))) {
      return false;
    }
    if (tokens.some(t => this.isTokenAutoSuccess(t))) {
      return true;
    }
    return (
      tokens.reduce((sum, t) => sum + this.getTokenModifier(t), 0) +
        skillMinusDifficulty >=
      0
    );
  }
 
  /**
   * Separate redraw tokens from non-redraw tokens in the supplied array of
   * tokens.
   *
   * @param {Token[]} tokens
   *   The tokens to separate.
   * @return {{ redrawTokens: Token[]; nonRedrawTokens: Token[] }}
   *   An object with `redrawTokens` containing the redraw tokens and
   *   `nonRedrawTokens` containing non-redraw tokens.
   */
  public separateRedrawTokens(
    tokens: Token[]
  ): { redrawTokens: Token[]; nonRedrawTokens: Token[] } {
    return {
      nonRedrawTokens: tokens.filter(t => !this.getEffect(t).isRedraw()),
      redrawTokens: tokens.filter(t => this.getEffect(t).isRedraw())
    };
  }
}
 
export const DefaultTokenEffects: TokenEffects = new TokenEffects([
  [Token.PLUS_ONE, new Modifier(1)],
  [Token.PLUS_ONE, new Modifier(1)],
  [Token.ZERO, new Modifier(0)],
  [Token.MINUS_ONE, new Modifier(-1)],
  [Token.MINUS_TWO, new Modifier(-2)],
  [Token.MINUS_THREE, new Modifier(-3)],
  [Token.MINUS_FOUR, new Modifier(-4)],
  [Token.MINUS_FIVE, new Modifier(-5)],
  [Token.MINUS_SIX, new Modifier(-6)],
  [Token.MINUS_SEVEN, new Modifier(-7)],
  [Token.MINUS_EIGHT, new Modifier(-8)],
  [Token.AUTOFAIL, new Autofail()],
  [Token.BLESS, new Modifier(2, true)],
  [Token.CURSE, new Modifier(-2, true)]
]);