【Pyxel】トランプのゲームを作ってみる(1)

## Pyxelでトランプゲームを作ってみる
　なんとなくトランプのゲームを作りたくなったので作ってみます。
　といっても細部を決めておらずゆったり製作していくので、製作過程をqiitaに置いておくイメージで記事書いていきますね。
　ちなみにトランプの何を作るんだよ！って話ですが、一応大富豪を作る気ではいます。ほんとにゆるくやってるのでもしかしたら途中で変えるかもしれませんが。

## トランプのモデル化
　ウインドウとかは後回しにしてまずトランプのモデルを作っちゃいましょう。
　トランプと言えば、四つのスートとジョーカーで分類できますよね。というわけでまずそこを定義します。
~~~python:スート
suits=["spade","heart","diamond","club","joker"]
~~~
　そして、各スートに対しては1~13までの数が割り当てられます。ジョーカーは場合にもよりますが、ここは2枚としておきましょう。
　suitsリストを利用した内包表記で一気にトランプ54枚分のデータを生成します。
~~~python:トランプカード
cards=[[i,j] for i in suits for j in range(1,14) if i!="joker" or j<=2]
~~~
　少々小難しい表現になってますが、やってることは単純です。`suits`の各要素に対して1~13の数字を生成し`[suit,number]`となるようなリストの列を生成しているだけですね。
　後ろのifはジョーカーの場合2枚しか存在しないので、その分の条件です。
　さて、ここでカードを引くということをプログラムで実現しましょう。カードを引くとは、つまり重複を許さずにカードリストから要素を任意の数取り出すということです。これは`random`モジュールの`sample`メソッドで実現できます。
~~~python:カードを引く
myCard=random.sample(cards,n)
~~~
　`sample`メソッドについては引数もシンプルなので割愛するとして、この方法では新しく手札を生成することはできても、すでにある手札に重複なしに追加することはできません。
　が、残りの分は自分でコードを書けばどうにでもなるでしょう。ゲームのソースコード本体で実装することとします。
## Pyxelのテンプレート
　トランプのモデル化が済んだので、ちゃっちゃとウインドウ出しましょう。テンプレートというよりは、私がPyxelを使う時いつも最初に書いてるコードってだけですが、まあこの程度のことで語弊はないと思います。
~~~python:tramp.py
#インポート
import pyxel as xel
import PyxelUniversalFont as puf
import random

#クラス
class Game:
    #コンストラクタ
    def __init__(self):
        #ウインドウ
        xel.init(256,256,display_scale=3,fps=15)
        #更新処理
        xel.run(self.update,self.draw)

    #更新関数
    def update(self):
        pass

    #描画関数
    def draw(self):
        pass

#メイン関数
if __name__=="__main__":
    #インスタンス生成
    main=Game()
~~~
　`pyxel.init`は必須の引数であるウインドウサイズの他にオプション引数が色々あります。細部の仕様が決まっていない現段階では適当にウインドウサイズを256,256（最大値）とし、`display_scale`(1ピクセルあたりの大きさの倍率)を3に設定。今は大した処理をしていないのでfpsはとりあえず15にしておきます。
## トランプカードの初期化
　先ほど考えたトランプのモデルを`Game`クラスのコンストラクタで実装しましょう。
~~~python:tramp.py
class Game:
    def __init__(self):
        #カードリスト
        self.enemy_count=3
        self.suits=["spade","heart","diamond","club","joker"]
        self.cards=[[i,j] for i in self.suits for j in range(1,14) if i!="joker" or j<=2]
        self.myCard=[]
        self.enemyCard=[[] for i in range(self.enemy_count)]
        
        #ウインドウ
        xel.init(256,256,display_scale=3,fps=15)
        xel.title("tramp_game")

        #更新処理
        xel.run(self.update,self.draw)
~~~
　カードリストを先述の要領で生成し、手札の格納用に暫定で`myCard`リストと`enemyCard`リストを定義しました。ウィンドウタイトルも適当につけておきます。
　次に、手札のドローを実装しましょう。

~~~python:tramp.py
class Game:

    #手札追加関数
    def addCard(self,cardList,n):
        ec=[]
        for i in self.enemyCard:
            ec+=i
        k=0
        while True:
            r=random.sample(range(len(self.cards)),n-k)
            for i in r:
                if i not in self.myCard and i not in ec:
                    cardList.append(i)
                    k+=1
                if k==n or len(self.myCard)+len(ec)==len(self.cards):break
            if k==n or len(self.myCard)+len(ec)==len(self.cards):break
        cardList=self.sortCard(cardList)
        return cardList

    #カードソート関数
    def sortCard(self,cardList):
        return sorted(cardList,key=lambda x:(self.suits.index(self.cards[x][0]),self.cards[x][1]))
