LoginSignup
1
0

Pythonで〇×ゲームのAIを一から作成する その66 枠線と文字列によるマークの描画

Last updated at Posted at 2024-03-24

目次と前回の記事

これまでに作成したモジュール

以下のリンクから、これまでに作成したモジュールを見ることができます。

ルールベースの AI の一覧

ルールベースの AI の一覧については、下記の記事を参照して下さい。

Artist を登録するメソッドに関する今後の説明の方法

最初に、Artist を登録 する メソッド に関する 今後説明の方法 を説明します。

前回の記事で、matplotlibplt.show()実行するまで は、画像描画しない と説明しました。例えば plot メソッドは、実際Axes折れ線 を表す 図形Artist登録するだけ で、画像の描画行いません

ただし、plot などの 説明 をする際に、「AxesArtist を登録 する」や、「実際の描画plt.show() で行う」のような 説明 を行うと、まわりくどい意味が分かづらい 説明になってしまいます。そのため、以後plot メソッド などArtist登録 する 処理の説明 を、「〇〇を描画する」 のように 説明する ことにします。

2 種類の座標の表記の区別

ゲーム盤マスの座標 と、matplotlibAxes が管理する 座標 は、異なる座標 です。この 2 つの座標混同 すると、間違った処理行われる点注意 して下さい。

前回の記事 では、どちらの座標(0, 0) のように 記述 してきましたが、どちらの座標を表すか区別しづらい ので、以後は ゲーム盤のマスの座標 は、これまでどおり (0, 0) のように 表記 し、Axes の座標(0, 0)A のように、右下小さな A表記 することにします。

ただし、プログラムコメントの中 での 表記区別しません

ゲーム盤の描画

今回の記事では、前回の記事で説明した matplotlib の機能を使って、〇×ゲームゲーム盤画像描画 します。まず、Marubatsu クラスに、ゲーム盤描画 するための 下記 のような メソッド定義 する事にします。

  • 名前ゲーム盤(board)を 描画(draw)するので、draw_board という 名前 にする
  • 処理ゲーム盤画像描画 する
  • 入力:なし
  • 出力:なし

下記は、draw_board メソッドの 定義 です。なお、メソッドの処理はこれから記述します。

from marubatsu import Marubatsu

def draw_board(self):
    pass

Marubatsu.draw_board = draw_board

ゲーム盤の描画で行う処理の考察

画像ゲーム盤表示 する 処理記述 する際に、何をすれば良いかがわからない場合は、これまでの 文字列 での ゲーム盤の表示 を行う 処理参考 にすると良いでしょう。

下記は、文字列ゲーム盤の表示 を行う プログラム です。

mb = Marubatsu()
mb.move(0, 0)
mb.move(1, 1)
print(mb)

実行結果

Turn o
o..
.X.
...

上記の 表示内容 から、ゲーム盤を描画 するためには、下記 の内容を 描画 する 必要がある ことが わかります

  • 手番ゲームの結果情報
  • ゲーム盤配置 された マーク

また、文字列 での ゲーム盤 には 表示していません が、画像ゲーム盤を描画 する際には、ゲーム盤の マスを区別 するための、枠線の描画必要 になります。

そこで、それら順番実装する ことにします。

ゲーム盤の枠線の描画

ゲーム開始時〇×ゲームゲーム盤画像で描画 する場合、おそらくほとんどの方は、下記 のような 画像思い浮かべる のではないかと思います。

この画像は、4 本の線1構成 されているので、Axesplot メソッドを 利用 して ゲーム盤枠線描画 することが できます

前回の記事では、plot メソッドで 折れ線グラフ描画 する方法を 紹介 しましたが、plot メソッドの 実引数記述 する、x 座標y 座標それぞれ座標の数2 つ にすることで、描画 することが できます

そこで、4 つの線両端座標どのように設定 するかについて 少し考えてみて 下さい。ピンとこない人 は、ノートグラフ用紙 などに 上記の図実際書いてみて、それぞれの線の両端の 点の座標どうなるか考えてみる良い でしょう。

枠線の両端の座標の設定例

