php设计模式-策略模式-例题学习

本文改自《设计模式-java语言中的应用》中的策略模式章节。作者:结城 浩(日本)

简单的说,策略模式就是算法替换,用不同的类实现不同的算法。
难点:需要根据算法设计出不同的方法,参数等。

程序示例
这里的程序是计算机游戏“剪刀石头布”。
猜拳时的策略有两种方法。第一种方法有点笨,“猜赢之后继续出同样的招式”(WinningStrategy),第2种方法则是“从上一次出的招式,以概率分配方式求出下一个招式的几率”(ProbStrategy)。

类一览表。
Hand:表示猜拳手势的类
HandStrategyInterface:表示猜拳战略的接口
Player:表示玩猜拳的游戏者的类
WinningStratrgy:策略1,表示猜赢之后继续出同样招式的战略的类
ProbStratrgy:策略2,表示根据上一次出的招式以概率计算出下一个招式的类。
Main:执行主类

代码在附件。

Hand.php
<?php
/**
 * 手势类
 *
 * @author 结城 浩
 */
class Hand
{
    public static $HANDVALUE_GUU = 0; //表示石头的值
    public static $HANDVALUE_CHO = 1; //表示剪刀的值
    public static $HANDVALUE_PAA = 2; //表示布的值

    private static $name_arr = array('石头', '剪刀', '布');

    /**
     * 手势的值
     *
     * @var int
     */
    private $handValue;

    /**
     * 私有构造方法
     *
     * @param int $handvalue 手势的值
     */
    private function __construct($handvalue)
    {
        $this->handValue = $handvalue;
    }

    /**
     * 该方法实现单例数组,里面存放3个对象
     *
     * @return array 对象数组
     */
    public static function getHandArr()
    {
        static $hand_arr = array();
        if (!$hand_arr) {
            $hand_arr = array(
            new Hand(self::$HANDVALUE_GUU),
            new Hand(self::$HANDVALUE_CHO),
            new Hand(self::$HANDVALUE_PAA),
            );
        }
        return $hand_arr;
    }

    /**
     * 外部调用,获取一个手势对象
     *
     * @param int $handvalue 手势的值
     * @return Hand 一个手势对象
     */
    public static function getHand($handvalue)
    {
        $hand = self::getHandArr();
        return $hand[$handvalue];
    }

    /**
     * 判断是否战胜另一个手势
     *
     * @param Hand $h 另一个手势对象
     * @param boolean
     */
    public function isStrongerThan(Hand $h)
    {
        return $this->fight($h) == 1;
    }

    /**
     * 判断是否输给另一个手势
     *
     * @param Hand $h 另一个手势对象
     * @param boolean
     */
    public function isWeakerThan(Hand $h)
    {
        return $this->fight($n) == -1;
    }

    /**
     * 与另一个手势判断输赢
     *
     * @param Hand $h 另一个手势对象
     * @return int 胜为1 ,负为-1,平局为0
     */
    private function fight(Hand $h)
    {
        if ($this->handValue == $h->handValue) {
            return 0;
        } elseif (($this->handValue + 1) % 3 == $h->handValue) {
            return 1;
        } else {
            return -1;
        }
    }

    /**
     * 字符串显示
     */
    function __toString()
    {
        return self::$name_arr[$this->handValue];
    }

}



HandStrategyInterface.php
<?php
/**
 * 手势策略接口
 */
interface HandStrategyInterface
{
    /**
     * 得到下一个手势
     */
    public function nextHand();
    
    /**
     * 策略的智能学习
     */
    public function study($win);
}


Player.php
<?php


require_once 'HandStrategyInterface.php';

/**
 * 游戏者类
 *
 * @author 结城 浩
 */
class Player
{
    /**
     * 游戏者名称
     * 
     * @var string
     */
    private $name;
    
    /**
     * 私有策略对象
     * 
     * @var HandStrategyInterface
     */
    private $strategy;
    
