12
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

雑多(一人用)Advent Calendar 2015

Day 20

Strategyパターンを触ってみる

Last updated at Posted at 2015-12-20

今日の目標

JavaのStrategyパターンとはどういったものなのかを知る

使うもの

ではスタート

はじめに

まずはStrategyパターンとはどのようなものなのかをWikipediaさんで調べてみる。

Strategyパターンは、コンピュータープログラミングの領域において、アルゴリズムを実行時に選択することができるデザインパターンである。(wikipedia-Strategyパターン

アルゴリズムのパターンをある程度用意しておいて、動的に切り替えられるようにするパターンのことかな。何となくわかるような。

本のサンプルを参考に自分で作ってみる

今回張り切ってサンプルを参考に自分で作ってみました。が、サンプルのほうがわかりやすいと思うので、わからなかったら本のほうを参考にしてください…。(本のサンプルはじゃんけんゲームです)

今回作るのは、テレビゲームのカジノとかでよく見かけるアップダウンゲームです。アップダウンゲームとは、トランプを一枚表にし、次にめくるカードがそのカードより「上か、下か」を当てる簡単なゲームです。これをStrategyパターンを使って作りました。
※本来ならトランプはA~Kまでですが、今回面倒だったので1~13という数字になってます。また、AはKより強いのですが、数字にした関係でAに当たる1は一番弱くなってます。ご了承ください

Strategyインタフェースは、shoutメソッドだけ定義してます。内部の実装は各実装クラスにお任せして、とりあえず「上だ!」「下だ!」ってことを叫んでね、ということ。

Strategy.java
package strategy;

import util.ShoutWord;

public interface Strategy {

	// 受け取ったnumberによってUPとDOWNどちらを宣言するか
	public abstract ShoutWord shout(int number);

}

1つ目のStrategy実装はこんな感じ。単純。

  • 1~6が出た場合⇒次のトランプはそれよりも上であると判断する
  • 7~13が出た場合⇒次のトランプはそれよりも下であると判断する
HalfStrategy.java
package strategy;

import util.ShoutWord;

public class HalfStrategy implements Strategy {

	@Override
	public ShoutWord shout(int number) {
		ShoutWord shoutWord;

		// もし数字が7以上だったら次のカードはDOWNと宣言する
		if(number>=7) {
			shoutWord = ShoutWord.DOWN;
		} else {
			shoutWord = ShoutWord.UP;
		}
		return shoutWord;
	}
}

2つ目のStrategy実装はこれ。

  • 1~4が出た場合⇒次のトランプはそれよりも上であると判断する
  • 10~13が出た場合⇒次のトランプはそれよりも下であると判断する
  • 5~9が出た場合⇒次の数字をランダムで予測して、予測した数字のほうが大きかったら上であると判断して、予測した数字のほうが小さかったら下であると判断する
OneThirdStrategy.java
package strategy;

import java.util.Random;

import util.ShoutWord;

public class OneThirdStrategy implements Strategy {

	@Override
	public ShoutWord shout(int number) {
		ShoutWord shoutWord;
		if(number>=10) {
			// 1-4: 上
			shoutWord = ShoutWord.DOWN;
		} else if(number<=4){
			// 10-13: 下
			shoutWord = ShoutWord.UP;
		} else {
			// 5-9: ランダム数字を生成し、次の数字を予想する
			Random random = new Random();
			int next = random.nextInt(13) + 1;
			if(number<=next) {
				shoutWord = ShoutWord.UP;
			} else {
				shoutWord = ShoutWord.DOWN;
			}
		}
		return shoutWord;
	}
}

PlayerはカードがめくられたらStrategyにお伺いを立てて、上か下かを叫びます。shout!

Player.java
package strategy;

import util.ResultWord;
import util.ShoutWord;

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 ShoutWord shout(int number) {
		return strategy.shout(number);
	}

	public void win() {
		wincount++;
		gamecount++;
	}

	public void lose() {
		losecount++;
		gamecount++;
	}

	public ResultWord result() {
		if(wincount>losecount) {
			return ResultWord.OK;
		}
		return ResultWord.NG;
	}

	public String toString() {
		return "[" + name + ":" + gamecount + " games, " + wincount + " win, " + losecount + " lose.";
	}
}

各enum。

ShoutWord.java
package util;

public enum ShoutWord {
	UP,
	DOWN
}
ResultWord.java
package util;

public enum ResultWord {
	OK,
	NG
}

実行するMainクラス。1000回勝負したうち、勝った回数と負けた回数を出力してます。
途中で、もし結果が「負けが多い」ということであれば(Playerのresultメソッド呼び出し)、別の戦略に乗り換えるようにしてます。OneThirdStrategyからHalfStrategyへ。(ロジックはコピペしてます。ホントはいけないんですが、今回はご容赦を)

Main.java
import java.util.Random;

import strategy.HalfStrategy;
import strategy.OneThirdStrategy;
import strategy.Player;
import util.ResultWord;
import util.ShoutWord;

public class Main {

	public static void main(String[] args) {
		Random rnd = new Random();

		Player player = new Player("torinist", new OneThirdStrategy());
		int trumpNumberOld = rnd.nextInt(13) + 1;

		for(int i=0;i<1000;i++) {
			int trumpNumberNew = rnd.nextInt(13) + 1;
			ShoutWord shoutWord = player.shout(trumpNumberOld);

			// 判定
			if(trumpNumberOld<trumpNumberNew) {
				// もし新しいカードのほうが数字が大きい場合
				if(ShoutWord.UP==shoutWord) {
					player.win();
				} else {
					player.lose();
				}
			} else if(trumpNumberOld>trumpNumberNew) {
				// もし新しいカードのほうが数字が小さい場合
				if(ShoutWord.DOWN==shoutWord) {
					player.win();
				} else {
					player.lose();
				}
			} else {
				// 同じ数字の場合は自動的にPlayerがwinになる
				player.win();
			}

			trumpNumberOld = trumpNumberNew;
		}

		if(ResultWord.NG == player.result()) {
			System.out.println("HalfStrategyに移行します。");
			player = new Player("torinist", new HalfStrategy());
			trumpNumberOld = rnd.nextInt(13) + 1;

			for(int i=0;i<1000;i++) {
				int trumpNumberNew = rnd.nextInt(13) + 1;
				ShoutWord shoutWord = player.shout(trumpNumberOld);

				// 判定
				if(trumpNumberOld<trumpNumberNew) {
					// もし新しいカードのほうが数字が大きい場合
					if(ShoutWord.UP==shoutWord) {
						player.win();
					} else {
						player.lose();
					}
				} else if(trumpNumberOld>trumpNumberNew) {
					// もし新しいカードのほうが数字が小さい場合
					if(ShoutWord.DOWN==shoutWord) {
						player.win();
					} else {
						player.lose();
					}
				} else {
					// 同じ数字の場合は自動的にPlayerがwinになる
					player.win();
				}

				trumpNumberOld = trumpNumberNew;
			}
		}

		System.out.println("Total result: " + player.toString());
	}

}

結果はこんな感じ。ぶっちゃけOneThirdStrategyが負けることがないので、HalfStrategyには移行しません。残念。
Total result: [torinist:1000 games, 782 win, 218 lose.

注目すべきは、Mainの中でPlayerが持つべきStrategyを変更しているということですかね。たぶんこの動的な切り替えがStrategyパターンなのだと理解してます。違ったら訂正して頂けるとありがたいです。
もし、戦略を増やしたい!ということであれば、Strategyインタフェースを実装したクラスを作って、MainでPlayerに渡すStrategy実装を変えれば良いのですね。

今日はこんな感じで!

12
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?