チェスアプリ開発(2) コードの修正

 

Python プログラムで動かすフェアリーチェスアプリ開発、連載第2回です。

前回は、既存のチェスのプログラムがどのように動いているか大枠を見てみました。

こちらのプログラムです。

今回は実際にコードを修正しつつ、細かい動作についてみていきたいと思います!

なお、このシリーズでは、あまりプログラミングに詳しくない人、はじめたばかりの人にも気軽に読んでもらいたいので、

できるだけ初歩的なところから解説していきたいと思っています。

ただ、基礎理論などは書かれているサイトがたくさんあるので、ここでは応用的な面を重視して書いていきます。


その前に

まず編集しやすくするために、コードを2つの モジュール import によって呼び出すことができるプログラムファイル に分けます。

main.py
1
2
3
4
5
6
7
8
from pieces import *
 
uniDict = ...
 
class Game:
    ...
 
Game()
pieces.py
1
2
3
4
5
WHITE = "white"
BLACK = "black"
 
class Piece:
    ...

ところで、このコード中にあるclassとかdef クラス オブジェクトの型、設計図のようなもの メソッド クラス内で定義される関数のようなもの 関数 処理のまとまり を定義しているだけで、実際に動作を起こすわけではありません。

例えば、次のコードを走らせても見た目上は何も起きません。

1
2
def f():
    print('abc')

しかし、これのあとにf()と書けば、コマンドラインにabcとプリントされます。

では、このチェスのコードにおいて一番最初に動き出すのはどこかというと、

一番最後の行のGame()です。

これによってGameのインスタンス(クラスをもとに作られた分身のようなもの)が生成され、

その際にコンストラクタ__init__()が呼び出されます。

コンストラクタは初期化メソッドで、インスタンス生成時に自動で実行されます。

main.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Game:
    def __init__(self):
        self.playersturn = BLACK
        self.message = "this is where prompts will go"
        self.gameboard = {}
        self.placePieces()
        print("chess program. enter moves in algebraic notation separated by space")
        self.main()
 
    ...

次に、コンストラクタの中でmain()が呼びだされます。

selfがついてたりついてなかったりするのは、初期の段階ではあまり気にしなくて大丈夫です!おまじないみたいなものだと思ってください)

main.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    ...
 
    def main(self):
 
        while True:
            self.printBoard()
            print(self.message)
            self.message = ""
            startpos,endpos = self.parseInput()
            ...

このmain()の中で、盤面やメッセージを表示したり、ユーザ入力を受け付けたりしているわけです。

そしてこのメソッド内で使うためにいろいろなメソッドが他の場所で定義されています。

それでは、ここからは実際にコードを修正していきましょう。


黒先→白先

チェスでは白が先手です。まずはこちらを修正したいと思います。

Game中のself.playersturnがにおいますね。

main.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
...
 
class Game:
    def __init__(self):
        self.playersturn = BLACK
        self.message = "this is where prompts will go"
        self.gameboard = {}
        self.placePieces()
        print("chess program. enter moves in algebraic notation separated by space")
        self.main()
    ...

ここのself.playersturn = BLACKself.playersturn = WHITEに変えればよさそうです。

コマンドライン

できた!


黒のクイーンとキングの位置

本来初期配置では d8 にクイーン、 e8 にキングが来るはずです。

駒の配置に問題があるので、名前だけ見るとplacePieces()が怪しいですね。

main.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Game:
    ...
 
    def placePieces(self):
 
        for i in range(0,8):
            self.gameboard[(i,1)] = Pawn(WHITE,uniDict[WHITE][Pawn],1)
            self.gameboard[(i,6)] = Pawn(BLACK,uniDict[BLACK][Pawn],-1)
 
        placers = [Rook,Knight,Bishop,Queen,King,Bishop,Knight,Rook]
 
        for i in range(0,8):
            self.gameboard[(i,0)] = placers[i](WHITE,uniDict[WHITE][placers[i]])
            self.gameboard[((7-i),7)] = placers[i](BLACK,uniDict[BLACK][placers[i]])
        placers.reverse()
...
 
uniDict = {WHITE : {Pawn : "♙", Rook : "♖", Knight : "♘", Bishop : "♗", King : "♔", Queen : "♕" },
    BLACK : {Pawn : "♟", Rook : "♜", Knight : "♞", Bishop : "♝", King : "♚", Queen : "♛" }}

なんだか難しそうですが、ちょっとずつ見ていきましょう。

gameboardは、コンストラクタでself.gameboard = {}とされているので、 辞書(ディクショナリ) キーと値が1対1に対応する形でまとめられた構造 です。

self.gameboard = {}は、空のディクショナリをつくる文です。

6~8行目を見ると、gameboardは二要素 タプル 多数のデータを格納する、要素が変更不可能な構造 キーkey 呼び出すほう 、 駒オブジェクトを 値value 呼び出されるほう とするディクショナリだとわかります。

Pawn(WHITE,uniDict[WHITE][Pawn],1)は、Pawn インスタンス クラスをもとに作られた分身のようなもの を生成しています。

確か、コードの下の方にPawnクラスがありましたね。

pieces.py
1
2
3
4
5
6
class Pawn(Piece):
    def __init__(self,color,name,direction):
        self.name = name
        self.Color = color
        #of course, the smallest piece is the hardest to code. direction should be either 1 or -1, should be -1 if the pawn is traveling "backwards"
        self.direction = direction

colorは駒の色。

nameは名前ですが、uniDictで絵文字が指定されることになります。

directionはポーンが進む方向ですね。(コメントは原文ママ。)

要は、7行目は「盤面のこの位置には白のポーンを置きますよ」と言っているだけです。

8行目も同様。

ということで、6~8行目は、「2ランクに白ポーン、7ランクに黒ポーンを配置」という意味になります。

さて、以降はこれを拡張しただけです。

main.py
1
2
3
4
5
placers = [Rook,Knight,Bishop,Queen,King,Bishop,Knight,Rook]
 
for i in range(0,8):
    self.gameboard[(i,0)] = placers[i](WHITE,uniDict[WHITE][placers[i]])
    self.gameboard[((7-i),7)] = placers[i](BLACK,uniDict[BLACK][placers[i]])

(行数を改めます。)

4行目では、例えばi0のとき、

(0, 0)(a1)の位置にplacers[0]すなわちRookが置かれます。

uniDict[WHITE][placers[0]]になりますね。

さて、問題は5行目です。

例えばi0のとき、

(7, 7)の位置に駒が置かれます。

これでは黒の駒と白の駒が盤面の中心に関して点対称に配置されていくのがわかりますでしょうか?

i3のとき、placers[3]Queenですが、

白クイーンは(3, 0)に置かれ、黒クイーンは(4, 7)に置かれますね。

本当は(3, 7)に置きたい。

点対称ではなく、線対称に配置したいのです。

ということで、5行目の((7-i),7)を、(i, 7)に変えてしまいましょう!

コマンドライン

やったね!

(ところで、メソッドの最後にあったplacers.reverse()は何がしたいのかよくわかりません。)


今回は比較的簡単に直せるところを直してみました。

他にもいろいろと追加すべき機能はありますが、ひとつずつ取り上げていこうと思います!

次回はこちら

ご覧いただきありがとうございました。

それではまた👋