LoginSignup
0

More than 1 year has passed since last update.

posted at

updated at

プログラミング上達講座5:オセロ

プログラミング上達講座の5回目です。

オセロを題材にして
プログラムを考えてみましょう。

解説動画はこちら

表示されなかったらすみません

オセロとは

オセロご存知でしょうか?

othello.rule5_.gif

結構昔からあるゲームで
やったことがある人も多いんじゃないかと。

一応のルールは8 x 8 のマスを用いて
白黒の石を置き、相手の石をひっくり返してゆくゲーム。

相手の石を自分の石で挟めば
ひっくり返しが成立し、ひっくり返せないところには置けません。

最終的に石の多い方が勝ちというルールではあります。

初級編:ボードデータを作成してみよう

グローバル変数boardとして作成する。

変数SIZEを用意しておいて
そのサイズの分だけ サイズ x サイズ のボードを作り
ボードデータは二次元のリスト型とする

初期配置として
WHITE(0),BLACK(1)
ボードの真ん中に交互に4つ置く

それ以外はNoneで埋める

osetth.png

中級編:石を置いたらひっくり返せるかどうかを判定する関数を考える

石を置いたらひっくり返せるかどうかを判定する
関数flippableを作成しよう。

置く場所には石がなくて、石は置けるものとする。

def flippable(y , x , player):
    処理
    return True or False

引数player は WHITE(0) or BLACK(1)が入る
y , x は縦 , 横方向の座標(0 - 7)が入るものとする

上級編:石を置く処理を行う関数を考える

石を置く処理を行う関数
flip_stoneを作成しよう。

1.おく場所に石があるかどうかを判定する。
石があったら置けないのでFalseを返す。

2.置いたらひっくり返せるかを判定する。
返せる石がなければ置けないのでFalseを返す。

3.石を置いてひっくり返す
相手の石をひっくり返して
ボードの状態を変更してTrueを返す。

def flip_stone(y ,  x , player):
    処理
    return True or False

ボードデータはグローバル変数の
boardとして考えてみましょう。

解答編

初級編の解答

オセロの盤面のデータ構造を考えます。
二次元のリストを作れば良いです。

# 初期設定
SIZE,WHITE,BLACK = 8,0,1

# ボードを作成
board = [[None for i1 in range(SIZE)] for i2 in range(SIZE)] # サイズ分の大きさのボードを作成
board[3][3],board[3][4],board[4][3],board[4][4] = WHITE,BLACK,BLACK,WHITE # 初期配置

print(board)
変数boardの内容
[[None, None, None, None, None, None, None, None],
 [None, None, None, None, None, None, None, None],
 [None, None, None, None, None, None, None, None],
 [None, None, None, 0, 1, None, None, None],
 [None, None, None, 1, 0, None, None, None],
 [None, None, None, None, None, None, None, None],
 [None, None, None, None, None, None, None, None],
 [None, None, None, None, None, None, None, None]]

こんな感じで石は0,1
石がなければNoneで格納すれば良いでしょう。

中級編の解答

プログラミングではよく用いられる手法ですが
ここでは「探索」というものが出てきます。

今その場所に石を置いたと考えて
(y,xを置く座標と考える)
8方向に探索に行きます。

8方向に進んだ時に
ひっくり返せる石が
有るかどうかを探します。

8方向の考え方:

今いる位置からどの方向に
進むのか数値で考えます。

image.png

右方向に探索する場合

自分が1の時
置いた場所から次の石が

None → 石が無いので置けない
1,0,0 → 最初が自分と同じなのでひっくり返せない
0,0,0 → 自分と同じ石が無いのでひっくり返せない
0,0,1 → 最初が自分と同じ石でなく
どこかに自分と同い石があるのでひっくり返せる

という感じになります。

あとは最初の石と、最後までの過程を
変数に記録しておけば探索できると思います。

コードはこんな感じになりました。

# ボードに石を置いたら返せるかどうかを判定する関数
def flippable(y ,  x , player):
    enemy = 1 if player==0 else 0
    # 8方向を探索する
    for dy,dx in [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]:
        depth,flg = 1,0
        for i in range(SIZE):
            # 検索対象の座標を設定する
            ry,rx = y + (dy * depth) , x + (dx * depth) 
            # ボードの範囲を超えていたら抜ける
            if any([rx<=-1,ry<=-1,rx>=SIZE,ry>=SIZE]):
                break
            # 返せる石がなくてNoneだったら抜ける
            if board[ry][rx] is None:
                break
            # 返せる石がなくてplayerと同じ石だったら抜ける
            elif flg==0 and board[ry][rx]==player:
                break
            # 返せる石が初めて見つかったらflg=1にする
            elif flg==0 and board[ry][rx]==enemy:
                flg = 1
            # flg=1の状態でplayerと同じ石が見つかったら処理を抜ける
            elif flg==1 and board[ry][rx]==player:
                return True
            depth += 1
    return False
