質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.61%

Pythonで実装したリバーシプログラム内で辞書型を使うと例外を吐かずに停止する

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 2
  • VIEW 1,474
退会済みユーザー

退会済みユーザー

 前提・実現したいこと

Python3.6.4でリバーシのプログラムを作成しています。GUI部分はtkinterで実装し、Threadingとソケット通信を使用して非同期処理でAI同士で対局を行わせるプログラムを作成しております。  
今回、新しい機能として「戻る」「進む」ボタンをクリックすることで対局中でもボードの状態を遡れるような機能を実装しようとしています。  

ボードの状態は辞書型のrecordという変数で管理し、具体的なコードではサーバーの処理側で、以下のようにrecordに毎ターンボードの状態を追加していこうと考えております。  

 board.record[board.turn_count] = {'board':copy.deepcopy(board.discs), 'newest_place':board.newest_place}

しかし、毎回17手目でプログラムが停止してしまい例外もキャッチできないため、解決方法が分からず困っております。  
以下のソースコードのBoardクラスのself.recordという変数で、BoardクラスのreverseDiscという関数内でrecordに要素を追加していっております。  

 該当のソースコード

サーバーのコード(クライアントであるAIx2とボードの状態、着手場所などをやり取りしております)

# -*- coding:utf-8 -*-
"""Script for Tkinter GUI VimReversi."""
from threading import Thread
import tkinter as tk
import socket
import select
import pickle
import copy

from vr_board import Board
from vr_gui import GUI


server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
readfds = set([server_sock])
host = '127.0.0.1' # server host
port = 4000 # port number
bufsize = 4096 # buffer size
backlog = 2 # max queue number


def server_core(board, gui,):
    try:
        server_sock.bind((host, port))
        server_sock.listen(backlog)

        while True:
            rready, wready, xready = select.select(readfds, [], [])
            for sock in rready:
                if sock is server_sock:
                    conn, address = server_sock.accept()
                    readfds.add(conn)
                else:
                    """ receive """
                    msg = sock.recv(bufsize)
                    msg = pickle.loads(msg)
                    player_turn = None
                    placeloc = None
                    cand_move = board.getCanPlace(board.turn)

                    print(msg) # print recv message

                    # if receive message is valid
                    player_turn = msg['turn']
                    placeloc = msg['placeloc']

                    # set player name
                    gui.setName(player_turn, msg['software_name'])

                    # place disc
                    if(player_turn == board.turn):
                        if(board.reverseDisc(player_turn, placeloc) == True):
                            board.newest_place = placeloc # last placed disc

                            board.record[board.turn_count] = {'board':copy.deepcopy(board.discs), 'newest_place':board.newest_place}

                            # draw game info on the listbox
                            gui.addList(player_turn, board.turn_count, placeloc)
                            board.turn_count += 1

                            board.switch_turn()
                            cand_move = board.getCanPlace(board.turn)
                            board.pass_count = 0

                        elif(msg['pass_flg'] == True): # player passes play
                            board.switch_turn()
                            cand_move = board.getCanPlace(board.turn)
                            board.pass_count += 1
                    """""""""""" # end receive

                    """ send """
                    server_info = {}
                    server_info['clicked_index'] = gui.clicked_index
                    server_info['board'] = copy.deepcopy(board)
                    server_info['candidate_move'] = cand_move

                    # the game hasn't start yet
                    if(not gui.start_flg):
                        server_info['board'].turn = 'None'
                        server_info['candidate_move'] = []

                    snd_msg = pickle.dumps(server_info) # dump pickle
                    sock.send(snd_msg)
                    """""""""""" # end send

                    # finish the game.
                    if(board.turn_count > 60 or board.pass_count >= 2):
                        print('game finished! gg!')
                        for rdd in readfds:
                            rdd.close()
                        return 

                    # end the game and shutdown the server.
                    if(gui.end_flg):
                        print('server shutdown!')
                        for rdd in readfds:
                            rdd.close()
                        return 
    finally:
        for sock in readfds:
            sock.close()


def main():
    root = tk.Tk()  # create root window
    root.title("VimRev")  # window title
    root.geometry("960x720")  # window size 960x720
    root.resizable(0, 0)  # Prohibit change of window size

    board = Board()
    gui = GUI(root)
    server_thread = Thread(target=server_core, name='server_thread', args=(board, gui,))
    server_thread.start()

    # left click callback
    root.bind("<Button-1>", gui.click)

    # key press callback
    root.bind("<Control-s>", gui.key) # start the game.
    root.bind("<Control-q>", gui.key) # end the game.
    root.bind("<Control-c>", gui.key) # end the game.

    root.after(100, gui.draw, board)
    root.mainloop()  # Starts GUI execution.


if __name__ == '__main__':
    main()

ボードクラスやAIクラスも実装しておりますが、文字数の関係で記載することができなかったため、下記のGithubのリンクを参照していただければ幸いです。  

 補足情報(FW/ツールのバージョンなど)

プログラムはPython3.6.4で実装しており、今回の機能を実装していないバージョンをGithubで公開しております。

プログラムを実行するには、以下の方法に従ってください。

  1. サーバーを起動します。  

python src/vr_server.py

  1. サーバーが起動している状態でプレーヤーx2をを起動します。

python src/vr_ai.py -n BlackPlayer -m Black <- 先手
python src/vr_ai.py -n WhitePlayer -m White <- 後手

  1. プレーヤーを2つ起動したら、ウィンドウをアクティブにした状態でCtrl-sで対局を開始できます。
    また、対局中にCtrl-c or Ctrl-qを押すことで、サーバーを停止し、対局を終了することができます。
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

check解決した方法

+1

半分投げやりな質問になってしまい、大変申し訳ございませんでした。
私の方で色々検証したところ、解決方法が分かりましたので以下に示します。

まず、Boardクラスのインスタンスboard内のrecordという辞書型に以下のように毎ターンボードの状態と、そのターンに着手した場所を追加していっております。

board.record[board.turn_count] = {'board':copy.deepcopy(board.discs), 'newest_place':board.newest_place}
(ここで、board.turn_countは何ターン目かをint型で保持し、board.discsは石の配置をllistで保持、board.newest_placeは最後に石が置かれた場所のインデックスを保持しております。)

その後、クライアントであるAIやプレーヤーにboardをPickle化して送信するのですが、毎ターンboard.recordの容量が増えていっているため、17ターン目でバッファサイズを上回り、正しく通信ができていない状態でした。

そこで、新たにGUIクラスを作成し、そちらにボードの状態を記録するようにしたところ実現したいことを達成することができました。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.61%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る