下記 は、両端の座標設定例 です。ゲーム盤左上の座標原点(0, 0)A とし、縦方向枠線 は、原点 から x 軸方向1 ずつ 離れた位置描画 します。横方向枠線同様 です。なお、ゲーム盤上図 のように 配置 されるのであれば、下図以外設定 でも 構いません。興味と時間がある方は、他の設定でゲーム盤を描画してみて下さい。

このような をノートなどに 書く ことで、枠線両端の座標理解しやすく なります。プログラムで 図形 などを 組み合わせ画像を描画 する際に、わからなくなった場合 は、書いてみる ことを 強くお勧めします

上図 は、matplotlib描画 したものです。詳細は長くなるので省略しますが、矢印 による 注釈 は、Axesannotate というメソッドを使って 描画 することができます。例えば、(0, 0)A矢印注釈 は、下記 のプログラムのように 記述 します。

ax.annotate('(0, 0)', xy=(0, 0), xytext=(0.35, 0.6),
            arrowprops=dict(facecolor='black', shrink=0.05))

annotate メソッドの詳細については、下記のリンク先を参照して下さい。

枠線の描画を行うプログラム

下記 は、上図参考 に、それぞれの 枠線両端の座標列挙 したものです。

  • (0, 1)A(3, 1)A
  • (0, 2)A(3, 2)A
  • (1, 0)A(1, 3)A
  • (2, 0)A(2, 3)A

下記 のプログラムは、plot メソッドに 上記の座標記述 して、4 つの線描画 するプログラムです。plot メソッドは 一度1 つの線(または折れ線)しか 描画 できないので、下記のように、それぞれ枠線ごと に、plot メソッドを 呼び出す必要あります

  • 4 行目subplots メソッドで FigureAxes作成 し、それぞれを 変数に代入 する
  • 5 ~ 8 行目Axesplot メソッドを呼び出して 4 つの線描画 する
1  import matplotlib.pyplot as plt
2  import japanize_matplotlib
3
4  fig, ax = plt.subplots(figsize=[3, 3])
5  ax.plot([0, 3], [1, 1]) # (0, 1) と (3, 1) を結ぶ線
6  ax.plot([0, 3], [2, 2]) # (0, 2) と (3, 2) を結ぶ線
7  ax.plot([1, 1], [0, 3]) # (1, 0) と (1, 3) を結ぶ線
8  ax.plot([2, 2], [0, 3]) # (2, 0) と (2, 3) を結ぶ線
行番号のないプログラム
import matplotlib.pyplot as plt
import japanize_matplotlib

fig, ax = plt.subplots(figsize=[3, 3])
ax.plot([0, 3], [1, 1]) # (0, 1) と (3, 1) を結ぶ線
ax.plot([0, 3], [2, 2]) # (0, 2) と (3, 2) を結ぶ線
ax.plot([1, 1], [0, 3]) # (1, 0) と (1, 3) を結ぶ線
ax.plot([2, 2], [0, 3]) # (2, 0) と (2, 3) を結ぶ線

実行結果

plot メソッド は、折れ線頂点それぞれの座標[0, 1] のように 記述 するの ではなくそれぞれの座標x 座標y 座標分けて記述 する点に 注意 して下さい。よくある間違い ですが、下記 のように 記述 すると、(0, 1)A(3, 1)A を結ぶ線 ではなく(0, 3)A(1, 1)A結ぶ線描画 されてしまいます。

fig, ax = plt.subplots(figsize=[3, 3])
ax.plot([0, 1], [3, 1])

実行結果

上記実行結果 には 示しませんでした が、上記 のプログラムを 実行 すると、最初下記 のような 表示行われます

JupyterLab では、セルの最後実行 された 処理の結果表示される ようになっており、これは、最後の行plot メソッドが 返す Artist です。

本記事では、以降 も、このような表示実行結果の表記 から 省略 します。

[<matplotlib.lines.Line2D at 0x29ffa9bc790>]

上記実行結果 のように、plot線を描画 すると、勝手ついて しまいます。これは、plot折れ線グラフ描画するため のメソッドなので、複数折れ線描画 した際に、区別がつく ように 自動的異なる色表示 するという 機能を持つ からです。