    /**
     * 总胜利次数
     * 
     * @var int
     */
    private $wincount;
    
    /**
     * 总失败次数
     * 
     * @var int
     */
    private $losecount;
    
    /**
     * 总游戏次数
     * 
     * @var int
     */
    private $gamecount;

    /**
     * 构造方法
     * 
     * @param string $name 游戏者名称
     * @param HandStrategyInterface $strategy 策略对象
     */
    function __construct($name, HandStrategyInterface $strategy)
    {
        $this->name = $name;
        $this->strategy = $strategy;
        $this->wincount = $this->losecount = $this->gamecount = 0;
    }

    /**
     * 根据策略返回游戏者的下一个手势
     * 
     * @return Hand 游戏者的下一个手势
     */
    public function nextHand()
    {
        $hand = $this->strategy->nextHand();
        echo $this->name . ":" . $hand ."<br>";
        return $hand;
    }
    
    /**
     * 胜利后的处理
     */
    public function win()
    {
        $this->strategy->study(true);
        $this->wincount++;
        $this->gamecount++;
    }

    /**
     * 失败后的处理
     */
    public function lose()
    {
        $this->strategy->study(false);
        $this->losecount++;
        $this->gamecount++;
    }

    /**
     * 平局后的处理
     */
    public function even()
    {
        $this->gamecount++;
    }

    /**
     * 魔术方法显示游戏者信息
     * 
     * @return string
     */
    function __toString()
    {
        return '[' . $this->name . ':' . $this->gamecount . ' games, ' .
        $this->wincount . ' win, ' . $this->losecount . ' lose]';
    }
}


WinningStrategy.php
<?php

require_once 'Hand.php';
require_once 'HandStrategyInterface.php';

/**
 * 简单策略类
 *
 * 这是第一个策略,非常简单,如果赢了,就继续同一个手势,如果输了,随机出手势。
 *
 * @author 结城 浩
 */
class WinningStrategy implements HandStrategyInterface
{
    /**
     * 与该策略算法有关的变量
     * 
     * @var boolean
     */
    private $won = false;
    
    /**
     * 上一个手势
     * 
     * @var Hand
     */
    private $prevhand;
    
    
    public function __construct()
    {
    }

    /**
     * 策略的算法核心。
     * 
     * 如果赢,就继续相同的手势,输则随机产生一个。
     * 
     * @return Hand
     */
    public function nextHand()
    {
        if (!$this->won) {
            $this->prevhand = Hand::getHand(mt_rand(0, 2));
        }
        
        return $this->prevhand;
    }

    /**
     * 学习方法,只记住当前的输赢结果
     * 
     * @param boolean 输赢结果
     */
    public function study($win)
    {
        $this->won = $win;
    }

}


ProbStrategy.php
<?php

require_once 'Hand.php';
require_once 'HandStrategyInterface.php';

/**
 * 复杂策略类
 *
 * 这是第二个策略,虽然下一次的手势都是由随机数决定,不过它会参考之前的输赢记录,机动性的更改手势的出现几率。
 * history字段是一份列出过去输赢记录的表格,用来计算几率。history是一个int类型的2维数组,各下标的含义是
 * history[上一次的手势][下一次的手势]
 * 这个表达式的值越大,则代表之前获胜的几率越高。用实际的数据可能会更清楚一点。
 * 这个作战策略的前提是对手的出拳方式也有另一种Pattern。
 *
 * @author 结城 浩
 */
class ProbStrategy implements HandStrategyInterface
{
    /**
     * 前面手势的值,统计用
     * 
     * @var int
     */
    private $prevHandValue = 0;
    
    /**
     * 后面手势的值,统计用
     * 
     * @var int
     */
    private $currentHandValue = 0;
    
