# perlclass 初探: 以模擬 Monty Hall Problem 為例

Monty Hall Problem （蒙提霍爾問題）是個奇妙而有趣的數學遊戲問題。維基百科上提供的問題本文為：

``````#!/usr/bin/env perl
use v5.38;
use feature 'class';
no warnings 'experimental::class';

class Game {
field \$doors :param;
field \$winningDoor = 1 + int rand(\$doors);

method doors { \$doors }
method winningDoor { \$winningDoor }

method loosingDoors {
grep { \$_ != \$winningDoor } (1..\$doors);
}
};

class FirstChoicePlayer {
field \$doors :param;
field \$firstChoice = 1 + int rand(\$doors);
field @loosingDoors;

method doors { \$doors }
method addLoosingDoors (\$n) { push @loosingDoors, \$n }
method loosingDoors () { @loosingDoors }
method firstChoice () { \$firstChoice }
method finalChoice () { \$firstChoice }
};

class ChangeChoicePlayer :isa(FirstChoicePlayer) {
method finalChoice () {
my %isLoosing = map { \$_ => 1 } \$self->loosingDoors();
my @choices = grep { \$_ != \$self->firstChoice() } grep { ! \$isLoosing{\$_} } 1..\$self->doors();

die "Illegal state" if @choices != 1;
return \$choices[0];
}
};

class GameMaster {
field \$doors :param;
method playWith (\$player) {
die "No player ?" unless defined \$player;

my \$game = Game->new( doors => \$doors );

my %untold = map { \$_ => 1 } (\$game->winningDoor, \$player->firstChoice);
while ((keys %untold) == 1) {
my \$door = 1 + int rand(\$doors);
\$untold{\$door} = 1;
}
my @revealLoosingDoors = grep { ! \$untold{\$_} } (1..\$doors);

for my \$door (@revealLoosingDoors) {
\$player->addLoosingDoors(\$door)
}

my \$finalChoice = \$player->finalChoice();

return \$finalChoice == \$game->winningDoor;
}
};

sub playOneRound(\$playerClass) {
my \$doors = 3;
my \$gm = GameMaster->new( doors => \$doors );
my \$player = \$playerClass->new( doors => \$doors );
return \$gm->playWith( \$player );
}

sub play (\$rounds, \$playerClass) {
my \$wins = 0;
for (1 .. \$rounds) {
my \$win = playOneRound(\$playerClass);
\$wins++ if \$win;
}
return \$wins;
}

sub sim (\$rounds) {
for my \$playerClass ("FirstChoicePlayer", "ChangeChoicePlayer") {
my \$wins = play(\$rounds, \$playerClass);
my \$pWin = \$wins / \$rounds;
say "\$playerClass wins \$wins / \$rounds. p(win) = \$pWin";
}
}

sim(shift // 1000);
``````

``````# play-monty-hall.pl
FirstChoicePlayer wins 323 / 1000. p(win) = 0.323
ChangeChoicePlayer wins 665 / 1000. p(win) = 0.665

# play-monty-hall.pl 1000000
FirstChoicePlayer wins 333606 / 1000000. p(win) = 0.333606
ChangeChoicePlayer wins 666348 / 1000000. p(win) = 0.666348``````

• `FirstChoicePlayer`: 以不變為其策略的玩家
• `ChangeChoicePlayer`: 以改變為其策略的玩家
• `GameMaster`: 與玩家互動的遊戲主持人。
• `Game`: 維護單一一局的遊戲初始資訊。基本上只有記住哪號門是贏門 (`\$winnigDoor`)，並提供列舉出所有輸門的方法 `loosingDoors`

Game 物件在建構、初始完畢後計基本上是個常數了，沒有什麼特別的地方。 FirstChoicePlayer 也差不多，只是會有個 `@loosingDoors` 內部狀態來記著那些由 GameMaster 提供的輸門號碼。