黒色描画 したい場合は、下記 のプログラムのように、c という キーワード引数 に、黒色 を表す "k" を記述します。わかりづらいと思った人は、color="black"記述 しても 構いません下記 のプログラムでは、5 行目そのように記述 してみました。

fig, ax = plt.subplots(figsize=[3, 3])
ax.plot([0, 3], [1, 1], c="k") # (0, 1) と (3, 1) を結ぶ線
ax.plot([0, 3], [2, 2], c="k") # (0, 2) と (3, 2) を結ぶ線
ax.plot([1, 1], [0, 3], c="k") # (1, 0) と (1, 3) を結ぶ線
ax.plot([2, 2], [0, 3], color="black") # (2, 0) と (2, 3) を結ぶ線
修正箇所
fig, ax = plt.subplots(figsize=[3, 3])
-ax.plot([0, 3], [1, 1]) # (0, 1) と (3, 1) を結ぶ線
+ax.plot([0, 3], [1, 1], c="k") # (0, 1) と (3, 1) を結ぶ線
-ax.plot([0, 3], [2, 2]) # (0, 2) と (3, 2) を結ぶ線
+ax.plot([0, 3], [2, 2], c="k") # (0, 2) と (3, 2) を結ぶ線
-ax.plot([1, 1], [0, 3], c="k") # (1, 0) と (1, 3) を結ぶ線
+ax.plot([1, 1], [0, 3]) # (1, 0) と (1, 3) を結ぶ線
-ax.plot([2, 2], [0, 3], color="black") # (2, 0) と (2, 3) を結ぶ線
+ax.plot([2, 2], [0, 3]) # (2, 0) と (2, 3) を結ぶ線

実行結果

実行結果 から、すべての枠線黒色描画 されることが 確認 できます。

Axes の 表示範囲に関する補足

plot メソッドなど、Axesグラフを描画 するメソッドの 多く は、実行 することで、Axes表示範囲 を、描画 する すべての図形 がぴったり 収まる ような 範囲より少しだけ広い範囲 になるように 自動的設定 してくれます。上図 の場合は x 座標y 座標範囲 には -0.15 ~ 3.15設定 されます。自動的設定 された 表示範囲問題がない 場合は、set_xlim 等のメソッドを 記述 する 必要ありません

〇×ゲームゲーム盤描画 する際に、表示範囲ゲーム盤大きさぴったり合わせる ことも できます が、上記の画像 で特に 違和感はない ので そのままにする ことにします。ぴったり合わせたい方は set_xlim 等のメソッドを記述して下さい。

Axesx 軸y 軸表示範囲 は、下記のプログラムのように、get_xlimget_ylim というメソッドで知ることができます。描画 される 画像同じ なので 省略 しました。

fig, ax = plt.subplots(figsize=[3, 3])
ax.plot([0, 3], [1, 1], c="k") # (0, 1) と (3, 1) を結ぶ線
ax.plot([0, 3], [2, 2], c="k") # (0, 2) と (3, 2) を結ぶ線
ax.plot([1, 1], [0, 3], c="k") # (1, 0) と (1, 3) を結ぶ線
ax.plot([2, 2], [0, 3], color="black") # (2, 0) と (2, 3) を結ぶ線
print(ax.get_xlim())
print(ax.get_ylim())
修正箇所
fig, ax = plt.subplots(figsize=[3, 3])
ax.plot([0, 3], [1, 1], c="k") # (0, 1) と (3, 1) を結ぶ線
ax.plot([0, 3], [2, 2], c="k") # (0, 2) と (3, 2) を結ぶ線
ax.plot([1, 1], [0, 3], c="k") # (1, 0) と (1, 3) を結ぶ線
ax.plot([2, 2], [0, 3], color="black") # (2, 0) と (2, 3) を結ぶ線
+print(ax.get_xlim())
+print(ax.get_ylim())

実行結果

(-0.15000000000000002, 3.15)
(-0.15000000000000002, 3.15)

