目录
程序源代码
程序源代码
import sysimport pygame# 要显示的窗口的宽、高WIDTH, HEIGHT = 750, 667class ClickBox(pygame.sprite.Sprite):"""选中棋子对象"""singleton = Nonedef __new__(cls, *args, **kwargs):if cls.singleton is None:cls.singleton = super().__new__(cls)return cls.singletondef __init__(self, screen, row, col, team):super().__init__()self.image = pygame.image.load("images/r_box.png")self.rect = self.image.get_rect()self.row, self.col = row, colself.rect.topleft = (50 + self.col * 57, 50 + self.row * 57)self.screen = screenself.team = team@classmethoddef show(cls):if cls.singleton:cls.singleton.screen.blit(cls.singleton.image, cls.singleton.rect)@classmethoddef clean(cls):"""清理上次的对象"""cls.singleton = Noneclass Dot(pygame.sprite.Sprite):"""可落棋子类"""group = list()def __init__(self, screen, position):super().__init__()self.image = pygame.image.load("images/dot2.png")self.rect = self.image.get_rect()self.row, self.col = position # 将元组拆包self.rect.topleft = (60 + self.col * 57, 60 + self.row * 57)self.group.append(self)self.screen = screen@classmethoddef show(cls):for dot in cls.group:dot.screen.blit(dot.image, dot.rect)@classmethoddef clean_last_postion(cls):"""清除上次落子位置"""cls.group.clear()@classmethoddef click(cls):"""点击棋子"""for dot in cls.group:if pygame.mouse.get_pressed()[0] and dot.rect.collidepoint(pygame.mouse.get_pos()):print("被点击了「可落子」对象")return dotclass Chess(pygame.sprite.Sprite):"""棋子类"""def __init__(self, screen, chess_name, row, col):self.screen = screenself.image = pygame.image.load("images/" + chess_name + ".png")self.rect = self.image.get_rect()self.rect.topleft = (50 + col * 57, 50 + row * 57)self.team = chess_name[0] # 队伍(红方 r、黑方b)self.name = chess_name[2] # 名字(炮p、马m等)self.row = rowself.col = coldef show(self):self.screen.blit(self.image, self.rect)@staticmethoddef click(player, chesses):"""点击棋子"""for chess in chesses:if pygame.mouse.get_pressed()[0] and chess.rect.collidepoint(pygame.mouse.get_pos()):if player == chess.team:print("被点击了")return chessdef update_postion(self, new_row, new_col):"""更新要显示的图片的坐标"""self.row = new_rowself.col = new_colself.rect.topleft = (50 + new_col * 57, 50 + new_row * 57)class ChessBoard(object):"""棋盘类"""def __init__(self, screen):self.screen = screenself.image = pygame.image.load("images/bg.png")self.topleft = (50, 50)self.__create_default_chess()def __create_default_chess(self):"""创建默认棋子"""self.map = [["b_c", "b_m", "b_x", "b_s", "b_j", "b_s", "b_x", "b_m", "b_c"],["", "", "", "", "", "", "", "", ""],["", "b_p", "", "", "", "", "", "b_p", ""],["b_z", "", "b_z", "", "b_z", "", "b_z", "", "b_z"],["", "", "", "", "", "", "", "", ""],["", "", "", "", "", "", "", "", ""],["r_z", "", "r_z", "", "r_z", "", "r_z", "", "r_z"],["", "r_p", "", "", "", "", "", "r_p", ""],["", "", "", "", "", "", "", "", ""],["r_c", "r_m", "r_x", "r_s", "r_j", "r_s", "r_x", "r_m", "r_c"],]for row, line in enumerate(self.map):for col, chess_name in enumerate(line):if chess_name:# 将创建的棋子添加到属性map中self.map[row][col] = Chess(self.screen, chess_name, row, col)else:self.map[row][col] = Nonedef show(self):# 显示棋盘self.screen.blit(self.image, self.topleft)# 显示棋盘上的所有棋子for line_chess in self.map:for chess in line_chess:if chess:chess.show()def get_put_down_postion(self, clicked_chess):"""计算当前棋子可以移动的位置"""# 存储当前棋子可以落子的位置all_position = list()# 拿到当前棋子的行、列row, col = clicked_chess.row, clicked_chess.col# 拿到当前棋子的team,即时红方r还是黑方bteam = clicked_chess.team# 计算当前选中棋子的所有可以落子位置if clicked_chess.name == "p": # 炮# 一行direction_left_chess_num = 0direction_right_chess_num = 0for i in range(1, 9):# 计算当前行中,棋子左边与右边可以落子的位置# 左边位置没有越界if direction_left_chess_num >= 0 and col - i >= 0:if not self.map[row][col - i] and direction_left_chess_num == 0:# 如果没有棋子,则将当前位置组成一个元组,添加到列表all_position.append((row, col - i))elif self.map[row][col - i]:# 如果当前位置有棋子,那么就判断是否能够吃掉它direction_left_chess_num += 1if direction_left_chess_num == 2 and self.map[row][col - i].team != team:all_position.append((row, col - i))direction_left_chess_num = -1 # 让其不能够在下次for循环时再次判断# 右边位置没有越界if direction_right_chess_num >= 0 and col + i <= 8:if not self.map[row][col + i] and direction_right_chess_num == 0:# 如果没有棋子,则将当前位置组成一个元组,添加到列表all_position.append((row, col + i))elif self.map[row][col + i]:# 如果当前位置有棋子,那么就判断是否能够吃掉它direction_right_chess_num += 1if direction_right_chess_num == 2 and self.map[row][col + i].team != team:all_position.append((row, col + i))direction_right_chess_num = -1# 一列direction_up_chess_num = 0direction_down_chess_num = 0for i in range(1, 10): # 这样就让i从1开始,而不是从0# 计算当前列中,棋子上边与下边可以落子的位置# 上边位置没有越界if direction_up_chess_num >= 0 and row - i >= 0:if not self.map[row - i][col] and direction_up_chess_num == 0:# 如果没有棋子,则将当前位置组成一个元组,添加到列表all_position.append((row - i, col))elif self.map[row - i][col]:# 如果当前位置有棋子,那么就判断是否能够吃掉它direction_up_chess_num += 1if direction_up_chess_num == 2 and self.map[row - i][col].team != team:all_position.append((row - i, col))direction_up_chess_num = -1# 下边位置没有越界if direction_down_chess_num >= 0 and row + i <= 9:if not self.map[row + i][col] and direction_down_chess_num == 0:# 如果没有棋子,则将当前位置组成一个元组,添加到列表all_position.append((row + i, col))elif self.map[row + i][col]:# 如果当前位置有棋子,那么就判断是否能够吃掉它direction_down_chess_num += 1if direction_down_chess_num == 2 and self.map[row + i][col].team != team:all_position.append((row + i, col))direction_down_chess_num = -1elif clicked_chess.name == "z": # 卒if team == "r": # 红方if row - 1 >= 0: # 只能向上移动if not self.map[row - 1][col] or self.map[row - 1][col].team != team:all_position.append((row - 1, col))else: # 黑方if row + 1 <= 9: # 只能向下移动if not self.map[row + 1][col] or self.map[row + 1][col].team != team:all_position.append((row + 1, col))# 左右判断if (team == "r" and 0 <= row <= 4) or (team == "b" and 5 <= row <= 9): # 左、右一步# 左if col - 1 >= 0 and (not self.map[row][col - 1] or self.map[row][col - 1].team != team):all_position.append((row, col - 1))# 右if col + 1 <= 8 and (not self.map[row][col + 1] or self.map[row][col + 1].team != team):all_position.append((row, col + 1))elif clicked_chess.name == "c": # 车# 一行left_stop = Falseright_stop = Falsefor i in range(1, 9):# 左边位置没有越界且没有遇到任何一个棋子if not left_stop and col - i >= 0:if not self.map[row][col - i]:# 如果没有棋子,则将当前位置组成一个元组,添加到列表all_position.append((row, col - i))else:left_stop = Trueif self.map[row][col - i].team != team:# 如果当前位置有棋子,那么就判断是否能够吃掉它all_position.append((row, col - i))# 右边位置没有越界且没有遇到任何一个棋子if not right_stop and col + i <= 8:if not self.map[row][col + i]:# 如果没有棋子,则将当前位置组成一个元组,添加到列表all_position.append((row, col + i))else:right_stop = Trueif self.map[row][col + i].team != team:# 如果当前位置有棋子,那么就判断是否能够吃掉它all_position.append((row, col + i))# 一列up_stop = Falsedown_stoop = Falsefor i in range(1, 10):# 上边位置没有越界且没有遇到任何一个棋子if not up_stop and row - i >= 0:if not self.map[row - i][col]:# 如果没有棋子,则将当前位置组成一个元组,添加到列表all_position.append((row - i, col))else:up_stop = Trueif self.map[row - i][col].team != team:# 如果当前位置有棋子,那么就判断是否能够吃掉它all_position.append((row - i, col))# 下边位置没有越界且没有遇到任何一个棋子if not down_stoop and row + i <= 9:if not self.map[row + i][col]:# 如果没有棋子,则将当前位置组成一个元组,添加到列表all_position.append((row + i, col))else:down_stoop = Trueif self.map[row + i][col].team != team:# 如果当前位置有棋子,那么就判断是否能够吃掉它all_position.append((row + i, col))elif clicked_chess.name == "m": # 马# 需要判断的是4个方向,每个方向对应2个位置# 上方if row - 1 >= 0 and not self.map[row - 1][col]: # 如果当前棋子没有被蹩马腿,那么再对这个方向的2个位置进行判断# 左上if row - 2 >= 0 and col - 1 >= 0 and (not self.map[row - 2][col - 1] or self.map[row - 2][col - 1].team != team):all_position.append((row - 2, col - 1))# 右上if row - 2 >= 0 and col + 1 <= 8 and (not self.map[row - 2][col + 1] or self.map[row - 2][col + 1].team != team):all_position.append((row - 2, col + 1))# 下方if row + 1 <= 9 and not self.map[row + 1][col]: # 如果当前棋子没有被蹩马腿,那么再对这个方向的2个位置进行判断# 左下if row + 2 >= 0 and col - 1 >= 0 and (not self.map[row + 2][col - 1] or self.map[row + 2][col - 1].team != team):all_position.append((row + 2, col - 1))# 右下if row + 2 >= 0 and col + 1 <= 8 and (not self.map[row + 2][col + 1] or self.map[row + 2][col + 1].team != team):all_position.append((row + 2, col + 1))# 左方if col - 1 >= 0 and not self.map[row][col - 1]: # 如果当前棋子没有被蹩马腿,那么再对这个方向的2个位置进行判断# 左上2(因为有左上了,暂且称为左上2吧)if row - 1 >= 0 and col - 2 >= 0 and (not self.map[row - 1][col - 2] or self.map[row - 1][col - 2].team != team):all_position.append((row - 1, col - 2))# 左下2if row + 1 <= 9 and col - 2 >= 0 and (not self.map[row + 1][col - 2] or self.map[row + 1][col - 2].team != team):all_position.append((row + 1, col - 2))# 右方if col + 1 <= 8 and not self.map[row][col + 1]: # 如果当前棋子没有被蹩马腿,那么再对这个方向的2个位置进行判断# 右上2(因为有右上了,暂且称为右上2吧)if row - 1 >= 0 and col + 2 <= 8 and (not self.map[row - 1][col + 2] or self.map[row - 1][col + 2].team != team):all_position.append((row - 1, col + 2))# 右下2if row + 1 <= 9 and col + 2 <= 8 and (not self.map[row + 1][col + 2] or self.map[row + 1][col + 2].team != team):all_position.append((row + 1, col + 2))elif clicked_chess.name == "x": # 象# 因为象是不能过河的,所以要计算出它们可以移动的行的范围row_start, row_stop = (0, 4) if team == "b" else (5, 9)# 有4个方向的判断(没有越界,且没有蹩象腿)if row - 2 >= row_start and col - 2 >= 0 and not self.map[row - 1][col - 1]: # 左上if not self.map[row - 2][col - 2] or self.map[row - 2][col - 2].team != team:all_position.append((row - 2, col - 2))if row - 2 >= row_start and col + 2 <= 8 and not self.map[row - 1][col + 1]: # 右上if not self.map[row - 2][col + 2] or self.map[row - 2][col + 2].team != team:all_position.append((row - 2, col + 2))if row + 2 <= row_stop and col - 2 >= 0 and not self.map[row + 1][col - 1]: # 左下if not self.map[row + 2][col - 2] or self.map[row + 2][col - 2].team != team:all_position.append((row + 2, col - 2))if row + 2 <= row_stop and col + 2 <= 8 and not self.map[row + 1][col + 1]: # 右下if not self.map[row + 2][col + 2] or self.map[row + 2][col + 2].team != team:all_position.append((row + 2, col + 2))elif clicked_chess.name == "s": # 士# 因为士是不能过河的,所以要计算出它们可以移动的行的范围row_start, row_stop = (0, 2) if team == "b" else (7, 9)if row - 1 >= row_start and col - 1 >= 3 and (not self.map[row - 1][col - 1] or self.map[row - 1][col - 1].team != team):all_position.append((row - 1, col - 1))if row - 1 >= row_start and col + 1 <= 5 and (not self.map[row - 1][col + 1] or self.map[row - 1][col + 1].team != team):all_position.append((row - 1, col + 1))if row + 1 <= row_stop and col - 1 >= 3 and (not self.map[row + 1][col - 1] or self.map[row + 1][col - 1].team != team):all_position.append((row + 1, col - 1))if row + 1 <= row_stop and col + 1 <= 5 and (not self.map[row + 1][col + 1] or self.map[row + 1][col + 1].team != team):all_position.append((row + 1, col + 1))elif clicked_chess.name == "j": # 将# 因为"将"是不能过河的,所以要计算出它们可以移动的行的范围row_start, row_stop = (0, 2) if team == "b" else (7, 9)# 有4个方向的判断if row - 1 >= row_start and (not self.map[row - 1][col] or self.map[row - 1][col].team != team):all_position.append((row - 1, col))if row + 1 <= row_stop and (not self.map[row + 1][col] or self.map[row + 1][col].team != team):all_position.append((row + 1, col))if col - 1 >= 3 and (not self.map[row][col - 1] or self.map[row][col - 1].team != team):all_position.append((row, col - 1))if col + 1 <= 5 and (not self.map[row][col + 1] or self.map[row][col + 1].team != team):all_position.append((row, col + 1))all_position = self.judge_delete_position(all_position, clicked_chess)# 返回可以落子的所有位置return all_positiondef judge_delete_position(self, all_position, clicked_chess):"""删除被"将军"的位置"""# 定义要删除的列表deleting_position = list()# 判断这些位置,是否会导致被"将军",如果是则从列表中删除这个位置for row, col in all_position:# 1. 备份# 备份当前棋子位置old_row, old_col = clicked_chess.row, clicked_chess.col# 备份要落子的位置的棋子(如果没有,则为None)position_chess_backup = self.map[row][col]# 2. 挪动位置# 移动位置self.map[row][col] = self.map[old_row][old_col]# 修改棋子的属性self.map[row][col].update_postion(row, col)# 清楚之前位置为Noneself.map[old_row][old_col] = None# 3. 判断对方是否可以发起"将军"if self.judge_attack_general("b" if clicked_chess.team == "r" else "r"):deleting_position.append((row, col))# 4. 恢复到之前位置self.map[old_row][old_col] = self.map[row][col]self.map[old_row][old_col].update_postion(old_row, old_col)self.map[row][col] = position_chess_backup# 5. 删除不能落子的位置all_position = list(set(all_position) - set(deleting_position))return all_positiondef move_chess(self, new_row, new_col):"""落子"""# 得到要移动的棋子的位置old_row, old_col = ClickBox.singleton.row, ClickBox.singleton.colprint("旧位置:", old_row, old_col, "新位置:", new_row, new_col)# 移动位置self.map[new_row][new_col] = self.map[old_row][old_col]# 修改棋子的属性self.map[new_row][new_col].update_postion(new_row, new_col)# 清楚之前位置为Noneself.map[old_row][old_col] = Nonedef judge_attack_general(self, attact_player):"""判断 attact_player方是否 将对方的军"""# 1. 找到对方"将"的位置general_player = "r" if attact_player == "b" else "b"general_position = self.get_general_position(general_player)# 2. 遍历我方所有的棋子for row, line in enumerate(self.map):for col, chess in enumerate(line):if chess and chess.team == attact_player:if chess.name == "z": # 兵# 传递5个参数(攻击方的标识,攻击方row,攻击方col,对方将row,对方将col)if self.judge_z_attack(chess.team, chess.row, chess.col, *general_position):return Trueelif chess.name == "p": # 炮if self.judge_c_and_p_attack(chess.name, chess.row, chess.col, *general_position):return Trueelif chess.name == "c": # 车if self.judge_c_and_p_attack(chess.name, chess.row, chess.col, *general_position):return Trueelif chess.name == "m": # 马if self.judge_m_attack(chess.row, chess.col, *general_position):return Trueelif chess.name == "x": # 象passelif chess.name == "s": # 士passelif chess.name == "j": # 将if self.judge_j_attack(chess.row, chess.col, *general_position):return Truedef judge_j_attack(self, attack_row, attack_col, general_row, general_col):"""判断 两个将是否相对"""if attack_col == general_col:# 在同一列min_row, max_row = (attack_row, general_row) if attack_row < general_row else (general_row, attack_row)chess_num = 0for i in range(min_row + 1, max_row):if self.map[i][general_col]:chess_num += 1if chess_num == 0:return Truedef judge_m_attack(self, attack_row, attack_col, general_row, general_col):"""判断马是否攻击到"将""""if attack_row == general_row or attack_col == general_col:return Falseelse:# "马走日",利用这个特点会得出,如果此马能够攻击到"将",那么两条边的平方和一定是5col_length = (attack_col - general_col) ** 2row_length = (attack_row - general_row) ** 2if col_length + row_length == 5:# 判断是否蹩马腿if col_length == 1:if general_row < attack_row and not self.map[attack_row - 1][attack_col]:return Trueelif general_row > attack_row and not self.map[attack_row + 1][attack_col]:return Trueelif col_length == 4:if general_col < attack_col and not self.map[attack_row][attack_col - 1]:return Trueelif general_col > attack_col and not self.map[attack_row][attack_col + 1]:return Truedef judge_c_and_p_attack(self, attack_chess_name, attack_row, attack_col, general_row, general_col):"""判断"车"、"炮"能否攻击到对方"将""""check_chess_num = 1 if attack_chess_name == "p" else 0chess_num = 0if attack_row == general_row:# 在同一行min_col, max_col = (attack_col, general_col) if attack_col < general_col else (general_col, attack_col)for i in range(min_col + 1, max_col):if self.map[attack_row][i]:chess_num += 1if chess_num == check_chess_num:return Trueelif attack_col == general_col:# 在同一列min_row, max_row = (attack_row, general_row) if attack_row < general_row else (general_row, attack_row)for i in range(min_row + 1, max_row):if self.map[i][general_col]:chess_num += 1if chess_num == check_chess_num:return Truedef judge_z_attack(self, attack_team, attack_row, attack_col, general_row, general_col):"""判断卒是否攻击到"将""""if attack_team == "r" and attack_row < general_row:return Falseelif attack_team == "b" and attack_row > general_row:return Falseelif (attack_row - general_row) ** 2 + (attack_col - general_col) ** 2 == 1:return Truedef get_general_position(self, general_player):"""找到general_player标记的一方的将的位置"""for row, line in enumerate(self.map):for col, chess in enumerate(line):if chess and chess.team == general_player and chess.name == "j":return chess.row, chess.coldef judge_win(self, attack_player):"""判断是否获胜"""# 依次判断是否被攻击方的所有棋子,是否有阻挡攻击的可能for line_chesses in self.map:for chess in line_chesses:if chess and chess.team != attack_player:move_position_list = self.get_put_down_postion(chess)if move_position_list: # 只要找到一个可以移动的位置,就表示没有失败,还是有机会的return Falsereturn Trueclass Game(object):"""游戏类"""def __init__(self, screen):self.screen = screenself.player = "r" # 默认走棋的为红方rself.player_tips_r_image = pygame.image.load("images/red.png")self.player_tips_r_image_topleft = (550, 500)self.player_tips_b_image = pygame.image.load("images/black.png")self.player_tips_b_image_topleft = (550, 100)self.show_attack = Falseself.show_attack_count = 0self.show_attack_time = 100self.attack_img = pygame.image.load("images/pk.png")self.show_win = Falseself.win_img = pygame.image.load("images/win.png")self.win_player = Nonedef get_player(self):"""获取当前走棋方"""return self.playerdef exchange(self):"""交换走棋方"""self.player = "r" if self.player == "b" else "b"return self.get_player()def show(self):if self.show_win:if self.win_player == "b":self.screen.blit(self.win_img, (550, 100))else:self.screen.blit(self.win_img, (550, 450))return# 通过计时,实现显示一会"将军"之后,就消失if self.show_attack:self.show_attack_count += 1if self.show_attack_count == self.show_attack_time:self.show_attack_count = 0self.show_attack = Falseif self.player == "r":self.screen.blit(self.player_tips_r_image, self.player_tips_r_image_topleft)# 显示"将军"效果if self.show_attack:self.screen.blit(self.attack_img, (230, 400))else:self.screen.blit(self.player_tips_b_image, self.player_tips_b_image_topleft)# 显示"将军"效果if self.show_attack:self.screen.blit(self.attack_img, (230, 100))def set_attack(self):"""标记"将军"效果"""self.show_attack = Truedef set_win(self, win_player):"""设置获胜方"""self.show_win = Trueself.win_player = win_playerdef main():# 初始化pygamepygame.init()# 创建用来显示画面的对象(理解为相框)screen = pygame.display.set_mode((WIDTH, HEIGHT))# 游戏背景图片background_img = pygame.image.load("images/bg.jpg")# 创建游戏对象game = Game(screen)# 创建一个游戏棋盘对象chess_board = ChessBoard(screen)# 创建计时器clock = pygame.time.Clock()# 主循环while True:# 事件检测(例如点击了键盘、鼠标等)for event in pygame.event.get():if event.type == pygame.QUIT:pygame.quit()sys.exit() # 退出程序# 如果游戏没有获胜方,则游戏继续,否则一直显示"获胜"if not game.show_win:# 检测是否点击了"可落子"对象clicked_dot = Dot.click()if clicked_dot:chess_board.move_chess(clicked_dot.row, clicked_dot.col)# 清理「点击对象」、「可落子位置对象」Dot.clean_last_postion()ClickBox.clean()# 判断此棋子走完之后,是否"将军"if chess_board.judge_attack_general(game.get_player()):# 检测对方是否可以挽救棋局,如果能挽救,就显示"将军",否则显示"胜利"if chess_board.judge_win(game.get_player()):game.set_win(game.get_player())else:# 如果攻击到对方,则标记显示"将军"效果game.set_attack()# 落子之后,交换走棋方game.exchange()# 检查是否点击了棋子clicked_chess = Chess.click(game.get_player(), [chess for line in chess_board.map for chess in line if chess])if clicked_chess:# 创建选中棋子对象ClickBox(screen, clicked_chess.row, clicked_chess.col, clicked_chess.team)# 清除之前的所有的可以落子对象Dot.clean_last_postion()# 真的点击了棋子,那么计算当前被点击的棋子可以走的位置all_position = chess_board.get_put_down_postion(clicked_chess)if all_position:# 清空上次可落子对象Dot.clean_last_postion()# 创建可落子对象for position in all_position:Dot(screen, position)# 显示游戏背景screen.blit(background_img, (0, 0))screen.blit(background_img, (0, 270))screen.blit(background_img, (0, 540))# 显示棋盘以及棋盘上的棋子chess_board.show()# 显示被点击的棋子ClickBox.show()# 显示可落子对象Dot.show()# 显示游戏相关信息game.show()# 显示screen这个相框的内容(此时在这个相框中的内容像照片、文字等会显示出来)pygame.display.update()# FPS(每秒钟显示画面的次数)clock.tick(60) # 通过一定的延时,实现1秒钟能够循环60次if __name__ == '__main__':main()