Poker hands

AUTHOR

Andrei Osipov

https://projecteuler.net/problem=54

The file, poker.txt, contains one-thousand random hands dealt to two players. Each line of the file contains ten cards (separated by a single space): the first five are Player 1's cards and the last five are Player 2's cards. You can assume that all hands are valid (no invalid characters or repeated cards), each player's hand is in no specific order, and in each hand there is a clear winner.

How many hands does Player 1 win?

use v6;



enum Rank <
    Two Three Four Five
    Six Seven Eight Nine
    Ten Jack Queen King Ace
>;

enum Suit <
    Hearts Diamonds Clubs Spades
>;

enum Hand <
        RoyalFlush StraightFlush FourOfKind
        FullHouse Flush Straight ThreeOfKind
        TwoPairs OnePair HighCard
>;

multi counts(Positional $h) {
    bag($h).invert
}

multi strigify(Hash $x) {
    join ' and ', do for $x.kv -> $k, $v {
        "$k of $v"
    }
}

class Card {
    has Rank  $.rank;
    has Suit  $.suit;

    method parse-rank(Str $r) returns Rank {
        given $r {
            when /\d/ { Rank($r.Int - 2) }
            when /T/  { Ten }
            when /J/  { Jack }
            when /Q/  { Queen }
            when /K/  { King }
            when /A/  { Ace }
        }
    }
    method parse-suit(Str $s) returns Suit {
        given $s {
            when /H/ { Hearts }
            when /D/ { Diamonds }
            when /C/ { Clubs }
            when /S/ { Spades }
        }
    }
    multi method CALL-ME(Str $c where $c.chars == 2)  {
        my ($r, $s) = $c.comb;
        self.new(rank => Card.parse-rank($r),
                 suit => Card.parse-suit($s));
    }
    multi method CALL-ME(Rank $v, Suit $s) {
        self.new(rank => $v, suit => $s)
    }
}

multi infix:Ā«<=>Ā»(Card $a, Card $b) is export returns Order {
    $a.rank <=> $b.rank
}


class Deal {
    subset Ranks where -> $r {
        $r ~~ Rank || $r ~~ Array[Rank]
    };

    has Card   @.cards;
    has Ranks  %.score{Hand};

    method CALL-ME(Str $h) {
        my $x = self.new(
            cards => map { Card($_) } , $h.split: /\s/
        );
        $x.score = $x!best-hand;
        $x;
    }
    method ACCEPTS(Hand $h) {
        so %.score{$h};
    }
    method !best-hand {
        self!royal-flush
        // self!straight-flush
        // self!full-house
        // self!flush
        // self!straight
        // self!four-of-kind
        // self!three-of-kind
        // self!two-pairs
        // self!one-pair
        // self!high-card
    }

    method !straight {
        my @v = @.cardsĀ».rank.sort;
        if @v eq (@v.min ... @v.max).map({Rank($_)}) {
            (Straight) => @v.max
        }
    }

    method !flush {
        if [~~] @.cardsĀ».suit {
            (Flush) => Array[Rank].new: |@.cardsĀ».rank;
        }
    }

    method !royal-flush {
        if self!flush && self!straight && @.cardsĀ».rank.max ~~ Ace {
            (RoyalFlush) => Ace
        }
    }

    method !straight-flush {
        if self!flush && my $s = self!straight  {
            (StraightFlush) => $s.value
        }
    };
    method !four-of-kind {
        # Four cards of the same value.
        my @ranks = @.cardsĀ».rank;
        my @four  = @ranks.&counts.grep(*.key == 4);
        if so @four {
            (FourOfKind) => my $x = @four[0].value,
            (HighCard)   => max grep { $_ !~~ $x }, @ranks
        }
    }
    method !full-house {
        # Three of a kind and a pair.
        my Ranks %x{Hand} = flat self!three-of-kind , self!one-pair;
        if %x{ThreeOfKind}.defined && %x{OnePair}.defined {
            (FullHouse) => Ace
        }
    }

    method !three-of-kind {
        my $rank = @.cardsĀ».rank.&counts.grep(*.key == 3)[0];

        if $rank {
            my Ranks %h{Hand} = (ThreeOfKind) =>  my $x = $rank.value;

            if my $one-pair = @.cardsĀ».rank.&counts.grep(*.key == 2)[0] {
                %h{OnePair}  =  $one-pair.value;
            }
            else {
                %h{HighCard} = max grep { $_ !~~ $x }, @.cardsĀ».rank;
            }
            %h;
        }
    }

