ヌルポインター親衛隊

社内でひとりエンジニアやってます。

Python学習7日め(オセロの反転部分のテスト)

はじめに

今日はオセロの"反転"を実装することが目標です。
しかし、朝しっかりと二度寝を決め込んだので、マイクロなテストコードの実装止まりとなりました。

反転

オセロにおいて、次のような盤面を想定します。

□ □ □ □ □ □ □ □ 
□ □ □ □ □ □ □ □ 
□ □ □ □ □ □ □ □ 
□ □ □ □ □ □ □ □ 
□ □ ● ○ ○ ○ □ □ 
□ □ □ □ □ □ □ □ 
□ □ □ □ □ □ □ □ 
□ □ □ □ □ □ □ □ 

通常のオセロでは、最初に白と黒を2つづつ中央の対角に設置するのでこのような盤面はありえませんが、反転を実装するためのテストなのでご愛嬌。それと、はてなブログ等幅フォントを表示させる方法がわからなかったので、見やすいように整形して出力するようにしてあります。 □は何も置かれていないことを表します。

この盤面で、次が黒の手番だとしたら、丁度全ての白を挟むような形の手が最適解でしょう。

□ □ □ □ □ □ □ □ 
□ □ □ □ □ □ □ □ 
□ □ □ □ □ □ □ □ 
□ □ □ □ □ □ □ □ 
□ □ ● ○ ○ ○ ● □ 
□ □ □ □ □ □ □ □ 
□ □ □ □ □ □ □ □ 
□ □ □ □ □ □ □ □ 

このとき挟まれた白を黒にするコードを考えます。

考え方

問題をシンプルにするために、コマを置いた場所の左側に限定します。そうすると、変更対象は新たにコマを置いた行に限定されます。

□ □ ● ○ ○ ○ ● □ 

新たにコマを置いた座標のすぐ左の座標の状態として考えられるのは、"●"、"○"、"□"です。そのうち、反転できうるのは"○"のみで、その他の場合は何も変更しません。まだ"●"で挟まれていることが保証されていないので、「できうる」です。

次に、座標を左にずらしてゆき、"○"が連続している間それを繰り返します。そして、"●"に当たった場合は最も遠い"○"の座標を記録します。

こうして、新しくコマ("●")を置いた座標と、連続する"○"の先頭の位置を記録した座標がわかりました。あとはrange()で繰り返して"○"を"●"に変更するだけです。

次が、今回書いたテストの全コードです。

#盤面を表示
def print_stage(pieces):
    stage = ""
    for y in range(0, 8):
        for x in range(0, 8):
            stage += pieces[y][x] + ' '
        stage += "\n"
    print(stage)
    
def reverse_row_left(pieces_row, x_offset): #x_offsetは新しく置いたコマの位置
    change_rocation_x = x_offset
    for x in range(x_offset-1, 0, -1):
        if (pieces_row[x] == "○"):
            continue #白が途切れて、途切れた次が"●"なら、その場所の座標を記録したい
        if (pieces_row[x] == "●") and (x is not x_offset):
            change_rocation_x = x
            break
        else:
            return pieces_row
    
    for x in range(x_offset, change_rocation_x, -1):
        pieces_row[x] = "●"
    
    return pieces_row


pieces = [["□" for x in range(8)] for y in range(8)] 

#横に並べた白を、黒が挟んでひっくり返せそうな状態を作ってみる
pieces[4][2] = "●"
for x in range(3, 6):
    pieces[4][x] = "○"
print_stage(pieces)

#挟んでみる
pieces[4][6] = "●"
print_stage(pieces)

pieces[4] = reverse_row_left(pieces[4], 6)

print_stage(pieces) #動いたぜええええええええええ

つまづいたとこ

最初、盤面の初期化を次のようにしてやっていました。

pieces = [[],[],[],[],[],[],[],[]]
for line in pieces:
    for i in range(8):
        line.append("□")

問題ないですけど、もっと簡単にかけるんじゃないかと思って、次のように書きました。

pieces = [["□"] * 8]*8

が、これはミスです。この初期化方法では、内側のリストのIDが同一になり、特定の行に変更を加えると、他の行も同じように変更されてしまいます。

#pieces[4][2] = "●"のコードで下のようにしたい
□ □ □ □ □ □ □ □            □ □ □ □ □ □ □ □ 
□ □ □ □ □ □ □ □            □ □ □ □ □ □ □ □
□ □ □ □ □ □ □ □      \     □ □ □ □ □ □ □ □
□ □ □ □ □ □ □ □   --  \    □ □ □ □ □ □ □ □
□ □ □ □ □ □ □ □   --  /    □ □ ● □ □ □ □ □
□ □ □ □ □ □ □ □      /     □ □ □ □ □ □ □ □
□ □ □ □ □ □ □ □            □ □ □ □ □ □ □ □
□ □ □ □ □ □ □ □            □ □ □ □ □ □ □ □

#が、現実は非情。
□ □ ● □ □ □ □ □
□ □ ● □ □ □ □ □
□ □ ● □ □ □ □ □
□ □ ● □ □ □ □ □
□ □ ● □ □ □ □ □
□ □ ● □ □ □ □ □
□ □ ● □ □ □ □ □
□ □ ● □ □ □ □ □

正しくは、次のようにしましょう(リスト内包表記と言うらしいです。)

pieces = [["□" for x in range(8)] for y in range(8)]

おわりに

「右と左と、上と下と、アッ斜め...」というように、全てをいっぺんに考えると死にます。私は以前に何回か死んでいて、その度に死んだままにして結局プログラミング向いてないんだろうなぁ、と匙を投げてしまっていました。今回も死にかけましたが、なんとか教訓を思い出して生きることができました。重要なのは、自分は天才だと思うことです。