上記の 実行結果 で、-0.15000000000000002 のように 表示 されるのは、2 進数 で、10 進数-0.15表現 すると、1.00110011・・・ のように、小数点以下0011無限に繰り返される ような 数値 になるためです。Pythonfloat22 進数数値を表現 するので、-0.15正確表現 することは できない ため、上記のような 微小な誤差発生 します。

上記 では 3.15正確表現できている ように 見えるかもしれません が、実は 3.15同様 に、2 進数 では 正確表現できません。上記で 3.15 のように 表示 されるのは、四捨五入 した 結果 が、3.15 になった からです。

なお、Axes表示範囲 の場合は、このような 微小な誤差 は、画像の描画影響を与えない ので、気にする必要ありません

筆者のホームページに、2 進数10 進数変換を行うツール を作りましたので、興味がある方はリンク先を見て下さい。他にも、画像のデジタル表現の解説や文字コードの変換ツールなどもあります。

get_xlimget_ylim の詳細については、下記のリンク先を参照して下さい。

軸の反転

上図は、先程の実行結果です。上図うまくいっている ように 見えるかもしれません が、実は上図には 大きな問題あります。それが何かを少し考えてみて下さい。

matplotlib は、グラフを描画 するために 利用 されることが 多い ので、y 座標数学と同様 に、上方向正の値 になります。一方、コンピュータ が扱う 画像の座標 は、一般的y 座標下方向正の値 になります。実際に、上図 をよく見ると、y 座標上方向正の値 になっていることがわかります。

コンピュータ画像 で、y 座標下方向正の値 になっているのは、コンピューターでは 一般的上から下文字など描画 するからだと 思います

matplotlib では、下記 のプログラムのように、Axesinvert_yaxis メソッドを実行することで、y 軸(y axis)の 方向逆にする(invert)ことが できます〇×ゲーム では 利用しません が、x 軸方向 にする invert_xaxis もあります。

fig, ax = plt.subplots(figsize=[3, 3])
ax.plot([0, 3], [1, 1], c="k") # (0, 1) と (3, 1) を結ぶ線
ax.plot([0, 3], [2, 2], c="k") # (0, 2) と (3, 2) を結ぶ線
ax.plot([1, 1], [0, 3], c="k") # (1, 0) と (1, 3) を結ぶ線
ax.plot([2, 2], [0, 3], color="black") # (2, 0) と (2, 3) を結ぶ線
ax.invert_yaxis()
修正箇所
fig, ax = plt.subplots(figsize=[3, 3])
ax.plot([0, 3], [1, 1], c="k") # (0, 1) と (3, 1) を結ぶ線
ax.plot([0, 3], [2, 2], c="k") # (0, 2) と (3, 2) を結ぶ線
ax.plot([1, 1], [0, 3], c="k") # (1, 0) と (1, 3) を結ぶ線
ax.plot([2, 2], [0, 3], color="black") # (2, 0) と (2, 3) を結ぶ線
+ax.invert_yaxis()

実行結果

実行結果 から y 座標上下が反転 していることが 確認 できます。

draw_board メソッドへの組み込み

上記 のプログラムを draw_board メソッドに 組み込む下記 のようになります。y 軸を反転 させる 処理 は、plt.show()画像実際に描画 する であれば、どこで行っても構わない ので、下記のプロラムでは、ゲーム盤の枠描画 するよりも 前で行う ことにしてみました。ax.invert_yaxis()最後に記述したい 人は、 順番入れ替えて 下さい。

matplotlib が、plt.show()実行するまで 画像を 描画しない理由一つ は、Artist登録するたび画像描画 すると、軸の方向 や、Axes表示範囲変更 した際に、それまでに描画した 画像描画し直す必要生じる からです。

なお、〇×ゲームゲーム盤を描画 する際に、Axes枠や目盛り描画消したほうが良い のですが、この後ゲーム盤〇 など描画 する際に、目盛り表示 されていた方が わかりやすい ので、しばらく目盛りを表示したまま にすることにします。

