Pythonでソリティアのクロンダイク開発Part1
こんにちは、にわこまです。
今回から数パートに分けてソリティアのクロンダイクを開発していきます。トランプのカードと山札を表現するクラスを作成します。
今までに、オセロやコネクトフォーを開発してきましたが、それらとは違った難しさがありました。
誤字脱字など何かございましたら、ご連絡お願い致します。
スポンサードサーチ
ソリティアのクロンダイク開発Part1
ソリティアとは、1人で遊ぶゲームのことです。例えば、トランプなど…
クロンダイクとは、28枚の場札と24枚の山札を使って、スペード、ハート、クラブ、ダイヤモンドにそれぞれ、1から13を順番に積み重ねることが出来たら勝利(成功)というゲームです。
クロンダイクのルール
・4つの組札(♠、♥、♣、♦)に小さい数から大きい数までカードを順番に重ねていきます。それぞれの山には、同一の組しか重ねることができません。
・エースが1番小さい数字で、キングが1番大きい数字です。よって組札の山は、エースで始まりキングで終わらなければなりません。
・場札の列に並んでいるカードまたは続きカードを他の場札の列に移動することができます。列では、大きい数字から小さい数字の順で、黒と赤が交互に並んでいなければなりません。
・組札のカードを場札の列に移動することもできます。
・山札からカードを1枚引き、そのカードを場札の列または組札に移動することができます。
・引いたカードは、移動させなくてもよいため、次のカードを引くこともできます。山札にカードがなくなったら、もとの順番に戻します。
・場札の列に空列がある場合は、空列にキングのカードか先頭がキングである続きカードを移動することができます。
読んだだけでは分かりにくいと思いますので、実際にプレイしてみるのが良いと思います。 Windowsのパソコンであれば、デフォルトで「Microsoft Solitaire Collection」がインストールされていると思います。その中の「Klondike」をプレイしてみてください。
開発環境
OS : Windows
Python Version : 3.7
Cardクラスの作成
Cardクラスは、トランプのカードを表現すためのクラスです。例えば、スペードの1、ハートの13など…
では、スペードの1やハートの13などに含まれるデータには、どのようなものがあるでしょうか?
トランプのカードに含まれる、データを考えます。
カード1枚のデータ
・マーク
・数字
・色
・King、Aceなどの呼び名
・表か裏かの状態
これら5つがカード1枚のデータだと考えました。
マークや数字、色は必ず必要であるためデータをして与えています。KingやAceなどの呼び名は必要ないですが、一応データとして与えています。
表裏の状態は、カードのデータを表示させるかどうかの判定に使うためデータとして与えています。
Cardクラスのコードを紹介します。ファイル名は「klondike.py」としました。
class Card:
numstr_dict = {
1:"Ace", 2:"Two", 3:"Three", 4:"Four", 5:"Five", 6:"Six", 7:"Seven",
8:"Eight", 9:"Nine", 10:"Ten", 11:"Jack", 12:"Queen", 13:"King"
}
color_dict = {
"Diamond":"Red",
"Heart" :"Red",
"Club" :"Black",
"Spade" :"Black"
}
def __init__(self, mark, number, fab=False):
self.mark = mark # mark : Diamond or Heart or Club or Spade
self.number = number # number: 1~13
self.color = self.color_dict[mark] # color : Red or Black
self.numstr = self.numstr_dict[number] # numstr: Ace~King
self.fab = fab # Front and Back: True=Front, False=Back
def getData(self):
return (self.mark, self.number, self.color, self.numstr, self.fab)
def getMAN(self):
# Mark And Number
man = self.mark[0] + "_" + str(self.number)
if(self.number <= 9):
man = self.mark[0] + "_" + "_" + str(self.number)
return man
コードの解説
2行目~5行目の「numstr_dict」は、数字をKingなどの呼び名に変換するためのdictionaryです。AceやKing、Queen、Jack以外の数字も変換できるようにしています。
6行目~11行目の「color_dict」は、マークを色に変換するためのdictionaryです。普通のトランプと同じく、スペードとクラブが黒、ハートとダイヤモンドが赤です。
12行目~17行目は、「__init__」によってインスタンス化を行っています。つまり、Cardクラスにマークと数字、色、呼び名、表裏の情報を与えています。
マークと数字があれば、色と呼び名は決まるため、引数にマークと数字を指定しています。また、表裏の状態はデフォルトで裏(False)にしています。
29、20行目の「getData()」は、カードの情報を得るための関数です。テスト・デバッグ時に使用します。
22行目~27行目の「getMAN()」は、コマンドプロンプトにカードのマークと数字を表示するための関数です。(例:スペードの1→S__1、ハートの13→H_13)
文字列の長さが異なると表示がずれるため、長さ4で調節しています。
動作確認
コマンドプロンプトを開き、対話型インタプリタを起動します。「python」と入力すると起動します。「>>>」が表示されれば起動しています。
# こんな感じ↓↓↓
C:\Users\Name\python3\ solitaire>python
Python 3.7.0b3 (v3.7.0b3:4e7efa9c6f, Mar 29 2018, 18:42:04) [MSC v.1913 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
次に、「klondike.py」からCardクラスをインポートします。
>>>from klondike import Card
さらにCardオブジェクトを作成し、getData()を実行してデータを確認します。
>>>s_01 = Card("Spade", 1)
>>>s_01.getData()
('Spade', 1, 'Black', 'Ace', False)
以上で動作確認は完了です。
スポンサードサーチ
Deckクラスの作成
Deckクラスは、山札を表現するためのクラスです。先ほど作ったCardクラスを使って、52枚のカードを作成します。
山札に必要なデータを考えます。
山札のデータ
・山札(各マークの1~13のカード)
・引いたカード
・インデックス(カードをどこまで引いたか分かるようにするためのもの)
これら3つのデータが必要であると考えました。
山札は当然必要です。また、引いたカードはデータとしておくことで処理の重複を避けます。
インデックスは引いたカードの山札に対するインデックスです。インデックスがあるおかげで次のカードや1つ前のカードを表現できます。
Deckクラスを紹介します。「import random」と「import copy」をklondike.pyの1番上の行に追記し、以下のコードを1番下の行に追記します。
class Deck:
# よりプレイヤー側に近いカードほどindexが大きい
def __init__(self):
self.deck = []
for mark in ["Diamond", "Heart", "Club", "Spade"]:
for num in range(1, 14):
self.deck.append(Card(mark, num))
self.frontCard = None
self.index = 0
def shuffleCards(self):
random.shuffle(self.deck)
def getInitCard(self):
# 常に一番上からカードを取得する
card = self.deck.pop(-1)
return card
def turnCard(self):
# Turn Up A Card
if(len(self.deck) != 0 and abs(self.index) < len(self.deck) ):
self.index -= 1
self.frontCard = self.deck[self.index]
elif(len(self.deck) != 0 and abs(self.index) >= len(self.deck) ):
self.frontCard = None
self.index = 0
print("山札をリセットしました。")
else:
print("山札にカードがありません。")
def getTempFrontCard(self):
if(self.frontCard != None):
card = copy.deepcopy(self.frontCard)
card.fab = True
return card
return None
def getFrontCard(self):
if(self.frontCard != None and (self.frontCard in self.deck)):
self.deck.remove(self.frontCard)
card = self.frontCard
card.fab = True
if(self.index == -1):
self.frontCard = None
self.index = 0
else:
self.index += 1
self.frontCard = self.deck[self.index]
return card
print("山札のカードが表示されていないため、選択することができません。")
return None
コードの解説
3行目~9行目は「__init__」によって、インスタンス化を行っています。つまり、Deckクラスに52枚のカードのリストと引いたカード、インデックスの情報を与えています。
Deck作成時に、引いたカードは存在しないためNoneとしています。インデックスは0としています。
11、12行目の「shuffleCards()」は、山札をシャッフルするための関数です。ここでramdomモジュールのshuffle()を使用するため、1番上の行に「import random」を追記しました。
14行目~17行目の「getInitCard()」は、初期状態をつくるために山札からカードを1枚取得するための関数です。そのカードは山札から削除されます。
19行目~29行目の「turnCard()」は、引いたカードを更新するための関数です。この関数には、3つの条件分岐があります。
1.引いたカードを更新する条件
2.引いたカードとインデックスを初期状態に更新する条件(山札のリセット)
3.1、2以外の条件(山札にカードがない)
条件1では、インデックスをマイナス1し、引いたカードを更新します。
条件2では、引いたカードとインデックスを初期状態に更新することで、山札のリセットを表現しています。
31行目~36行目の「getTempFrontCard()」は、一時的に引いたカードを取得するための関数です。そのため引いたカードを山札から削除しません。以下の図を用いて説明します。
左の図のようにカードセット時にエラーが発生した場合、山札から削除したカードを戻すことは難しいため、一時的に取得する関数を作成しました。右の図が現在のプログラムです。
38行目~51行目の「getFrontCard()」は、引いたカードを取得するための関数です。この関数では、引いたカードを山札から削除します。 また、取得したカードよりも前に引いたカードがある場合は、そのカードに引いたカードに更新します。そうでない場合は、引いたカードとインデックスを初期値に変更します。
動作確認
コマンドプロンプトを開き、対話型インタプリタを起動します。「python」と入力すると起動します。「>>>」が表示されれば起動しています。
# こんな感じ↓↓↓
C:\Users\Name\python3\ solitaire>python
Python 3.7.0b3 (v3.7.0b3:4e7efa9c6f, Mar 29 2018, 18:42:04) [MSC v.1913 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
次に、「klondike.py」からCardクラスとDeckクラスをインポートします。
>>>from klondike import Card, Deck
さらに、Deckオブジェクトを作成し、shuffleCards()の動作確認を行います。
>>>mydeck = Deck()
>>>mydeck.deck[0].getData()
('Diamond', 1, 'Red', 'Ace', False)
>>>mydeck.shuffleCards()
>>>mydeck.deck[0].getData()
('Club', 3, 'Black', 'Three', False)
シャッフル前と後で山札の同じインデックスに異なるカードがあることが確認できます。(同じカードになる可能性もある、その場合はもう一回シャッフルする。)
簡単にgetInitCard()の動作確認を行います。
>>> mydeck.deck[-1].getData()
('Diamond', 1, 'Red', 'Ace', False)
>>> len(mydeck.deck)
52
>>> mydeck.getInitCard().getData()
('Diamond', 1, 'Red', 'Ace', False)
>>> len(mydeck.deck)
51
deckの1番後ろのカードとgetInitCard()で取得したカードが同じであることが確認できます。また、getInitCard()前後でdeckの長さが1減ることが確認できます。
turnCard()の動作確認を行います。
>>> mydeck.turnCard()
>>> mydeck.frontCard.getData()
('Spade', 11, 'Black', 'Jack', False)
>>> mydeck.turnCard()
>>> mydeck.frontCard.getData()
('Club', 12, 'Black', 'Queen', False)
turnCard()を実行するたびに、異なるカードが選択されていることが確認できます。
getTempFrontCard()とgetFrontCard()の動作確認を行います。
>>> len(mydeck.deck)
52
>>> mydeck.getTempFrontCard().getData()
('Club', 12, 'Black', 'Queen', True)
>>> len(mydeck.deck)
52
>>> mydeck.getFrontCard().getData()
('Club', 12, 'Black', 'Queen', True)
>>> len(mydeck.deck)
51
getTempFrontCard()では、deckの大きさは変化しないが、getFrontCard()では、deckの大きさが変化することを確認できます。また、getTempFrontCard()とgetFrontCard()が同じカードを取得することも確認できます。
以上でDeckクラスの動作確認は完了です。
まとめ
今回は、CardクラスとDeckクラスを作成しました。Cardクラスはトランプ1枚1枚のこと。Deckクラスは、クロンダイクで使う山札のこと。
クラスのデータや関数を紹介しましたが、なんとなく理解できていれば問題ありません。
しかしあと2つ、クラスを作成します。場札の列(ColumnDeck)と組札(MarkDeck)です。次回は、この2つのクラスを紹介します。
最後までお読み頂きありがとうございます。
スポンサードサーチ