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

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

新規登録して質問してみよう
ただいま回答率
85.31%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Socket.IO

Socket.IOはNode.js上で動くライブラリであり、すべてのブラウザとモバイルデバイスでリアルタイムのアプリを作動させる事を目的としています。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

244閲覧

Pythonでのクライアントサーバー型タイピングゲームでinputが使えない

__n.um

総合スコア2

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Socket.IO

Socket.IOはNode.js上で動くライブラリであり、すべてのブラウザとモバイルデバイスでリアルタイムのアプリを作動させる事を目的としています。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2024/12/16 14:46

実現したいこと

Python初心者です。
現在、Pythonでクライアントサーバー型の対戦タイピングゲームを作成しています。1台のPCでサーバーをバックグラウンドで起動し、複数のクライアントが接続してゲームを行う構成です。

発生している問題・分からないこと

1回目の単語は正常に送受信・入力が表示され、結果が表示がされるんですが、
2回目以降の単語では入力プロンプトがクライアントに表示されず、ゲームが途中で止まってしまいます。
サーバー側では次の単語を正しく送信しており、クライアントも受信ログで確認済みです。
しかし、クライアントのinput()が呼び出されずに止まったままです。
この問題の原因や解決方法についてアドバイスをいただきたいです。
現在の主要なコード(簡略版)も下記に記載します。
つたないコードで見にくいと思いますが、アプローチ方法をご教授いただければ幸いです。

該当のソースコード

#サーバー側 import socket import typing_game_func as tgf class TypingServer: def __init__(self, host='0.0.0.0', port=65432): self.server_address = (host, port) self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.server_socket.bind(self.server_address) self.server_socket.listen() self.client_sockets = [] self.player_names = [] self.num_players = 0 print("サーバーが起動しました...") def send_message(self, socket, message): socket.sendall(message.encode('utf-8')) def recv_message(self, socket): return socket.recv(1024).decode('utf-8').strip() def start(self): first_client_socket, _ = self.server_socket.accept() self.client_sockets.append(first_client_socket) print("最初のクライアントが接続しました") self.send_message(self.client_sockets[0], "num_players") self.num_players = int(self.recv_message(self.client_sockets[0])) print(f"あと{self.num_players - 1}人待っています...") for i in range(self.num_players - 1): client_socket, _ = self.server_socket.accept() self.client_sockets.append(client_socket) print(f"クライアント{i+2}が接続しました") game = tgf.TypingGame(self.server_socket, self.client_sockets) game.start_game() for client_socket in self.client_sockets: client_socket.close() self.server_socket.close() if __name__ == "__main__": server = TypingServer() server.start()
#クライアント側 import socket import time class TypingClinet: def __init__(self, host='127.0.0.1', port=65432): self.server_address = (host, port) self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.client_socket.connect(self.server_address) print("サーバーに接続しました。") def send_message(self, socket, message): socket.sendall(message.encode('utf-8')) def recv_message(self, socket): return socket.recv(1024).decode('utf-8').strip() def start(self): try: while True: message = self.recv_message(self.client_socket) if message == "num_players": num_plyaers = input("プレイヤー人数の入力>> ") self.send_message(self.client_socket, num_plyaers) elif message == "name": name = input("名前を入力>> ") self.send_message(self.client_socket, name) elif "ゲーム開始" in message: print(message) break while True: # サーバーから単語を受け取る word = self.recv_message(self.client_socket) print("word_recv", flush=True) if word == "end": break # ゲーム終了時 elif "単語" in word: print(word) start_time = time.time() player_input = input("入力: ") #ここが上手くいきません。 player_time = time.time() - start_time # 入力結果とタイムをサーバーに送信 self.send_message(self.client_socket, f"{player_input}, {player_time:.2f}") result = self.recv_message(self.client_socket) print(result) result = self.recv_message(self.client_socket) print(result) except Exception as e: print(f"エラー: {e}") finally: self.client_socket.close()
#ゲーム内容 import random import time class TypingGame: def __init__(self, server_socket, client_sockets): self.server_socket = server_socket self.client_sockets = client_sockets self.score_limit = 5 self.words = [] self.player_scores = [0] * (len(client_sockets)) self.player_names = [] def send_message(self, socket, message): socket.sendall(message.encode('utf-8')) def recv_message(self, socket): return socket.recv(1024).decode('utf-8').strip() def broadcast(self, message): #全プレイヤーにメッセージを送信 for client_socket in self.client_sockets: self.send_message(client_socket, message) def load_words(file): with open(file, 'r') as f: return [line.strip() for line in f] def collect_inputs(self): #全プレイヤーの入力を集めて、結果を返す player_times = [] player_inputs = [] # クライアントの入力を受信 for client_socket in self.client_sockets: client_input, client_time = self.recv_message(client_socket).split(',') player_inputs.append(client_input) player_times.append(float(client_time)) return player_inputs, player_times def send_results_to_clients(self, player_times, player_scores, player_names): results = "\ntime :" for i in range(len(player_times)): if i != 0: results += " - " results += f"[{player_names[i]}] {player_times[i]:.2f}sec" results += "\nscore:" for i in range(len(player_scores)): if i != 0: results += " - " results += f"[{player_names[i]}] {player_scores[i]}" self.broadcast(results) def start_game(self): #ニックネーム設定 for client_socket in self.client_sockets: self.send_message(client_socket, "name") client_name = self.recv_message(client_socket) self.player_names.append(client_name) self.words = self.load_words('words.txt') self.broadcast("ゲーム開始まで2秒...") time.sleep(2) # ゲーム進行 while max(self.player_scores) < self.score_limit: word = random.choice(self.words) self.words.remove(word) self.broadcast(f"\n単語: {word}") # 全プレイヤーの入力を集める player_inputs, player_times = self.collect_inputs() # 正誤判定 correct_inputs = [] for i in player_inputs: if i == word: correct_inputs.append(True) else: correct_inputs.append(False) #タイム判定 correct_times = [] for i, t in zip(correct_inputs, player_times): if i: correct_times.append(t) if correct_times: min_time = min(correct_times) else: min_time = None # スコアを更新 for i, correct in enumerate(correct_inputs): if min_time != None and correct and player_times[i] == min_time: self.player_scores[i] += 1 #結果を表示 self.send_results_to_clients(player_times, self.player_scores, self.player_names) self.broadcast("end") # 勝敗の決定 winner = self.player_scores.index(max(self.player_scores)) self.broadcast(f"{self.player_names[winner]}が勝利しました!")

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

