LoginSignup
26
33

More than 3 years have passed since last update.

Python3 を子供に教えるためにオセロを作ってみた(5)

Last updated at Posted at 2020-05-27

オセロクラスについて解説していきましょう その2

型について話しましょう

 血液型ってありますよね?A型、B型、O型、AB型・・・と本当かどうかはさておいて「几帳面なA型」「自分勝手なB型」みたいな感じで型にはその型なりの特徴があります。プログラミングでは型というのは血液型のように重要な区分けをするために使われます。

 今まで変数について色々説明してきましたが、敢えて型について触れてきませんでした。配列は list() というクラスだと説明しましたし、ディクショナリ変数も dict() というクラスです。プログラムで言うデータ型は血液型同様、そのデータの性質を表すものです。

データ型の名前 データ型 記述
文字列 str "文字" または '文字'
整数 int +-数値
浮動小数 float +-浮動小数
虚数 complex +-実数部+-虚数部j
真偽 bool True または False
日付時刻 datetime 日付と時刻
リスト(配列) list データ型を問わない配列
タプル(参照配列) tuple データ型を問わない配列
セット(一意配列) set データが一意で順不同のブロック配列
ディクショナリ(キーマップ配列) dict キー対データ配列

 Python では変数を使う場合は特に意識しなくても勝手に型が割り当たります。これを動的型付け(dynamic typing)と呼びます。例えば age = 12 って書けば Python は型を int() にしてくれます。割り当てられた型を知るには type(age) とやれば age という変数に割り当てられた型を知ることができます。前回も書きましたが全ての型はそのデータ型を扱うのに必要な関数などが含まれたクラスになっています。もし意図しない型に割り当てられた場合は、float() などと明示的に型指定をしたり型を変換することもできます。

オセロゲームのソース解説(2) オセロクラスの関数編

キーボード入力

 まずはキーボード入力を行っている関数です。引数として渡された文字列を表示し、キーボード入力を待ちます。エンターキーを空打ちしたか、1〜8以外の値を入力されたら再度キーボード入力に戻ります。正しい数値が入力されたら、それを int() で整数に変換して戻します。このオセロクラスの重要な値はXとYの1〜8の数値です。仮にグラフィカルなオセロになって、マウスで場所をクリックされても内部的には 1〜8 のX座標とY座標に変換することで、現在のクラスの関数はそのまま利用することができるのです。

    # 入力
    def ot_inputXY(self,otstr):
        while True:
            myXY = input(otstr)
            if myXY == "":
                continue
            if '1' <= myXY <= '8' :
                return int(myXY)

