|
|

楼主 |
发表于 2026-7-10 14:25:34
|
显示全部楼层
- # -*- coding: utf-8 -*-
- # 完整中国象棋 tkinter 版(人机对战)
- # 兼容 Python 3.4
- import tkinter as tk
- from tkinter import messagebox
- import random
- # ---------- 常量 ----------
- SIZE_ROW = 10
- SIZE_COL = 9
- CELL_SIZE = 50
- # 棋子中文名
- PIECE_CHARS = {
- 'r':'车','n':'马','b':'象','a':'士','k':'将','c':'炮','p':'卒',
- 'R':'车','N':'马','B':'相','A':'仕','K':'帅','C':'炮','P':'兵'
- }
- # 红黑颜色标志
- RED_PIECES = {'R','N','B','A','K','C','P'}
- BLACK_PIECES = {'r','n','b','a','k','c','p'}
- # ---------- 棋盘逻辑 ----------
- def init_board():
- board = [['.' for _ in range(SIZE_COL)] for __ in range(SIZE_ROW)]
- # 黑方(小写)
- board[0][0]='r'; board[0][1]='n'; board[0][2]='b'; board[0][3]='a'; board[0][4]='k'
- board[0][5]='a'; board[0][6]='b'; board[0][7]='n'; board[0][8]='r'
- board[2][1]='c'; board[2][7]='c'
- for c in [0,2,4,6,8]: board[3][c]='p'
- # 红方(大写)
- board[9][0]='R'; board[9][1]='N'; board[9][2]='B'; board[9][3]='A'; board[9][4]='K'
- board[9][5]='A'; board[9][6]='B'; board[9][7]='N'; board[9][8]='R'
- board[7][1]='C'; board[7][7]='C'
- for c in [0,2,4,6,8]: board[6][c]='P'
- return board
- def is_inside(r,c):
- return 0 <= r < SIZE_ROW and 0 <= c < SIZE_COL
- def is_king(piece):
- return piece.lower() == 'k'
- def piece_color(piece):
- if piece in RED_PIECES:
- return 'red'
- elif piece in BLACK_PIECES:
- return 'black'
- else:
- return None
- def can_move(board, r1, c1, r2, c2):
- """返回 (True/False, 错误信息)"""
- if not is_inside(r1,c1) or not is_inside(r2,c2):
- return False, "坐标越界"
- piece = board[r1][c1]
- target = board[r2][c2]
- if piece == '.':
- return False, "源格无棋子"
- if target != '.' and piece_color(piece) == piece_color(target):
- return False, "目标格有己方棋子"
- dr = r2 - r1
- dc = c2 - c1
- p = piece.lower()
- # 将帅
- if p == 'k':
- if abs(dr) + abs(dc) != 1:
- return False, "将/帅只能走一格"
- # 九宫内
- if piece in RED_PIECES:
- if not (7 <= r2 <= 9 and 3 <= c2 <= 5):
- return False, "帅在九宫内"
- else:
- if not (0 <= r2 <= 2 and 3 <= c2 <= 5):
- return False, "将在九宫内"
- return True, ""
- # 士/仕
- if p == 'a':
- if abs(dr) != 1 or abs(dc) != 1:
- return False, "士走斜一格"
- if piece in RED_PIECES:
- if not (7 <= r2 <= 9 and 3 <= c2 <= 5):
- return False, "仕在九宫内"
- else:
- if not (0 <= r2 <= 2 and 3 <= c2 <= 5):
- return False, "士在九宫内"
- return True, ""
- # 象/相
- if p == 'b':
- if abs(dr) != 2 or abs(dc) != 2:
- return False, "象走田"
- # 象眼
- eye_r = (r1+r2)//2
- eye_c = (c1+c2)//2
- if board[eye_r][eye_c] != '.':
- return False, "象眼被塞"
- # 不能过河
- if piece in RED_PIECES:
- if not (5 <= r2 <= 9):
- return False, "相不过河"
- else:
- if not (0 <= r2 <= 4):
- return False, "象不过河"
- return True, ""
- # 马
- if p == 'n':
- if abs(dr) == 2 and abs(dc) == 1:
- block_r = r1 + (1 if dr > 0 else -1)
- block_c = c1
- elif abs(dr) == 1 and abs(dc) == 2:
- block_r = r1
- block_c = c1 + (1 if dc > 0 else -1)
- else:
- return False, "马走日"
- if board[block_r][block_c] != '.':
- return False, "马脚被蹩"
- return True, ""
- # 车
- if p == 'r':
- if dr != 0 and dc != 0:
- return False, "车走直线"
- if dr != 0:
- step = 1 if dr > 0 else -1
- for r in range(r1+step, r2, step):
- if board[r][c1] != '.':
- return False, "中间有阻挡"
- else:
- step = 1 if dc > 0 else -1
- for c in range(c1+step, c2, step):
- if board[r1][c] != '.':
- return False, "中间有阻挡"
- return True, ""
- # 炮
- if p == 'c':
- if dr != 0 and dc != 0:
- return False, "炮走直线"
- count = 0
- if dr != 0:
- step = 1 if dr > 0 else -1
- for r in range(r1+step, r2, step):
- if board[r][c1] != '.':
- count += 1
- else:
- step = 1 if dc > 0 else -1
- for c in range(c1+step, c2, step):
- if board[r1][c] != '.':
- count += 1
- if target == '.':
- if count != 0:
- return False, "炮移动不能有阻挡"
- else:
- if count != 1:
- return False, "炮需一个炮架才能吃"
- return True, ""
- # 兵/卒
- if p == 'p':
- # 红兵向上 (r减小)
- if piece in RED_PIECES:
- if dr >= 0:
- return False, "兵不能后退"
- if abs(dr) > 1 or abs(dc) > 1:
- return False, "兵走一格"
- if r1 > 4: # 未过河(红方未过河时 r >= 5,即 r1 >= 5 未过河)
- if dc != 0:
- return False, "未过河不能横走"
- else: # 已过河 r1 <= 4
- if abs(dr) + abs(dc) != 1:
- return False, "兵走一格"
- return True, ""
- else: # 黑卒向下 (r增大)
- if dr <= 0:
- return False, "卒不能后退"
- if abs(dr) > 1 or abs(dc) > 1:
- return False, "卒走一格"
- if r1 < 5: # 未过河(黑方 r1 <= 4 未过河)
- if dc != 0:
- return False, "未过河不能横走"
- else: # 已过河 r1 >= 5
- if abs(dr) + abs(dc) != 1:
- return False, "卒走一格"
- return True, ""
- return False, "未知棋子"
- def is_check(board, color):
- """判断 color方('red'或'black')是否被将军"""
- # 找到己方将/帅位置
- king_pieces = ('K' if color == 'red' else 'k')
- kr = kc = -1
- for r in range(SIZE_ROW):
- for c in range(SIZE_COL):
- if board[r][c] == king_pieces:
- kr, kc = r, c
- break
- if kr != -1:
- break
- if kr == -1:
- return True # 被吃将,等同于将军
- # 检查对方所有棋子能否吃掉将
- enemy = 'black' if color == 'red' else 'red'
- enemy_pieces = BLACK_PIECES if enemy == 'black' else RED_PIECES
- for r in range(SIZE_ROW):
- for c in range(SIZE_COL):
- p = board[r][c]
- if p != '.' and piece_color(p) == enemy:
- # 检查能否走到将的位置
- if can_move(board, r, c, kr, kc)[0]:
- # 还需要模拟是否自将?临时模拟
- temp = [row[:] for row in board]
- temp[kr][kc] = temp[r][c]
- temp[r][c] = '.'
- # 检查走完后是否对方将/帅仍被将军(即是否这步导致自将)
- # 如果对方将/帅还在且未被将军,则当前将军成立
- # 简化:不考虑自将,对于将军检测足够
- return True
- return False
- def is_self_check(board, r1, c1, r2, c2, color):
- """模拟移动后,判断color方是否被将军(不能自将)"""
- temp = [row[:] for row in board]
- temp[r2][c2] = temp[r1][c1]
- temp[r1][c1] = '.'
- # 检查color方是否被将军
- if is_check(temp, color):
- return True
- else:
- return False
- # ---------- GUI 应用 ----------
- class ChessApp:
- def __init__(self, master):
- self.master = master
- master.title("中国象棋 - 人机对战")
- # 初始化数据
- self.board = init_board()
- self.turn = 'red' # 红方先手
- self.selected = None # 选中的格子 (r,c)
- self.valid_moves = [] # 当前选中的合法移动列表
- self.game_over = False
- self.move_history = [] # 棋谱记录
- # 控制栏
- top_frame = tk.Frame(master)
- top_frame.pack(pady=5)
- self.status_label = tk.Label(top_frame, text="", font=("SimHei",14))
- self.status_label.pack(side=tk.LEFT, padx=5)
- tk.Button(top_frame, text="重新开始", command=self.restart).pack(side=tk.LEFT, padx=5)
- tk.Button(top_frame, text="悔棋", command=self.undo_move).pack(side=tk.LEFT, padx=5)
- # 棋盘画布
- canvas_width = SIZE_COL * CELL_SIZE
- canvas_height = SIZE_ROW * CELL_SIZE
- self.canvas = tk.Canvas(master, width=canvas_width, height=canvas_height,
- bg='#f0c060')
- self.canvas.pack(padx=10, pady=5)
- self.canvas.bind("<Button-1>", self.on_click)
- # 棋谱显示
- bottom_frame = tk.Frame(master)
- bottom_frame.pack(pady=5)
- tk.Label(bottom_frame, text="棋谱:", font=("SimHei",12)).pack(side=tk.LEFT)
- self.move_text = tk.Text(bottom_frame, width=60, height=8, state=tk.DISABLED)
- self.move_text.pack(side=tk.LEFT, padx=5)
- # 绘制初始棋盘
- self.draw_board()
- self.update_status()
- def restart(self):
- self.board = init_board()
- self.turn = 'red'
- self.selected = None
- self.valid_moves = []
- self.game_over = False
- self.move_history = []
- self.move_text.config(state=tk.NORMAL)
- self.move_text.delete(1.0, tk.END)
- self.move_text.config(state=tk.DISABLED)
- self.draw_board()
- self.update_status()
- def undo_move(self):
- # 只能悔一步(且不能是AI走后的瞬间)
- if len(self.move_history) < 2:
- return
- # 撤回两步(玩家和AI各一步)
- for _ in range(2):
- (r1,c1,r2,c2,piece) = self.move_history.pop()
- self.board[r1][c1] = piece
- self.board[r2][c2] = '.'
- self.turn = 'red'
- self.selected = None
- self.valid_moves = []
- self.game_over = False
- # 更新棋谱显示
- self.move_text.config(state=tk.NORMAL)
- self.move_text.delete(1.0, tk.END)
- for i,m in enumerate(self.move_history):
- self.move_text.insert(tk.END, "{0}. {1} {2}\n".format(i//2+1, m[4] if i%2==0 else '黑', '...'))
- self.move_text.config(state=tk.DISABLED)
- self.draw_board()
- self.update_status()
- def update_status(self):
- if self.game_over:
- self.status_label.config(text="游戏结束")
- else:
- txt = "红方走棋" if self.turn == 'red' else "黑方思考中..."
- self.status_label.config(text=txt)
- def draw_board(self):
- self.canvas.delete("all")
- # 画网格
- for r in range(SIZE_ROW):
- # 横线
- self.canvas.create_line(0, r*CELL_SIZE, SIZE_COL*CELL_SIZE, r*CELL_SIZE, fill='black')
- for c in range(SIZE_COL):
- # 竖线(上半部分)
- self.canvas.create_line(c*CELL_SIZE + CELL_SIZE//2, 0,
- c*CELL_SIZE + CELL_SIZE//2, 4*CELL_SIZE, fill='black')
- # 竖线(下半部分)
- self.canvas.create_line(c*CELL_SIZE + CELL_SIZE//2, 5*CELL_SIZE,
- c*CELL_SIZE + CELL_SIZE//2, 9*CELL_SIZE, fill='black')
- # 两端的竖线延伸到边框
- if c == 0 or c == SIZE_COL-1:
- self.canvas.create_line(c*CELL_SIZE + CELL_SIZE//2, 0,
- c*CELL_SIZE + CELL_SIZE//2, 9*CELL_SIZE, fill='black')
- # 画九宫格斜线
- # 上方九宫
- self.canvas.create_line(3*CELL_SIZE + CELL_SIZE//2, 0, 5*CELL_SIZE + CELL_SIZE//2, 2*CELL_SIZE, fill='black')
- self.canvas.create_line(5*CELL_SIZE + CELL_SIZE//2, 0, 3*CELL_SIZE + CELL_SIZE//2, 2*CELL_SIZE, fill='black')
- # 下方九宫
- self.canvas.create_line(3*CELL_SIZE + CELL_SIZE//2, 7*CELL_SIZE, 5*CELL_SIZE + CELL_SIZE//2, 9*CELL_SIZE, fill='black')
- self.canvas.create_line(5*CELL_SIZE + CELL_SIZE//2, 7*CELL_SIZE, 3*CELL_SIZE + CELL_SIZE//2, 9*CELL_SIZE, fill='black')
- # 画楚河汉界
- self.canvas.create_text(4.5*CELL_SIZE, 4.5*CELL_SIZE, text="楚 河 漢 界",
- font=("SimHei", 14), fill='black')
- # 画棋子
- for r in range(SIZE_ROW):
- for c in range(SIZE_COL):
- piece = self.board[r][c]
- if piece == '.':
- continue
- x = c*CELL_SIZE + CELL_SIZE//2
- y = r*CELL_SIZE + CELL_SIZE//2
- # 背景圆
- color = 'red' if piece in RED_PIECES else 'black' if piece in BLACK_PIECES else 'gray'
- self.canvas.create_oval(x-18, y-18, x+18, y+18, fill='#f5deb3', outline='gray', width=2)
- # 棋子文字
- ch = PIECE_CHARS.get(piece, piece)
- self.canvas.create_text(x, y, text=ch, font=("SimHei", 18), fill=color)
- # 画选中标记
- if self.selected:
- r,c = self.selected
- x = c*CELL_SIZE + CELL_SIZE//2
- y = r*CELL_SIZE + CELL_SIZE//2
- self.canvas.create_oval(x-20, y-20, x+20, y+20, outline='blue', width=3, dash=(3,3))
- # 画合法移动标记
- for (r,c) in self.valid_moves:
- x = c*CELL_SIZE + CELL_SIZE//2
- y = r*CELL_SIZE + CELL_SIZE//2
- if self.board[r][c] == '.':
- self.canvas.create_oval(x-5, y-5, x+5, y+5, fill='green')
- else:
- self.canvas.create_oval(x-18, y-18, x+18, y+18, outline='red', width=3, dash=(3,3))
- def on_click(self, event):
- if self.game_over or self.turn != 'red':
- return
- col = int(event.x // CELL_SIZE)
- row = int(event.y // CELL_SIZE)
- if not is_inside(row, col):
- return
- # 如果已有选中
- if self.selected:
- r1,c1 = self.selected
- # 如果点击的是己方棋子,则切换选中
- if self.board[row][col] != '.' and piece_color(self.board[row][col]) == 'red':
- self.selected = (row,col)
- self.valid_moves = self.get_legal_moves(row,col)
- self.draw_board()
- return
- # 尝试移动到 (row,col)
- if (row,col) in self.valid_moves:
- # 执行移动
- self.do_move(r1,c1, row,col)
- self.selected = None
- self.valid_moves = []
- self.draw_board()
- # 检查游戏结束
- if self.check_game_over():
- return
- # 切换回合到黑方,触发AI
- self.turn = 'black'
- self.update_status()
- self.master.after(500, self.ai_move) # 延迟500ms让界面更新
- else:
- # 点击无效,清空选中
- self.selected = None
- self.valid_moves = []
- self.draw_board()
- self.update_status()
- else:
- # 选中己方棋子
- piece = self.board[row][col]
- if piece != '.' and piece_color(piece) == 'red':
- self.selected = (row,col)
- self.valid_moves = self.get_legal_moves(row,col)
- self.draw_board()
- def get_legal_moves(self, r,c):
- """返回(r,c)处棋子的所有合法移动位置(考虑不能自将)"""
- moves = []
- piece = self.board[r][c]
- color = piece_color(piece)
- for tr in range(SIZE_ROW):
- for tc in range(SIZE_COL):
- if (r,c) == (tr,tc):
- continue
- if can_move(self.board, r,c, tr,tc)[0]:
- if not is_self_check(self.board, r,c, tr,tc, color):
- moves.append((tr,tc))
- return moves
- def do_move(self, r1,c1, r2,c2):
- piece = self.board[r1][c1]
- target = self.board[r2][c2]
- self.move_history.append((r1,c1,r2,c2,piece))
- self.board[r2][c2] = piece
- self.board[r1][c1] = '.'
- # 更新棋谱
- ch1 = PIECE_CHARS.get(piece, piece)
- ch2 = PIECE_CHARS.get(target, target) if target != '.' else '空'
- move_str = "{0}从({1},{2})到({3},{4}) 吃掉{5}".format(ch1, r1,c1, r2,c2, ch2)
- self.move_text.config(state=tk.NORMAL)
- self.move_text.insert(tk.END, move_str + "\n")
- self.move_text.see(tk.END)
- self.move_text.config(state=tk.DISABLED)
- def check_game_over(self):
- # 检查将帅是否被吃
- red_king = any('K' in row for row in self.board)
- black_king = any('k' in row for row in self.board)
- if not red_king:
- messagebox.showinfo("游戏结束", "黑方获胜!")
- self.game_over = True
- self.update_status()
- return True
- if not black_king:
- messagebox.showinfo("游戏结束", "红方获胜!")
- self.game_over = True
- self.update_status()
- return True
- return False
- def ai_move(self):
- if self.game_over:
- return
- # AI为黑方,随机选择一个合法移动
- black_pieces = []
- for r in range(SIZE_ROW):
- for c in range(SIZE_COL):
- if self.board[r][c] in BLACK_PIECES:
- black_pieces.append((r,c))
- all_moves = []
- for (r,c) in black_pieces:
- legal = self.get_legal_moves(r,c)
- for (tr,tc) in legal:
- all_moves.append((r,c,tr,tc))
- if not all_moves:
- # 无步可走,判负
- messagebox.showinfo("游戏结束", "黑方无步可走,红方获胜!")
- self.game_over = True
- self.update_status()
- return
- # 随机选择一个
- r1,c1,r2,c2 = random.choice(all_moves)
- self.do_move(r1,c1, r2,c2)
- self.draw_board()
- if self.check_game_over():
- return
- self.turn = 'red'
- self.update_status()
- # ---------- 主程序 ----------
- if __name__ == "__main__":
- root = tk.Tk()
- app = ChessApp(root)
- root.mainloop()
复制代码
这个版本好玩一些兵过河左右不了,左右移动不了 |
|