def has_any_valid_cell(self) -> bool: for row_index in range_from_one(board_size): for col_index in range_from_one(board_size): if self.is_valid_cell(row_index, col_index, self.current_step_cell()): return True return False
def has_any_valid_cell_for(self, cell_color) -> bool: for row_index in range_from_one(board_size): for col_index in range_from_one(board_size): if self.is_valid_cell(row_index, col_index, cell_color): return True return False
def get_valid_cells(self, current_step_cell) -> List[Tuple[int, int]]: valid_cells = [] for row_index in range_from_one(board_size): for col_index in range_from_one(board_size): if self.is_valid_cell(row_index, col_index, current_step_cell): valid_cells.append((row_index, col_index)) return valid_cells
def is_game_over(self) -> bool: for row_index in range_from_one(board_size): for col_index in range_from_one(board_size): cell = self.board[row_index][col_index] if cell == cell_empty: return False return True
def score(self, cell_color) -> int: score = 0 for row_index in range_from_one(board_size): for col_index in range_from_one(board_size): cell = self.board[row_index][col_index] if cell == cell_color: score += 1 return score
def weight_sum(self, ai_step_cell) -> int: weights = 0 for row_index in range_from_one(board_size): for col_index in range_from_one(board_size): if self.board[row_index][col_index] not in [cell_blue, cell_red]: continue weights += weight_map[row_index - 1][col_index - 1] * self.board[row_index][col_index] return ai_step_cell * weights
def is_game_over(self) -> bool: if not self.has_any_valid_cell_for(self.step_cell) and \ not self.has_any_valid_cell_for(self.other_step_cell(self.step_cell)): # 游戏已经结束 return True for row_index in range_from_one(board_size): for col_index in range_from_one(board_size): cell = self.board[row_index][col_index] if cell == cell_empty: return False return True
def init_invalid_cells_randomly(self): # 随机选择五个位置不可下棋 possiable_invalid_cells = list(filter(lambda v: not ( (v[0] == 4 and v[1] == 4) or (v[0] == 5 and v[1] == 5) or (v[0] == 4 and v[1] == 5) or (v[0] == 5 and v[1] == 4) ), [(row, col) for col in range_from_one(board_size) for row in range_from_one(board_size)])) for row, col in random.sample(possiable_invalid_cells, k=invalid_cell_count): self.board[row][col] = cell_invalid self.invalid_cell_count = self.invalid_cell_count + 1 self.paint()
def get_current_winner_info(self) -> Tuple[int, int, int]: # 数子 counter = Counter() for row_index in range_from_one(board_size): for col_index in range_from_one(board_size): cell = self.board[row_index][col_index] counter[cell] += 1 blue, red = counter[cell_blue], counter[cell_red] if blue > red: winner = cell_blue else: winner = cell_red return (blue, red, winner)
def show_game_result(self): # 数子 counter = Counter() for row_index in range_from_one(board_size): for col_index in range_from_one(board_size): cell = self.board[row_index][col_index] counter[cell] += 1 blue, red = counter[cell_blue], counter[cell_red] if blue > red: winner = self.cell_name(cell_blue) else: winner = self.cell_name(cell_red) logger.info(f"{self.cell_name(cell_blue)}={blue}") logger.info(f"{self.cell_name(cell_red)}={red}") logger.info(color("bold_yellow") + f"胜方为{winner}")
uploader.history_version_prefix), ])] logger.info(color("bold_green") + f"具体上传列表如下:") for upload_folder, upload_list in upload_info_list: logger.info(color("bold_cyan") + f"\t{upload_folder.name}:") for local_filepath, history_file_prefix in upload_list: logger.info(f"\t\t{local_filepath}") logger.info('\n') for upload_folder, upload_list in upload_info_list: for local_filepath, history_file_prefix in reversed(upload_list): # 逆序遍历,确保同一个网盘目录中,列在前面的最后才上传,从而在网盘显示时显示在最前方 total_try_count = 1 for try_index in range_from_one(total_try_count): upload_ok = uploader.upload_to_lanzouyun( local_filepath, upload_folder, history_file_prefix=history_file_prefix) if upload_ok: break logger.warning( f"第{try_index}/{total_try_count}次尝试上传{local_filepath}失败,等待一会后重试" ) if try_index < total_try_count: count_down("上传到网盘", 5 * try_index) else: logger.error("蓝奏云登录失败")
def release(): # ---------------准备工作 prompt = f"如需直接使用默认版本号:{now_version} 请直接按回车\n或手动输入版本号后按回车:" version = input(prompt) or now_version version_reg = r"\d+\.\d+\.\d+" if re.match(version_reg, version) is None: logger.info(f"版本号格式有误,正确的格式类似:1.0.0 ,而不是 {version}") pause_and_exit(-1) # 最大化窗口 change_console_window_mode_async(disable_min_console=True) version = "v" + version run_start_time = datetime.now() show_head_line(f"开始发布版本 {version}", color("bold_yellow")) set_title_cmd = f"title 发布 {version}" os.system(set_title_cmd) # 先声明一些需要用到的目录的地址 dir_src = os.path.realpath(".") dir_all_release = os.path.realpath(os.path.join("releases")) release_dir_name = f"DNF蚊子腿小助手_{version}_by风之凌殇" release_7z_name = f"{release_dir_name}.7z" dir_github_action_artifact = "_github_action_artifact" # ---------------构建 # 调用构建脚本 os.chdir(dir_src) build() # ---------------清除一些历史数据 make_sure_dir_exists(dir_all_release) os.chdir(dir_all_release) clear_github_artifact(dir_all_release, dir_github_action_artifact) # ---------------打包 os.chdir(dir_src) package(dir_src, dir_all_release, release_dir_name, release_7z_name, dir_github_action_artifact) # ---------------构建增量补丁 create_patch_for_latest_n_version = 3 # ---------------构建增量包 os.chdir(dir_all_release) show_head_line(f"开始构建增量包,最多包含过去{create_patch_for_latest_n_version}个版本到最新版本的补丁", color("bold_yellow")) create_patch(dir_src, dir_all_release, create_patch_for_latest_n_version, dir_github_action_artifact) # ---------------获取补丁地址(分开方便调试) os.chdir(dir_all_release) patch_file_name = create_patch( dir_src, dir_all_release, create_patch_for_latest_n_version, dir_github_action_artifact, get_final_patch_path_only=True, ) # ---------------标记新版本 show_head_line("提交版本和版本变更说明,并同步到docs目录,用于生成github pages", color("bold_yellow")) os.chdir(dir_src) commit_new_version() # ---------------上传到蓝奏云 show_head_line("开始上传到蓝奏云", color("bold_yellow")) os.chdir(dir_src) with open("upload_cookie.json") as fp: cookie = json.load(fp) os.chdir(dir_all_release) uploader = Uploader() uploader.login(cookie) if uploader.login_ok: logger.info("蓝奏云登录成功,开始上传压缩包") def path_in_src(filepath_relative_to_src: str) -> str: return os.path.realpath(os.path.join(dir_src, filepath_relative_to_src)) realpath = os.path.realpath upload_info_list = [ ( uploader.folder_djc_helper, [ (realpath(release_7z_name), uploader.history_version_prefix), (path_in_src("utils/auto_updater.exe"), ""), (path_in_src("使用教程/使用文档.docx"), ""), (path_in_src("使用教程/视频教程.txt"), ""), (path_in_src("付费指引/付费指引.docx"), ""), (path_in_src("utils/不要下载增量更新文件_这个是给自动更新工具使用的.txt"), ""), (realpath(patch_file_name), uploader.history_patches_prefix), ], ), ( uploader.folder_dnf_calc, [ (realpath(release_7z_name), uploader.history_version_prefix), ], ), ] logger.info(color("bold_green") + "具体上传列表如下:") for upload_folder, upload_list in upload_info_list: logger.info(color("bold_cyan") + f"\t{upload_folder.name}:") for local_filepath, _history_file_prefix in upload_list: logger.info(f"\t\t{local_filepath}") logger.info("\n") for upload_folder, upload_list in upload_info_list: for local_filepath, history_file_prefix in reversed(upload_list): # 逆序遍历,确保同一个网盘目录中,列在前面的最后才上传,从而在网盘显示时显示在最前方 total_try_count = 1 for try_index in range_from_one(total_try_count): upload_ok = uploader.upload_to_lanzouyun( local_filepath, upload_folder, history_file_prefix=history_file_prefix ) if upload_ok: break logger.warning(f"第{try_index}/{total_try_count}次尝试上传{local_filepath}失败,等待一会后重试") if try_index < total_try_count: count_down("上传到网盘", 5 * try_index) else: logger.error("蓝奏云登录失败") # ---------------推送版本到github # 打包完成后git添加标签 os.chdir(dir_src) show_head_line("开始推送到github", color("bold_yellow")) push_github(version) # ---------------结束 logger.info("+" * 40) logger.info(color("bold_yellow") + f"{version} 发布完成,共用时{datetime.now() - run_start_time},请检查上传至蓝奏云流程是否OK") logger.info("+" * 40) os.system("PAUSE")
def create_patch(dir_src, dir_all_release, create_patch_for_latest_n_version, dir_github_action_artifact, get_final_patch_path_only=False) -> str: latest_version = now_version path_bz = os.path.join(dir_src, "utils/bandizip_portable", "bz.exe") old_cwd = os.getcwd() os.chdir(dir_all_release) if not get_final_patch_path_only: logger.info(f"工作目录已调整为{os.getcwd()},最新版本为v{latest_version}") uploader = Uploader() if not get_final_patch_path_only: logger.info( f"尝试从网盘查找在{latest_version}版本之前最近{create_patch_for_latest_n_version}个版本的信息" ) old_version_infos = [] # type: List[HistoryVersionFileInfo] # 获取当前网盘的最新版本,若比当前发布版本低,也加入 netdisk_latest_version_fileinfo = uploader.find_latest_version() netdisk_latest_version = uploader.parse_version_from_djc_helper_file_name( netdisk_latest_version_fileinfo.name) if version_less(netdisk_latest_version, latest_version): old_version_infos.append( HistoryVersionFileInfo(netdisk_latest_version_fileinfo, netdisk_latest_version)) # 从历史版本网盘中查找旧版本 for page in range_from_one(100): folder_info = uploader.get_folder_info_by_url( uploader.folder_history_files.url, get_this_page=page) for file in folder_info.files: filename = file.name # type: str if not filename.startswith(uploader.history_version_prefix): # 跳过非历史版本的文件 continue file_version = uploader.parse_version_from_djc_helper_file_name( filename) info = HistoryVersionFileInfo(file, file_version) if not version_less(file_version, latest_version): continue if info in old_version_infos: # 已经加入过(可能重复) continue old_version_infos.append(info) if len(old_version_infos) >= create_patch_for_latest_n_version + 2: # 已经找到超过前n+2个版本,因为网盘返回的必定是按上传顺序排列的,不过为了保险起见,多考虑一些 break if create_patch_for_latest_n_version > len(old_version_infos): create_patch_for_latest_n_version = len(old_version_infos) old_version_infos = sorted( old_version_infos)[-create_patch_for_latest_n_version:] # 确认最终文件名 patch_oldest_version = old_version_infos[0].version patch_newest_version = old_version_infos[-1].version patches_dir = f"DNF蚊子腿小助手_增量更新文件_v{patch_oldest_version}_to_v{patch_newest_version}" temp_dir = "patches_temp" patch_7z_file = f"{patches_dir}.7z" if get_final_patch_path_only: return patch_7z_file logger.info(f"需要制作补丁包的版本为{old_version_infos}") # 确保版本都在本地 logger.info(f"确保以上版本均已下载并解压到本地~") for info in old_version_infos: local_folder_path = os.path.join(dir_all_release, f"DNF蚊子腿小助手_v{info.version}_by风之凌殇") local_7z_path = local_folder_path + ".7z" if os.path.isdir(local_folder_path): # 本地已存在对应版本,跳过 continue logger.info(f"本地发布目录不存在 {local_folder_path}") if not os.path.isfile(local_7z_path): logger.info(f"本地不存在{info.fileinfo.name}的7z文件,将从网盘下载") uploader.download_file(info.fileinfo, dir_all_release) logger.info(f"尝试解压 {info.fileinfo.name} 到 {local_folder_path}") decompress_dir_with_bandizip(local_7z_path, dir_src) # --------------------------- 实际只做补丁包 --------------------------- logger.info(color("bold_yellow") + f"将为【{old_version_infos}】版本制作补丁包") shutil.rmtree(patches_dir, ignore_errors=True) os.mkdir(patches_dir) shutil.rmtree(temp_dir, ignore_errors=True) os.mkdir(temp_dir) def temp_path(dir_name): return os.path.realpath(os.path.join(temp_dir, dir_name)) def preprocess_before_patch(temp_version_path): for filename in ["config.toml", "utils/auto_updater.exe"]: filepath = os.path.join(temp_version_path, filename) if os.path.isfile(filepath): os.remove(filepath) # 为旧版本创建patch文件 target_version_dir = f"DNF蚊子腿小助手_v{latest_version}_by风之凌殇" logger.info(f"目标版本目录为{target_version_dir}") shutil.copytree(target_version_dir, temp_path(target_version_dir)) preprocess_before_patch(temp_path(target_version_dir)) for idx, version_info in enumerate(old_version_infos): version = version_info.version patch_file = f"{patches_dir}/{version}.patch" logger.info("-" * 80) logger.info( color("bold_yellow") + f"[{idx + 1}/{len(old_version_infos)}] 创建从v{version}升级到v{latest_version}的补丁{patch_file}" ) version_dir = f"DNF蚊子腿小助手_v{version}_by风之凌殇" shutil.copytree(version_dir, temp_path(version_dir)) preprocess_before_patch(temp_path(version_dir)) subprocess.call([ os.path.realpath(os.path.join(dir_src, "utils/hdiffz.exe")), f"-p-{multiprocessing.cpu_count()}", # 设置系统最大cpu数 os.path.realpath(os.path.join(temp_dir, version_dir)), os.path.realpath(os.path.join(temp_dir, target_version_dir)), patch_file, ]) filesize = os.path.getsize(patch_file) logger.info(f"创建补丁{patch_file}结束,最终大小为{human_readable_size(filesize)}") # 移除临时目录 shutil.rmtree(temp_dir, ignore_errors=True) # 压缩打包 compress_dir_with_bandizip(patches_dir, patch_7z_file, dir_src) # 额外备份一份最新的供github action 使用 shutil.copyfile( patch_7z_file, os.path.join(dir_github_action_artifact, 'djc_helper_patches.7z')) os.chdir(old_cwd) return patch_7z_file
def stable_score(self, current_step_cell) -> int: # 一些辅助函数 def add(cell_position, direction): return tuple(v + delta for v, delta in zip(cell_position, direction)) def reverse(direction): return tuple(-delta for delta in direction) def continuously_nonempty_cell_count(first_cell_position, direction, max_count) -> int: not_empty = 0 current_position = first_cell_position for i in range(7): cell = get_cell(current_position) if cell == cell_empty: break not_empty += 1 current_position = add(current_position, direction) return not_empty def get_cell(cell_position) -> int: row, col = cell_position return self.board[row][col] # 返回各自所属的 左上到右下的对角线(1-15),左下到右上的对角线(1-15) def get_diagonal(row, col) -> Tuple[int, int]: upper_diagonal = col - row + 8 lower_diagonal = col + row - 1 return (upper_diagonal, lower_diagonal) # 角、边、其他(八个方向都无空位) 之差 # note: 与参考文献不同,自己和对方取差值,而不是相加,因为对方越多稳定子,对自己不利 corner, edge, other = 0, 0, 0 # 计算角 corner_cell_positions = [ (1, 1), (1, 8), (8, 1), (8, 8), ] for row, col in corner_cell_positions: cell = self.board[row][col] if cell in [cell_blue, cell_red]: corner += current_step_cell * cell # 计算边 edge_cell_positions = [ ((0, 1), [(1, col) for col in range(2, 7 + 1)]), # 上 ((0, 1), [(8, col) for col in range(2, 7 + 1)]), # 下 ((1, 0), [(row, 1) for row in range(2, 7 + 1)]), # 左 ((1, 0), [(row, 8) for row in range(2, 7 + 1)]), # 右 ] for direction, cell_positions in edge_cell_positions: # 计算两个边界格子 lower = add(cell_positions[0], reverse(direction)) upper = add(cell_positions[-1], direction) # 计算lower->upper方向格子连续非空的数目 lu = continuously_nonempty_cell_count(lower, direction, 7) # 计算upper->lower方向格子连续非空的数目 ul = continuously_nonempty_cell_count(upper, reverse(direction), 7) # 计算本边上与边界间连续无空格的位置数目 for idx, _position in enumerate(cell_positions): cell = self.board[_position[0]][_position[1]] if cell not in [cell_blue, cell_red]: continue index = 2 + idx if (index <= lu and get_cell(lower) != cell_empty) or \ (index >= board_size - ul + 1 and get_cell(upper) != cell_empty): edge += current_step_cell * cell # 计算其他位置(八个方向都无空位) # 预计算 # 非空行 not_empty_rows = set(row for row in range_from_one(8)) # 非空列 not_empty_cols = set(col for col in range_from_one(8)) # 非空的左上到右下方向的对角线 not_empty_upper_diagonal = set(dia for dia in range_from_one(15)) # 非空的左下到右上方向的对角线 not_empty_lower_diagonal = set(dia for dia in range_from_one(15)) for row in range_from_one(board_size): for col in range_from_one(board_size): cell = self.board[row][col] if cell != cell_empty: continue # 标记所在行列和两个对角线为非空 upper_diagonal, lower_diagonal = get_diagonal(row, col) not_empty_rows.discard(row) not_empty_cols.discard(col) not_empty_upper_diagonal.discard(upper_diagonal) not_empty_lower_diagonal.discard(lower_diagonal) # 实际计算出非边角位置的八方向都无空格的格子 for row in range(2, 7 + 1): for col in range(2, 7 + 1): cell = self.board[row][col] if cell not in [cell_blue, cell_red]: continue upper_diagonal, lower_diagonal = get_diagonal(row, col) if row in not_empty_rows and \ col in not_empty_cols and \ upper_diagonal in not_empty_upper_diagonal and \ lower_diagonal in not_empty_lower_diagonal: other += cell * current_step_cell return corner + edge + other
def init_ui(self): width = 800 height = 580 self.setFixedSize(width, height) # 设置棋盘背景 oBackGroundImage = QImage("reversi_images/board.png") sBackGroundImage = oBackGroundImage.scaled(QSize( width, height)) # resize Image to widgets size palette = QPalette() palette.setBrush(QPalette.Window, QBrush(sBackGroundImage)) self.setPalette(palette) # 初始化棋盘元素 self.label_count_down = QLabel('', self) self.label_count_down.setStyleSheet( f"color: orange; font-size: 30px; font-weight: bold; font-family: Microsoft YaHei" ) self.label_count_down.setGeometry(350, 0, 500, 60) self.label_turn = QLabel('蓝方回合', self) self.label_turn.setStyleSheet( f"color: blue; font-size: 24px; font-weight: bold; font-family: Microsoft YaHei" ) self.label_turn.setGeometry(320, 60, 500, 40) self.label_blue_name = QLabel('蓝方-AI托管', self) self.label_blue_name.setStyleSheet( f"color: gray; font-size: 18px; font-weight: bold; font-family: Microsoft YaHei" ) self.label_blue_name.setGeometry(150, 40, 180, 20) self.label_blue_score = QLabel('2', self) self.label_blue_score.setStyleSheet( f"color: yellow; font-size: 24px; font-weight: bold; font-family: Microsoft YaHei" ) self.label_blue_score.setGeometry(180, 60, 120, 30) self.label_red_name = QLabel('大师南瓜球', self) self.label_red_name.setStyleSheet( f"color: gray; font-size: 18px; font-weight: bold; font-family: Microsoft YaHei" ) self.label_red_name.setGeometry(520, 40, 180, 20) self.label_red_score = QLabel('2', self) self.label_red_score.setStyleSheet( f"color: yellow; font-size: 24px; font-weight: bold; font-family: Microsoft YaHei" ) self.label_red_score.setGeometry(570, 60, 120, 30) self.btn_manunal_bye = QPushButton('手动轮空', self) self.btn_manunal_bye.setStyleSheet( f"color: #cf8160; font-size: 18px; font-weight: bold; font-family: Microsoft YaHei; background-color: #89090a" ) self.btn_manunal_bye.setGeometry(685, 460, 80, 30) self.btn_manunal_bye.clicked.connect(self.manunal_bye) self.btn_restart = QPushButton('重新开始', self) self.btn_restart.setStyleSheet( f"color: #cf8160; font-size: 18px; font-weight: bold; font-family: Microsoft YaHei; background-color: #89090a" ) self.btn_restart.setGeometry(685, 505, 80, 30) self.btn_restart.clicked.connect(self.restart) # 180 120 # 445 -> 480 (row 1 -> 8 top ) mid_top_x, mid_top_y = 400, 120 self.btn_list_board = [] self.qicon_blue = QIcon(QPixmap("reversi_images/blue.png")) self.qicon_red = QIcon(QPixmap("reversi_images/red.png")) self.qicon_empty = QIcon() self.qicon_next_step = QIcon(QPixmap("reversi_images/next_step.png")) self.qicon_invalid = QIcon(QPixmap("reversi_images/invalid.png")) self.qicon_current_blue = QIcon( QPixmap("reversi_images/current_blue.png")) self.qicon_current_red = QIcon( QPixmap("reversi_images/current_red.png")) for row_index in range_from_one(board_size): label_row = [] row_width = 445 + int((480 - 445) / 7 * (row_index - 1)) for col_index in range_from_one(board_size): cell = self.board[row_index][col_index] x, y = mid_top_x - row_width // 2 + row_width // 8 * ( col_index - 1), mid_top_y + 47 * (row_index - 1) btn = QPushButton(self) btn.setIconSize(QSize(60, 50)) btn.setGeometry(x, y, row_width // 8, 50) btn.setStyleSheet( "QPushButton { background-color: transparent; border: 0px }" ) def cb(ri, ci): def _cb(): logger.debug(f"clicked row={ri}, col={ci}") # 初始化无效格子 if self.invalid_cell_count < invalid_cell_count: if self.board[ri][ci] != cell_empty: logger.info("该格子不为空,不能设置为无效格子") return self.board[ri][ci] = cell_invalid self.invalid_cell_count = self.invalid_cell_count + 1 logger.info(f"设置第{self.invalid_cell_count}个无效位置") self.paint() if self.invalid_cell_count == invalid_cell_count: # 记录点击次数,到达五个按钮时进入正式游戏模式(尝试ai点击)并隐藏提示按钮 self.ai_try_put_cell() return if self.current_step_cell( ) in self.ai_cells and not self.ai_moving: logger.info("当前回合由机器人托管,将无视该点击") return self.ai_moving = False # 判断是否可行 if self.is_game_over(): self.game_over() return if not self.has_any_valid_cell(): logger.info("本轮无任何可行落子,将轮空") self.next_turn() self.loop_index += 1 if not self.has_any_valid_cell(): logger.info("双方均不可再落子,游戏结束") self.game_over() return # 记录下当前方 current_step_cell = self.current_step_cell() # 落子 is_valid = self.put_cell(ri, ci) is not None if is_valid: self.loop_index += 1 # 计算落子后当前方局面分 current_score = self.evaluate(current_step_cell) if current_score >= 0: cr = "bold_red" else: cr = "bold_green" logger.info( color(cr) + f"落子后当前{self.cell_name_without_color(current_step_cell)}局面分为{current_score}" ) # 重绘界面 self.paint() # 若轮到机器人 self.ai_try_put_cell() return _cb btn.clicked.connect(cb(row_index, col_index)) label_row.append(btn) self.btn_list_board.append(label_row) self.paint() self.show()
def paint(self, show_cui_detail=False, game_overd=False): logger.info('-' * 20) blue_score = self.with_color(f"蓝方:{self.score(cell_blue)}", "blue") red_score = self.with_color(f"红方:{self.score(cell_red)}", "red") logger.info(f"{blue_score}\t{red_score}") if show_cui_detail: logger.info(' '.join( [' ', *[str(col_idx + 1) for col_idx in range(board_size)]])) for row_index in range_from_one(board_size): state = [f'{chr(ord("a") + row_index - 1)} '] for col_index in range_from_one(board_size): cell = self.board[row_index][col_index] if cell in [cell_blue, cell_red]: if cell == cell_blue: val, show_color = 'B', 'blue' else: val, show_color = 'R', 'red' if row_index == self.last_step[ 0] and col_index == self.last_step[1]: show_color = "bold_purple" state.append(self.with_color(val, show_color)) elif cell == cell_empty: if self.is_valid_cell(row_index, col_index, self.current_step_cell()): current_color = 'blue' if self.current_step_cell() == cell_red: current_color = 'red' state.append(self.with_color('*', current_color)) else: state.append(' ') elif cell == cell_invalid: state.append(self.with_color('X', 'bold_white')) state.append('') logger.info('|'.join(state)) if not self.has_any_valid_cell(): logger.info("本轮无任何可行落子,将轮空") # gui # 绘制格子 for row_index in range_from_one(board_size): for col_index in range_from_one(board_size): cell = self.board[row_index][col_index] btn = self.btn_list_board[row_index - 1][col_index - 1] ico = None if cell in [cell_blue, cell_red]: if row_index == self.last_step[ 0] and col_index == self.last_step[1]: if cell == cell_blue: ico = self.qicon_current_blue else: ico = self.qicon_current_red else: if cell == cell_blue: ico = self.qicon_blue else: ico = self.qicon_red elif cell == cell_empty: if self.is_valid_cell(row_index, col_index, self.current_step_cell()): ico = self.qicon_next_step else: ico = self.qicon_empty else: # cell_invalid ico = self.qicon_invalid btn.setIcon(ico) # 绘制其他界面元素 if self.invalid_cell_count < invalid_cell_count: self.label_turn.setText( f"请继续点击{invalid_cell_count - self.invalid_cell_count}个格子,设置为无效格子" ) self.label_turn.setStyleSheet( f"color: cyan; font-size: 24px; font-weight: bold; font-family: Microsoft YaHei" ) else: turn_name = "" if self.current_step_cell() == cell_blue: turn_name = "蓝方回合" self.label_turn.setStyleSheet( f"color: blue; font-size: 24px; font-weight: bold; font-family: Microsoft YaHei" ) else: turn_name = "红方回合" self.label_turn.setStyleSheet( f"color: red; font-size: 24px; font-weight: bold; font-family: Microsoft YaHei" ) if self.current_step_cell() in self.ai_cells: turn_name += "-AI托管" self.label_turn.setText(f"{self.loop_index}-{turn_name}") if not self.has_any_valid_cell(): logger.info("本轮无任何可行落子,将轮空") if len(self.ai_cells) < 2: self.notify( self.cell_name(self.current_step_cell(), with_color=False) + '轮空,请点击任意位置结束本轮') blue_evaluted_score = self.evaluate(cell_blue, ignore_game_over=game_overd) red_evaluted_score = -blue_evaluted_score self.label_blue_score.setText( f"{self.score(cell_blue)}({blue_evaluted_score})") self.label_red_score.setText( f"{self.score(cell_red)}({red_evaluted_score})") self.update()