~~~
　ソートはスート→カードの数字の順番でビルトインの`sorted`を使い実装します。
　手札の追加は単純で、全てのプレイヤーの手札の中身と重複していないカードをランダムで取り出し、指定の枚数引き終わるか全体のカードの枚数と手札の総和が一致するとループが終了するというものです。
　さらにキーバインドを設定してドローを動的に実行します。
~~~python:tramp.py
    #更新関数
    def update(self):
        if xel.btnp(xel.KEY_D):
            self.myCard=self.addCard(self.myCard,1)
        if xel.btnp(xel.KEY_E):
            r=random.randint(0,self.enemy_count-1)
            self.enemyCard[r]=self.addCard(self.enemyCard[r],1)
~~~
　Dキーで自分の手札を一枚ドロー、Eキーで敵の誰か一人が一枚ドローですね。もちろん暫定です。
　最後にドローができていることを確認するために手札を描画する関数を作り、わかりやすいようにスートのリソースも作っちゃいましょう。

<img width=250 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3850890/9e98496f-84fc-a7ab-becc-369da54fb808.png>


~~~python:tramp.py
class Game:
    def __init__(self):
        #カードリスト
        self.enemy_count=3
        self.suits=["spade","heart","diamond","club","joker"]
        self.cards=[[i,j] for i in self.suits for j in range(1,14) if i!="joker" or j<=2]
        self.myCard=[]
        self.enemyCard=[[] for i in range(self.enemy_count)]

        #手札初期化
        self.myCard=self.addCard(self.myCard,7)
        self.enemyCard=list(map(lambda x:self.addCard(x,7),self.enemyCard))
        
        #ウインドウ
        xel.init(256,256,display_scale=3,fps=15)
        xel.title("tramp_game")

        #リソース
        xel.load("tramp.pyxres")

        #更新処理
        xel.run(self.update,self.draw)

    #描画関数
    def draw(self):
        xel.cls(7)
        self.drawCard(self.myCard,4,8)
        for i in range(self.enemy_count):
            self.drawCard(self.enemyCard[i],64+60*i,8)

    #カード描画関数
    def drawCard(self,cardList,x,y):
        for i in range(len(cardList)):
            j=self.suits.index(self.cards[cardList[i]][0])
            xel.blt(x,y+8*i,0,j*8,0,8,8)
            xel.text(x+8,y+8*i,f",{str(self.cards[cardList[i]][1])}",0)
~~~
　まだゲームではないですが、これである程度トランプは形になったのではないでしょうか。
 
### 一旦完成
~~~python:tramp.py
#インポート
import random
import pyxel as xel
import PyxelUniversalFont as puf

#クラス
class Game:
    #コンストラクタ
    def __init__(self):
        #カードリスト
        self.enemy_count=3
        self.suits=["spade","heart","diamond","club","joker"]
        self.cards=[[i,j] for i in self.suits for j in range(1,14) if i!="joker" or j<=2]
        self.myCard=[]
        self.enemyCard=[[] for i in range(self.enemy_count)]

        #手札初期化
        self.myCard=self.addCard(self.myCard,7)
        self.enemyCard=list(map(lambda x:self.addCard(x,7),self.enemyCard))
        
        #ウインドウ
        xel.init(256,256,display_scale=3,fps=15)
        xel.title("tramp_game")

        #リソース
        xel.load("tramp.pyxres")

        #更新処理
        xel.run(self.update,self.draw)

    #更新関数
    def update(self):
        if xel.btnp(xel.KEY_D):
            self.myCard=self.addCard(self.myCard,1)
        if xel.btnp(xel.KEY_E):
            r=random.randint(0,self.enemy_count-1)
            self.enemyCard[r]=self.addCard(self.enemyCard[r],1)

    #描画関数
    def draw(self):
        xel.cls(7)
        self.drawCard(self.myCard,4,8)
        for i in range(self.enemy_count):
            self.drawCard(self.enemyCard[i],64+60*i,8)

    #カード描画関数
    def drawCard(self,cardList,x,y):
        for i in range(len(cardList)):
            j=self.suits.index(self.cards[cardList[i]][0])
            xel.blt(x,y+8*i,0,j*8,0,8,8)
            xel.text(x+8,y+8*i,f",{str(self.cards[cardList[i]][1])}",0)

    #手札追加関数
    def addCard(self,cardList,n):
        ec=[]
        for i in self.enemyCard:
            ec+=i
        k=0
        while True:
            r=random.sample(range(len(self.cards)),n-k)
            for i in r:
                if i not in self.myCard and i not in ec:
                    cardList.append(i)
                    k+=1
                if k==n or len(self.myCard)+len(ec)==len(self.cards):break
            if k==n or len(self.myCard)+len(ec)==len(self.cards):break
        cardList=self.sortCard(cardList)
        return cardList

    #カードソート関数
    def sortCard(self,cardList):
        return sorted(cardList,key=lambda x:(self.suits.index(self.cards[x][0]),self.cards[x][1]))

#メイン関数
if __name__=="__main__":
    #インスタンス生成
    main=Game()
~~~

<img width=500 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3850890/ac60914e-4a31-2dac-b213-e346d7dfe8de.png>

## 今回はここまで
　次回は具体的にゲームの中身を作っていこうと思います。と言いつつ、いつやるかの予定を作っていないんですけど……。まあ、そのうちやる（はず）。