「あなたの番です」を司る関数(文字列と三項演算子)

 ot_yourturn() という関数は「●の手です!」とかどちらのプレイヤーの番かを表示するだけの関数です。表示するのは "〜" でくくられた文字列なのですが、"文字列" は前述した通り文字列クラス str() ですから、"あいうえお" と書いただけの文字列でも str() クラスに含まれる関数がそのまま利用できるのです。

 文字列型を使うときにここで記述している {} と format() はとてもよく使われる表現なのでここで覚えておきましょう。{}置換フィールドなどと呼びます。str("あか{}たな".format("さ") とやると {} の中を format() 関数で指定したデータに置き換えてくれる便利な記述です。Python3.6 以降では f文字列操作として置換フィールドにダイレクトに変数を入れられるようになり可読性が上がっています。

    name = input("お名前を入力してください=")
    print("よく来たな勇者{}よ!!魔王を討ち滅ぼす英雄を待ち望んでおったぞ!".format(name))
    # Python3.6以降では以下のように書けて便利です!
    print(f"よく来たな勇者{name}よ!!魔王を討ち滅ぼす英雄を待ち望んでおったぞ!")

 実際のオセロクラスでのot_yourturn()では、置換フィールドに現在の手が格納された変数ot_offdefが指定されているので●または○が表示されます。

    # あなたの番です表示
    def ot_yourturn(self):
        print("{} の手です!".format(self.ot_offdef))

 ot_changeturn() 関数は、攻め手の番を切り替える関数です。

    # あなたの番です
    def ot_changeturn(self):
        # 攻め手のコマを格納する
        self.ot_offdef = '●' if self.ot_offdef == '○' else '○'
        # 攻め手と逆のコマを格納する
        self.ot_search = '●' if self.ot_offdef == '○' else '○'

 プログラムには三項演算子という書き方があります。Excel のマクロでIF文を書く感覚に近いのですが、上のロジックと下のロジックはやっていることは全く同じです。条件に合致した時の値 if 条件 else 条件に合致しない時の値と書くことで if 条件文を1行に簡潔に納めることができるようになります。三項演算子は書き方によってはプログラムをスッキリさせる効果もありますが、無理矢理使いまくるととても可読性の悪いソースになりますので注意して使うことをお勧めします。もし分かりづらいな〜と感じるようでしたら、以下のソースに置き換えていただいても大丈夫です。

    # あなたの番です
    def ot_changeturn(self):
        # 攻め手のコマを格納する
        if self.ot_offdef == '○':
            self.ot_offdef = '●'
        else:
            self.ot_offdef = '○'
        # 攻め手と逆のコマを格納する
        if self.ot_offdef == '○':
            self.ot_search = '●'
        else:
            self.ot_search = '○'

オセロゲームの次の動作を考える関数

 ot_checkendofgame() 関数はオセロゲームが終了するかどうか考えるプログラムです。この一連の記事の(2)で動作について書いてありますのでそちらをご覧くださいね。

 この中でやっていることは以下のようになります。

  1. ot_canplacemypeace()関数で攻め手のコマを置く場所があるか確認します
  2. 攻め手のコマが置けない場合 ot_changeturn() で攻め手のコマを相手ターンに切り替えます
  3. ot_canplacemypeace()関数で攻め手のコマを置く場所があるか確認します
  4. 両者とも置く場所がないと判断した場合は、●と○の数を比較してどちらが勝ったか引き分けかを判断しその結果を表示し 1 を返してゲームが終了することを教えます
  5. 上記の 1. で攻め手だけ置ける場所がなかった場合は、「相手のターンに変わります」というメッセージを表示して -1 を返しその旨を通知します
  6. 攻め手がそのままコマを置けると判断した場合は 0 を返しその旨を通知します

 この関数の中で myot.ot_bit.count('●')と呼んでいる count() は list() クラスの配列に含まれるデータの個数を返してくれる関数です。

    # 終了かどうか判定する(自分の手が置けないだけの場合はターンを変える)
    def ot_checkendofgame(self):
        # 自分の手を置ける場所がなく、相手の手も置けない
        if 0 > self.ot_canplacemypeace():
            self.ot_changeturn()
            if 0 > self.ot_canplacemypeace():
                bc = myot.ot_bit.count('●')
                wc = myot.ot_bit.count('○')
                if ( bc - wc ) > 0:
                    bws = "●の勝利"
                elif ( bc - wc ) < 0:
                    bws = "○の勝利"
                else:
                    bws = "引き分け"
                print("{}です。お疲れ様でした!".format(bws))
                return 1
            else:
                print("{}を置ける場所がありません。相手のターンに変わります!!".format(self.ot_search))
                return -1
        else:
            return 0

全てのひっくり返せる場所を探す関数

 先ほどの ot_checkendofgame() ではゲームが終了なのか、自分の手を置く場所があるのか、なければ相手の手を置く場所があるのかを判断していました。その「手を置く場所があるのか?」を考えるのが、ot_canplacemypeace()関数です。

   # 自分の手を置ける場所があるかどうか探す
    def ot_canplacemypeace(self):
        for n in range(64):
            if self.ot_bit[n] != '・':
                continue
            for d in self.ot_direction:
                if 1 == self.ot_next_onepeace(int(n%8)+1,int(n/8)+1, d):
                    return 0
        # 一個も置ける場所がないとここに来る
        return -1

 やっていることは以下の通りです。

  1. 8x8の盤面で既にコマが置いてある場所はどんどんスキップしていきます。
  2. コマが置かれていない場所があったら、0〜63のその位置をX座標とY座標に変換します。8で割った余りに1を足したものが1相対のX座標、8で割った値に1を足したものがY座標です。
  3. 変換したX座標とY座標位置に自分の手を置けるか(相手のコマをひっくり返せる場所があるか?)を全方向(8方向)に対して見ていきます。for d in self.ot_direction:の部分では、ディクショナリ変数の数分繰り返され、ディクショナリに格納された方位を示すキー文字列が変数 d に格納されます。
  4. コマを置く場所が見つかった場合は 0 を返してその旨を通知します。
  5. コマを置く場所がなかった場合は -1 を返してその旨を通知します。

指定されたXとYの座標にコマを置く

 前回も話した通り、指定した場所にコマを置いた場合に、そこが置ける場所なのかを判断して、置けるのであれば相手のコマをひっくり返すということをしていかなければなりません。そして、もし置いた場所がそもそも置いてはいけない場所だった場合はその旨を表示しないとなりません。

    # オセロの盤面に手を置く
    def ot_place(self, ot_x, ot_y):
        ot_change = False
        for d in self.ot_direction:
            if 1 == self.ot_next_onepeace(ot_x,ot_y,d):
                self.ot_peace(ot_x,ot_y,self.ot_offdef)
                self.ot_changepeace(ot_x,ot_y,d)
                ot_change = True
        # 1個も有効な方向が無かった場合
        if not ot_change:
            print("{}を置く事ができませんでした".format(self.ot_offdef))
            return -1
        # 画面を表示して手を相手のターンに変更
        self.ot_display()
        self.ot_changeturn()
        return 0

 この関数は所定の場所にコマを置こうとした時に呼び出される関数です。所定の場所はX、Yがそれぞれ1〜8の値で指定された座標となります。
それではこの関数のプログラムについて解説していきましょう。

  1. コマが置けたかどうかのフラグ変数として ot_change を False で初期化します。コマが置けた場合にここは True になります。
  2. ot_direction の配列の数分(つまり8方向分)の繰り返しを行います。変数 d には ot_direction のキー値が格納されます。
  3. ot_next_onepeace()関数を呼び出し、現在の方向がひっくり返せるかどうか、ひっくり返せるならどこまでひっくり返せるかを調べます。関数からの戻り値が 1 の場合は、この方向はひっくり返せる方向であることを示します。
  4. 3番目の引数に自分の手を指定して ot_peace() 関数を呼び出し、指定された座標に自分のコマを置きます。ot_peace()関数は3番目の引数を省略した場合は、指定座標のコマ1つを戻してくれる関数でもあります。3番目の引数を指定した場合は、指定座標のコマを指定された文字で置き換えを行います。
  5. ot_changepeace() 関数を呼び出して、ひっくり返せる場所までひっくり返す作業を行います。
  6. コマが置けた場合は ot_changeフラグを True にセットします。8方向全てひっくり返せなかった場合は、False のままです。
  7. 繰り返しから抜けてきた時にot_changeフラグが False かどうかを確認します。False の場合はそもそも置けない座標を指定した場合なので、その場所には置けない旨のメッセージを表示して戻ります。
  8. 繰り返しから抜けてきた時にot_changeフラグが True であれば、ひっくり返す処理が盤面データである ot_bit の書き換えを完了していますので、ot_display()関数を呼び出して盤面を再表示し、攻め手のターンを変更すべく ot_changeturn()関数を呼び出します。

次回でプログラムの説明は最後です!

 次回は一番ややこしいかもしれない、ot_next_onepeace()関数と、ot_changepeace() 関数について解説します。この2つの関数は再帰関数と呼ばれる自分を自分が呼び出すということをやっている関数なので、それについて解説したいと思います。

 それではまた!!

c u

<<Python3 を子供に教えるためにオセロを作ってみた(4)
  Python3 を子供に教えるためにオセロを作ってみた(6)>>

26
33
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
33