オセロクイズサイト

おせろく

プログラミング奮闘記

pythonで棋譜変換プログラムを自作しました

更新日:

Twitterでも言いましたが、pythonを勉強してプログラムを自作してみました。

  • 詳しい方に添削して欲しい
  • 気になる方へのコードの開示
  • 自分の理解を深める

などの理由でブログにまとめておきます。

プログラミングが分からない方にもある程度理解できる記事だと思います。

Pythonって?

pythonはプログラミング言語の一つです。

最近、話題の言語のようで、プログラミング言語初心者でもとっつきやすいものでした。

トップオセラーのうみがめさんが使っているみたいなので真似して勉強してみました。

どんなプログラムを作ったん?

棋譜(f5d6c3...)からOX形式にするプログラムです。

どんな感じで進むかというと、

入力

棋譜を入力してください:

の後に、棋譜を入力します。

例えば、f5d6c3(虎定石)と入力します。

棋譜を入力してください:f5d6c3

出力

すると、次のように出力されます。

一列にした文字列:

 ------------------X--------XX------OXX-----O--------------------

8×8で視覚的に表示:

--------

--------

--X-----

---XX---

---OXX--

---O----

--------

--------

黒がX、白がO、何もないマスが-です。

虎定石になってますよね。

作った目的

先程出した「------------------X--------XX------OXX-----O--------------------」という文字列が先述したOX形式です。

このブログで使っているhamliteの初期配置を設定するのにこの形式が必要なので、棋譜から作れるプログラムを作りました。

これが目的です。

プログラムのコード

ひとまずコードを書いておきます。

この後に、工夫した点全体の流れを書くのでまずはサラッと見てもらえればと思います。

(スマホは飛び出すので横画面表示にしてね。)

工夫した点

工夫した点は次の三点です。

  • 8×8ではなく、10×10の盤面を用意した。
  • パスに対応するために様々な工夫をした。
  • 返す方向を8つのベクトルで表現した。

軽く説明していきます。

流れをすっ飛ばしているので分からない部分も多いと思いますが、流しながら見て頂けると嬉しいです。

8×8ではなく、10×10の盤面を用意した

8×8の外側に常時空白のマスを設置しました。

これのメリットは次の二つです。

  • 「変数search」が「探索」をやめるサインになる
  • 桁が統一されて直感的に理解しやすくなる。

この後詳しく説明しますが、変数searchに盤上を「探索」させます。

その時に「この方向はハズレだよ」と教えてくれるサインになります。

(なんのこっちゃ分からないですよね...笑。)

個人的にこれを思いついた時は興奮しました笑。

パスに対応するために様々な工夫をした

プログラムを作るにあたって、パスの存在が結構厄介でした。

棋譜の文字列だけではパスを判断できないからです。

例えば、...h1PAc1... のようにパスが記述されていればそれを元にパスさせればいいのですが、実際は ...h1c1... と詰めて表示されています。

対策として、

  • パスか否かを表現する変数pass_judg
  • パスをカウントする変数pass_count

を導入しました。

最終的にデバック作業で手こずったのがこのパスの処理でした。

返す方向を8つのベクトルで表現した。

8つの方向を変数searchで探索するのですが、その時の計算を簡略化し、直感的に捉えやすくするためにベクトルを使いました。

xy = [(1,1),(1,0),(1,-1),(0,1),(0,-1),(-1,1),(-1,0),(-1,-1)]

x軸は右を正に、y軸は下を正にとっています。

例えば(1,1)は右下、(-1,0)は左を表しています。

数学が得意な人からすれば「方向なんだからそりゃベクトル使うっしょ」と感じるかもしれませんが、そうでもない僕からすると奇抜なナイスアイデアだったなと感じています。

ベクトルって実生活で役に立ちそうで立たない数学分野という印象で、これを応用出来たのは結構嬉しかったです。

 

全体の流れ

全体の流れを簡単に説明します。

丁寧に説明すると長くなるので、要点だけで。

そもそもどういう仕組みなの?

(少しイメージの話をします。)

入力された棋譜情報(f5d6c3...)を元にプログラム内に作ったオセロ盤で石を返していきます。

こんな感じ。(スタート)

初期配置

--------

--------

--------

---OX---

---XO---

--------

--------

--------

文字情報「f5」を受け取り、f5に打つ

--------

