modules
├ cardgame
└ playingcards
としており、「トランプのデッキ」のクラスはplayingcardsに、残りはcardgameに入れています。modulesフォルダ全体をimportの参照先にするよう設定していますので、これは同様の設定であればどこでもかまいません。# coding: UTF-8
from enum import IntEnum
class Rank(IntEnum):
u'''トランプのランクを表す整数列挙型。Aが14、数字は数字通り、JQKは11,12,13。'''
Ace = 14
Two = 2
Three = 3
Four = 4
Five = 5
Six = 6
Seven = 7
Eight = 8
Nine = 9
Ten = 10
Jack = 11
Queen = 12
King = 13
# 自分の名前を正式の(長い)文字列で返す
def getFullName(self):
return str(self).split('.')[-1]
# 自分の名前を頭文字で返す
def getName(self):
if self.value == 0:
return str(self).split('.')[-1]
elif self.value < 11:
return str(self.value)
else:
return str(self).split('.')[-1][0]
# coding: UTF-8
from enum import IntEnum
class Suit(IntEnum):
u'''トランプのスートを表す整数列挙型。ブリッジオーダー。'''
X = 5 #ジョーカー
Spades = 4
Hearts = 3
Diamonds = 2
Clubs = 1
def getMark(self):
suits = {Suit.Spades:u'♠', Suit.Hearts:u'♡', Suit.Diamonds:u'♢', Suit.Clubs:u'♣', Suit.X:u'J'}
return suits[self]
class Card():
u'''カードゲームのカードを表すクラス。
ランクとスートを設定して生成し、並べ替えや強さの比較が可能。
大小比較は、スートの強さを比べ、同じならランクの強さを比べる。
ランクとスートの型指定はなく、ダックタイピングに基づく独自実装でもよい。'''
rank = ''
suit = ''
def __init__(self, rank, suit):
u'''コンストラクタ。ランクとスートをセットする。'''
self.rank = rank
self.suit = suit
# 文字列表現(解析用としてスートは文字出力)
def __str__(self):
return str(self.suit).split('.')[-1] + '.' + str(self.rank.value)
# ソート比較用
def __eq__(self, obj):
return (self.suit == obj.suit) and (self.rank == obj.rank)
def __ne__(self, obj):
return (self.suit != obj.suit) or (self.rank != obj.rank)
def __lt__(self, obj):
if (self.suit != obj.suit):
judge = (self.suit < obj.suit)
else:
judge = (self.rank < obj.rank)
return judge
def __le__(self, obj):
if (self.suit != obj.suit):
judge = (self.suit <= obj.suit)
else:
judge = (self.rank <= obj.rank)
return judge
def __gt__(self, obj):
if (self.suit != obj.suit):
judge = (self.suit > obj.suit)
else:
judge = (self.rank > obj.rank)
return judge
def __ge__(self, obj):
if (self.suit != obj.suit):
judge = (self.suit >= obj.suit)
else:
judge = (self.rank >= obj.rank)
return judge
# ゲッターとセッター
def setRank(self, rank):
u'''ランクのセッター。'''
self.rank = rank
def getRank(self):
u'''ランクのゲッター。'''
return self.rank
def setSuit(self, suit):
u'''スートのセッター。'''
self.suit = suit
def getSuit(self):
u'''スートのゲッター。'''
return self.suit
def setPhoto(self, photo):
u'''画像イメージのセッター。'''
self.photo = photo
def getPhoto(self):
u'''画像イメージのゲッター。'''
return self.photo
# カード内容を文字列で返す(CUI等の表示用にスートは記号)
def string(self):
return (self.suit.getMark() + self.rank.getName())
# coding: UTF-8
import random
from playingcards.rank import Rank
from playingcards.suit import Suit
from playingcards.card import Card
class Deck():
u'''カードゲームの山札を表すクラス。
指定したランク・スートに基づく山札を生成し、各種操作を行う。'''
deck = []
def __init__(self, joker=False):
u'''コンストラクタ。ランク×スートの総当たりデッキを作成する。'''
ranks = [rank for rank in Rank]
suits = [suit for suit in Suit if suit is not Suit.X]
self.deck = [Card(rank, suit) for rank in ranks for suit in suits]
if joker:
self.deck.append(Card(Rank.Ace, Suit.X))
self.deck.append(Card(Rank.Two, Suit.X))
def __setitem__(self, key, value):
u'''添字アクセス用のセッター。'''
self.deck[key] = value
def __getitem__(self, key):
u'''添字アクセス用のゲッター。'''
return self.deck[key]
def __len__(self):
u'''len()関数用のゲッター。'''
return len(self.deck)
def shuffle(self):
u'''山札をシャッフルし、山札自体を返す。'''
random.shuffle(self.deck)
return self
def pick(self):
u'''山札の上から1枚抜いて返す。'''
return self.deck.pop(0)
def add(self, card):
u'''山札の下に1枚追加する。'''
self.deck.append(card)
return self
def extend(self, cards):
u'''山札にカードのリストを追加する。'''
self.deck.extend(cards)
return self
def size(self):
u'''山札の枚数を返す。'''
return len(self.deck)
# coding: UTF-8
import random
from cardgame.playertype import PlayerType
class Player():
u'''カードゲームのプレイヤーを表すクラス。
手札をリストで保持して操作を行う(リストのシンタックスシュガー)。'''
def __init__(self, ptype=PlayerType.AI_RANDOM, hands=[], pname='Default'):
u'''コンストラクタ。プレイヤータイプと初期手札をセットする。'''
self.ptype = ptype
self.hands = hands
self.pname = pname
self.pointCards = []
def setType(self, ptype):
u'''プレイヤータイプのセッター。0が人間、1がコンピュータ。'''
self.ptype = ptype
def getType(self):
u'''プレイヤータイプのゲッター。0が人間、1がコンピュータ。'''
return self.ptype
def getHands(self):
u'''全手札のゲッター。手札からは抜かず、単に参照を返す。'''
return self.hands
def getHand(self, i):
u'''手札1枚のゲッター。手札からは抜かず、単に参照を返す。'''
return self.hands[i]
def setHands(self, hands):
u'''手札のリストをセットする。'''
self.hands = hands
def getPointCards(self):
u'''全獲得札のゲッター。'''
return self.pointCards
def getPointCard(self, i):
u'''獲得札1枚のゲッター。'''
return self.pointCards[i]
def setPointCard(self, card):
u'''獲得札1枚をセットする。'''
self.pointCards.append(card)
def sort(self):
u'''手札のリストをソートする。ソートした手札を返す。'''
self.hands.sort()
def addCard(self, card):
u'''カードを1枚、手札の末尾に追加する。'''
self.hands.append(card)
def playCard(self, i):
u'''選んだカードを1枚プレイする。手札から抜いて(インデックス, カード)のタプルを返す。
intを設定すれば順番指定、カードを入れたら手札の最初に該当するものを選ぶ。
存在しないものを指定するとNoneを返す。'''
if isinstance(i, int):
return (i, self.hands.pop(i))
if i in self.hands:
return (self.hands.index(i), self.hands.pop(self.hands.index(i)))
return None
def clear(self):
u'''手札と獲得札のリストをクリアする。'''
del self.hands[:]
del self.pointCards[:]
def chooseCard(self, table):
u'''ルールを参照し、プレイ可能なカードを手札からランダムに選び、そのカードを返す。
選び方を別途実装する場合、このメソッドを上書きする。'''
return random.choice(
[card for i, card in enumerate(self.hands) if table.rule.isPlayable(table.playedCards, self.hands, i)]
)
# coding: UTF-8
from enum import IntEnum
class PlayerType(IntEnum):
u'''プレイヤーの種類を表す整数列挙型。'''
HUMAN = 0
AI_RANDOM = 1
AI_MODEST = 2
AI_LOSE = 3
# coding: UTF-8
from playingcards.rank import Rank
from playingcards.suit import Suit
from playingcards.card import Card
from playingcards.deck import Deck
class TTRule(object):
u'''トリックテイキングのプレイ規則、トリック勝敗を定義する。
マストフォロー、指定した切札スートを用いて判定を行う。
切札はセッターでカードを指定し、ノートランプはゲッターがNoneを返す。'''
def __init__(self, handsize=0):
u'''コンストラクタ。手札枚数を設定する。'''
self.trump = None
self.handsize = handsize
def setHandSize(self, handsize):
u'''手札枚数を設定する。'''
self.handsize = handsize
def getHandSize(self):
u'''手札枚数を取得する。'''
return self.handsize
def setTrump(self, card):
u'''切り札指定のカードを設定する。'''
self.trump = card
def getTrump(self):
u'''切り札指定のカードを取得する。'''
return self.trump
def isPlayable(self, played, hands, i):
u'''渡された場札、手札、選んだカードから、そのカードが出せるかどうかを判定する。
引数は場札(リスト)、手札(リスト)、選ぶカード(手札のインデクス)。'''
# 場が空なら、何でもOK
if len(played) < 1:
return True
# リードスートを取得
leadsuit = played[0].getSuit()
# リードスートが手札にあればそれを選んでいること、なければ何でもOK
if leadsuit in [hand.getSuit() for hand in hands]:
return hands[i].getSuit() == leadsuit
else:
return True
def whoWins(self, played):
u'''プレイ済カードリストから、勝ったカードのインデックスを返す。'''
winner = played[0]
for card in played:
# ウィナーとスートが一致して、かつランクがより高ければ勝ち
if (card.getSuit() == winner.getSuit()) and (card.getRank().value > winner.getRank().value):
winner = card
# 切札がある場合、ウィナーが切札でなく、かつこのカードが切札でも勝ち
elif (self.trump is not None) \
and (winner.getSuit() != self.trump.getSuit()) \
and (card.getSuit() == self.trump.getSuit()):
winner = card
return played.index(winner)
# coding: UTF-8
from cardgame.player import Player
class Table():
u'''ゲーム卓。ゲームに必要なデータを保持する。'''
def __init__(self, rule, deck, inData):
u'''コンストラクタ。インスタンス変数を生成する。'''
# 必須データ
self.rule = rule
self.deck = deck
self.inData = inData
# プレイヤーはinDataから作成
self.players = [Player(ptype=ptype, pname=pname) for ptype, pname in zip(inData['player_types'], inData['player_names'])]
# 初期化
self.event = {}
self.dealer = None
self.turn = None
self.playedCards = []
self.scores = [0] * len(self.players)
# coding: utf-8
inData = {
'player_names': [],
'player_types': [],
'choice': -1,
'clear': False
}
def setNames(names):
inData['player_names'] = names
def setTypes(types):
inData['player_types'] = types
def setChoice(choice):
inData['choice'] = choice
def setClear(clear):
inData['clear'] = clear
def makeTestData(number=4):
from cardgame.playertype import PlayerType as T
setNames(['Kazuma', 'Abigail', 'Benjamin', 'Camille', 'Dennis'])
setTypes([T.HUMAN, T.AI_RANDOM, T.AI_RANDOM, T.AI_RANDOM, T.AI_RANDOM])
del inData['player_names'][number:5]
del inData['player_types'][number:5]
return inData
# coding: UTF-8
from enum import Enum
class EventType(Enum):
u'''カードゲームのイベント種類を表す列挙型。文字列によって状態を表す。'''
BEGIN_DEAL = 'BEGIN_DEAL'
BEGIN_TRICK = 'BEGIN_TRICK'
USER_TURN = 'USER_TURN'
USER_APPROVED = 'USER_APPROVED'
OPPONENT_TURN = 'OPPONENT_TURN'
RESOLVE_TRICK = 'RESOLVE_TRICK'
DEAL_RESULT = 'DEAL_RESULT'
# coding: UTF-8
from cardgame.table import Table
from cardgame.ttrule import TTRule
from playingcards.deck import Deck
from cardgame.procdeal import ProcDeal
from cardgame.proctrickinit import ProcTrickInit
from cardgame.prochumanplay import ProcHumanPlay
from cardgame.proccompplay import ProcCompPlay
from cardgame.proctrickresult import ProcTrickResult
from cardgame.procdealresult import ProcDealResult
from cardgame.eventtype import EventType as ev
from cardgame.playertype import PlayerType
class TTGame():
u'''トリックテイキングのゲーム論理手順。各手順を実行し、表示に必要な辞書データを返す。'''
def __init__(self, inData):
u'''コンストラクタ。ゲーム卓を作成し、プロシージャ定義を行う。引数はプレイ人数。'''
self.table = Table(TTRule(), Deck(), inData)
self.defineProc()
def defineProc(self):
u'''プロシージャ定義。処理をオーバーライドしたら、ここの定義を上書きする。'''
self.procdic = {
'deal' : ProcDeal(),
'trickinit' : ProcTrickInit(),
'trickresult' : ProcTrickResult(),
'humanplay' : ProcHumanPlay(),
'compplay' : ProcCompPlay(),
'dealresult' : ProcDealResult()
}
def start(self):
u'''ディールの開始処理。'''
self.proc = self.procdic['deal']
self.proc.do(self.table)
return self.table.event
def isDealEnd(self):
u'''ディールの終了判定処理。場札も全員の手札もなくなったらディール終了。
終了ならTrue、まだならFalseを返す。'''
return (len(self.table.playedCards) == 0) \
and sum([len(player.getHands()) for player in self.table.players]) < 1
def next(self, inData):
u'''ゲームのメイン処理。表示用イベントを返す。'''
# 入力をテーブルにセット
self.table.inData = inData
# 初期値
self.proc = None
cardCnt = len(self.table.playedCards)
# プロシージャを設定
self.setProc(cardCnt)
# 対応するプロシージャを実行し、イベントを返す
if self.proc is not None:
self.proc.do(self.table)
# カードが出ていれば、手番を次に進める
if len(self.table.playedCards) > cardCnt:
self.table.turn = (self.table.turn + 1) % len(self.table.players)
# イベントを返す
return self.table.event
def setProc(self, cardCnt):
u'''ゲーム状態に応じて実行すべきプロシージャオブジェクトを設定する。'''
# ディールが終了したら終了処理
if self.isDealEnd():
self.proc = self.procdic['dealresult']
# クリアフラグがオンなら、トリックのリセット(次トリックの開始処理)
elif self.table.inData['clear']:
self.table.inData['clear'] = False
self.proc = self.procdic['trickinit']
# トリック未解決で全員が出したら、トリック解決
elif cardCnt >= len(self.table.players):
self.proc = self.procdic['trickresult']
# トリックの途中なら、プレイヤーの手番
else:
if self.table.players[self.table.turn].getType() == PlayerType.HUMAN:
self.proc = self.procdic['humanplay']
else:
self.proc = self.procdic['compplay']
# coding: UTF-8
from abc import ABC, abstractmethod
class Proc(ABC):
u'''カードゲームの手順を定義する親クラス。このクラスをStateパターンで継承して各処理を記述する。'''
def __init__(self):
u'''コンストラクタ。'''
pass
@abstractmethod
def do(self):
u'''処理の実装。'''
pass
# coding: UTF-8
import random
from cardgame.proc import Proc
from cardgame.eventtype import EventType as ev
class ProcDeal(Proc):
u'''ディール処理の実装。'''
def do(self, table):
u'''ディール処理。山札をシャッフルし、最初のリードを決め、プレイヤーに指定枚数の手札を配る。
手札枚数はルールに記載がなければデッキ配りきり。'''
# プレイされた札をリセットする
del table.playedCards[:]
# 手札と獲得札をすべて集め、プレイヤーのカードを空にする
for player in table.players:
table.deck.extend(player.hands)
table.deck.extend(player.pointCards)
player.clear()
# 山札をシャッフル
table.deck.shuffle()
# ディーラーがいなければランダムに決め、続きなら左隣にする
table.dealer = random.randrange(len(table.players)) if table.dealer is None else (table.dealer + 1) % len(table.players)
# 打ち出しをディーラーの左隣にする
table.turn = (table.dealer + 1) % len(table.players)
# プレイヤーに手札を配ってソート
handsize = int(table.deck.size() / len(table.players)) if (table.rule.getHandSize() < 1) else table.rule.getHandSize()
for player in table.players:
player.setHands([table.deck.pick() for i in range(handsize)])
player.getHands().sort()
# 初期値設定
self.setEvent(table)
def setEvent(self, table):
u'''ゲーム卓の出力用辞書eventに値を設定する。'''
table.event = {}
table.event['EVENT_TYPE'] = ev.BEGIN_DEAL
table.event['DEALER'] = table.dealer
table.event['OPENING_LEAD'] = table.turn
table.event['TURN_PLAYER'] = table.turn
table.event['PLAYER_NAMES'] = table.inData['player_names']
table.event['WIN_COUNTS'] = [0] * len(table.players)
table.event['SCORES'] = table.scores
table.event['HANDS'] = [p.getHands() for p in table.players]
table.event['PLAYED_CARDS'] = table.playedCards
table.event['IS_PLAYABLE'] = True
# coding: UTF-8
from cardgame.proc import Proc
from cardgame.eventtype import EventType as ev
class ProcTrickInit(Proc):
u'''各トリック開始時の出力データ初期化処理の実装。'''
def do(self, table):
u'''テーブルのプレイカードと表示用のeventをトリック前の状態にする。'''
del table.playedCards[:]
table.event['EVENT_TYPE'] = ev.BEGIN_TRICK
table.event['IS_PLAYABLE'] = True
table.event['MY_CHOICE'] = -1
table.event['HANDS'] = [p.getHands() for p in table.players]
table.event['TURN_PLAYER'] = table.turn
table.event['PLAYED_CARDS'] = table.playedCards
table.event['TRICK_WINNER'] = -1
# coding: UTF-8
from cardgame.proc import Proc
from cardgame.eventtype import EventType as ev
class ProcHumanPlay(Proc):
u'''人間プレイ処理の実装。'''
def do(self, table):
u'''人間のプレイ処理。入力値を判定し、選べるならそれを選ぶ。'''
# 手番をセット
table.event['TURN_PLAYER'] = table.turn
# カード未選択なら入力待ちとして終了
if table.inData['choice'] is None:
table.event['EVENT_TYPE'] = ev.USER_TURN
return
# プレイ可否を判定し、不可なら入力待ちとして終了
human = table.players[table.turn]
playOK = table.rule.isPlayable(table.playedCards, human.getHands(), table.inData['choice'])
table.event['IS_PLAYABLE'] = playOK
if not playOK:
table.event['EVENT_TYPE'] = ev.USER_TURN
return
# カードが選択済み、かつプレイ可能であれば実際にプレイ
table.playedCards.append(human.playCard(table.inData['choice'])[1])
table.event['MY_CHOICE'] = table.inData['choice']
table.event['PLAYED_CARDS'] = table.playedCards
table.event['EVENT_TYPE'] = ev.USER_APPROVED
# coding: UTF-8
from cardgame.proc import Proc
from cardgame.eventtype import EventType as ev
class ProcCompPlay(Proc):
u'''コンピュータプレイ処理の実装。'''
def do(self, table):
u'''コンピュータのプレイ。手札から1枚プレイする。'''
cpu = table.players[table.turn]
card = cpu.playCard(cpu.chooseCard(table))
table.playedCards.append(card[1])
# イベント設定
table.event['MY_CHOICE'] = card[0]
table.event['TURN_PLAYER'] = table.turn
table.event['PLAYED_CARDS'] = table.playedCards
table.event['EVENT_TYPE'] = ev.OPPONENT_TURN
# coding: UTF-8
from cardgame.proc import Proc
from cardgame.eventtype import EventType as ev
class ProcTrickResult(Proc):
u'''トリック結果判定の実装。'''
def do(self, table):
u'''トリック結果を判定し、勝者を記録して次のリードに指定する。'''
# 勝者を判定して次のリードに指定し、カードを取らせる
table.turn = (table.rule.whoWins(table.playedCards) + table.turn) % len(table.players)
table.players[table.turn].pointCards.extend(table.playedCards)
# 表示データを更新
table.event['PLAYED_CARDS'] = table.playedCards
table.event['TRICK_WINNER'] = table.turn
table.event['WIN_COUNTS'][table.turn] += 1
table.event['EVENT_TYPE'] = ev.RESOLVE_TRICK
# coding: UTF-8
from cardgame.proc import Proc
from cardgame.eventtype import EventType as ev
class ProcDealResult(Proc):
u'''ディール結果判定の実装。'''
def do(self, table):
u'''ディール結果判定。デフォルトでは、トリック数を直接スコアに加算する。
何らかの処理を行う場合、このメソッドを上書きする。'''
for i, tricks in enumerate(table.event['WIN_COUNTS']):
table.scores[i] += tricks
table.event['SCORES'] = table.scores
table.event['EVENT_TYPE'] = ev.DEAL_RESULT
←No.45 引き算型のゲーム作りと、そのための細かい技術 No.47 トリックテイキングをプログラミングで一人回しする【後編】→
コラム一覧へ トップページへ