← 一覧に戻る
振る舞いに関するパターン

Strategy

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

概要

データ構造に対して適用する一連のアルゴリズムをカプセル化し、アルゴリズムの切替えを容易にする。

マンガでわかる Strategy

マンガでわかる Strategy #デザインパターン - Qiita

でざぱたんで覚える Strategy

ちびキャラは「ストラテジーたん」、ステートたん(State)の双子の兄。内に状態を抱え込まず、その場その場で「やり方」だけを差し替えて同じ顔で応対する話術師。本書の整理では、StrategyはStateから「状態をオブジェクトで管理する」を省いて、関数オブジェクトによるポリモーフィズムに特化した考え方——妹と同じ顔(同一インターフェース)で、中身のアルゴリズムだけ交換可能、という対比で双子として描かれる。

出典: いしだけ『でざぱたん: ちびキャラで覚えるデザインパターン』(P.014〜)

登場人物

  • Strategy: 戦略名インタフェース
  • ConcreteStrategy: 戦略を実装 (具体的な作戦、方策、方法、アルゴリズムの切り替え)
  • Context: Strategy利用者

クラス図

500px-StrategyPatternClassDiagram.svg.png (500×213)

このサイトの実装(じゃんけん)での対応関係:

classDiagram
  class Strategy {
    <<interface>>
    +nextHand() Hand
    +study(win)
  }
  class WinningStrategy
  class ProbStrategy
  class Player {
    +nextHand() Hand
    +win()
    +lose()
    +even()
  }
  Strategy <|.. WinningStrategy
  Strategy <|.. ProbStrategy
  Player o-- Strategy

やり方

  • ConcreteStrategy達にStrategyを実装させる
  • ContextはConcreteStrategyインスタンスを持つが、Strategyインタフェースを呼び出す

なんのクラスを呼び出すかについての指定は、リフレクションにより動的に行うことで、Contextはクラス名を知る必要がなくなる

メリット(用途)

所感

これただのインタフェースの使い方では?

Java
ProbStrategy.java
import java.util.Random;

public class ProbStrategy implements Strategy {
    private Random random;
    private int prevHandValue = 0;
    private int currentHandValue = 0;
    private int[][] history = {
        { 1, 1, 1, },
        { 1, 1, 1, },
        { 1, 1, 1, },
    };
    public ProbStrategy(int seed) {
        random = new Random(seed);
    }
    public Hand nextHand() {
        int bet = random.nextInt(getSum(currentHandValue));
        int handvalue = 0;
        if (bet < history[currentHandValue][0]) {
            handvalue = 0;
        } else if (bet < history[currentHandValue][0] + history[currentHandValue][1]) {
            handvalue = 1;
        } else {
            handvalue = 2;
        }
        prevHandValue = currentHandValue;
        currentHandValue = handvalue;
        return Hand.getHand(handvalue);
    }
    private int getSum(int hv) {
        int sum = 0;
        for (int i = 0; i < 3; i++) {
            sum += history[hv][i];
        }
        return sum;
    }
    public void study(boolean win) {
        if (win) {
            history[prevHandValue][currentHandValue]++;
        } else {
            history[prevHandValue][(currentHandValue + 1) % 3]++;
            history[prevHandValue][(currentHandValue + 2) % 3]++;
        }
    }
}
Strategy.java
public interface Strategy {
    public abstract Hand nextHand();
    public abstract void study(boolean win);
}
WinningStrategy.java
import java.util.Random;