    /**
     * 存放历史数据的数组
     * 
     * history[0][0] 石头之后出石头的不败(获胜或平手)次数。
     * history[0][1] 石头之后出剪刀的不败(获胜或平手)次数。
     * history[0][2] 石头之后出布的不败(获胜或平手)次数。
     * history[1][0] 剪刀之后出石头的不败(获胜或平手)次数。
     * history[1][1] 剪刀之后出剪刀的不败(获胜或平手)次数。
     * history[1][2] 剪刀之后出布的不败(获胜或平手)次数。
     * history[2][0] 布之后出石头的不败(获胜或平手)次数。
     * history[2][1] 布之后出剪刀的不败(获胜或平手)次数。
     * history[2][2] 布之后出布的不败(获胜或平手)次数。
     * 
     * @var array 二维数组
     */
    private $history = array(
        array(1, 1, 1),
        array(1, 1, 1),
        array(1, 1, 1),
    );

    public function __construct()
    {

    }

    /**
     * 算法核心
     * 
     * 根据历史数据,计算出某一个手势的下一个手势的3个概率(比如6:4:13),然后
     * 也不一定就是那个最大概率的手势,而是依然根据概率得到下一个手势。
     */
    public function nextHand()
    {
        $bet = mt_rand(0, $this->getSum($this->currentHandValue) - 1);
        $handvalue = 0;
        if ($bet < $this->history[$this->currentHandValue][0]) {
            $handvalue = 0;
        } elseif ($bet < ($this->history[$this->currentHandValue][0] +
        $this->history[$this->currentHandValue][1]) ) {
            $handvalue = 1;
        } else {
            $handvalue = 2;
        }
        $this->prevHandValue = $this->currentHandValue;
        $this->currentHandValue = $handvalue;
        $hand = Hand::getHand($handvalue);
        return $hand;
    }

    /**
     * 从历史数据中得到某一个手势的次数之和,算概率用的
     * 
     * @param int $hv
     * @return int 
     */
    private function getSum($hv)
    {
        $sum = 0;
        for ($i = 0; $i < 3; $i++) {
            $sum += $this->history[$hv][$i];
        }
        return $sum;
    }

    /**
     * 该策略的学习就是把结果记入统计数据里。
     * 
     * @param boolean $win 本次的输赢结果
     */
    public 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]++;
        }
    }
}


Main.php
<?php

require_once 'Hand.php';
require_once 'WinningStrategy.php';
require_once 'ProbStrategy.php';
require_once 'Player.php';


/**
 * 执行主类
 * 
 * 使用方法,把这几个类一起拷贝到web服务器的docment_root路径下,然后
 * http://localhost/Main.php
 *
 * @author 结城 浩
 */
class Main
{
    public static function run()
    {
        //得到两个游戏者,策略不同
        $player1 = new Player('张三', new WinningStrategy());
        $player2 = new Player('李四', new ProbStrategy());

        //1000次运算,得到结果,并不停的输出。
        for ($i = 0; $i < 1000; $i++) {
            $nextHand1 = $player1->nextHand();
            $nextHand2 = $player2->nextHand();
            if ($nextHand1->isStrongerThan($nextHand2)) {
                $player1->win();
                $player2->lose();
                self::echoinfo('胜利者: ' . $player1);
            } elseif ($nextHand2->isStrongerThan($nextHand1)) {
                $player1->lose();
                $player2->win();
                self::echoinfo('胜利者: ' . $player2);
            } else {
                $player1->even();
                $player2->even();
                self::echoinfo('平手。 ');
            }
            self::echoinfo(' ');
        }
        //统计
        self::echoinfo('======================= 低调滴分割线  =======================');
        self::echoinfo($player1);
        self::echoinfo($player2);
    }

    /**
     * 只是一个便利的输出换行
     * 
     * @param  string $s 待输出信息
     * @return string 加了换行后的输出
     */
    public static function echoinfo($s)
    {
        echo $s . "<br />\n";
    }
}

//执行
Main::run();


运行结果示例:
php设计模式-策略模式-例题学习_第1张图片

代码在附件。

你可能感兴趣的:(设计模式,游戏,算法,PHP,ITeye)