# ボードを作成
board = [[None for i1 in range(SIZE)] for i2 in range(SIZE)] # サイズ分の大きさのボードを作成
board[3][3],board[3][4],board[4][3],board[4][4] = WHITE,WHITE,BLACK,WHITE # 初期配置
flippable(2 ,  5 , 1)

True

           

上級編の解答

考え方としてはまずはボードが空いていないなら
Falseを返します。

置けるかどうかは中級編のflippableを再利用し

ひっくり返せる所を探索して、その座標を記録しておき
最後に石をひっくり返す処理を行います。

コードはこんな感じになりました。

# 石をひっくり返す
def flip_stone(y ,  x , player):
    # ボードが空いていなければ処理を抜ける
    if board[y][x] is not None:
        return False

    # 置いたらひっくり返せるかを判定する
    if not flippable(y ,  x , player):
        # ひっくり返せないなら置けない
        return False

    # ひっくり返せるなら、石をひっくり返してボードを更新する
    else:
        enemy = 1 if player==0 else 0
        result = [(y ,  x)]
        for dy,dx in [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]:
            depth,flg = 1,0
            tmp = []
            for i in range(SIZE):
                # 検索対象の座標を設定する
                ry,rx = y + (dy * depth) , x + (dx * depth) 
                # ボードの範囲を超えていたら抜ける
                if any([rx<=-1,ry<=-1,rx>=SIZE,ry>=SIZE]):
                    break
                # 返せる石がなくてNoneだったら抜ける
                if board[ry][rx] is None:
                    break
                # 返せる石がなくてplayerと同じ石だったら抜ける
                elif flg==0 and board[ry][rx]==player:
                    break
                # 返せる石が初めて見つかったらflg=1にする
                elif flg==0 and board[ry][rx]==enemy:
                    flg = 1
                    # 返せる座標を追加する
                    tmp.append((ry,rx))
                # 返せる石が初めて見つかったら返せる座標を追加する
                elif flg==1 and board[ry][rx]==enemy:
                    tmp.append((ry,rx))
                # flg=1の状態でplayerと同じ石が見つかったらresultに追加する
                elif flg==1 and board[ry][rx]==player:
                    result += tmp
                    break
                depth += 1

        # 石をひっくり返す
        for y2,x2 in result:
            board[y2][x2]=player
        return True

さてこれでオセロゲームの用意が出来ました。
描画用の関数なども用意して
オセロゲームを試してみましょう。

# ゲームを試してみる

# ボードを作成
board = [[None for i1 in range(SIZE)] for i2 in range(SIZE)] # サイズ分の大きさのボードを作成
board[3][3],board[3][4],board[4][3],board[4][4] = WHITE,BLACK,BLACK,WHITE # 初期配置

# ボードの状態を描画する
def print_board():
    print('',end='\t')
    for x0 in range(8):
        print('X:{0}'.format(x0),end='\t')
    print()
    for y1,b in enumerate(board):
        print('Y:{0}'.format(y1),end='\t')
        for x1,s in enumerate(b):
            print(' ' if s is None else s,end='\t')
        print()

# 初期配置を確認
print_board()

スクリーンショット 2020-08-09 16.15.04.png

print(flip_stone(2 , 3 , 1) )

# 更新後の配置
print_board()

スクリーンショット 2020-08-09 16.15.10.png

まとめ

自分一人でやるのは寂しいので
このあと、コードを加えて
オセロゲームっぽく仕上げて
AIなんかも搭載してあげれば
立派なオセロゲームになると思います。

プログラミングの基本的な考え方を
身につけるにはちょうど良い題材だと思いますので
暇な方は試してみてください。

過去にはこんなオセロも
作っていましたね。

三つ巴対決盛り上がる説を検証するためにオセロゲーム作ってみた

今回はこれまでです。

それでは。

作者の情報

乙pyのHP:
http://www.otupy.net/

Youtube:
https://www.youtube.com/channel/UCaT7xpeq8n1G_HcJKKSOXMw

Twitter:
https://twitter.com/otupython

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
What you can do with signing up
0