--------

--------

---OX---

---XXX--

--------

--------

--------

↓...(以下同様)

こんな感じ。(終わり)

そして最後まで行ったらその盤面を文字情報に変換して出力する、という仕組みです。

こういうと簡単そうですが、それをプログラミングで実行するには地道な変換作業が必要になります。

順番に見ていきましょう。

1.オセロ盤をどう表現するか

最初の課題はプログラム内でどうやってオセロ盤を表現するか、です。

まず、オセロ盤の特定のマスを表すのに座標では扱いにくいので番号で表現します。

左上から右下へ1マスずつ1~64の番号を割り当てるということです。

が、先程言ったように便宜上10×10のマスを使うので、同様にして1~100で番号付けします。

1 2 3 4 5 6 7 8 9 10
11 12(a1) 13(b1) 14(c1)     17(f1)   19(h1) 20
21                 30
31                 40
41                 50
51           57(f5)     60
61                 70
71                 80
81                 90
91                 100

(スマホは飛び出すので横表示にしてね。)

例:f5は57番、d6は65番。

2.座標から番号への変換

オセロ盤の表現方法が決まったので、次の課題は座標から番号への変換です。

具体的に言うと、文字列'f5d6c3...'をリストscores[57,65,34,...]に変換します。

「f5」の数字部分が10の位に、アルファベット部分の順番(fなら6番目)に+1したものが1の位になっているのがポイントです。

アルファベットの順番を返す関数としてord関数を使いました。

ord(A)=65,ord(B)=66,ord(F)=70

みたいな関数なので、(大雑把...笑)

ord(数字にしたいalphabet)-63

をすれば1の位になります。

以上より、次のように変換します。

文字列「F5」から「F」,「5」を抽出。

番号=10×「5」 + ord(「F」)-63 =57

求めた番号をリストに追加。

こんな感じ。

3.オセロ盤に石を配置(初期盤面設定)

さて、次は盤面の石の色の情報をどう表現するかです。

これはまあ簡単で、0,1,2を使って表します。

黒は1、白は2、無(空きマス)は0としました。

100マスの色の情報をリストcolors[]に収納します。

初期盤面として、46番,55番に1、45番,56番に2、他に0を代入します。

colors[0,0,...,2,1,...,1,2,...,0]

と言った感じになります。

4.石を返していく

いよいよ石を返していきます。

ポチ
でも、そもそもどうやって石を返していけばいいの?

 

人間は視覚を使って、挟むことができる相手の石を認識しますが、プログラムには視覚がありません。

これを解決する作業が先ほどから登場している「探索」です。

変数searchに8方向を探索させます。

以下のような条件分岐を行います。

  • 置いた場所の隣が 無or手番と同じ色(黒番で黒,白番で白) だった場合、返らない
  • 置いた場所の隣が 手番と違う色 だった場合、その先を調べる。
    • 一つ先のマスが無だった場合、返らない
    • 手番と同じ色だった場合、返る。(・・・①)
    • 手番と違う色だった場合、その先を調べる。(無か手番と同じ色に当たるまでループ)

このループの際に8×8盤面の端までたどり着いた時に探索をやめさせるには、どうすればいいか悩みました。

これの解決策が10×10盤面です。

常に端に0(無)を置いておくことでそれを探知して探索を止めることができます。

 

具体的に見てましょう。

まず、変数searchにさぐってほしい位置(location)を代入します。

例えば、初手F5を考えてみましょう。

まず、上方向が返せるか確認するために上方向へ探索させます。

F5が57番なので、一つ上は47番です。

location=47 (変数loctionに47を代入)として、colors[loction]を求めます。

(colors[n]はリストcolorsのn番目を取り出す作業)

今回の例だと、colors[47]=0 (無) となります。

この場合、返せる石がないので上方向が返せないと分かり、別方向を探索させます。

こんな感じです。

5.パスへの対応

パスへの対応に手こずりました。

ポチ
棋譜からパスを判定できないって言ってたけど、パス判定できないと具体的に何に困るの?

パスになると何が困るかと言うと、手番(turn)が入れ替わってしまうのです。

変数searchの探索の際に手番の情報が必要になります。

手番が実際の手番と逆になってしまうと、返る場所が変わってしまうのです。

これを解決するために二つの変数を導入しました。

  • パスか否かを表現する変数pass_judg
  • パスをカウントする変数pass_count

