ぱそきいろのIT日記

ぱそきいろがITに関する記事を書いていきます。

Quiz Knockの計算をPythonで力ずくで解く

こんにちは、ぱそきいろです。

Quiz Knockで以下の動画が出ました。
www.youtube.com

タイトルの最後に【みんなもやってみてね】とあるので、大人の力(Python)を使ってやってみようと思います。
そもそも東大院生が手計算なのでこちらはPythonを使います。

方針

解析的に解くのは厳しいので、Pythonで五目並べをするコードを書いて、あとは回数をこなして確率を算出します。
以下で使った、正確な値は出ないけど頭は使わず、脳筋で解けるので割と好きな方法です。
www.takacpu55.xyz

結果

コードは最後に載せます。
まずは、最初に勝敗が付く8手目を計算します。
10^6回試した方法なのでそれなりの確率になっていると思います。

full_count:1000000
winX_count:4367
winO_count:39389
黒が揃う確率
3.9389 %
白が揃う確率
0.43670000000000003 %

そして、動画内の計算結果は3.969%と小数点第一位くらいまで一致していますね。

なお、白が揃う確率も0.441%と小数点第一位まで一致です。

ちなみに動画内で結果があった最後の手番は15手目で結果は以下となりました。

full_count:1000000
winX_count:167927
winO_count:41173
黒が揃う確率
4.1173 %
白が揃う確率
16.7927 %

まぁ、それなりの精度は出ていますね。

最後に一手ごとの確率を載せておきます。

回数, 黒の勝ち, 白の勝ち
1,0.000000,0.000000
2,0.000000,0.000000
3,0.000000,0.000000
4,0.000000,0.000000
5,0.000000,0.000000
6,0.000000,0.000000
7,0.000000,0.000000
8,3.938900,0.436700
9,3.967900,0.444900
10,3.959100,0.442100
11,3.955400,0.441400
12,4.040200,12.285300
13,4.062500,14.984700
14,4.050300,15.025000
15,4.117300,16.792700
16,4.065600,16.791800
17,8.840300,16.825500
18,13.203000,16.023500
19,18.803400,15.922500
20,18.966300,16.022700
21,18.952500,15.961200
22,18.985100,16.085000
23,30.474100,14.421700
24,28.226200,35.617700
25,33.338500,34.336300

まとめ

確率の計算ができなくなってきたなぁと思う今日この頃です。
計算に時間をかけるならとっとと実行してしまえばいいなぁと・・・

コード
import random


class Gomoku:
    def __init__(self, size=15):
        self.size = size
        self.board = [['.' for _ in range(size)] for _ in range(size)]
        self.temp_board = [[0.0 for _ in range(size)] for _ in range(size)]
        self.current_player = 'X'


    def print_board(self):
        for row in self.board:
            print(' '.join(row))
        print()

    def make_move(self, x, y ,stone):
        if self.temp_board[x][y] == 0.0:
            self.temp_board[x][y] = stone
            self.obserbation_bord()

        else:
            print("Invalid move. Try again.")
        return False

    def judge(self):
        if self.check_winner()=='O':
            return 'O'
        if self.check_winner()=='X':
            return 'X'
        return False

    def obserbation_bord(self):
        for i in range(self.size):
            for j in range(self.size):
                if self.temp_board[i][j]==0.0:
                    self.board[i][j]='.'
                else:
                    if random.random()<self.temp_board[i][j]:
                        self.board[i][j]='O'
                    else:
                        self.board[i][j]='X'

    def check_winner(self):
        directions = [(1, 0), (0, 1), (1, 1), (1, -1)]
        for x in range(self.size):
            for y in range(self.size):
                if self.board[x][y] != '.':
                    current_player = self.board[x][y]
                    for dx, dy in directions:
                        count = 1
                        for i in range(1, 5):
                            nx, ny = x + i * dx, y + i * dy
                            if 0 <= nx < self.size and 0 <= ny < self.size and self.board[nx][ny] == current_player:
                                count += 1
                            else:
                                break
                        for i in range(1, 5):
                            nx, ny = x - i * dx, y - i * dy
                            if 0 <= nx < self.size and 0 <= ny < self.size and self.board[nx][ny] == current_player:
                                count += 1
                            else:
                                break
                        if count >= 5:
                            return current_player


    def is_full(self):
        for row in self.board:
            if '.' in row:
                return False
        return True


# ゲームのプレイ例
game = Gomoku()
game_over = False
movesarraay = [(6, 4 , 0.3), (7, 4, 0.1), (6, 5, 0.9), (7, 5, 0.1), (5, 6, 0.9), (6, 6, 0.7), (6, 7, 0.7), (6, 8, 0.3),
               (7, 6, 0.9), (8, 6, 0.1), (9, 7, 0.7), (5, 3, 0.3), (4, 2, 0.9), (7, 3, 0.1), (7, 2, 0.7), (5, 4, 0.3),
              (4, 6, 0.9), (3, 6, 0.1), (6, 3, 0.7), (2, 6, 0.3), (4, 3, 0.9), (4, 4, 0.1), (8, 7, 0.7), (3, 4, 0.3), (3, 2, 0.9)]# テストのための固定された手順

moves=[]
num=0
logs=[]
for move in movesarraay:
    num+=1

    full_count=1000000
    winX_count=0
    winO_count=0
    game.make_move(*move)

    for i in range(full_count):
        if i%(full_count/10)==0:
            print("{} {}".format((i/full_count)*100,"%"))
        game.obserbation_bord()
        game_over=game.judge()
        if game_over=='X':
            winX_count+=1
        if game_over=='O':
            winO_count+=1
    game.print_board()

    print("full_count:"+str(full_count))
    print("winX_count:"+str(winX_count))
    print("winO_count:"+str(winO_count))

    print("黒が揃う確率")
    print("{} {}".format(winO_count/full_count*100,"%"))

    print("白が揃う確率")
    print("{} {}".format(winX_count/full_count*100,"%"))

    logs.append([num,winO_count/full_count*100,winX_count/full_count*100])

print("回数, 黒の勝ち, 白の勝ち")
for i in range(len(logs)):
    print("{:2},{:.6f},{:.6f}".format(logs[i][0],logs[i][1],logs[i][2]))