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つのクラスを紹介します。

 

最後までお読み頂きありがとうございます。


スポンサードサーチ