Created
February 14, 2019 14:53
-
-
Save xtetsuji/d53b7305c33b8ac7d53c77b4d4f5546c to your computer and use it in GitHub Desktop.
ダイスギャンブル、乱数で総当たり版と、パターン全部から期待値洗い出し版 https://twitter.com/xtetsuji/status/1088390124751056897
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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