Skip to content

Instantly share code, notes, and snippets.

@xtetsuji
Created February 14, 2019 14:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xtetsuji/d53b7305c33b8ac7d53c77b4d4f5546c to your computer and use it in GitHub Desktop.
Save xtetsuji/d53b7305c33b8ac7d53c77b4d4f5546c to your computer and use it in GitHub Desktop.
ダイスギャンブル、乱数で総当たり版と、パターン全部から期待値洗い出し版 https://twitter.com/xtetsuji/status/1088390124751056897
#!/usr/bin/perl
# N回サイコロを振ったときの出る目の全ての場合を割り出して期待値を計算する
use strict;
use warnings;
use List::Util qw(sum);
use constant BONUS_TRIO1 => 10_000;
use constant BONUS_TRIO456 => 2_000;
use constant DEBUG => $ENV{DEBUG};
my $rolling_times = shift || '';
if ( $rolling_times !~ /^\d+$/ ) {
print "test mode\n";
do_test();
exit;
}
my $total_money = 0;
my ($bonus_trio1_total, $bonus_trio456_total) = (0, 0);
my @rolling_sequence = (1) x $rolling_times;
while (@rolling_sequence) {
my ($cost, $bonus_trio1_count, $bonus_trio456_count)
= calc_total_cost(@rolling_sequence);
$total_money += $cost;
$bonus_trio1_total += $bonus_trio1_count;
$bonus_trio456_total += $bonus_trio456_count;
warn "// @rolling_sequence => $cost [$total_money]\n" if DEBUG;
} continue {
@rolling_sequence = next_vec(@rolling_sequence);
}
print "rolling times: $rolling_times\n";
print "pattern total: ", 6**$rolling_times, "\n";
print "money result: $total_money\n";
print "bonus trio1 total: $bonus_trio1_total\n";
print "bonus trio456 total: $bonus_trio456_total\n";
print "expectation: ", $total_money / 6**$rolling_times, "\n";
# next_vec
# N回サイコロを投げたときの目の数を全て導き出すための関数
# 各要素が 1 から 6 である N 個の要素を持ったリスト(配列)から次のリストを導く
# (6,6,6,6) のように次がなければカラを返す
# 6進数として見た時にプラス1したものを導いているのと同じ
sub next_vec {
my @elements = @_;
if ( @elements == 1 && $elements[0] == 6 ) {
# 繰り上がり無しなので次は無い
return;
} elsif ( $elements[-1] <= 5 ) {
# 普通に末尾に足すだけ
$elements[-1]++;
return @elements;
} else {
# 末尾6の場合は繰り上がり処理が必要になるけれど、それは再帰で解決する
# (2,3,6) の場合は (2,4,1) を返し、(6,6,6) の場合は次はないので空リスト
pop @elements;
@elements = next_vec(@elements);
return @elements ? (@elements, 1) : ();
}
}
# calc_total_cost
# 与えられたサイコロの目のリストでいったい差し引きいくらの損得かを計算する
# (1,1,1) なら 10000 + 100*3 - 500*3
# (2,3,4) なら 200+300+400 - 500*3
# (4,5,6) なら 400+500+600+2000 - 500*3
# (1,1,1,1) なら 10000 + 100*3 - 500*4
sub calc_total_cost {
my @elements = @_;
# 正規表現でやってしまうとどうなるかな
my $elements = join "", @elements;
# 置換回数を s/// の左辺で受ける(置換文字列は適当だけど、"411156" の時とかを考えると空文字ではダメ)
my $bonus_trio1_count = $elements =~ s/1{3}/A/g || 0;
my $bonus_trio456_count = $elements =~ s/[456]{3}/B/g || 0;
my $total_cost = 100 * sum( @elements )
+ $bonus_trio1_count * BONUS_TRIO1
+ $bonus_trio456_count * BONUS_TRIO456
- 500 * @elements;
warn ">>> @elements => $elements, ¥$total_cost\n" if DEBUG;
return( $total_cost, $bonus_trio1_count, $bonus_trio456_count);
}
###
### 以下テスト
###
sub do_test {
use Test::More;
is( (calc_total_cost(1,1,1))[0], 10_000 + 300 - 500*3);
is( (calc_total_cost(1,1,1,1))[0], 10_000 + 400 - 500*4);
is( (calc_total_cost(2,3,4))[0], 200+300+400-500*3);
is( (calc_total_cost(4,5,6))[0], 2000+400+500+600-500*3);
is( (calc_total_cost(6,6,6,6))[0], 2000+600*4-500*4);
is( (calc_total_cost(1,1,1,1,5))[0], 10_000 + 100*4 + 500 - 500*5);
is( (calc_total_cost(1,1,1,1,1,1))[0], 20_000 + 100*6 - 500*6);
is( (calc_total_cost(1,1,1,4,5,6,3))[0], 10_000 + 2_000 + 100*3 + 400 + 500 + 600 + 300 - 500*7);
subtest "next_vec total" => sub {
for my $d (1..7) {
my $total = 6**$d;
is( _next_vec_total($d), $total, "$d-dimensions dice vector total is $total");
}
};
done_testing();
}
sub _next_vec_total {
my $dimension = shift;
if ( !$dimension || $dimension =~ /\D/ ) {
die "1st argument is required as dimension, integer\n";
}
my @current_vec = (1) x $dimension;
my $count = 1;
$count++ while @current_vec = next_vec(@current_vec);
return $count;
}
rolling times: 1
pattern total: 6
money result: -900
bonus trio1 total: 0
bonus trio456 total: 0
expectation: -150
rolling times: 2
pattern total: 36
money result: -10800
bonus trio1 total: 0
bonus trio456 total: 0
expectation: -300
rolling times: 3
pattern total: 216
money result: -33200
bonus trio1 total: 1
bonus trio456 total: 27
expectation: -153.703703703704
rolling times: 4
pattern total: 1296
money result: -181600
bonus trio1 total: 11
bonus trio456 total: 243
expectation: -140.123456790123
rolling times: 5
pattern total: 7776
money result: -984000
bonus trio1 total: 96
bonus trio456 total: 1944
expectation: -126.543209876543
rolling times: 6
pattern total: 46656
money result: -3802400
bonus trio1 total: 757
bonus trio456 total: 15309
expectation: -81.4986282578875
rolling times: 7
pattern total: 279936
money result: -14588800
bonus trio1 total: 5627
bonus trio456 total: 111537
expectation: -52.1147690900777
rolling times: 8
pattern total: 1679616
money result: -38179200
bonus trio1 total: 40272
bonus trio456 total: 787320
expectation: -22.7309099222679
rolling times: 9
pattern total: 10077696
money result: 106422400
bonus trio1 total: 280693
bonus trio456 total: 5452191
expectation: 10.5601915358431
rolling times: 10
pattern total: 60466176
money result: 2533412000
bonus trio1 total: 1918523
bonus trio456 total: 37023723
expectation: 41.8980026122373
#!/usr/bin/perl
use v5.10;
use strict;
use warnings;
use Getopt::Long qw(:config posix_default no_ignore_case bundling auto_help);
use Pod::Usage qw(pod2usage);
use Term::ANSIColor qw(colored);
use constant MEMORY_SAVING_MODE => 0;
use constant VIEW_STEP_MODE => 0;
use constant GAME_PRICE => 500;
use constant ONE_3_PRIZE => 10_000;
use constant FOUR_HIGHER_3_PRIZE => 2_000;
GetOptions(
\my %opt,
"try-number|t=i", "bench-number|b=i",
);
my $try_number = $opt{"try-number"} || 100;
my $bench_number = $opt{"bench-number"};
if ( $bench_number ) {
# ベンチマークモード
my $i = $bench_number;
my $total_money = 0;
while ( $i-- > 0 ) {
my $gambler = get_initial_gambler();
gamble_whole($gambler);
$total_money += $gambler->{money};
}
printf "ベンチマーク回数: %d / 1回のゲームでサイコロを降る回数: %d / 損益合計: %d\n",
$bench_number, $try_number, $total_money;
printf "期待値: %.4f\n", $total_money / $bench_number;
} else {
# 標準N回モード
my $gambler = get_initial_gambler();
gamble_whole($gambler);
printf "最終所持金: %d\n", $gambler->{money};
}
sub dice {
return int(rand(6)+1);
}
sub get_initial_gambler {
my $gambler = {
money => 0,
dice_history => [],
throw_count => 0
};
return $gambler;
}
sub gamble_onetime {
my $gambler = shift;
my $dice_history = $gambler->{dice_history};
$gambler->{throw_count}++;
$gambler->{money} -= GAME_PRICE;
my $dice_number = dice();
push @$dice_history, $dice_number;
my $bonus = calculate_bonus($dice_history);
my $dice_profit = $dice_number * 100;
if ( $bonus ) {
my $total_profit = $dice_profit + $bonus;
# 履歴に連続区切りのダミーデータ -1 を入れておく
push @$dice_history, -1;
}
$gambler->{money} += $total_profit;
if ( VIEW_STEP_MODE ) {
my @dice_history_latest =
@$dice_history >= 3 ? @$dice_history[-1, -2, -3] : reverse @$dice_history;
$dice_history_latest[$_] ||= "-" for 0..2;
printf "試行: %d回目 / サイコロの目: %s / 1回前の目: %s / 2回前の目: %s\n",
$gambler->{throw_count}, (map { $_ || "-" } @dice_history_latest);
printf "損益: %d\n", $total_profit - GAME_PRICE;
printf " ゲーム料金: %d + サイコロで得た金: %d + ボーナス: %d\n",
GAME_PRICE, $dice_profit, $bonus;
printf "通算: %d\n", $gambler->{money};
print "-" x 10, "\n";
}
if ( MEMORY_SAVING_MODE ) {
shift @{$gambler->{dice_history}} if @{$gambler->{dice_history}} >= 5;
}
}
sub gamble_whole {
my $gambler = shift;
my $i = $try_number;
gamble_onetime($gambler) while $i-- > 0;
}
sub calculate_bonus {
my $dice_history = shift;
return 0 if @$dice_history < 3;
my ($m_1, $m_2, $m_3) = @$dice_history[-1, -2, -3];
if ( $m_1 == 1 && $m_2 == 1 && $m_3 == 1 ) {
return ONE_3_PRIZE;
}
if ( $m_1 >= 4 && $m_2 >= 4 && $m_3 >= 4 ) {
return FOUR_HIGHER_3_PRIZE;
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment