forget for get

覚えるために忘れる

Strategyパターン

アルゴリズムをごっそり切り替える

Strategy(戦略):Strategy
インタフェースを規定
ConcreteStrategy(具体的戦略):WinningStrategy、ProbStrategy
Context(文脈):Player
Strategyを利用。ConcreteStrategyのインスタンスを保持。

Hand.php

class Hand {
  const GUU = 0;
  const CHO = 1;
  const PAA = 2;
  static $hand = [];
  static private $name = ["グー","チョキ","パー"];
  private $handvalue;
  static function initialize() {
    self::$hand = [new Hand(self::GUU), new Hand(self::CHO), new Hand(self::PAA)];
  }
  function __construct($handvalue) {
    $this->handvalue = $handvalue;
  }
  static function getHand($handvalue) {
    return self::$hand[$handvalue];
  }
  function isStrongerThan(Hand $h) {
    return $this->fight($h) == 1;
  }
  function isWeekerThan(Hand $h) {
    return $this->fight($h) == -1;
  }
  private function fight(Hand $h) {
    if ($this == $h) return 0;
    if (($this->handvalue + 1) % 3 == $h->handvalue) return 1;
    return -1;
  }
  function toString() {
    return self::$name[$this->handvalue];
  }
}

Strategy.php

interface Strategy {
  function nextHand(): Hand;
  function study(bool $win);
}

WinningStrategy.php

// 勝ったら同じ手を出す戦略
class WinningStrategy implements Strategy {
  private $won = false;
  private $prevHand;
  function nextHand(): Hand {
    if (!$this->won) $this->prevHand = Hand::getHand(rand(0, 2));
    return $this->prevHand;
  }
  function study(bool $win) {
    $this->won = $win;
  }
}

ProbStrategy.php

// 勝敗履歴から確率を計算する戦略
class ProbStrategy implements Strategy {
  private $prevHandValue = 0;
  private $currentHandValue = 0;
  private $history = [[1,1,1], [1,1,1], [1,1,1]];
  function nextHand(): Hand {
    $bet = rand(0, $this->getSum($this->currentHandValue));
    $handvalue = 0;
    if ($bet < $this->history[$this->currentHandValue][0]) {
      $handvalue = 0;
    } else if ($bet < $this->history[$this->currentHandValue][0] + $this->history[$this->currentHandValue][1]) {
      $handvalue = 1;
    } else {
      $handvalue = 2;
    }
    $this->prevHandValue = $this->currentHandValue;
    $this->currentHandValue = $handvalue;
    return Hand::getHand($handvalue);
  }
  private function getSum($hv) {
    $sum = 0;
    for ($i=0;$i<3;$i++) {
      $sum += $this->history[$hv][$i];
    }
    return $sum;
  }
  function study($win) {
    if ($win) {
      $this->history[$this->prevHandValue][$this->currentHandValue]++;
    } else {
      $this->history[$this->prevHandValue][($this->currentHandValue + 1) % 3]++;
      $this->history[$this->prevHandValue][($this->currentHandValue + 2) % 3]++;
    }
  }
}

Player.php

class Player {
  private $name;
  private $strategy;
  private $wincount = 0;
  private $losecount = 0;
  private $gamecount = 0;
  function __construct($name, $strategy) {
    $this->name = $name;
    $this->strategy = $strategy;
  }
  function nextHand() {
    return $this->strategy->nextHand();
  }
  function win() {
    $this->strategy->study(true);
    $this->wincount++;
    $this->gamecount++;
  }
  function lose() {
    $this->strategy->study(false);
    $this->losecount++;
    $this->gamecount++;
  }
  function even() {
    $this->gamecount++;
  }
  function toString() {
    return "[" . $this->name . ":" . $this->gamecount . " games, " . $this->wincount . " win, " . $this->losecount . " lose]";
  }
}

Main.php

require "../autoload.php";
Hand::initialize();
$player1 = new Player("Taro", new WinningStrategy());
$player2 = new Player("Jiro", new ProbStrategy());
for ($i=0;$i<10;$i++) {
  $nextHand1 = $player1->nextHand();
  $nextHand2 = $player2->nextHand();
  if ($nextHand1->isStrongerThan($nextHand2)) {
    echo "Winner:" . $player1->toString() . "\n";
    $player1->win();
    $player2->lose();
  } else if ($nextHand2->isStrongerThan($nextHand1)) {
    echo "Winner:" . $player2->toString() . "\n";
    $player2->win();
    $player1->lose();
  } else {
    echo "Even...\n";
    $player1->even();
    $player2->even();
  }
}
echo "Total result:\n";
echo $player1->toString() . "\n";
echo $player2->toString() . "\n";