5-1.pass_judg

まず、プログラム上でパスを定義してやらなくてはなりません。

そのために変数pass_judgを使いました。

まずpass_judg=0 (pass_judgに0を代入) として、0のままならパス、1になったらパスではない、と定義します。

先ほど紹介した探索の条件分岐で、石を返した時(①の所)、同時にpass_judg=1 (1を代入) という処理をします。

もしも、8方向を探索して石が返ってなかったらpass_judg==0(pass_judgが0に等しい) のままのはずです。

この時、手番を入れ替え、pass_count+=1(pass_countの値に+1をする) として、もう一度8方向探索させます。

ポチ
ずっとpass_judg==0だと無限ループになっちゃうんじゃないの?

確かに、その通りですが、棋譜が正しいという前提があれば、どちらかがその場所に打ったわけなので、無限ループにはなりません。

ただ、棋譜が間違っていた場合、無限ループになってしまう可能性があるので、その対策は必要ですね。

後で追記しなくては。

(追記しました)

5-2.pass_count

基本的に、手番は今何手目か(n手目)で判断します。

奇数なら黒番、偶数なら白番です。(1手目は黒、2手目は白ですよね。)

しかし、パスになるとそれが入れ替わります。

なので、pass_countにパスの回数を記録して、(n + pass_count)の偶奇で手番を判断するようにしました。

これで手番を修正できます。

6.盤面情報をOX形式に変換

最後に、終局となった盤面情報(リストcolors[1,2,...2])をOX形式に変換します。

これも簡単で、

0→「-」(無),1→「X」(黒),2→「O」(白)

と変換すれば完了です。

終わり!

これにて全行程の大まかな流れを紹介し終えました。

お疲れ様でした。

作った感想

  • 疲れた
  • pythonの勉強になった
  • デバック大変
  • なんでブログでこんな説明してるんだ

さよなら、バイバイ!

追記

Twitterのリプライにてベテランプログラマーからアドバイス頂いたのですが、入力値が正しいことが前提のコードになっていました。

なので、次の場合にエラーを表示するように追記、変更しました。

  • F5D6形式以外の文字列やA1~H8の範囲外が入力された時
  • 既に石が置かれているマスに置かれた時
  • 延々挟む場所がないなど、8方向の探索が無限ループになる時

追記内容

sysモジュールの使用

sys.exit()を使用。

エラーの際、プログラムを終了できる。

実際に書くコード

4行目に追記

import sys

a1~h1の範囲外の棋譜の入力への対策

偶数がアルファベットのa~hか確認。

ord(score[2*int(i)]) == 65~72

奇数が数字の1~8か確認。

int(score[2*int(i)+1]) == 1~8

実際に書くコード

14行目に追記。

 #棋譜がF5D6形式か確認。
if not 65<= ord(score[2*int(i)]) <=72 or not 1<= int(score[2*int(i)+1]) <=8 :
    print("エラー: \n入力された棋譜が適切でありません。\n棋譜はF5D6形式で入力してください。")
    sys.exit()

 

既に置いたマスに置かれた時の対策

置いたマスが無であるか毎回確認する。

実際に書くコード

43行目に追記

#置いた場所にすでに置かれている場合、エラー。
    if not colors[location-1] == 0 :
        print("エラー: \nすでに埋まったマスに置かれました。")
        sys.exit()

置けないはずのマスに置かれた時(turnが無限に入れ替わる)の対策

ループの回数を数え、3回目に突入したら終了。

55行目に追記

loop_count=0

57行目を書き換える

before

while  pass_judg==0 :

after

while  pass_judg==0 and loop_count<2 :

106行目

loop_count+=1

109行目

if loop_count == 2 :

    print("エラー: \n置けないマスに置かれました。")

    sys.exit()

 

適用されているか確認

ちゃんと間違った入力値にエラーを出すことに成功しました。

今度こそ終わり!

入力エラーも考慮しなくてはなんですね。

勉強になりました。

ただ、もう一つ追記したいことが...

F5D6ではなくf5d6とすると、エラーになってしまいます。(ord(F)≠ord(f)なので)

疲れた&緊急性がないので後で追記します(^^;)

広告

広告

-プログラミング奮闘記

Copyright© おせろく , 2021 All Rights Reserved Powered by AFFINGER4.