def draw_board(self):
    fig, ax = plt.subplots(figsize=[3, 3])

    # y 軸を反転させる
    ax.invert_yaxis()

    # ゲーム盤の枠を描画する
    ax.plot([0, 3], [1, 1], c="k") # (0, 1) と (3, 1) を結ぶ線
    ax.plot([0, 3], [2, 2], c="k") # (0, 2) と (3, 2) を結ぶ線
    ax.plot([1, 1], [0, 3], c="k") # (1, 0) と (1, 3) を結ぶ線
    ax.plot([2, 2], [0, 3], color="black") # (2, 0) と (2, 3) を結ぶ線

Marubatsu.draw_board = draw_board

下記 のプログラムで、Marubatsu クラスの インスタンスを作成 し、draw_board実行 してみます。実行結果 から ゲーム盤描画される ことが 確認 できました。

mb = Marubatsu()
mb.draw_board()

実行結果

draw_board メソッドの修正 その 1

上記draw_board メソッドでは、4 本枠線描画 するために、plot メソッドを 4 回 呼び出していますが、その処理繰り返し処理 を使って 記述 することが できます。また、そのように修正 することで、例えば 5 x 5 など の、3 x 3 以外大きさ〇×ゲームゲーム盤表示できる ようになるという 利点得られます

まず、下記横線を描画 するプログラムを、繰り返し表示する方法 を考えて下さい。

    ax.plot([0, 3], [1, 1], c="k") # (0, 1) と (3, 1) を結ぶ線
    ax.plot([0, 3], [2, 2], c="k") # (0, 2) と (3, 2) を結ぶ線

上記 のプログラムを 観察 すると 下記 の事が わかります

  • x 座標 を表す [0, 3]共通 する
  • x 座標 のデータのうちの 3 は、ゲーム盤大きさ表す 3 である
  • y 座標 を表すデータは、[1, 1][2, 2] のように 規則正しく増えて いる
  • y 座標 を表すデータは、1 から ゲーム盤大きさ を表す 3 未満 まで 増えて いる

ゲーム盤のサイズ は、BOARD_SIZE 属性に 代入 されているので、上記 のプログラムは、下記 のように for 文 を使って 記述 することが できます

    for i in range(1, self.BOARD_SIZE):
        ax.plot([0, self.BOARD_SIZE], [i, i], c="k") # 横方向の枠線

また、縦線描画 するプログラムも、同様 に、下記 のプログラムで 記述 できます。

    for i in range(1, self.BOARD_SIZE):
        ax.plot([i, i], [0, self.BOARD_SIZE], c="k") # 縦方向の枠線

上記 の 2 つのプログラムは、1 つにまとめる ことが できるの で、draw_board下記 のように 定義 することが できます

def draw_board(self):
    fig, ax = plt.subplots(figsize=[3, 3])

    # y 軸を反転させる
    ax.invert_yaxis()

    # ゲーム盤の枠を描画する
    for i in range(1, self.BOARD_SIZE):
        ax.plot([0, self.BOARD_SIZE], [i, i], c="k") # 横方向の枠線
        ax.plot([i, i], [0, self.BOARD_SIZE], c="k") # 縦方向の枠線        

Marubatsu.draw_board = draw_board
修正箇所
def draw_board(self):
    fig, ax = plt.subplots(figsize=[3, 3])

    # y 軸を反転させる
    ax.invert_yaxis()

    # ゲーム盤の枠を描画する
    # ゲーム盤の枠を描画する
-   ax.plot([0, 3], [1, 1], c="k") # (0, 1) と (3, 1) を結ぶ線
-   ax.plot([0, 3], [2, 2], c="k") # (0, 2) と (3, 2) を結ぶ線
-   ax.plot([1, 1], [0, 3], c="k") # (1, 0) と (1, 3) を結ぶ線
-   ax.plot([2, 2], [0, 3], color="black") # (2, 0) と (2, 3) を結ぶ線
+   for i in range(1, self.BOARD_SIZE):
+       ax.plot([0, self.BOARD_SIZE], [i, i], c="k") # 横方向の枠線
+       ax.plot([i, i], [0, self.BOARD_SIZE], c="k") # 縦方向の枠線        

Marubatsu.draw_board = draw_board

下記 のプログラムで、修正 した draw_boardゲーム盤を描画 すると、実行結果 から、ゲーム盤正しく描画 されることが 確認 できます。

