python - 如何在井字游戏 PyQT5 中显示获胜棋步?
问题描述
我目前正在熟悉强化学习的基础知识,为了方便起见(而不是在终端中手动输入坐标),我创建了一个非常简单的 UI 来测试训练有素的代理和玩游戏。出于测试目的,我创建了一种方法,该方法在人类玩家单击单元格后生成随机移动,并且在单击/随机移动之后显示每个移动,但是当移动是获胜移动时,它不会显示并且棋盘会重置。我不知道为什么会发生这种情况,但看起来执行顺序不是连续的/有些我不明白。我尝试time.sleep(1)
在板更新后使用,以确保我不会忽略实际显示的移动,但是移动永远不会显示,窗口只是冻结。
预期结果:
我实际得到的(假设我将单击单元格 (0, 2)):
from PyQt5.QtWidgets import (
QMainWindow,
QDesktopWidget,
QApplication,
QWidget,
QHBoxLayout,
QVBoxLayout,
QPushButton,
QLabel,
)
from PyQt5.QtCore import Qt
import numpy as np
import sys
class TicCell(QPushButton):
"""
Tic Tac Toe cell.
"""
def __init__(self, location):
"""
Initialize cell location.
Args:
location: Tuple, (row, col).
"""
super().__init__()
self.location = location
class TicUI(QMainWindow):
"""
Tic Tac Toe interface.
"""
def __init__(
self,
window_title='Smart Tic Tac Toe',
board_size=3,
empty_value=0,
x_value=1,
o_value=2,
agent=None,
):
"""
Initialize game settings.
Args:
window_title: Display window name.
board_size: int, the board will be of size(board_size, board_size).
empty_value: int representation of an empty cell.
x_value: int representation of a cell containing X.
o_value: int representation of a cell containing O.
agent: Trained TicAgent object.
"""
super().__init__()
self.setWindowTitle(window_title)
self.board_size = board_size
self.empty_value = empty_value
self.x_value = x_value
self.o_value = o_value
self.agent = agent
self.text_map = {
x_value: 'X',
o_value: 'O',
empty_value: '',
'X': x_value,
'O': o_value,
'': empty_value,
}
win_rectangle = self.frameGeometry()
center_point = QDesktopWidget().availableGeometry().center()
win_rectangle.moveCenter(center_point)
self.central_widget = QWidget(self)
self.main_layout = QVBoxLayout()
self.score_layout = QHBoxLayout()
self.human_score = 0
self.agent_score = 0
self.score_board = QLabel()
self.update_score_board()
self.setStyleSheet('QPushButton:!hover {color: yellow}')
self.cells = [
[TicCell((c, r)) for r in range(board_size)]
for c in range(board_size)
]
self.cell_layouts = [QHBoxLayout() for _ in self.cells]
self.board = np.ones((board_size, board_size)) * self.empty_value
self.adjust_layouts()
self.adjust_cells()
self.update_cell_values()
self.show()
def adjust_layouts(self):
"""
Adjust score board and cell layouts.
Returns:
None
"""
self.main_layout.addLayout(self.score_layout)
for cell_layout in self.cell_layouts:
self.main_layout.addLayout(cell_layout)
self.central_widget.setLayout(self.main_layout)
self.setCentralWidget(self.central_widget)
def adjust_cells(self):
"""
Adjust display cells.
Returns:
None
"""
self.score_layout.addWidget(self.score_board)
self.score_board.setAlignment(Qt.AlignCenter)
for row_index, row in enumerate(self.cells):
for cell in row:
cell.setFixedSize(50, 50)
cell.clicked.connect(self.game_step)
self.cell_layouts[row_index].addWidget(cell)
def get_empty_cells(self):
"""
Get empty cell locations.
Returns:
A list of indices that reepresent currently empty cells.
"""
empty_locations = np.where(self.board == self.empty_value)
empty_locations = list(zip(empty_locations[0], empty_locations[1]))
for empty_location in empty_locations:
r, c = empty_location
cell_text = self.cells[r][c].text()
assert cell_text == self.text_map[self.empty_value], (
f'location {empty_location} has a cell value of {cell_text}'
f'and board value of {self.board[r][c]} {self.board}'
)
return empty_locations
def update_score_board(self):
"""
Update the display scores.
Returns:
None
"""
self.score_board.setText(
f'Human {self.human_score} - ' f'{self.agent_score} Agent'
)
def check_win(self, player_value):
"""
Check current game state for winner.
Args:
player_value: int, self.x_value or self.o_value.
Returns:
True if player_value won, False otherwise.
"""
return (
np.all(self.board == player_value, axis=0).any()
or np.all(self.board == player_value, axis=1).any()
or np.all(self.board.diagonal() == player_value)
or np.all(self.board[::-1].diagonal() == player_value)
)
def reset_cell_colors(self):
"""
Reset display cell text colors.
Returns:
None
"""
for row_idx in range(self.board_size):
for col_idx in range(self.board_size):
self.cells[row_idx][col_idx].setStyleSheet('color: yellow')
def reset_game(self, winner=None):
"""
Reset board and display cells and update display scores.
Args:
winner: int, self.x_value or self.o_value.
Returns:
None
"""
self.board = (
np.ones((self.board_size, self.board_size)) * self.empty_value
)
self.update_cell_values()
if winner == self.x_value:
self.human_score += 1
if winner == self.o_value:
self.agent_score += 1
self.update_score_board()
self.reset_cell_colors()
def modify_step(self, cell_location, value):
"""
Modify board and display cells.
Args:
cell_location: tuple, representing indices(row, col).
value: int, self.x_value or self.o_value.
Returns:
True if the clicked cell is not empty, None otherwise.
"""
r, c = cell_location
board_value = self.board[r, c]
cell_value = self.cells[r][c].text()
if not board_value == self.empty_value:
return True
assert cell_value == self.text_map[self.empty_value], (
f'mismatch between board value({board_value}) '
f'and cell value({cell_value}) for location {(r, c)}'
)
if value == self.x_value:
self.cells[r][c].setStyleSheet('color: red')
self.board[r, c] = value
self.cells[r][c].setText(f'{self.text_map[value]}')
def game_step(self):
"""
Post cell-click step(human step and agent step)
Returns:
None
"""
cell = self.sender()
stop = self.modify_step(cell.location, self.x_value)
if stop:
return
x_win = self.check_win(self.x_value)
if x_win:
self.reset_game(self.x_value)
return
empty_locations = self.get_empty_cells()
if not empty_locations:
self.reset_game()
return
choice = np.random.choice(range(len(empty_locations)))
if self.agent:
choice = self.agent.generate_move(self.board, empty_locations)
self.modify_step(empty_locations[choice], self.o_value)
o_win = self.check_win(self.o_value)
if o_win:
self.reset_game(self.o_value)
def update_cell_values(self):
"""
Sync display cells with self.board
Returns:
None
"""
for row_index, row in enumerate(self.board):
for col_index, col in enumerate(row):
update_value = self.text_map[self.board[row_index][col_index]]
self.cells[row_index][col_index].setText(f'{update_value}')
if __name__ == '__main__':
test = QApplication(sys.argv)
window = TicUI()
sys.exit(test.exec_())
解决方案
在大多数 GUI 工具包中,您从未真正直接在屏幕上绘图。相反,正在发生的事情是您要么绘制到辅助缓冲区,要么只更新小部件的对象模型,然后当您将控制权交给工具包的主循环时,工具包会将其复制到实际屏幕。
如果要更新实际界面,则需要从回调方法返回。因此,如果您想更新屏幕并在几秒钟后再次更新,您需要在设置计时器后从第一个回调返回以进行回调以进行第二次屏幕更新。在 QT 中,您可以为此使用 QTimer。
推荐阅读
- css - 动态更新 scss 文件中的 css 属性(Angular 2+)
- python - 石头剪刀布python问题等中的得分功能
- javascript - 我想将 jquery 函数更改为纯 javascript 函数多选选项
- machine-learning - 有没有办法总结python中具有数字和表格的文本数据,无论是提取方式还是抽象方式?
- ios - Apple 使用指南 5.2.5 - 法律 - 知识产权拒绝我的应用程序,这是怎么回事?
- python - 在python中使用opencv进行流式传输的异步视频列表
- flutter - 输入'_InternalLinkedHashMap
' 不是类型转换中类型 'Topic' 的子类型 - dialogflow-es - 如何在 Google 对话流中实现“帐户登录”助手?得到一个“代理返回一个空的 TTS”
- php - 如何在不手动指定列的情况下以 HTML 显示所有 DB 列的记录?
- bash - 使用 Bash 脚本从两列中获取唯一的对称对