Я пишу карточную онлайн-игру с использованием модулей python 3.7.0, PyQt5.12.2 и socket
. Игра отлично работает в течение нескольких раундов, пока сразу после того, как один из игроков закончит свой ход, приложение gui просто закроется без каких-либо сообщений об ошибках. Последнее, что выполняется, - это метод play_cards()
класса Scene
. Я пытался запустить клиенты с python -u -m trace -t client.py
, но сообщения об ошибках не появлялись. Я использую сигналы для ожидания сервера, чтобы gui оставался отзывчивым, и я знаю, что gui должен быть доступен только из основного потока, и я думаю, что моя программа делает это. Я также использую мьютексы для предотвращения одновременного доступа двух переменных к переменной. Я использую Windows 10 и CMD для запуска моих сценариев. Может кто-нибудь найти причину этого отказа? Любые советы приветствуются!
Вот файл client.py, где я отметил место, после которого gui выходит через пару раундов. WaitScene
- это место, где я использую свои Worker
и WorkerSignal
классы:
# client.py
from PyQt5.QtGui import QFont, QColor, QBrush
from PyQt5.QtWidgets import (QGraphicsView, QGraphicsRectItem, QGraphicsScene, QApplication,
QGraphicsTextItem, QLineEdit, QGraphicsDropShadowEffect)
from PyQt5.QtCore import QObject, pyqtSignal, QRunnable, pyqtSlot, Qt, QMutex, QThreadPool, QTimer
import traceback, sys
import socket
import pickle
import random
class WorkerSignals(QObject):
finished = pyqtSignal()
error = pyqtSignal(str)
result = pyqtSignal(object)
class Worker(QRunnable):
def __init__(self, fn, *args, **kwargs):
super(Worker, self).__init__()
self.fn = fn
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
@pyqtSlot()
def run(self):
try:
result = self.fn(*self.args, **self.kwargs)
except:
traceback.print_exc()
self.signals.error.emit("Could't connect to server")
else:
self.signals.result.emit(result)
finally:
self.signals.finished.emit()
class Camera(QGraphicsView):
def __init__(self, size, parent=None):
QGraphicsView.__init__(self, parent)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.width = size.height()
self.height = size.height()-150
self.setFixedSize(self.width, self.height)
self.centerOn(self.width/2, self.height/2)
self.show()
def update_scene(self, scene):
self.setScene(scene)
class Button(QGraphicsRectItem):
def __init__(self, x, y, w, h, text, parent=None):
QGraphicsRectItem.__init__(self, x, y, w, h, parent)
font = QFont()
font.setPointSize(30)
self.text = QGraphicsTextItem(text)
self.text.setFont(font)
self.text.setParentItem(self)
self.setBrush(QBrush(QColor(213, 128, 255)))
self.text.setDefaultTextColor(QColor(0, 0, 0))
text_center_x = (w - self.text.boundingRect().width())/2.0
text_center_x += x
text_center_y = (h-self.text.boundingRect().height())/2.0
text_center_y += y
center_coords = self.text.mapFromParent(text_center_x, text_center_y)
self.text.setPos(center_coords.x(), center_coords.y())
class Menu(QGraphicsScene):
def __init__(self, cam, parent=None):
QGraphicsScene.__init__(self, parent)
self.cam = cam
self.setSceneRect(0, 0, self.cam.width, self.cam.height)
font = QFont()
font.setPointSize(45)
self.text = QGraphicsTextItem("King and servant")
self.text.setFont(font)
self.text.setParent(self)
self.text.setDefaultTextColor(QColor(213, 128, 255))
start_point = (self.cam.width - self.text.boundingRect().width())/2.0
self.text.setPos(start_point, self.cam.height/4.0)
self.addItem(self.text)
font.setPointSize(20)
self.line = QLineEdit()
self.line.setPlaceholderText("Your name here")
self.line.selectAll()
self.line.setAutoFillBackground(True)
p = self.line.palette()
p.setColor(self.line.backgroundRole(), QColor(213, 128, 255))
self.line.setPalette(p)
self.line.setFixedWidth(self.cam.width/3.0)
self.line.setFixedHeight(self.cam.height/10.0)
center_x = (self.cam.width - self.line.width())/2.0
self.line.move(center_x, (self.cam.height/3.0)*1.5)
self.line.setFont(font)
self.addWidget(self.line)
self.line.setFocus()
self.line.returnPressed.connect(self.play)
self.play = Button((self.cam.width/3.0), (self.cam.height/3.0)*2, self.cam.width/3.0,
self.cam.height/10.0, "PLAY")
self.addItem(self.play)
self.setBackgroundBrush(QBrush(QColor(38, 38, 38)))
self.cam.update_scene(self)
self.update()
print(self.text.boundingRect().width())
def mousePressEvent(self, event):
if self.play.contains(event.scenePos()):
wait_scene = WaitScene(cam, self.line.text())
def play(self):
wait_scene = WaitScene(cam, self.line.text())
class WaitScene(QGraphicsScene):
def __init__(self, cam, player_name, parent=None):
QGraphicsScene.__init__(self, parent)
self.cam = cam
self.player_name = player_name
self.players = []
self.setSceneRect(0, 0, self.cam.width, self.cam.height)
self.setBackgroundBrush(QBrush(QColor(38, 38, 38)))
self.threadpool = QThreadPool()
self.connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.is_connected = False
self.mutex = QMutex()
font = QFont()
font.setPointSize(45)
self.text = QGraphicsTextItem("Waiting For Other Players")
self.text.setDefaultTextColor(QColor(213, 128, 255))
self.text.setFont(font)
self.text.setParent(self)
self.center_x = (self.cam.width - self.text.boundingRect().width())/2.0
self.center_y = (self.cam.height - self.text.boundingRect().height())/2.0
self.text.setPos(self.center_x, self.center_y)
self.addItem(self.text)
self.cam.update_scene(self)
self.update()
worker = Worker(self.connect_to_server, self.connection, self.player_name)
worker.signals.error.connect(self.on_error)
worker.signals.result.connect(self.set_players)
worker.signals.finished.connect(self.thread_complete)
self.threadpool.start(worker)
def thread_complete(self):
if self.mutex.tryLock(1000):
if self.is_connected:
scene = Scene(self.cam, self.connection, self.player_name, self.players)
self.mutex.unlock()
else:
print("FAIL threadcomplete")
def set_players(self, p):
if self.mutex.tryLock(1000):
if p[0]:
self.is_connected = True
self.players = p[1]
self.mutex.unlock()
else:
print("FAIL setplayers")
def on_error(self, s):
self.text.setPlainText(s)
center_x = (self.cam.width - self.text.boundingRect().width())/2.0
center_y = self.cam.height/3.0
self.text.setPos(center_x, center_y)
self.button = Button((self.cam.width/3.0), (self.cam.height/3.0)*2, self.cam.width/3.0,
self.cam.height/10.0, "TRY AGAIN")
self.addItem(self.button)
self.update()
def connect_to_server(self, conn, name):
if self.mutex.tryLock(1000):
print("Connecting to server")
conn.connect((socket.gethostname(), 8000))
conn.send(pickle.dumps(name))
players = pickle.loads(conn.recv(1024))
self.mutex.unlock()
return [True, players]
else:
print("FAIL connecttoserver")
return [False, []]
def mousePressEvent(self, event):
try:
if self.button.contains(event.scenePos()):
print("HEP")
self.text.setPlainText("Waiting For Other Players")
self.text.setPos(self.center_x, self.center_y)
self.removeItem(self.button)
self.update()
self.worker = Worker(self.connect_to_server)
self.worker.signals.error.connect(self.on_error)
self.worker.signals.result.connect(self.set_players)
self.worker.signals.finished.connect(self.thread_complete)
self.threadpool.start(self.worker)
except AttributeError:
pass
class Player(object):
def __init__(self, name):
self.cards = []
self.name = name
self.has_turn = False
def get_selected_cards(self):
ret = []
for i in self.cards:
if i.selected:
ret.append(i)
return ret
class Card(QGraphicsRectItem):
def __init__(self, val, suit, width, height, scene, parent = None):
suits = {
1:"H",
2:"Ru",
3:"P",
4:"Ri"
}
self.width = width/10
self.height = height/5
QGraphicsRectItem.__init__(self, 0, 0, self.width, self.height, parent)
shaddow = QGraphicsDropShadowEffect()
shaddow.setOffset(-4, -4)
self.setGraphicsEffect(shaddow)
self.scene = scene
self.setBrush(QBrush(Qt.white))
self.selected = False
self.value = str(val)
self.suit = suits[suit]
self.val = QGraphicsTextItem(self.value+"-"+self.suit)
self.val.setParentItem(self)
self.val.setPos(2, 2)
self.setAcceptHoverEvents(True)
def hoverEnterEvent(self, event):
self.setPos(self.x(), self.y()-(self.height/4))
self.scene.update()
def hoverLeaveEvent(self, event):
self.setPos(self.x(), self.y()+(self.height/4))
self.scene.update()
def unselect(self):
self.selected = False
self.setBrush(QBrush(Qt.white))
def mousePressEvent(self, event):
if not self.selected:
self.setBrush(QBrush(Qt.red))
self.selected = True
else:
self.setBrush(QBrush(Qt.white))
self.selected = False
class Scene(QGraphicsScene):
def __init__(self, cam, connection, player_name, players, parent=None):
QGraphicsScene.__init__(self, parent)
self.cam = cam
self.connection = connection
self.stack = {"amount":1, "value":0}
self.stack_str = QGraphicsTextItem(str(self.stack))
self.stack_str.setPos(self.cam.width/15, self.cam.height/3.0)
self.addItem(self.stack_str)
self.players = players
self.player_text_items = []
self.player = Player(player_name)
self.setSceneRect(0, 0, self.cam.width, self.cam.height)
self.setBackgroundBrush(QBrush(QColor(38, 38, 38)))
self.mutex = QMutex()
self.timer = QTimer()
self.timer.setInterval(1000)
self.timer.timeout.connect(self.timer_event)
line = QGraphicsRectItem(0, (self.cam.height/3.0)*2, self.cam.width, 5)
line.setBrush(QBrush(Qt.white))
line2 = QGraphicsRectItem((self.cam.width/3.0)*2, 0, 5, (self.cam.height/3.0)*2)
line2.setBrush(QBrush(Qt.white))
self.addItem(line)
self.addItem(line2)
self.add_names()
self.get_cards_from_server()
self.get_starter()
self.cam.update_scene(self)
self.update()
self.timer.start()
def get_cards_from_server(self):
cards = pickle.loads(self.connection.recv(1024))
for card in sorted(cards):
self.player.cards.append(Card(card[0], card[1], self.cam.width, self.cam.height, self))
for c, card in enumerate(self.player.cards):
card.setPos(self.cam.width/(len(self.player.cards))*c, self.cam.height-card.height-20)
self.addItem(card)
def reset_cards(self):
for c, card in enumerate(self.player.cards):
card.setPos(self.cam.width/(len(self.player.cards))*c, self.cam.height-card.height-20)
self.update()
def get_starter(self):
starter = pickle.loads(self.connection.recv(1024))
if self.player.name == starter:
self.player.has_turn = True
for item in self.player_text_items:
if item.toPlainText() == starter:
item.setDefaultTextColor(QColor(0, 255, 0))
def add_names(self):
font = QFont()
font.setPointSize(20)
dist = (self.cam.height/3.0)/len(self.players)
for c,name in enumerate(self.players):
t = QGraphicsTextItem(name)
self.player_text_items.append(t)
t.setDefaultTextColor(QColor(213, 128, 255))
t.setFont(font)
t.setPos((self.cam.width/3.0)*2+(self.cam.width/19), dist*c)
if (t.boundingRect().width() + t.pos().x()) > self.cam.width:
font2 = QFont()
font2.setPointSize(15)
t.setFont(font2)
self.addItem(t)
def keyPressEvent(self, event):
# Cards can be played by pressing the "s" key
if event.key() == 83 and self.player.has_turn and self.mutex.tryLock(1000):
self.play_cards()
self.mutex.unlock()
else:
print("FAIL keypressevent")
def send_and_recv(self):
if self.mutex.tryLock(1000):
print(self.player.name, "START SEND AND RECV")
self.connection.send(pickle.dumps([False]))
msg = pickle.loads(self.connection.recv(1024))
self.stack = msg[1]
if self.player.name == msg[0]:
self.player.has_turn = True
print("END SEND AND RECV")
self.mutex.unlock()
else:
print("FAIL sendandrecv")
def send_my_played_cards(self, stack):
print(self.player.name, "START SEND PLAY")
self.connection.send(pickle.dumps([True, stack]))
msg = pickle.loads(self.connection.recv(1024))
self.stack = msg[1]
if self.player.name == msg[0]:
self.player.has_turn = True
print("END SEND PLAY")
def reset_gui_stack(self):
self.stack_str.setPlainText(str(self.stack))
self.update()
def timer_event(self):
self.send_and_recv()
self.reset_gui_stack()
def play_cards(self):
self.timer.stop()
self.player.has_turn = False
played_cards = []
play_stack = {
"amount": 0,
"value" : 0
}
sel_cards = self.player.get_selected_cards()
if len(sel_cards) < 1:
amount = self.stack["amount"]
msgbox = QMessageBox.question(
self.cam,
'Error',
f"You have to select at least {amount} card(s) to play or skip this round",
QMessageBox.Ok)
self.player.has_turn = True
return
else:
val = sel_cards[0].value
for card in sel_cards:
if val != card.value:
msgbox = QMessageBox.question(
self.cam,
'Error',
f"Can't play different valued cards",
QMessageBox.Ok)
for i in self.player.cards:
i.unselect()
self.update()
self.player.has_turn = True
return
val = card.value
if int(val) <= self.stack["value"]:
val = self.stack["value"]
msgbox = QMessageBox.question(
self.cam,
'Error',
f"Value of played card(s) must be bigger than {val}",
QMessageBox.Ok)
for i in self.player.cards:
i.unselect()
self.update()
self.player.has_turn = True
return
elif self.stack["value"] == 0 and int(val) != 2:
msgbox = QMessageBox.question(
self.cam,
'Error',
f"First card must be 2 of Clubs",
QMessageBox.Ok)
for i in self.player.cards:
i.unselect()
self.update()
self.player.has_turn = True
return
for card in sel_cards:
played_cards.append(card)
self.removeItem(card)
self.player.cards.remove(card)
card.selected = False
play_stack["amount"] = len(played_cards)
play_stack["value"] = int(played_cards[0].value)
self.send_my_played_cards(play_stack)
self.reset_cards()
self.timer.start()
# This is the place after which the quitting occurs!!!!
if __name__ == "__main__":
sys._excepthook = sys.excepthook
def exception_hook(exctype, value, traceback):
print(exctype, value, traceback)
sys._excepthook(exctype, value, traceback)
sys.exit(1)
sys.excepthook = exception_hook
app = QApplication(sys.argv)
cam = Camera(app.primaryScreen().size())
menu = Menu(cam)
sys.exit(app.exec_())
И server.py:
# server.py
import socket
import pickle
import time
import select
import sys
import random
class Player(object):
def __init__(self, connection, que_num):
self.connection = connection
self.que_num = que_num
self.name = ""
self.cards = []
self.que_num = que_num
self.is_starter = False
def add_card(self, card):
if card == (2,4):
self.is_starter = True
self.cards.append(card)
PORT = 8000
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((socket.gethostname(), PORT))
s.listen(5)
cards = []
names = []
for i in range(1,5):
for j in range(2, 15):
cards.append((j, i))
count = int(input("Player amount: "))
players = {}
for i in range(count):
cli,_ = s.accept()
players[cli] = Player(cli, i)
print("all connected")
# deal cards
random.shuffle(cards)
c = 0
a = True
while a:
for p in players:
try:
players[p].add_card(cards[c])
c += 1
except IndexError:
a = False
break
for i in players:
players[i].name = pickle.loads(i.recv(1024))
names.append(players[i].name)
for p in players:
p.send(pickle.dumps(names))
time.sleep(2)
for p in players:
p.send(pickle.dumps(players[p].cards))
time.sleep(1)
for p in players:
if players[p].is_starter:
now_playing = players[p].name
for player in players:
player.send(pickle.dumps(players[p].name))
break
print("looping now")
curr_stack = {"amount":0,"value":1}
while True:
read,_,_ = select.select(list(players.keys()),[],[])
for conn in read:
data = pickle.loads(conn.recv(1024))
if data[0]:
print(data)
curr_stack = data[1]
temp = names.index(now_playing)
if temp == len(names)-1:
temp = 0
now_playing = names[temp]
else:
temp += 1
now_playing = names[temp]
conn.send(pickle.dumps([now_playing, curr_stack]))
else:
conn.send(pickle.dumps([now_playing, curr_stack]))
Дайте мне знать, если нужна дополнительная информация.