mb.draw_board()

実行結果

また、下記 のプログラムで、ゲーム盤サイズ5 x 5 にした場合でも draw_boardゲーム盤正しく描画 されることが 確認 できます。

mb = Marubatsu(board_size=5)
mb.draw_board()

実行結果

draw_board メソッドの修正 その 2

上記の draw_board は、figsize[3, 3]サイズ画像を描画 しますが、ゲーム盤画像の大きさ自由調整できる ほうが 便利 です。そこで、draw_board仮引数 に、ゲーム盤画像の大きさ代入 する size という 仮引数追加 します。また、size に対して、以下 のような 工夫を行う ことにします。

  • 〇×ゲームゲーム盤正方形 なので、size[3, 3] のような list記述 するの ではなく一つ3 のような 数値を代入 するようにする
  • draw_board呼び出す 際に、毎回 size対応 する 実引数記述 するのは 面倒 なので、3デフォルト値 とする デフォルト引数 にする

下記は、そのように draw_board修正 したプログラムです。

  • 1 行目デフォルト値3設定 した デフォルト引数 size追加 する
  • 2 行目figsize[size, size]設定 して subplots呼び出す ように 修正 することで、描画 する 画像の大きさsize設定 できる ようにする
def draw_board(self, size=3):
    fig, ax = plt.subplots(figsize=[size, size])
以下同じなので略
Marubatsu.draw_board = draw_board
行番号のないプログラム
def draw_board(self, size=3):
    fig, ax = plt.subplots(figsize=[size, size])

    # y 軸を反転させる
    ax.invert_yaxis()

    # ゲーム盤の枠を描画する
    for i in range(1, self.BOARD_SIZE):
        ax.plot([0, self.BOARD_SIZE], [i, i], c="k") # 横方向の枠線
        ax.plot([i, i], [0, self.BOARD_SIZE], c="k") # 縦方向の枠線        

Marubatsu.draw_board = draw_board
修正箇所
-def draw_board(self):
+def draw_board(self, size=3):
-   fig, ax = plt.subplots(figsize=[3, 3])
+   fig, ax = plt.subplots(figsize=[size, size])
以下同じなので略
Marubatsu.draw_board = draw_board

下記 は、draw_board実引数 の記述で size を省略 した場合と、size5 を設定 した場合のプログラムです。前回の記事で説明したように、1 つJupyterLab のセル で、複数画像を描画 する場合は、plt.show()実行 する 必要あります実行結果 から、ゲーム盤画像の大きさ変える ことが できるようになった とが 確認 できます。

なお、先程 mb5 x 5ゲーム盤を代入 したので、下記 では mb作り直し ています。

mb = Marubatsu()
mb.draw_board()
plt.show()
mb.draw_board(size=5)

実行結果



文字列によるマークの描画

次は、×マークゲーム盤に描画 する 処理記述 します。

これまでと同様 に、×文字列画像に描画 するという 方法 も考えられますが、文字列× などを 描画 する 方法 には、下記 のような 問題あります

  • 表示する 文字列位置うまく調整 しないと、マスの真ん中表示できない
  • 文字列大きさ変える と、位置調整やり直す必要 がある
  • ×線の太さ変更 することが 難しい

わかりづらいと思いますので、具体例 を挙げながら 説明 します。

まず、(0, 0) のマスに 文字列で描画 するプログラムを 記述 することにします。前回の記事で説明したように、文字列Axestext メソッドで 描画 することが できます が、その際に、描画する 文字列左下の座標指定 する 必要あります。その座標をどのように設定すればよいかについて少し考えてみて下さい。

座標をどのように設定すればよいかが良くわからない場合は、先程説明したように、ノートグラフ用紙 などに、想定する画像書いてみる ことを 強くお勧めします下図 は、(0, 0) のマスに 〇 を配置 した ゲーム盤画像一例 です。

上図 から、この画像描画 するためには、text メソッドで "〇"(0, 1)A座標付近描画 すればよいことが わかります下記その処理 を行うプログラムです。

  • 3 行目左下の座標(0, 1)A位置"〇"描画 する