    method !two-pairs {
        my @pairs = @.cardsĀ»\
        .rank.&counts\
        .sort(*.key).grep(*.key == 2);
        if +@pairs == 2 {
            (OnePair)   => my $x= @pairsĀ».value.min,
            (TwoPairs)  => my $y= @pairsĀ».value.max,
            (HighCard)  => max grep { $_ !~~ $x | $y },@.cardsĀ».rank;
        }
    }

    method !one-pair {
        my $pair = @.cardsĀ»\
        .rank.&counts\
        .sort(*.key).grep(*.key == 2)[0];
        if $pair {
            (OnePair)  => my $x = $pair.value,
            (HighCard) => max grep { $_ !~~ $x}, @.cardsĀ».rank;
        }
    }

    method !high-card {
        (HighCard) => @.cardsĀ».rank.max;
    }
}

multi infix:Ā«<=>Ā»(Deal $a, Deal $b) returns Order {
    for Hand.enums.sort(*.value).keys.map({Hand($_)}) -> $h {
        return More if $a.score{$h}.defined && !$b.score{$h}.defined;
        return Less if $b.score{$h}.defined && !$a.score{$h}.defined;
        next unless $a.score{$h}.defined & $b.score{$h}.defined;

        if $a.score{$h} & $b.score{$h} ~~ List {
            my $cmp = max $a.score{$h} Z<=> $b.score{$h};
            return Less if $cmp ~~ Less;
            return More if $cmp ~~ More;
        }

        my $cmp =  $a.score{$h} <=> $b.score{$h};

        next if $cmp ~~ Same;
        return $cmp;
    }
    Same;
}

sub MAIN(Bool :$verbose    = False,
         Bool :$run-tests  = False,
         :$file   = $*SPEC.catdir($*PROGRAM-NAME.IO.dirname, 'poker.txt'),
         :$lines  = Inf, # read only X lines from file
        ) {
    die "'$file' is missing" unless $file.IO.e ;
    return TEST if $run-tests;

    say [+] gather for $file.IO.lines[^$lines] -> $line is copy {
        $line ~~ s:nth(5)/\s/;/;
        my ($h1,$h2) = $line.split: /';'/;
        my $d1 = Deal($h1);
        my $d2 = Deal($h2);
        if $d1 <=> $d2 ~~ More {
            say "player1 wins on $line \n\twith {$d1.score.&strigify} against {$d2.score.&strigify} " if $verbose ;
            take 1;
        }
    }

}

sub TEST {
    use Test;
    ok Card("TC") <=> Card("TD") ~~ Same, "cards are equal if ranks are equal ";
    ok Card("2C") <=> Card("AC") ~~ Less, "2C < AC";
    ok (Straight ~~ Deal("5H 6C 7S 8D 9D") )   &&
    (Straight !~~ Deal("2H 6C 7S 8D 9D"))  , "Detects straight";
    ok (Flush ~~ Deal("5H 7H 8H AH TH")) &&
    (Flush !~~ Deal("5H 7H 8H AC TH")), "Detects flush ";
    ok RoyalFlush ~~ Deal("TH JH QH KH AH") , "Detects royal flush ";
    ok Deal("5H 5C 6S 7S KD") <=> Deal("2C 3S 8S 8D TD") ~~ Less,"Player 2 wins [1]";
    ok Deal("5D 8C 9S JS AC") <=> Deal("2C 5C 7D 8S QH") ~~ More, "Player 1 wins [2]";
    ok Deal("2D 9C AS AH AC") <=> Deal("3D 6D 7D TD QD") ~~ Less, "Player 2 wins [3]";
    ok Deal("4D 6S 9H QH QC") <=> Deal("3D 6D 7H QD QS") ~~ More, "Player 1 wins [4]";
    ok Deal("2H 2D 4C 4D 4S") <=> Deal("3C 3D 3S 9S 9D") ~~ Same, "Nobody wins [5]";
    ok Deal("7C 5H KC QH JD") <=> Deal("AS KH 4C AD 4S") ~~ Less, "Player 2 wins [6]";
    ok Deal("KS KC 9S 6D 2C") <=> Deal("QH 9D 9H TS TC") ~~ Less, "Problem [1]";
    ok Deal("TS QH 6C 8H TH") <=> Deal("5H 3C 3H 9C 9D") ~~ Less, "Problem [2]";
    ok Deal("AH QC JC 4C TC") <=> Deal("8C 2H TS 2C 7D") ~~ Less, "Problem [3]";
    ok Deal("7C KS 6S 5S 2S") <=> Deal("2D TC 2H 5H QS") ~~ Less, "Problem [4]";
    ok Deal("JC TH 4S 6S JD") <=> Deal("2D 4D 6C 3D 4C") ~~ More, "Problem [5]";
    done;
}

