📓

Strategyパターンを学ぶ【Python】

2022/02/27に公開

一言で言うと

簡単に別の種類のConcreteStrategy(具体的な戦略)に変更できる。

コードサンプル

ディレクトリ構造
strategy_pattern
├── hand.py
├── main.py
├── player.py
├── prob_strategy.py
├── strategy.py
└── winning_strategy.py
main.py
import sys

from player import Player
from prob_strategy import ProbStrategy
from hand import Hand
from winning_strategy import WinningStrategy


def main() -> None:
    args = sys.argv
    if len(args) != 3:
        print('python main randomseed1 randomseed2')
        exit(0)

    seed1: int = int(args[1])
    seed2: int = int(args[2])
    player1: Player = Player('Taro', WinningStrategy(seed1))
    player2: Player = Player('Hana', ProbStrategy(seed2))

    for _ in range(10):
        next_hand1: Hand = player1.next_hand()
        next_hand2: Hand = player2.next_hand()
        if next_hand1.is_stronger_than(next_hand2):
            print(f'Winner:{player1}')
            player1.win()
            player2.lose()
        elif next_hand1.is_weeker_than(next_hand2):
            print(f'Winner:{player2}')
            player1.lose()
            player2.win()
        else:
            print('Even...')
            player1.even()
            player2.even()

    print('Total result:')
    print(player1)
    print(player2)


if __name__ == '__main__':
    main()
hand.py
import sys

from player import Player
from prob_strategy import ProbStrategy
from hand import Hand
from winning_strategy import WinningStrategy


def main() -> None:
    args = sys.argv
    if len(args) != 3:
        print('python main randomseed1 randomseed2')
        exit(0)

    seed1: int = int(args[1])
    seed2: int = int(args[2])
    player1: Player = Player('Taro', WinningStrategy(seed1))
    player2: Player = Player('Hana', ProbStrategy(seed2))

    for _ in range(10):
        next_hand1: Hand = player1.next_hand()
        next_hand2: Hand = player2.next_hand()
        if next_hand1.is_stronger_than(next_hand2):
            print(f'Winner:{player1}')
            player1.win()
            player2.lose()
        elif next_hand1.is_weeker_than(next_hand2):
            print(f'Winner:{player2}')
            player1.lose()
            player2.win()
        else:
            print('Even...')
            player1.even()
            player2.even()

    print('Total result:')
    print(player1)
    print(player2)


if __name__ == '__main__':
    main()
player.py
from strategy import Strategy
from hand import Hand


class Player:
    __wincount: int = 0
    __losecount: int = 0
    __gamecount: int = 0

    def __init__(self, name: str, strategy: Strategy) -> None:
        self.__name: str = name
        self.__strategy: Strategy = strategy

    def next_hand(self) -> Hand:
        return self.__strategy.next_hand()

    def win(self) -> None:
        self.__strategy.study(True)
        self.__wincount += 1
        self.__gamecount += 1

    def lose(self) -> None:
        self.__strategy.study(False)
        self.__losecount += 1
        self.__gamecount += 1

    def even(self) -> None:
        self.__gamecount += 1

    def __str__(self) -> str:
        return f'[{self.__name}:{self.__gamecount} games, ' + \
               f'{self.__wincount} win, {self.__losecount} lose]'
strategy.py
from abc import ABCMeta, abstractmethod
from hand import Hand


class Strategy(metaclass=ABCMeta):
    @abstractmethod
    def next_hand(self) -> Hand:
        pass

    @abstractmethod
    def study(self, win: bool) -> None:
        pass
prob_strategy.py
import random
from functools import reduce
from operator import add

from hand import Hand, HandValue
from strategy import Strategy


class ProbStrategy(Strategy):
    __prev_hand_value: int = HandValue.ROCK
    __current_hand_value: int = HandValue.ROCK
    __history: list[list[int]] = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]

    def __init__(self, seed: int) -> None:
        random.seed(seed)

    def next_hand(self) -> Hand:
        bet: int = random.randint(0, reduce(
            add, [self.__history[self.__current_hand_value][i] for i in range(2)])
        )

        hand_value: int = 0
        if bet < self.__history[self.__current_hand_value][HandValue.ROCK.value]:
            hand_value = HandValue.ROCK.value
        elif bet < self.__history[self.__current_hand_value][HandValue.ROCK.value] + \
                self.__history[self.__current_hand_value][HandValue.SCISSORS.value]:
            hand_value = HandValue.SCISSORS.value
        else:
            hand_value = HandValue.PAPER.value

        self.__prev_hand_value = self.__current_hand_value
        self.__current_hand_value = hand_value
        return Hand.get_hand(hand_value)

    def study(self, win: bool) -> None:
        if win:
            self.__history[self.__prev_hand_value][self.__current_hand_value] += 1
        else:
            self.__history[self.__prev_hand_value][(self.__current_hand_value + 1) % 3] += 1
            self.__history[self.__prev_hand_value][(self.__current_hand_value + 2) % 3] += 1
winning_strategy.py
import random

from hand import Hand
from strategy import Strategy


class WinningStrategy(Strategy):
    __won: bool = False
    __prev_hand: Hand

    def __init__(self, seed: int) -> None:
        random.seed(seed)

    def next_hand(self) -> Hand:
        if not self.__won:
            self.__prev_hand = Hand.get_hand(random.randint(0, 2))
        return self.__prev_hand

    def study(self, win: bool) -> None:
        self.__won = win

参考文献

実践python3
増補改訂版Java言語で学ぶデザインパターン入門
https://qiita.com/ttsubo/items/b65dab0d494bebada223

Discussion