1  def draw_board(self, size=3):
元と同じなので省略
2      # (0, 0) のマスに 〇 を描画する
3      ax.text(0, 1, "")
4  
5  Marubatsu.draw_board = draw_board
行番号のないプログラム
def draw_board(self, size=3):
    fig, ax = plt.subplots(figsize=[size, size])

    # y 軸を反転させる
    ax.invert_yaxis()

    # ゲーム盤の枠を描画する
    for i in range(1, self.BOARD_SIZE):
        ax.plot([0, self.BOARD_SIZE], [i, i], c="k") # 横方向の枠線
        ax.plot([i, i], [0, self.BOARD_SIZE], c="k") # 縦方向の枠線   

    # (0, 0) のマスに 〇 を描画する
    ax.text(0, 1, "")

Marubatsu.draw_board = draw_board
修正箇所
def draw_board(self, size=3):
元と同じなので省略
    # (0, 0) のマスに 〇 を描画する
+   ax.text(0, 1, "")

Marubatsu.draw_board = draw_board

下記は、修正 した draw_boardゲーム盤を描画 するプログラムです。

mb.draw_board()

実行結果

実行結果 からわかるように、 は確かに (0, 0)マスの中描画 されますが、文字大きさ小さすぎ ます。文字大きさ は、text メソッドの fontsize という キーワード引数設定 できますが、ちょうどいい大きさ〇 を描画 するためには、さまざまな大きさ〇 を描画 し、自分の目確かめる という、試行錯誤行う必要あります

〇 のフォントサイズの調整

フォントサイズ変更 する際に、毎回 draw_board メソッドを 定義し直す のは 大変 です。そこで、下記 のプログラムの 5 行目 のように、draw_board メソッドが、その内部の処理で 作成 した Axes代入 されている ax返り値 として 返す ように 修正 することで、draw_board メソッドを 実行した後 で、描画できる ようにします。

  • 2 行目記述 されていた (0, 0)〇 を描画 する 処理削除 する
  • 5 行目ax返り値 として 返す ように 修正 する
1  def draw_board(self, size=3):
元と同じなので省略
2      # ここにあった、(0, 0) のマスに 〇 を描画するプログラムを削除する
3
4      # ax を返り値として返す
5      return ax
6
7  Marubatsu.draw_board = draw_board
行番号のないプログラム
def draw_board(self, size=3):
    fig, ax = plt.subplots(figsize=[size, size])

    # y 軸を反転させる
    ax.invert_yaxis()

    # ゲーム盤の枠を描画する
    for i in range(1, self.BOARD_SIZE):
        ax.plot([0, self.BOARD_SIZE], [i, i], c="k") # 横方向の枠線
        ax.plot([i, i], [0, self.BOARD_SIZE], c="k") # 縦方向の枠線   

    # ax を返り値として返す
    return ax

Marubatsu.draw_board = draw_board
修正箇所
def draw_board(self, size=3):
元と同じなので省略
    # (0, 0) のマスに 〇 を描画する
-   ax.text(0, 1, "")
    
    # ax を返り値として返す
+   return ax

Marubatsu.draw_board = draw_board

上記 のように draw_board修正 することで、下記プログラム のように、draw_board返り値 である Axes を使って、ゲーム盤の枠線描画した後 で、引き続き 〇 を描画 するプログラムを 記述できる ようになります。実行結果は先程と同じなので省略します。

ax = mb.draw_board()
ax.text(0, 1, "")

下記 は、for 文 を使って、fontsize2030405060設定 して 〇 を描画 するプログラムです。なお、実際実行結果 は、画像縦に並んで描画 されますが、わかりやすさ重視 して、下記の 実行結果 では、画像横に並べて表記 します。

for size in range(20, 70, 10):
    ax = mb.draw_board()
    ax.text(0, 1, "", fontsize=size)
    plt.show()

実行結果

実行結果 から、fontsize50 にすると ちょうど良さそう であることが わかります。なお、どれくらい の大きさが ちょうど良い かは、人によって 感じ方が 異なる ので、50 以外 の大きさが 良い思った人 は、自由に変更 しても かまいません

