/* * Copyright (C) 2017 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ "use strict"; class CardDeck { constructor() { this.newDeck(); } newDeck() { // Make a shallow copy of a new deck this._cards = CardDeck._newDeck.slice(0); } shuffle() { this.newDeck(); for (let index = 52; index !== 0;) { // Select a random card let randomIndex = Math.floor(Math.random() * index); index--; // Swap the current card with the random card let tempCard = this._cards[index]; this._cards[index] = this._cards[randomIndex]; this._cards[randomIndex] = tempCard; } } dealOneCard() { return this._cards.shift(); } static cardRank(card) { // This returns a numeric value for a card. // Ace is highest. let rankOfCard = card.codePointAt(0) & 0xf; if (rankOfCard == 0x1) // Make Aces higher than Kings rankOfCard = 0xf; return rankOfCard; } static cardName(card) { if (typeof(card) == "string") card = card.codePointAt(0); return this._rankNames[card & 0xf]; } } CardDeck._rankNames = [ "", "Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "", "Queen", "King" ]; CardDeck._newDeck = [ // Spades "\u{1f0a1}", "\u{1f0a2}", "\u{1f0a3}", "\u{1f0a4}", "\u{1f0a5}", "\u{1f0a6}", "\u{1f0a7}", "\u{1f0a8}", "\u{1f0a9}", "\u{1f0aa}", "\u{1f0ab}", "\u{1f0ad}", "\u{1f0ae}", // Hearts "\u{1f0b1}", "\u{1f0b2}", "\u{1f0b3}", "\u{1f0b4}", "\u{1f0b5}", "\u{1f0b6}", "\u{1f0b7}", "\u{1f0b8}", "\u{1f0b9}", "\u{1f0ba}", "\u{1f0bb}", "\u{1f0bd}", "\u{1f0be}", // Clubs "\u{1f0d1}", "\u{1f0d2}", "\u{1f0d3}", "\u{1f0d4}", "\u{1f0d5}", "\u{1f0d6}", "\u{1f0d7}", "\u{1f0d8}", "\u{1f0d9}", "\u{1f0da}", "\u{1f0db}", "\u{1f0dd}", "\u{1f0de}", // Diamonds "\u{1f0c1}", "\u{1f0c2}", "\u{1f0c3}", "\u{1f0c4}", "\u{1f0c5}", "\u{1f0c6}", "\u{1f0c7}", "\u{1f0c8}", "\u{1f0c9}", "\u{1f0ca}", "\u{1f0cb}", "\u{1f0cd}", "\u{1f0ce}" ]; class Hand { constructor() { this.clear(); } clear() { this._cards = []; this._rank = 0; } takeCard(card) { this._cards.push(card); } score() { // Sort highest rank to lowest this._cards.sort((a, b) => { return CardDeck.cardRank(b) - CardDeck.cardRank(a); }); let handString = this._cards.join(""); let flushResult = handString.match(Hand.FlushRegExp); let straightResult = handString.match(Hand.StraightRegExp); let ofAKindResult = handString.match(Hand.OfAKindRegExp); if (flushResult) { if (straightResult) { if (straightResult[1]) this._rank = Hand.RoyalFlush; else this._rank = Hand.StraightFlush } else this._rank = Hand.Flush; this._rank |= CardDeck.cardRank(this._cards[0]) << 16 | CardDeck.cardRank(this._cards[1]) << 12; } else if (straightResult) this._rank = Hand.Straight | CardDeck.cardRank(this._cards[0]) << 16 | CardDeck.cardRank(this._cards[1]) << 12; else if (ofAKindResult) { // When comparing lengths, a matched unicode character has a length of 2. // Therefore expected lengths are doubled, e.g a pair will have a match length of 4. if (ofAKindResult[0].length == 8) this._rank = Hand.FourOfAKind | CardDeck.cardRank(this._cards[0]); else { // Found pair or three of a kind. Check for two pair or full house. let firstOfAKind = ofAKindResult[0]; let remainingCardsIndex = handString.indexOf(firstOfAKind) + firstOfAKind.length; let secondOfAKindResult; if (remainingCardsIndex <= 6 && (secondOfAKindResult = handString.slice(remainingCardsIndex).match(Hand.OfAKindRegExp))) { if ((firstOfAKind.length == 6 && secondOfAKindResult[0].length == 4) || (firstOfAKind.length == 4 && secondOfAKindResult[0].length == 6)) { let threeOfAKindCardRank; let twoOfAKindCardRank; if (firstOfAKind.length == 6) { threeOfAKindCardRank = CardDeck.cardRank(firstOfAKind.slice(0,2)); twoOfAKindCardRank = CardDeck.cardRank(secondOfAKindResult[0].slice(0,2)); } else { threeOfAKindCardRank = CardDeck.cardRank(secondOfAKindResult[0].slice(0,2)); twoOfAKindCardRank = CardDeck.cardRank(firstOfAKind.slice(0,2)); } this._rank = Hand.FullHouse | threeOfAKindCardRank << 16 | threeOfAKindCardRank < 12 | threeOfAKindCardRank << 8 | twoOfAKindCardRank << 4 | twoOfAKindCardRank; } else if (firstOfAKind.length == 4 && secondOfAKindResult[0].length == 4) { let firstPairCardRank = CardDeck.cardRank(firstOfAKind.slice(0,2)); let SecondPairCardRank = CardDeck.cardRank(secondOfAKindResult[0].slice(0,2)); let otherCardRank; // Due to sorting, the other card is at index 0, 4 or 8 if (firstOfAKind.codePointAt(0) == handString.codePointAt(0)) { if (secondOfAKindResult[0].codePointAt(0) == handString.codePointAt(4)) otherCardRank = CardDeck.cardRank(handString.slice(8,10)); else otherCardRank = CardDeck.cardRank(handString.slice(4,6)); } else otherCardRank = CardDeck.cardRank(handString.slice(0,2)); this._rank = Hand.TwoPair | firstPairCardRank << 16 | firstPairCardRank << 12 | SecondPairCardRank << 8 | SecondPairCardRank << 4 | otherCardRank; } } else { let ofAKindCardRank = CardDeck.cardRank(firstOfAKind.slice(0,2)); let otherCardsRank = 0; for (let card of this._cards) { let cardRank = CardDeck.cardRank(card); if (cardRank != ofAKindCardRank) otherCardsRank = (otherCardsRank << 4) | cardRank; } if (firstOfAKind.length == 6) this._rank = Hand.ThreeOfAKind | ofAKindCardRank << 16 | ofAKindCardRank << 12 | ofAKindCardRank << 8 | otherCardsRank; else this._rank = Hand.Pair | ofAKindCardRank << 16 | ofAKindCardRank << 12 | otherCardsRank; } } } else { this._rank = 0; for (let card of this._cards) { let cardRank = CardDeck.cardRank(card); this._rank = (this._rank << 4) | cardRank; } } } get rank() { return this._rank; } toString() { return this._cards.join(""); } } Hand.FlushRegExp = new RegExp("([\u{1f0a1}-\u{1f0ae}]{5})|([\u{1f0b1}-\u{1f0be}]{5})|([\u{1f0c1}-\u{1f0ce}]{5})|([\u{1f0d1}-\u{1f0de}]{5})", "u"); Hand.StraightRegExp = new RegExp("([\u{1f0a1}\u{1f0b1}\u{1f0d1}\u{1f0c1}][\u{1f0ae}\u{1f0be}\u{1f0de}\u{1f0ce}][\u{1f0ad}\u{1f0bd}\u{1f0dd}\u{1f0cd}][\u{1f0ab}\u{1f0bb}\u{1f0db}\u{1f0cb}][\u{1f0aa}\u{1f0ba}\u{1f0da}\u{1f0ca}])|[\u{1f0ae}\u{1f0be}\u{1f0de}\u{1f0ce}][\u{1f0ad}\u{1f0bd}\u{1f0dd}\u{1f0cd}][\u{1f0ab}\u{1f0bb}\u{1f0db}\u{1f0cb}][\u{1f0aa}\u{1f0ba}\u{1f0da}\u{1f0ca}][\u{1f0a9}\u{1f0b9}\u{1f0d9}\u{1f0c9}]|[\u{1f0ad}\u{1f0bd}\u{1f0dd}\u{1f0cd}][\u{1f0ab}\u{1f0bb}\u{1f0db}\u{1f0cb}][\u{1f0aa}\u{1f0ba}\u{1f0da}\u{1f0ca}][\u{1f0a9}\u{1f0b9}\u{1f0d9}\u{1f0c9}][\u{1f0a8}\u{1f0b8}\u{1f0d8}\u{1f0c8}]|[\u{1f0ab}\u{1f0bb}\u{1f0db}\u{1f0cb}][\u{1f0aa}\u{1f0ba}\u{1f0da}\u{1f0ca}][\u{1f0a9}\u{1f0b9}\u{1f0d9}\u{1f0c9}][\u{1f0a8}\u{1f0b8}\u{1f0d8}\u{1f0c8}][\u{1f0a7}\u{1f0b7}\u{1f0d7}\u{1f0c7}]|[\u{1f0aa}\u{1f0ba}\u{1f0da}\u{1f0ca}][\u{1f0a9}\u{1f0b9}\u{1f0d9}\u{1f0c9}][\u{1f0a8}\u{1f0b8}\u{1f0d8}\u{1f0c8}][\u{1f0a7}\u{1f0b7}\u{1f0d7}\u{1f0c7}][\u{1f0a6}\u{1f0b6}\u{1f0d6}\u{1f0c6}]|[\u{1f0a9}\u{1f0b9}\u{1f0d9}\u{1f0c9}][\u{1f0a8}\u{1f0b8}\u{1f0d8}\u{1f0c8}][\u{1f0a7}\u{1f0b7}\u{1f0d7}\u{1f0c7}][\u{1f0a6}\u{1f0b6}\u{1f0d6}\u{1f0c6}][\u{1f0a5}\u{1f0b5}\u{1f0d5}\u{1f0c5}]|[\u{1f0a8}\u{1f0b8}\u{1f0d8}\u{1f0c8}][\u{1f0a7}\u{1f0b7}\u{1f0d7}\u{1f0c7}][\u{1f0a6}\u{1f0b6}\u{1f0d6}\u{1f0c6}][\u{1f0a5}\u{1f0b5}\u{1f0d5}\u{1f0c5}][\u{1f0a4}\u{1f0b4}\u{1f0d4}\u{1f0c4}]|[\u{1f0a7}\u{1f0b7}\u{1f0d7}\u{1f0c7}][\u{1f0a6}\u{1f0b6}\u{1f0d6}\u{1f0c6}][\u{1f0a5}\u{1f0b5}\u{1f0d5}\u{1f0c5}][\u{1f0a4}\u{1f0b4}\u{1f0d4}\u{1f0c4}][\u{1f0a3}\u{1f0b3}\u{1f0d3}\u{1f0c3}]|[\u{1f0a6}\u{1f0b6}\u{1f0d6}\u{1f0c6}][\u{1f0a5}\u{1f0b5}\u{1f0d5}\u{1f0c5}][\u{1f0a4}\u{1f0b4}\u{1f0d4}\u{1f0c4}][\u{1f0a3}\u{1f0b3}\u{1f0d3}\u{1f0c3}][\u{1f0a2}\u{1f0b2}\u{1f0d2}\u{1f0c2}]|[\u{1f0a1}\u{1f0b1}\u{1f0d1}\u{1f0c1}][\u{1f0a5}\u{1f0b5}\u{1f0d5}\u{1f0c5}][\u{1f0a4}\u{1f0b4}\u{1f0d4}\u{1f0c4}][\u{1f0a3}\u{1f0b3}\u{1f0d3}\u{1f0c3}][\u{1f0a2}\u{1f0b2}\u{1f0d2}\u{1f0c2}]", "u"); Hand.OfAKindRegExp = new RegExp("(?:[\u{1f0a1}\u{1f0b1}\u{1f0d1}\u{1f0c1}]{2,4})|(?:[\u{1f0ae}\u{1f0be}\u{1f0de}\u{1f0ce}]{2,4})|(?:[\u{1f0ad}\u{1f0bd}\u{1f0dd}\u{1f0cd}]{2,4})|(?:[\u{1f0ab}\u{1f0bb}\u{1f0db}\u{1f0cb}]{2,4})|(?:[\u{1f0aa}\u{1f0ba}\u{1f0da}\u{1f0ca}]{2,4})|(?:[\u{1f0a9}\u{1f0b9}\u{1f0d9}\u{1f0c9}]{2,4})|(?:[\u{1f0a8}\u{1f0b8}\u{1f0d8}\u{1f0c8}]{2,4})|(?:[\u{1f0a7}\u{1f0b7}\u{1f0d7}\u{1f0c7}]{2,4})|(?:[\u{1f0a6}\u{1f0b6}\u{1f0d6}\u{1f0c6}]{2,4})|(?:[\u{1f0a5}\u{1f0b5}\u{1f0d5}\u{1f0c5}]{2,4})|(?:[\u{1f0a4}\u{1f0b4}\u{1f0d4}\u{1f0c4}]{2,4})|(?:[\u{1f0a3}\u{1f0b3}\u{1f0d3}\u{1f0c3}]{2,4})|(?:[\u{1f0a2}\u{1f0b2}\u{1f0d2}\u{1f0c2}]{2,4})", "u"); Hand.RoyalFlush = 0x900000; Hand.StraightFlush = 0x800000; Hand.FourOfAKind = 0x700000; Hand.FullHouse = 0x600000; Hand.Flush = 0x500000; Hand.Straight = 0x400000; Hand.ThreeOfAKind = 0x300000; Hand.TwoPair = 0x200000; Hand.Pair = 0x100000; class Player extends Hand { constructor(name) { super(); this._name = name; this._wins = 0; this._handTypeCounts = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; } scoreHand() { this.score(); let handType = this.rank >> 20; this._handTypeCounts[handType]++; } wonHand() { this._wins++ } get name() { return this._name; } get hand() { return super.toString(); } get wins() { return this._wins; } get handTypeCounts() { return this._handTypeCounts; } } function playHands(players) { let cardDeck = new CardDeck(); let handsPlayed = 0; let highestRank = 0; do { cardDeck.shuffle(); for (let player of players) player.clear(); for (let i = 0; i < 5; i++) { for (let player of players) player.takeCard(cardDeck.dealOneCard()); } for (let player of players) player.scoreHand(); handsPlayed++; highestRank = 0; for (let player of players) { if (player.rank > highestRank) highestRank = player.rank; } for (let player of players) { // We count ties as wins for each player. if (player.rank == highestRank) player.wonHand(); } } while (handsPlayed < 2000); }