ポーカーの役を判定する

TypeScriptの練習がてら、ポーカーの役を判定するプログラムを書きました。

```poker_checker.ts
// Setup

export enum Suit {
  spade,
  heart,
  club,
  diamond
}

export class Card {
  suit: Suit
  numeral: number

  constructor(suit: Suit, numeral: number) {
      this.suit = suit
      this.numeral = numeral
  }
}

const cardsLength = 5
// 実際のプログラムならユーザーからの入力を代入する形になる
const cards: Card[] = [
  new Card(Suit.heart, 8),
  new Card(Suit.club, 10),
  new Card(Suit.spade, 7),
  new Card(Suit.spade, 10),
  new Card(Suit.diamond, 8)
]
customSort(cards)

// Functions

export function customSort(cards: Card[]) {
  cards.sort(function (c1, c2) {
    if (c1.numeral < c2.numeral) return -1
    if (c1.numeral > c2.numeral) return 1
    if (c1.suit < c2.suit) return -1
    if (c1.suit > c2.suit) return 1
    return 0
  })
}

export function validate(cards: Card[]): Error | null {
  if (cards.length !== cardsLength) throw new Error("CardsLength is not 5.")
  if (!cards.every(c => c.numeral >= 1 && c.numeral <= 13)) throw new Error("Some numeral is not within 1-13.")
  for (let i = 0; i < cardsLength - 1; i++) if (JSON.stringify(cards[i]) === JSON.stringify(cards[i + 1])) throw new Error("There are duplicate cards.")
  return null
}

export function isStraight(cards: Card[]): boolean {
  const numerals = cards.map(c => c.numeral)
  if (numerals.includes(1) && numerals.includes(10)) {
      return numerals.includes(11) && numerals.includes(12) && numerals.includes(13)
  } else {
      return numerals.every((n, index) => cards[0].numeral === (n - index))
  }
}

export function isFlush(cards: Card[]): boolean {
  return cards.every(c => cards[0].suit === c.suit)
}

export function isStraightFlush(cards: Card[]): boolean {
  return isFlush(cards) && isStraight(cards)
}

export function isRoyalFlush(cards: Card[]): boolean {
  const numerals = cards.map(c => c.numeral)
  return isStraightFlush(cards) && numerals.includes(1) && numerals.includes(10)
}

// どの数字が何枚含まれているかを数える
export function counter(cards: Card[]): { [key: number]: number } {
  let counter: { [key: number]: number } = {}
  for (let i = 0; i < cards.length; i++) {
      const n = cards[i].numeral
      if (Object.keys(counter).includes(String(n))) {
          counter[n] += 1
      } else {
          counter[n] = 1
      }
  }
  return counter
}

export function isFourOfAKind(cards: Card[]): boolean {
  return Object.values(counter(cards)).includes(4)
}

export function isFullHouse(cards: Card[]): boolean {
  return Object.values(counter(cards)).includes(2) && Object.values(counter(cards)).includes(3)
}

export function isThreeOfAKind(cards: Card[]): boolean {
  return Object.values(counter(cards)).includes(3)
}

export function isTwoPair(cards: Card[]): boolean {
  return Object.values(counter(cards)).includes(2) && Object.keys(counter(cards)).length === 3
}

export function isOnePair(cards: Card[]): boolean {
  return Object.values(counter(cards)).includes(2) && Object.keys(counter(cards)).length === 4
}

export function isHighCard(cards: Card[]): boolean {
  return Object.keys(counter(cards)).length === cardsLength
}

// Execution

console.log(cards) // e.g [ { "suit": 0, "numeral": 7 }, { "suit": 1, "numeral": 8 }, { "suit": 3, "numeral": 8 }, { "suit": 0, "numeral": 10 }, { "suit": 2, "numeral": 10 } ] 
console.log(counter(cards)) // e.g. { "7": 1, "8": 2, "10": 2 }
try {
  validate(cards)
  if (isRoyalFlush(cards)) console.log("isRoyalFlush")
  else if (isStraightFlush(cards)) console.log("isStraightFlush")
  else if (isFourOfAKind(cards)) console.log("isFourOfAKind")
  else if (isFullHouse(cards)) console.log("isFullHouse")
  else if (isFlush(cards)) console.log("isFlush")
  else if (isStraight(cards)) console.log("isStraight")
  else if (isThreeOfAKind(cards)) console.log("isThreeOfAKind")
  else if (isTwoPair(cards)) console.log("isTwoPair") // この例ではisTwoPairが出力される
  else if (isOnePair(cards)) console.log("isOnePair")
  else if (isHighCard(cards)) console.log("isHighCard")
  else throw new Error("No hands.")
} catch (e) {
  console.log(e)
}

```