public class WinningStrategy implements Strategy {
    private Random random;
    private boolean won = false;
    private Hand prevHand;
    public WinningStrategy(int seed) {
        random = new Random(seed);
    }
    public Hand nextHand() {
        if (!won) {
            prevHand = Hand.getHand(random.nextInt(3));
        }
        return prevHand;
    }
    public void study(boolean win) {
        won = win;
    }
}
Hand.java
public class Hand {
    public static final int HANDVALUE_GUU = 0;  // グーを表す値
    public static final int HANDVALUE_CHO = 1;  // チョキを表す値
    public static final int HANDVALUE_PAA = 2;  // パーを表す値
    public static final Hand[] hand = {         // じゃんけんの手を表す3つのインスタンス
        new Hand(HANDVALUE_GUU),
        new Hand(HANDVALUE_CHO),
        new Hand(HANDVALUE_PAA),
    };
    private static final String[] name = {      // じゃんけんの手の文字列表現
        "グー", "チョキ", "パー",
    };
    private int handvalue;                      // じゃんけんの手の値
    private Hand(int handvalue) {
        this.handvalue = handvalue;
    }
    public static Hand getHand(int handvalue) { // 値からインスタンスを得る
        return hand[handvalue];
    }
    public boolean isStrongerThan(Hand h) {     // thisがhより強いときtrue
        return fight(h) == 1;
    }
    public boolean isWeakerThan(Hand h) {       // thisがhより弱いときtrue
        return fight(h) == -1;
    }
    private int fight(Hand h) {                 // 引き分けは0, thisの勝ちなら1, hの勝ちなら-1
        if (this == h) {
            return 0;
        } else if ((this.handvalue + 1) % 3 == h.handvalue) {
            return 1;
        } else {
            return -1;
        }
    }
    public String toString() {                  // 文字列表現へ変換
        return name[handvalue];
    }
}
Player.java
public class Player {
    private String name;
    private Strategy strategy;
    private int wincount;
    private int losecount;
    private int gamecount;
    public Player(String name, Strategy strategy) {         // 名前と戦略を授けられる
        this.name = name;
        this.strategy = strategy;
    }
    public Hand nextHand() {                                // 戦略におうかがいを立てる
        return strategy.nextHand();
    }
    public void win() {                 // 勝った
        strategy.study(true);
        wincount++;
        gamecount++;
    }
    public void lose() {                // 負けた
        strategy.study(false);
        losecount++;
        gamecount++;
    }
    public void even() {                // 引き分け
        gamecount++;
    }
    public String toString() {
        return "[" + name + ":" + gamecount + " games, " + wincount + " win, " + losecount + " lose" + "]";
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        if (args.length != 2) {
            System.out.println("Usage: java Main randomseed1 randomseed2");
            System.out.println("Example: java Main 314 15");
            System.exit(0);
        }
        int seed1 = Integer.parseInt(args[0]);
        int seed2 = Integer.parseInt(args[1]);
        Player player1 = new Player("Taro", new WinningStrategy(seed1));
        Player player2 = new Player("Hana", new ProbStrategy(seed2));
        for (int i = 0; i < 10000; i++) {
            Hand nextHand1 = player1.nextHand();
            Hand nextHand2 = player2.nextHand();
            if (nextHand1.isStrongerThan(nextHand2)) {
                System.out.println("Winner:" + player1);
                player1.win();
                player2.lose();
            } else if (nextHand2.isStrongerThan(nextHand1)) {
                System.out.println("Winner:" + player2);
                player1.lose();
                player2.win();
            } else {
                System.out.println("Even...");
                player1.even();
                player2.even();
            }
        }
        System.out.println("Total result:");
        System.out.println(player1.toString());
        System.out.println(player2.toString());
    }
}
Go

Java版と同じ題材(じゃんけん)を移植。interface を暗黙実装する点、Hand を静的インスタンス配列でなく型付き定数(enum風)+Stringerで表す点がGoらしい差分。

実行: go run ./GoF/patterns/Strategy/go [seed1 seed2]

$ go run ./GoF/patterns/Strategy/go
strategy.go
package main

// Strategy はじゃんけんの「戦略」を表すインタフェース。
// ConcreteStrategy(WinningStrategy / ProbStrategy)がこれを実装する。
//
// Go の interface は暗黙実装(implements を書かない)。
// ここが Java との一番わかりやすい違い。
type Strategy interface {
	NextHand() Hand // 次の手を返す
	Study(win bool) // 直前の手が勝ったか負けたかを学習する
}
hand.go
package main

// Hand はじゃんけんの手を表す。
// Java版は Hand の静的インスタンス配列で表現していたが、
// Go では「型付き定数(enum風)」+ Stringer で自然に書ける。
type Hand int

const (
	Guu Hand = iota // グー
	Cho             // チョキ
	Paa             // パー
)

var handNames = [...]string{"グー", "チョキ", "パー"}

// String は fmt.Stringer を満たす(Java の toString 相当)。
func (h Hand) String() string {
	return handNames[h]
}

// IsStrongerThan は h より自分(this)が強いとき true。
func (h Hand) IsStrongerThan(other Hand) bool {
	return h.fight(other) == 1
}

