LoginSignup
1
0

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-08-09

プログラミング上達講座の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

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