###########################################
# Blackjack.pm
# Mike Schilli, 2003 (m@perlmeister.com)
###########################################
use warnings; use strict;

#==========================================
package Blackjack::Shoe; #=================
#==========================================

use Algorithm::GenerateSequence;
use Algorithm::Numerical::Shuffle 
    qw(shuffle);

###########################################
sub new {
###########################################
    my($class, @options) = @_;

    my $self = {nof_decks => 1, @options};

    bless $self, $class;
    $self->reshuffle();
    return $self;
}

###########################################
sub reshuffle {
###########################################
    my($self) = @_;

    my @cards = 
      (Algorithm::GenerateSequence->new(
       [qw( Heart Diamond Spade Club )],
       [qw( A 2 3 4 5 6 7 8 9 10 J Q K )])
       ->as_list()) x $self->{nof_decks};

    $self->{cards} = shuffle \@cards;
}

###########################################
sub remaining {
###########################################
    my($self) = @_;

    return scalar @{$self->{cards}};
}

###########################################
sub draw_card {
###########################################
    my($self) = @_;

    return shift @{$self->{cards}};
}

#==========================================
package Blackjack::Hand; #=================
#==========================================
use Quantum::Superpositions;

###########################################
sub new {
###########################################
    my($class, @options) = @_;

    my $self = { cards => [], @options };

    die "No shoe" if !exists $self->{shoe};
    bless $self, $class;
}

###########################################
sub draw {
###########################################
    my($self) = @_;

    push @{$self->{cards}}, 
         $self->{shoe}->draw_card();
}

###########################################
sub count {
###########################################
    my($self, $how) = @_;

    my $counts = any(0);

    for(@{$self->{cards}}) {
        if($_->[1] =~ /\d/) {
            $counts += $_->[1];
        } elsif($_->[1] eq 'A') {
            $counts = any($counts+1, 
                          $counts+11);
        } else {
            $counts += 10;
        }
    }
       # Delete busted hands
    $counts = ($counts <= 21);
                             
       # Busted!!
    return undef if ! eigenstates($counts);

    return $counts unless defined $how;

    if($how eq "hard") {
            # Return minimum
        return int($counts <= 
               all(eigenstates($counts)));
    } elsif($how eq "soft") {
            # Return maxium
        return int($counts >= 
               all(eigenstates($counts)));
    }
}

###########################################
sub blackjack {
###########################################
    my($self) = @_;

    my $c = $self->count();

    return 1 if $c == 21 and $c == 11 and 
             @{$self->{cards}} == 2;
    return 0;
}

###########################################
sub as_string {
###########################################
    my($self) = @_;

    return "[" . join(',', map({ "@$_" } 
                @{$self->{cards}})) .  "]";
}

###########################################
sub count_as_string {
###########################################
    my($self) = @_;

    return $self->busted() ?
     "Busted" : $self->blackjack() ?
     "Blackjack" : $self->count("soft");
}

###########################################
sub busted {
###########################################
    my($self) = @_;

    return ! defined $self->count();
}

###########################################
sub score {
###########################################
    my($self, $dealer) = @_;

    return -1 if $self->busted();

    return 1 if $dealer->busted();

    return 0 if $self->blackjack() and
                $dealer->blackjack();

    return 1.5 if $self->blackjack();

    return -1 if $dealer->blackjack();

    return $self->count("soft") <=>
           $dealer->count("soft");
}

1;