// IsWeakerThan は h より自分(this)が弱いとき true。
func (h Hand) IsWeakerThan(other Hand) bool {
	return h.fight(other) == -1
}

// fight は引き分けなら 0、自分の勝ちなら 1、相手の勝ちなら -1。
func (h Hand) fight(other Hand) int {
	switch {
	case h == other:
		return 0
	case (int(h)+1)%3 == int(other):
		return 1
	default:
		return -1
	}
}
winning_strategy.go
package main

import "math/rand"

// WinningStrategy は ConcreteStrategy の一つ。
// 「勝ったら次も同じ手を出す。負けたら次はランダムに変える」戦略。
type WinningStrategy struct {
	random   *rand.Rand
	won      bool
	prevHand Hand
}

// NewWinningStrategy はコンストラクタ相当。
// Java の new Random(seed) と対応させるため、シードから乱数源を作る。
func NewWinningStrategy(seed int64) *WinningStrategy {
	return &WinningStrategy{random: rand.New(rand.NewSource(seed))}
}

func (s *WinningStrategy) NextHand() Hand {
	if !s.won {
		s.prevHand = Hand(s.random.Intn(3))
	}
	return s.prevHand
}

func (s *WinningStrategy) Study(win bool) {
	s.won = win
}
prob_strategy.go
package main

import "math/rand"

// ProbStrategy は ConcreteStrategy の一つ。
// 過去の勝敗履歴(history)から、次にどの手を出すと有利かを確率的に決める戦略。
// history[前回の手][今回の手] に勝った回数を貯めていく。
type ProbStrategy struct {
	random           *rand.Rand
	prevHandValue    int
	currentHandValue int
	history          [3][3]int
}

func NewProbStrategy(seed int64) *ProbStrategy {
	return &ProbStrategy{
		random: rand.New(rand.NewSource(seed)),
		history: [3][3]int{
			{1, 1, 1},
			{1, 1, 1},
			{1, 1, 1},
		},
	}
}

func (s *ProbStrategy) NextHand() Hand {
	bet := s.random.Intn(s.getSum(s.currentHandValue))
	var handValue int
	switch {
	case bet < s.history[s.currentHandValue][0]:
		handValue = 0
	case bet < s.history[s.currentHandValue][0]+s.history[s.currentHandValue][1]:
		handValue = 1
	default:
		handValue = 2
	}
	s.prevHandValue = s.currentHandValue
	s.currentHandValue = handValue
	return Hand(handValue)
}

// getSum は history[hv] の合計(=賭けの母数)を返す。
func (s *ProbStrategy) getSum(hv int) int {
	sum := 0
	for i := 0; i < 3; i++ {
		sum += s.history[hv][i]
	}
	return sum
}

func (s *ProbStrategy) Study(win bool) {
	if win {
		s.history[s.prevHandValue][s.currentHandValue]++
	} else {
		s.history[s.prevHandValue][(s.currentHandValue+1)%3]++
		s.history[s.prevHandValue][(s.currentHandValue+2)%3]++
	}
}
player.go
package main

import "fmt"

// Player は Strategy の利用者(Context)。
// 自分がどんな戦略を持っているかは Strategy インタフェース越しにしか触らない。
// = 戦略の中身を差し替えても Player のコードは変わらない、が Strategy パターンの肝。
type Player struct {
	name      string
	strategy  Strategy
	winCount  int
	loseCount int
	gameCount int
}

// NewPlayer は名前と戦略を授けられる。
func NewPlayer(name string, strategy Strategy) *Player {
	return &Player{name: name, strategy: strategy}
}

// NextHand は戦略におうかがいを立てる。
func (p *Player) NextHand() Hand {
	return p.strategy.NextHand()
}

func (p *Player) Win() { // 勝った
	p.strategy.Study(true)
	p.winCount++
	p.gameCount++
}

func (p *Player) Lose() { // 負けた
	p.strategy.Study(false)
	p.loseCount++
	p.gameCount++
}

func (p *Player) Even() { // 引き分け
	p.gameCount++
}

// String は fmt.Stringer を満たす(Java の toString 相当)。
func (p *Player) String() string {
	return fmt.Sprintf("[%s: %d games, %d win, %d lose]", p.name, p.gameCount, p.winCount, p.loseCount)
}
main.go
package main

import (
	"fmt"
	"os"
	"strconv"
)

