今回はStateパターン。なかなかエキセントリックなデザインパターンです。
要は
- メソッドの実装をある状態に応じて切り替える(条件分岐させる)
- ある状態というクラスがメソッドの実装を記述する
もう少し概念的な説明すると、
- 普通の条件分岐…『この状態ならこういう処理にする』
- Stateパターン…『この状態がこういう処理をする』
というように、状態が主語になるイメージです。
いつ使うのか。メリットは何か。
使う場面を間違えると却って混乱しそうなStateパターン。自分で例を考えて実装してみて、有効だと感じた点を列挙します。
- どのメソッドにも同じような条件分岐が散らばるほどに重要な『状態』がある時
まあこれは当たり前っちゃあ当たり前。『この状態の時に呼ばれたらこう動く』という説明のメソッドがあったら要チェック。
- 将来状態が増減しそうな時
これも当たり前かな
- 状態を矛盾なく取り扱える
本書でも触れてますが、このパターンを適用する時は各Stateは排反である必要があるので、必竟自己矛盾が起きません。
これはプログラミングする上でとても大事です。『このifはelse ifにしないと動かない』みたいな分かりづらさから解放されます。
- 状態が増えすぎるとしんどい
特にベクトルの違う複数の状態の組み合わせだと、状態数が指数的に増えるおそれがある
今回は完全オリジナル。どこかのブログのパクリで、『マリオでStateパターン』です。
1ターンにランダムで
- きのこを食べる
- フラワーを食べる
- スターを食べる(スターは3ターンで切れる)
- 敵に当たる
- 穴に落ちる
のなかから一つの行動をします。State(状態)は
- チビマリオ
- デカマリオ
- フラワーマリオ
- 上記3種のスター状態
の計6種類です。
これだと、例えば『しっぽマリオを追加する』なんて時にSippoMarioStateクラスとStarSippoMarioStateクラスを追加するだけで実現できてしまいます(他所はいじる必要なし!)。Stateパターンの有効なケースですね。
実行結果
[残5:チビマリオ] スターを食べた! チビマリオ★ になった! [残5:チビマリオ★] ... [残5:チビマリオ★] キノコを食べた! デカマリオ★ になった! [残5:デカマリオ★] スターが切れた! デカマリオ になった! [残5:デカマリオ] スターを食べた! デカマリオ★ になった! [残5:デカマリオ★] 敵に接触! 敵を倒した! [残5:デカマリオ★] スターを食べた! デカマリオ★ になった! [残5:デカマリオ★] 穴に落ちた! 死んだ! 残り残機:4 [残4:チビマリオ] キノコを食べた! デカマリオ になった! [残4:デカマリオ] ... [残4:デカマリオ] 穴に落ちた! 死んだ! 残り残機:3 [残3:チビマリオ] ... [残3:チビマリオ] フラワーを食べた! デカマリオ になった! [残3:デカマリオ] スターを食べた! デカマリオ★ になった! [残3:デカマリオ★] フラワーを食べた! ファイアーマリオ★ になった! [残3:ファイアーマリオ★] フラワーを食べた! 意味はない! [残3:ファイアーマリオ★] スターが切れた! ファイアーマリオ になった! [残3:ファイアーマリオ] ... [残3:ファイアーマリオ] 穴に落ちた! 死んだ! 残り残機:2 [残2:チビマリオ] フラワーを食べた! デカマリオ になった! [残2:デカマリオ] 敵に接触! チビマリオ になった! [残2:チビマリオ] 穴に落ちた! 死んだ! 残り残機:1 [残1:チビマリオ] キノコを食べた! デカマリオ になった! [残1:デカマリオ] スターを食べた! デカマリオ★ になった! [残1:デカマリオ★] フラワーを食べた! ファイアーマリオ★ になった! [残1:ファイアーマリオ★] キノコを食べた! 意味はない! [残1:ファイアーマリオ★] スターが切れた! ファイアーマリオ になった! [残1:ファイアーマリオ] フラワーを食べた! 意味はない! [残1:ファイアーマリオ] 敵に接触! チビマリオ になった! [残1:チビマリオ] フラワーを食べた! デカマリオ になった! [残1:デカマリオ] 敵に接触! チビマリオ になった! [残1:チビマリオ] 敵に接触! 死んだ! 残り残機:0 [残0:チビマリオ] 穴に落ちた! GAME OVER 30ターンプレイしました
ソースコード
#!/usr/bin/env python
# -*- coding: utf8 -*-
class __MarioState:
def __init__(self): raise NotImplementedError
def doGetKinoko(self, context): raise NotImplementedError # キノコ取得
def doGetFlower(self, context): raise NotImplementedError # フラワー取得
def doGetStar(self, context): raise NotImplementedError # スター取得
def doLostStar(self, context): raise NotImplementedError # スター消滅
def doEncounter(self, context): raise NotImplementedError # 敵に接触
def doFall(self, context): raise NotImplementedError # 穴に落ちる
def __str__(self): raise NotImplementedError
class NormalMario(__MarioState):
def __init__(self): pass
def doGetKinoko(self, context): context.changeState(BigMario())
def doGetFlower(self, context): context.changeState(BigMario())
def doEncounter(self, context): context.dead()
def doGetStar(self, context): context.changeState(StarNormalMario())
def doLostStar(self, context): print "bug"
def doFall(self, context): context.dead()
def __str__(self): return "チビマリオ"
class BigMario(__MarioState):
def __init__(self): pass
def doGetKinoko(self, context): context.nope()
def doGetFlower(self, context): context.changeState(FlowerMario())
def doEncounter(self, context): context.changeState(NormalMario())
def doGetStar(self, context): context.changeState(StarBigMario())
def doLostStar(self, context): print "bug"
def doFall(self, context): context.dead()
def __str__(self): return "デカマリオ"
class FlowerMario(__MarioState):
def __init__(self): pass
def doGetKinoko(self, context): context.nope()
def doGetFlower(self, context): context.nope()
def doEncounter(self, context): context.changeState(NormalMario())
def doGetStar(self, context): context.changeState(StarFlowerMario())
def doLostStar(self, context): print "bug"
def doFall(self, context): context.dead()
def __str__(self): return "ファイアーマリオ"
class StarNormalMario(__MarioState):
def __init__(self): pass
def doGetKinoko(self, context): context.changeState(StarBigMario())
def doGetFlower(self, context): context.changeState(StarBigMario())
def doGetStar(self, context): context.changeState(StarNormalMario())
def doLostStar(self, context): context.changeState(NormalMario())
def doEncounter(self, context): context.defeat()
def doFall(self, context): context.dead()
def __str__(self): return "チビマリオ★"
class StarBigMario(__MarioState):
def __init__(self): pass
def doGetKinoko(self, context): context.nope()
def doGetFlower(self, context): context.changeState(StarFlowerMario())
def doGetStar(self, context): context.changeState(StarBigMario())
def doLostStar(self, context): context.changeState(BigMario())
def doEncounter(self, context): context.defeat()
def doFall(self, context): context.dead()
def __str__(self): return "デカマリオ★"
class StarFlowerMario(__MarioState):
def __init__(self): pass
def doGetKinoko(self, context): context.nope()
def doGetFlower(self, context): context.nope()
def doGetStar(self, context): context.changeState(StarFlowerMario())
def doLostStar(self, context): context.changeState(FlowerMario())
def doEncounter(self, context): context.defeat()
def doFall(self, context): context.dead()
def __str__(self): return "ファイアーマリオ★"
class __Context:
def __init__(self): raise NotImplementedError
def changeState(self, mariostate): raise NotImplementedError # 状態が変化した
def defeat(self): raise NotImplementedError # 敵を倒した
def dead(self): raise NotImplementedError # 死んだ
def nope(self): raise NotImplementedError # 何も起きない
import sys
from threading import Timer
class Game(__Context):
def __init__(self):
self.state = NormalMario()
self.zanki = 5 # 残機
self.turncount = 0 # 何ターン目か
self.starcount = 99999 # スター用カウンタ
def changeState(self, mariostate):
self.state = mariostate
print str(mariostate)+" になった!"
def defeat(self):
print "敵を倒した!"
def dead(self):
self.zanki -= 1
if self.zanki < 0:
print "GAME OVER"
print "%dターンプレイしました" % self.turncount
sys.exit(0)
print "死んだ! 残り残機:%d" % self.zanki
self.state = NormalMario()
self.starcount = 99999
def nope(self):
print "意味はない!"
#--
def youGetKinoko(self):
print "[残%d:%s] キノコを食べた! " % (self.zanki, self.state),
self.state.doGetKinoko(self)
def youGetFlower(self):
print "[残%d:%s] フラワーを食べた! " % (self.zanki, self.state),
self.state.doGetFlower(self)
def youEncounter(self):
print "[残%d:%s] 敵に接触! " % (self.zanki, self.state),
self.state.doEncounter(self)
def youFall(self):
print "[残%d:%s] 穴に落ちた! " % (self.zanki, self.state),
self.state.doFall(self)
def nothingHappened(self):
print "[残%d:%s] ..." % (self.zanki, self.state)
def youGetStar(self):
print "[残%d:%s] スターを食べた! " % (self.zanki, self.state),
self.state.doGetStar(self)
self.starcount = 0
def __youLostStar(self):
print "[残%d:%s] スターが切れた! " % (self.zanki, self.state),
self.state.doLostStar(self)
def youCountTurn(self):
self.turncount += 1
self.starcount += 1
if self.starcount == 3:
self.__youLostStar()
from time import sleep
from random import randint
def main():
m_game = Game()
while True:
# random action
m_game.youCountTurn()
act = randint(1,6)
if act==1: m_game.youGetKinoko()
elif act==2: m_game.youGetFlower()
elif act==3: m_game.youGetStar()
elif act==4: m_game.youEncounter()
elif act==5: m_game.youFall()
elif act==6: m_game.nothingHappened()
sleep(0.1)
if __name__=='__main__': main()
0 件のコメント:
コメントを投稿