PyQt5.12.2 сохраняет «случайный» выход без сообщений об ошибках - PullRequest
0 голосов
/ 22 апреля 2020

Я пишу карточную онлайн-игру с использованием модулей 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]))

Дайте мне знать, если нужна дополнительная информация.

...