// 実行:
//
//	go run ./GoF/patterns/Strategy/go            # デフォルトseed(314, 15)
//	go run ./GoF/patterns/Strategy/go 314 15     # seed指定
//
// Java版は毎ゲームの勝者を1万行出力するが、うるさいので最終結果だけ出す。
func main() {
	seed1, seed2 := int64(314), int64(15)
	if len(os.Args) == 3 {
		s1, err1 := strconv.ParseInt(os.Args[1], 10, 64)
		s2, err2 := strconv.ParseInt(os.Args[2], 10, 64)
		if err1 != nil || err2 != nil {
			fmt.Println("Usage: go run ./GoF/patterns/Strategy/go randomseed1 randomseed2")
			os.Exit(1)
		}
		seed1, seed2 = s1, s2
	}

	// 同じ Player でも、渡す ConcreteStrategy を差し替えるだけで挙動が変わる。
	player1 := NewPlayer("Taro", NewWinningStrategy(seed1))
	player2 := NewPlayer("Hana", NewProbStrategy(seed2))

	for i := 0; i < 10000; i++ {
		hand1 := player1.NextHand()
		hand2 := player2.NextHand()
		switch {
		case hand1.IsStrongerThan(hand2):
			player1.Win()
			player2.Lose()
		case hand2.IsStrongerThan(hand1):
			player1.Lose()
			player2.Win()
		default:
			player1.Even()
			player2.Even()
		}
	}

	fmt.Println("Total result:")
	fmt.Println(player1)
	fmt.Println(player2)
}
PHP
index.php
<?php  // ストラテジパターン

/**
 * Strategy 抽象的な戦略を定義する
 * ConcreteStrategy Strategy 具体的な戦略を定義する
 * Context ConcreteStrategyを所有する Strategyを利用する
 */

interface Strategy
{
  public function behave();
}

class LadyStrategy  implements Strategy
{
  public function behave()
  {
    var_dump("本当の親のように甘える");
  }
}

class OlomanStrategy  implements Strategy
{
  public function behave()
  {
    var_dump("持っているものを褒める");
  }
}


class AngryManStrategy  implements Strategy
{
  public function behave()
  {
    var_dump("子供らしく振舞う");
  }
}


// Context : strategy本人

class ConcreteStrategy
{
  private $strategy;

  public function __construct($strategy)
  {
    $this->strategy = $strategy;
  }
  public function behave()
  {
    $this->strategy->behave();
  }
}



function main()
{
  $person = new ConcreteStrategy(new LadyStrategy());
  $person->behave();
  $person = new ConcreteStrategy(new OlomanStrategy());
  $person->behave();
  $person = new ConcreteStrategy(new AngryManStrategy());
  $person->behave();
}

main();
TypeScript
$ npx tsx GoF/patterns/Strategy/typescript/main.ts
main.ts
// Strategy パターン: じゃんけん (Java版と同じ題材)
//
// 実行:
//   npx tsx main.ts            # デフォルトseed(314, 15)
//   npx tsx main.ts 314 15     # seed指定
//
// Java版は毎ゲームの勝者を1万行出力するが、Go版に倣ってうるさいので最終結果だけ出す。

// Node.js の標準ライブラリにはシード指定可能な乱数がない(Math.randomはシード不可)。
// Go版のrand.NewSource(seed)相当を自前で用意する(xorshift32)。
class SeededRandom {
  private state: number;
  constructor(seed: number) {
    // 0だとxorshiftが機能しないので下駄を履かせる
    this.state = (seed >>> 0) || 0x9e3779b9;
  }
  // [0, bound) の整数を返す。Java の Random#nextInt(bound) / Go の rand.Intn(bound) 相当。
  nextInt(bound: number): number {
    this.state ^= this.state << 13;
    this.state ^= this.state >>> 17;
    this.state ^= this.state << 5;
    this.state >>>= 0;
    return this.state % bound;
  }
}

// Hand: じゃんけんの手。
// TypeScriptでもJava版同様に「クラス+静的インスタンス配列」でキャッシュ表現できる。
// Go版はenum風の型付き定数で表現したので、その対比としてここはJava寄りの書き方を採用する。
class Hand {
  static readonly HANDVALUE_GUU = 0;
  static readonly HANDVALUE_CHO = 1;
  static readonly HANDVALUE_PAA = 2;