〇 の描画位置の調整

上記fontsize50設定 した場合の から、位置 をもう 少し上に移動 したほうが よさそう であることが わかります大きさの調整同様 に、ちょうどよい位置 は、試行錯誤調べる必要あります

下記 は、for 文 を使って、表示位置y 座標0.750.80.850.90.95設定 して 〇 を描画 するプログラムです。なお、range整数しか扱えない3 ので、list を使って 繰り返し処理行っています

for y in [0.75, 0.8, 0.85, 0.9, 0.95]:
    ax = mb.draw_board()
    ax.text(0, y, "", fontsize=50)
    plt.show()

実行結果 から、y 座標0.85 にすると ちょうど良さそう であることが わかりますこの位置気に入らない 方は、x 座標含めて自由に調整 しても かまいません

文字列による 〇 の描画の問題点

上記の手順 を見て、かなり面倒 だと 思った人多いのではないか と思いますが、文字列 による 〇 の描画 には 他にも問題 があります。その一つは 文字列描画 では 枠の太さ柔軟に変更 することが できない という問題です。text メソッドは、fontweight という キーワード引数 を使って、文字列の太さ変更できる のですが、フォントの種類 によっては、設定行っても 文字列の 太さが変わらない 場合が あるようです実際 に、筆者のパソコン では、下記 のプログラムの 実行結果 のように、fontweight細字 を表す "light"太字 を表す "bold"設定 しても 文字列の太さ変わりませんでした

plt.text(0, 0, "abc", fontsize=50, fontweight="light")
plt.text(0.3, 0, "abc", fontsize=50, fontweight="bold")

他の問題点 として、画像の大きさ変更 した場合に、Axes表示範囲変化しない ので、図形位置と大きさ はそれにあわせて 変化 しますが、表示される 文字列の大きさ変わらない というものが あります文字列位置変化します)。

下記は、draw_board の実引数に size=5 を記述することで 大きなゲーム盤描画した後 で、先程同じ位置大きさ〇 を描画 するプログラムです。

mb.draw_board(size=5)
plt.text(0, 0.85, "", fontsize=50)

実行結果

実行結果 からわかるように、画像のサイズ変化 したことで、枠線それに合わせた位置大きさ描画 されています。一方、文字列描画以下 のように 行われます

  • 左下座標位置 は、画像のサイズ変化合わせた位置表示 される
  • 文字列描画大きさ は、画像のサイズ変化 しても 変わらない

従って、マスの中 での 描画位置 が、真ん中 から 左下ずれて しまいます。この 問題を解決 するためには、再び 文字の大きさや位置を 試行錯誤で調整 する 必要 があるため 大変 です。なお、この問題 は、×文字列描画 する際にも 発生 します。興味がある方は、実際に × を同じ方法で描画してみて下さい。

× のような 図形を描画 する際に、文字列 を使って 描画 を行うと、いくつかの問題発生する ことが、実感 できたのではないでしょうか。

今回の記事のまとめ

今回の記事 では、〇×ゲームゲーム盤枠線 と、文字列 による マーク描画 の方法について説明しました。また、文字列マーク を描画する方法の 欠点 を説明しました。

次回の記事 では、図形 を使って マーク描画 する方法を 説明 し、ゲーム盤描画 するプログラムを 完成 させます。

本記事で入力したプログラム

以下のリンクから、本記事で入力して実行した JupyterLab のファイルを見ることができます。

以下のリンクは、今回の記事で更新した marubatsu.py です。

次回の記事

  1. 数学 では、このような 両端がある線 のことを、線分 と呼びますが、一般的 には 線分 のことも 線と呼ぶ ことが 多い ので、本記事 では 線分 のことも 線と表記 します

  2. 本記事では取り扱いませんが、Python には、10 進数数値正確に表現 するための、decimal という データ型あります

  3. 整数以外 の値を 扱う ことが できるrange同様の機能 を持つ 関数 に、numpy というモジュールの arange という 関数あります が、説明 すると 長くなる ので、今回の記事 では list を利用 しました。numpy や arange については今後の記事で説明する予定です

1
0
0

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
1
0