ブラックジャックの Perl プログラム

ブラックジャックの Perl プログラム

以下は、Perl によるブラックジャックのプログラムの一例です。


#!/usr/bin/env perl

use strict;
use warnings;
use diagnostics;

#定数
use constant NDECK => 1;#実際のカジノでは、6 デックを 1 スタックとすることが多い
                        #(ブラックジャックの場合)

#グローバル変数
#なし

BEGIN {
}

CHECK {
}

INIT {
}

END {
}

{
  my ($nround);     #ラウンド数
  my ($PlayerPoint);#プレーヤーの合計点数
  my ($DealerPoint);#ディーラーの合計点数
  my (@PlayerHands);#プレーヤーの手札
  my (@DealerHands);#ディーラーの手札
  my (@stack);      #カードの山

  #カードを混ぜる
  &shuffle(\@stack);

  $nround = 0;
  while ($#stack >= 10) {
    undef @PlayerHands;#プレーヤーの手札
    undef @DealerHands;#ディーラーの手札

    $nround++;
    print "--- Round $nround ---\n";

    #カードを配る
    for (0 .. 1) {
      push(@PlayerHands, shift(@stack));
      push(@DealerHands, shift(@stack));
    }

    #合計点数を計算する
    $PlayerPoint = &calc(@PlayerHands);
    $DealerPoint = &calc(@DealerHands);

    #プレーヤーの処理を行う
    &player(\@stack, \@PlayerHands, \$PlayerPoint, \@DealerHands, $DealerPoint);

    #ディーラーの処理を行う
    &dealer(\@stack, \@DealerHands, \$DealerPoint) if ($PlayerPoint);

    #カードを画面に表示する
    &display(1, \@PlayerHands, $PlayerPoint, \@DealerHands, $DealerPoint);
  }

  print "--- Dealer doesn't have enough cards to continue. ---\n";

  exit(0);#正常終了
}

#カードを混ぜる
sub shuffle(\@) {
  my ($stack) = @_;
  my (@deck); #1 組 52 枚のカード
  my (@ndeck);#NDECK デックのカード

  foreach my $s ('S', 'H', 'D', 'C') {#カードの印 spade, heart, diamond, club
    for ( 1 ..  9) {#カードの数字
      push(@deck, "$s" . "0$_");
    }
    for (10 .. 13) {#カードの数字
      push(@deck, "$s" .  "$_");
    }
  }

  for (my $n = 0; $n < NDECK; $n++) {
    foreach (@deck) {
      push(@ndeck, $_);
    }
  }

  if ($] >= 5.008) {#特殊変数 $] は、Perl のバージョンを識別する文字列
    use List::Util;

    @$stack = List::Util::shuffle(@ndeck);
  } else {
    #擬似乱数を初期化する
    srand;

    #@ndeck から 1 枚ずつ @stack に移動する
    while ($#ndeck >= 0) {
      #rand の引数に数値 X を指定→0 以上 X 未満の小数を返す
      #rand の引数を省略         →0 以上 1 未満の小数を返す
      push(@$stack, splice(@ndeck, int(rand($#ndeck + 1)), 1));
    }
  }
}

#合計点数を計算する
sub calc(@) {
  my (@hands) = @_;
  my ($point);  #合計点数
  my ($na);     #A の枚数
  my (@numbers);#カードの数字

  #数字を取出す
  foreach (@hands) {
    #「アルファベットと数字」の数字を取出す
    /^([SHDC])(\d{1,2})$/;#選択 S|H|D|C は遅い
    push(@numbers, $2);
  }

  $point = $na = 0;
  foreach (@numbers) {
    if ($_ <= 10) {
      $point += $_;

      $na++ if ($_ == 1);#A の枚数を数える
    } else {#絵札は 10 点
      $point += 10;
    }
  }

  if      ($point <= 11) {#A は 1 点としても 11 点としてもよい
    $point += 10 if ($na);
  } elsif ($point >= 22) {#合計点数が 21 点を超えていたら 0 点にする
    $point = 0;
  }
  return($point);
}

#カードを画面に表示する
sub display($\@$\@$) {
  my ($flag, $PlayerHands, $PlayerPoint, $DealerHands, $DealerPoint) = @_;

  print "PlayerHands : ", join(', ', @$PlayerHands), " [$PlayerPoint]\n";

  if      ($flag == 0) {
    print "DealerHands : ", $$DealerHands[0], ", ?\n";
  } elsif ($flag == 1) {
    print "DealerHands : ", join(', ', @$DealerHands), " [$DealerPoint]\n";

    if      ($PlayerPoint == 0) {
      print "Player Busted.\n";
    } elsif ($DealerPoint == 0) {
      print "Dealyer Busted.\n";
    } elsif ($PlayerPoint >  $DealerPoint) {
      print "Player Won.\n";
    } elsif ($PlayerPoint <  $DealerPoint) {
      print "Dealer Won.\n";
    } elsif ($PlayerPoint == $DealerPoint) {
      print "Push.\n";
    }
  }
}

#プレーヤーの処理を行う
sub player(\@\@\$\@$) {
  my ($stack, $PlayerHands, $PlayerPoint, $DealerHands, $DealerPoint) = @_;

  while (1) {
    my ($i);

    #カードを画面に表示する
    &display(0, $PlayerHands, $$PlayerPoint, $DealerHands, $DealerPoint);

    #プレーヤーの処理を行う
    print "  Draw(D, d) or Stand(S, s) or Quit(Q, q)? : ";
    chomp($i = <STDIN>);
    if      (($i eq 'D') || ($i eq 'd')) {#カードを引く
      print "  Draw\n";
      push(@$PlayerHands, shift(@$stack));
      $$PlayerPoint = &calc(@$PlayerHands);
      last if ($$PlayerPoint == 0);
    } elsif (($i eq 'S') || ($i eq 's')) {#カードを引かない
      print "  Stand\n";
      last;
    } elsif (($i eq 'Q') || ($i eq 'q')) {#終了する
      exit(0);#正常終了
    } else {
      print "Input Error!\n";
    }
  }
}

#ディーラーの処理を行う
sub dealer(\@\@\$) {
  my ($stack, $DealerHands, $DealerPoint) = @_;

  while (1) {
    last if ($$DealerPoint >= 17);#16 でドロー(ヒット)/17 でスタンド(ステイ)

    push(@$DealerHands, shift(@$stack));
    $$DealerPoint = &calc(@$DealerHands);
    last if ($$DealerPoint == 0);
  }
}

__END__

各ルーチンについて、以下に説明します。