  private static readonly names = ["グー", "チョキ", "パー"] as const;
  private static readonly instances: Hand[] = [0, 1, 2].map((v) => new Hand(v));

  private constructor(private readonly handValue: number) {}

  static getHand(handValue: number): Hand {
    return Hand.instances[handValue];
  }

  isStrongerThan(h: Hand): boolean {
    return this.fight(h) === 1;
  }

  isWeakerThan(h: Hand): boolean {
    return this.fight(h) === -1;
  }

  private fight(h: Hand): number {
    if (this === h) return 0;
    return (this.handValue + 1) % 3 === h.handValue ? 1 : -1;
  }

  toString(): string {
    return Hand.names[this.handValue];
  }
}

// Strategy: TypeScriptのinterfaceはJavaのinterfaceとほぼ同じ感覚で書ける。
// (Go版のような暗黙実装ではなく、`implements`を明示する点はJavaと同じ)
interface Strategy {
  nextHand(): Hand;
  study(win: boolean): void;
}

// WinningStrategy: 勝ったら次も同じ手、負けたらランダムに変える。
class WinningStrategy implements Strategy {
  private random: SeededRandom;
  private won = false;
  private prevHand!: Hand;

  constructor(seed: number) {
    this.random = new SeededRandom(seed);
  }

  nextHand(): Hand {
    if (!this.won) {
      this.prevHand = Hand.getHand(this.random.nextInt(3));
    }
    return this.prevHand;
  }

  study(win: boolean): void {
    this.won = win;
  }
}

// ProbStrategy: 過去の勝敗履歴から確率的に手を決める。
class ProbStrategy implements Strategy {
  private random: SeededRandom;
  private prevHandValue = 0;
  private currentHandValue = 0;
  private history: number[][] = [
    [1, 1, 1],
    [1, 1, 1],
    [1, 1, 1],
  ];

  constructor(seed: number) {
    this.random = new SeededRandom(seed);
  }

