DEV Community

Discussion on: Daily Challenge #8 - Scrabble Word Calculator

Collapse
 
wolverineks profile image
Kevin Sullivan • Edited

codesandbox.io/s/daily-challenges-...

import { pipe, toUpper } from "ramda";

// CHARACTERS, POINTS, BONUSES //////////////////////////////////////
const A = { char: "A", points: 1 } as const;
const B = { char: "B", points: 3 } as const;
const C = { char: "C", points: 3 } as const;
const D = { char: "D", points: 2 } as const;
const E = { char: "E", points: 1 } as const;
const F = { char: "F", points: 4 } as const;
const G = { char: "G", points: 2 } as const;
const H = { char: "H", points: 4 } as const;
const I = { char: "I", points: 1 } as const;
const J = { char: "J", points: 8 } as const;
const K = { char: "K", points: 5 } as const;
const L = { char: "L", points: 1 } as const;
const M = { char: "M", points: 3 } as const;
const N = { char: "N", points: 1 } as const;
const O = { char: "O", points: 1 } as const;
const P = { char: "P", points: 3 } as const;
const Q = { char: "Q", points: 10 } as const;
const R = { char: "R", points: 1 } as const;
const S = { char: "S", points: 1 } as const;
const T = { char: "T", points: 1 } as const;
const U = { char: "U", points: 1 } as const;
const V = { char: "V", points: 4 } as const;
const W = { char: "W", points: 4 } as const;
const X = { char: "X", points: 8 } as const;
const Y = { char: "Y", points: 4 } as const;
const Z = { char: "Z", points: 10 } as const;
// prettier-ignore
const charMap = {Q,W,E,R,T,Y,U,I,O,P,A,S,D,F,G,H,J,K,L,Z,X,C,V,B,N,M} as const;
const tripleWordBonus = "(T)";
const doubleWordBonus = "(D)";
const tripleLetterBonus = "**";
const doubleLetterBonus = "*";

// TYPES ////////////////////////////////////////////////////////////
type ValueOf<T> = T[keyof T];
type Char = ValueOf<typeof charMap>;
type LetterTile = { type: "LETTER" };
type BlankTile = { type: "BLANK" };
type Square = { letterBonus: 1 | 2 | 3; wordBonus: 1 | 2 | 3 };

type Tile = ((LetterTile) | BlankTile) & Char & Square;

// CONVERT STRING TO TILES //////////////////////////////////////////
const REGEX = /[A-Z](\*|\^|\((T|D)\))*/g; // https://bit.ly/31LZ0mZ
const parse = (input: string): Tile[] => {
  const strings: string[] = input.match(REGEX) || [];
  return strings.map(toTile);
};

const type = (input: string) => (input.includes("^") ? "BLANK" : "LETTER");
const letterAndPoints = (char: Tile["char"]) => charMap[char];
const letterBonus = (input: string) => {
  if (input.includes(tripleLetterBonus)) return 3;
  if (input.includes(doubleLetterBonus)) return 2;
  return 1;
};
const wordBonus = (input: string) =>
  input.includes(tripleWordBonus) ? 3 : input.includes(doubleWordBonus) ? 2 : 1;
const toTile = (input: string): Tile => {
  const chars = input.split("");
  const [char, ...rest] = chars;
  const modifiers = rest.join("");
  return {
    type: type(modifiers),
    ...letterAndPoints(char),
    letterBonus: letterBonus(modifiers),
    wordBonus: wordBonus(modifiers)
  };
};

// CALCULATE SCORE //////////////////////////////////////////////////
const letterPoints = (tiles: Tile[]) =>
  tiles.reduce(
    (result, { type, char, letterBonus }) =>
      type === "BLANK" ? result : result + charMap[char].points * letterBonus,
    0
  );
const wordBonusMultiplier = (tiles: Tile[]) =>
  tiles.reduce((result, { wordBonus }) => result * wordBonus, 1);
const BINGO = { length: 7, points: 50 };
const bingoPoints = (tiles: Tile[]) =>
  tiles.length === BINGO.length ? BINGO.points : 0;

const scoreTiles = (tiles: Tile[]) =>
  letterPoints(tiles) * wordBonusMultiplier(tiles) + bingoPoints(tiles);

// WHOLE ENCHILADA //////////////////////////////////////////////////
export const getScore = pipe(
  toUpper,
  parse,
  scoreTiles
);