# vim: expandtab shiftwidth=4 ft=perl6

See Also

prob001-cspencer.raku

Multiples of 3 and 5

prob001-eric256.raku

Multiples of 3 and 5

prob001-grondilu.raku

Multiples of 3 and 5

prob001-hexmode.raku

Multiples of 3 and 5

prob001-unobe.raku

Multiples of 3 and 5

prob002-eric256.raku

Even Fibonacci numbers

prob002-gerdr.raku

Even Fibonacci numbers

prob002-hexmode.raku

Even Fibonacci numbers

prob003-eric256.raku

Largest prime factor

prob003-gerdr.raku

Largest prime factor

prob003-hexmode.raku

Largest prime factor

prob003-lanny.raku

Largest prime factor

prob004-unobe.raku

Largest palindrome product

prob004-xfix.raku

Largest palindrome product

prob005-unobe.raku

Smallest multiple

prob005-xfix.raku

Smallest multiple

prob006-polettix.raku

Sum square difference

prob007-polettix.raku

10001st prime

prob008-duff.raku

Largest product in a series

prob008-duff2.raku

Largest product in a series

prob009-gerdr-feeds.raku

Special Pythagorean triplet

prob009-gerdr.raku

Special Pythagorean triplet

prob009-polettix.raku

Special Pythagorean triplet

prob010-polettix.raku

Summation of primes

prob011-moritz.raku

Largest product in a grid

prob012-polettix.raku

Highly divisible triangular number

prob013-grondilu.raku

Large sum

prob014-felher.raku

Longest Collatz sequence

prob015-felher.raku

Lattice paths

prob016-grondilu.raku

Power digit sum

prob017-duff.raku

Number letter counts

prob018-felher.raku

Maximum path sum I

prob019-grondilu.raku

Counting Sundays

prob020-grondilu.raku

Factorial digit sum

prob021-gerdr.raku

Amicable numbers

prob022-grondilu.raku

Names scores

prob023-shlomif.raku

Non-abundant sums

prob024-moritz.raku

Lexicographic permutations

prob025-polettix.raku

1000-digit Fibonacci number

prob026-shlomif.raku

Reciprocal cycles

prob027-shlomif.raku

Quadratic primes

prob028-shlomif.raku

Number spiral diagonals

prob029-gerdr.raku

Distinct powers

prob029-polettix.raku

Distinct powers

prob031-shlomif.raku

Coin sums

prob033-andreoss.raku

Digit cancelling fractions

prob034-quinny.raku

Digit factorials

prob036-xenu.raku

Double-base palindromes

prob038-andreoss.raku

Pandigital multiples

prob039-quinny.raku

Integer right triangles

prob041-heyajulia-alternative.raku

Pandigital Prime

prob041-heyajulia.raku

Pandigital Prime

prob042-shlomif.raku

Coded triangle numbers

prob047-gerdr.raku

Distinct primes factors

prob052-duff.raku

Permuted multiples

prob053-duff.raku

Combinatoric selections

prob053-gerdr.raku

Combinatoric selections

prob055-shlomif.raku

Lychrel numbers

prob056-shlomif.raku

prob059-andreoss.raku

XOR decryption

prob063-moritz.raku

Powerful digit counts

prob063-polettix.raku

Powerful digit counts

prob065-andreoss.raku

Convergents of e

prob065-grondilu.raku

prob066-andreoss.raku

Diophantine equation

prob067-felher.raku

Maximum path sum II

prob080-andreoss.raku

Square root digital expansion

prob081-moritz.raku

Path sum: two ways

prob089-andreoss.raku

Roman numerals

prob092-moritz.raku

Square digit chains

prob097-andreoss.raku

Large non-Mersenne prime

prob098-andreoss.raku

Anagramic squares

prob099-andreoss.raku

Largest exponential

README.md

The Camelia image is copyright 2009 by Larry Wall. "Raku" is trademark of the Yet Another Society. All rights reserved.

Built with Podlite — the markup and publishing tools behind this site.