以下のコードで"word_recv"というデバックを用意しましたが、なぜか二個目の単語の際はこのprintが表示されず、print(word)だけ表示されます。しかし、input("入力:")はでてきません。

# サーバーから単語を受け取る word = self.recv_message(self.client_socket) print("word_recv", flush=True)#デバック if word == "end": break # ゲーム終了時 elif "単語" in word: print(word) start_time = time.time() player_input = input("入力: ") #ここが上手くいきません。 player_time = time.time() - start_time

補足

何か私のコードでわからない部分があればいつでも聞いてください。

気になる質問をクリップする

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

質問とは別のエラーですが、 load_files の第一引数修正し、テキストファイルを準備して起動を確認

問題点:

  • クライアント側のコードで result 受け取りが二重になっている (elif ブロックの中と外)
    • 2つ目のresult で単語を受け取っているので
      その後の # サーバーから単語を受け取る でサーバー・クライアント共にお互い待ち状態
    • 解決策: どちらか片方にすることで正常動作を確認
  • ループの終了判定 if word == "end" or not word:
    • ワード数が4以下の時 random.choice で失敗して、クライアント側は空ループに陥りました。

デバッグ方法について、同様の問題の防止案
wordと result のprint では、どこの print の実行化が解らなくなってるので、logging モジュールを活用するか、
print(f"{result=}') のように変数名だけでも合わせて表示すると、何処で止まってるかがわかりやすくなります。

懸念点: 質問とは関係ありませんが、最初はこっちを疑った。
恐らくローカルサーバー接続だと正常稼働しますが、
recv() でのデータ受け取りは、期待するデータが得られない場合があります。
参考: ソケットプログラミング HOWTO
send()の場合はsendall() でよいのですが、recv でのより高水準な読み込みは
バッファリングに配慮した socket.makefile、read や readline での読み込み方法を調べてみてください。
テキストでのメッセージのやり取りなら行単位が処理しやすいです。

投稿2024/12/16 17:40

編集2024/12/16 17:45
teamikl

総合スコア8817

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

__n.um

2024/12/16 18:23

回答いただき誠にありがとうございます。 ・load_files の第一引数修正し →どのように修正したか教えていただいてもよろしいでしょうか? ・クライアント側のコードで result 受け取りが二重になっている (elif ブロックの中と外) →これのせいで入力が出てきませんでした。おかげさまでゲームが進行しました。 ・socket.makefile、read や readline での読み込み方法を調べてみてください。 →socket.makefileというものを初めて聞いたので調べてみようと思います。 本当に質問にお答えいただきありがとうございました。 もしよろしければ、ほかにこのコードの書き方が気になるとか、こっちの機能の方が使いやすいよなどあれば教えていただけると幸いです。 ベストアンサーにさせていただきますので、本当にありがとうございました!
teamikl

2024/12/16 19:48

load_files は、とりあえずエラーに対応する為に第一引数の self を追加しましたが、 他に修正方法としては、staticmethod にすることもできます。 メソッド内部で self への参照はないので、 定義の前の行に @staticmethod 追加でいいかもしれない。 エラーで実行できないはずなので、質問へ掲載したコードと違うのかな? デバッグの所で少し触れたけど、print() の代わりに logging モジュールを導入すると、 デバッグ時は情報を表示して、遊ぶときには不要なデバッグ用の情報を出さないといった制御が簡単にできます。 https://docs.python.org/ja/3/library/logging.html Python はデバッグ実行時(最適化オプション無し実行) __debug__ という変数が True なので、 if __debug__: loggingの設定 python -O ~~で起動するときは表示されなくして、デバッグの為のprint代用として活用できます。 気になる点、全部を見たわけではないけど目についたのを一点 Python での += での文字列連結は、毎回新規オブジェクトが生成されるので ループ内で何度も使われる場合はパフォーマンスが良くありません。 (今回のコード規模で問題になるほどでは全然ありません、一例として紹介) なので、一度リスト入れたりジェネレーターで実装して、 文字列のjoinで一気に文字列オブジェクトを生成する方法が好まれます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.31%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問