  nextHand(): Hand {
    const bet = this.random.nextInt(this.getSum(this.currentHandValue));
    let handValue: number;
    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 getSum(hv: number): number {
    return this.history[hv].reduce((a, b) => a + b, 0);
  }

  study(win: boolean): void {
    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: Strategyの利用者(Context)。戦略の中身はinterface越しにしか触らない。
class Player {
  private winCount = 0;
  private loseCount = 0;
  private gameCount = 0;

  constructor(private readonly name: string, private readonly strategy: Strategy) {}

  nextHand(): Hand {
    return this.strategy.nextHand();
  }

  win(): void {
    this.strategy.study(true);
    this.winCount++;
    this.gameCount++;
  }

  lose(): void {
    this.strategy.study(false);
    this.loseCount++;
    this.gameCount++;
  }

  even(): void {
    this.gameCount++;
  }

  toString(): string {
    return `[${this.name}: ${this.gameCount} games, ${this.winCount} win, ${this.loseCount} lose]`;
  }
}

function main(): void {
  const args = process.argv.slice(2);
  let seed1 = 314;
  let seed2 = 15;
  if (args.length === 2) {
    seed1 = Number.parseInt(args[0], 10);
    seed2 = Number.parseInt(args[1], 10);
    if (Number.isNaN(seed1) || Number.isNaN(seed2)) {
      console.log("Usage: npx tsx main.ts randomseed1 randomseed2");
      process.exit(1);
    }
  }

  const player1 = new Player("Taro", new WinningStrategy(seed1));
  const player2 = new Player("Hana", new ProbStrategy(seed2));

  for (let i = 0; i < 10000; i++) {
    const hand1 = player1.nextHand();
    const hand2 = player2.nextHand();
    if (hand1.isStrongerThan(hand2)) {
      player1.win();
      player2.lose();
    } else if (hand2.isStrongerThan(hand1)) {
      player1.lose();
      player2.win();
    } else {
      player1.even();
      player2.even();
    }
  }

  console.log("Total result:");
  console.log(player1.toString());
  console.log(player2.toString());
}

main();
Python
$ python3 GoF/patterns/Strategy/python/main.py
main.py
"""Strategy パターン: じゃんけん (Java版と同じ題材)

実行:
    python3 main.py            # デフォルトseed(314, 15)
    python3 main.py 314 15     # seed指定

Java版は毎ゲームの勝者を1万行出力するが、Go/TS版に倣ってうるさいので最終結果だけ出す。
"""

from __future__ import annotations

import random
import sys
from abc import ABC, abstractmethod


class Hand:
    """じゃんけんの手。Java版同様「クラス+静的インスタンスのキャッシュ」で表現する。"""

    HANDVALUE_GUU = 0
    HANDVALUE_CHO = 1
    HANDVALUE_PAA = 2
    _names = ("グー", "チョキ", "パー")

    def __init__(self, hand_value: int) -> None:
        self._hand_value = hand_value

    @classmethod
    def get_hand(cls, hand_value: int) -> "Hand":
        return _HAND_INSTANCES[hand_value]

    def is_stronger_than(self, h: "Hand") -> bool:
        return self._fight(h) == 1

    def is_weaker_than(self, h: "Hand") -> bool:
        return self._fight(h) == -1

    def _fight(self, h: "Hand") -> int:
        if self is h:
            return 0
        return 1 if (self._hand_value + 1) % 3 == h._hand_value else -1

    def __str__(self) -> str:
        return self._names[self._hand_value]


_HAND_INSTANCES = [Hand(0), Hand(1), Hand(2)]


class Strategy(ABC):
    """PythonにはJavaのinterfaceに相当する言語機能はないため、抽象基底クラス(ABC)で表す。
    Goのような暗黙実装ではなく、Java同様に継承(Strategyを継承)して満たす必要がある。
    """

    @abstractmethod
    def next_hand(self) -> Hand: ...

    @abstractmethod
    def study(self, win: bool) -> None: ...


class WinningStrategy(Strategy):
    """勝ったら次も同じ手、負けたらランダムに変える。"""

    def __init__(self, seed: int) -> None:
        self._random = random.Random(seed)
        self._won = False
        self._prev_hand: Hand | None = None

    def next_hand(self) -> Hand:
        if not self._won:
            self._prev_hand = Hand.get_hand(self._random.randrange(3))
        assert self._prev_hand is not None
        return self._prev_hand

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


class ProbStrategy(Strategy):
    """過去の勝敗履歴から確率的に手を決める。"""

    def __init__(self, seed: int) -> None:
        self._random = random.Random(seed)
        self._prev_hand_value = 0
        self._current_hand_value = 0
        self._history = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]

    def next_hand(self) -> Hand:
        bet = self._random.randrange(self._get_sum(self._current_hand_value))
        row = self._history[self._current_hand_value]
        if bet < row[0]:
            hand_value = 0
        elif bet < row[0] + row[1]:
            hand_value = 1
        else:
            hand_value = 2
        self._prev_hand_value = self._current_hand_value
        self._current_hand_value = hand_value
        return Hand.get_hand(hand_value)

    def _get_sum(self, hv: int) -> int:
        return sum(self._history[hv])

    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


class Player:
    """Strategyの利用者(Context)。戦略の中身はStrategyのインタフェース越しにしか触らない。"""

    def __init__(self, name: str, strategy: Strategy) -> None:
        self._name = name
        self._strategy = strategy
        self._win_count = 0
        self._lose_count = 0
        self._game_count = 0

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

    def win(self) -> None:
        self._strategy.study(True)
        self._win_count += 1
        self._game_count += 1

    def lose(self) -> None:
        self._strategy.study(False)
        self._lose_count += 1
        self._game_count += 1

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

    def __str__(self) -> str:
        return f"[{self._name}: {self._game_count} games, {self._win_count} win, {self._lose_count} lose]"


def main() -> None:
    seed1, seed2 = 314, 15
    if len(sys.argv) == 3:
        try:
            seed1, seed2 = int(sys.argv[1]), int(sys.argv[2])
        except ValueError:
            print("Usage: python3 main.py randomseed1 randomseed2")
            sys.exit(1)

    player1 = Player("Taro", WinningStrategy(seed1))
    player2 = Player("Hana", ProbStrategy(seed2))

    for _ in range(10000):
        hand1 = player1.next_hand()
        hand2 = player2.next_hand()
        if hand1.is_stronger_than(hand2):
            player1.win()
            player2.lose()
        elif hand2.is_stronger_than(hand1):
            player1.lose()
            player2.win()
        else:
            player1.even()
            player2.even()

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


if __name__ == "__main__":
    main()