|
|
好的,下面提供一份完整的、可直接运行的 tkinter 图形界面中国象棋代码。代码包含:
- 汉字棋子(红黑双色)
- 鼠标点击选择棋子并移动到目标格子
- 所有棋子的走法规则(车、马、象/相、士/仕、将/帅、炮、兵/卒)
- 楚河汉界文字显示
- 吃子与胜利判定(吃将/帅即胜利)
- 悔棋功能(按钮)
- 重新开始按钮
- 操作提示与错误信息(显示在状态栏)
- 兼容 Python 3.4(全部使用 `format()` 格式化)
您只需复制代码到 `.py` 文件,运行即可。
- # -*- coding: utf-8 -*-
- """
- 简易中国象棋 - tkinter 鼠标版
- 兼容 Python 3.4 (无 f-string)
- """
- import tkinter as tk
- from tkinter import messagebox
- # ------------------ 常量 ------------------
- SIZE_ROW = 10
- SIZE_COL = 9
- CELL_SIZE = 50 # 每个格子像素大小
- PIECE_RADIUS = 20
- OFFSET_X = 30
- OFFSET_Y = 30
- BOARD_TOP = OFFSET_Y
- BOARD_LEFT = OFFSET_X
- BOARD_BOTTOM = OFFSET_Y + (SIZE_ROW - 1) * CELL_SIZE
- BOARD_RIGHT = OFFSET_X + (SIZE_COL - 1) * CELL_SIZE
- # 棋子字符(红方大写,黑方小写)
- # 但为了显示方便,使用汉字映射
- PIECE_NAMES = {
- 'K': '帥', 'A': '仕', 'B': '相', 'N': '馬', 'R': '車', 'C': '炮', 'P': '兵',
- 'k': '將', 'a': '士', 'b': '象', 'n': '馬', 'r': '車', 'c': '砲', 'p': '卒',
- }
- # ------------------ 棋盘逻辑 ------------------
- def init_board():
- """初始化棋盘,返回二维列表"""
- board = [['.' for _ in range(SIZE_COL)] for _ in range(SIZE_ROW)]
- # 红方(第5~9行,图中红方在下)
- 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'
- board[6][0] = 'P'; board[6][2] = 'P'; board[6][4] = 'P'; board[6][6] = 'P'; board[6][8] = 'P'
- # 黑方(第0~4行)
- 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'
- board[3][0] = 'p'; board[3][2] = 'p'; board[3][4] = 'p'; board[3][6] = 'p'; board[3][8] = 'p'
- return board
- def is_inside(r, c):
- return 0 <= r < SIZE_ROW and 0 <= c < SIZE_COL
- def is_red(piece):
- return piece.isupper()
- def is_black(piece):
- return piece.islower()
- def get_player_color(board):
- # 未使用,改为全局变量 current_player
- pass
- def can_move(board, r1, c1, r2, c2):
- """检查走法是否合法(不含吃子检查),返回 (True, "") 或 (False, 原因)"""
- p = board[r1][c1]
- if p == '.':
- return False, "源位置无棋子"
- target = board[r2][c2]
- if target != '.':
- if (is_red(p) and is_red(target)) or (is_black(p) and is_black(target)):
- return False, "不能吃己方棋子"
- dr = r2 - r1
- dc = c2 - c1
- # 车 R/r
- if p in ('R','r'):
- if dr != 0 and dc != 0:
- return False, "车只能走直线"
- if dr == 0:
- step = 1 if dc > 0 else -1
- for c in range(c1+step, c2, step):
- if board[r1][c] != '.':
- return False, "车路径有阻挡"
- else:
- step = 1 if dr > 0 else -1
- for r in range(r1+step, r2, step):
- if board[r][c1] != '.':
- return False, "车路径有阻挡"
- return True, ""
- # 马 N/n
- if p in ('N','n'):
- dr_abs = abs(dr)
- dc_abs = abs(dc)
- if (dr_abs, dc_abs) not in ((2,1),(1,2)):
- return False, "马走日"
- # 蹩马脚
- if dr_abs == 2:
- block_r = r1 + (1 if dr > 0 else -1)
- block_c = c1
- else:
- block_r = r1
- block_c = c1 + (1 if dc > 0 else -1)
- if board[block_r][block_c] != '.':
- return False, "马脚被蹩"
- return True, ""
- # 象/相 B/b
- if p in ('B','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 is_red(p) and not (5 <= r2 <= 9):
- return False, "象不能过河"
- if is_black(p) and not (0 <= r2 <= 4):
- return False, "象不能过河"
- return True, ""
- # 士/仕 A/a
- if p in ('A','a'):
- if abs(dr) != 1 or abs(dc) != 1:
- return False, "士走斜一步"
- # 必须在九宫格内
- if is_red(p):
- 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, ""
- # 将/帅 K/k
- if p in ('K','k'):
- if abs(dr) + abs(dc) != 1:
- return False, "将/帅走一步直"
- # 必须在九宫格内
- if is_red(p):
- 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, ""
- # 炮 C/c
- if p in ('C','c'):
- if dr != 0 and dc != 0:
- return False, "炮走直线"
- # 统计中间棋子数
- count = 0
- if dr == 0:
- step = 1 if dc > 0 else -1
- for c in range(c1+step, c2, step):
- if board[r1][c] != '.':
- count += 1
- else:
- step = 1 if dr > 0 else -1
- for r in range(r1+step, r2, step):
- if board[r][c1] != '.':
- count += 1
- if target == '.':
- if count != 0:
- return False, "炮移动时不能翻山"
- else:
- if count != 1:
- return False, "炮吃子必须隔一个棋子"
- return True, ""
- # 兵/卒 P/p
- if p in ('P','p'):
- if is_red(p):
- # 红兵向上走(减少行号)
- if dr >= 0: # 不能后退或横走(未过河时不能横走)
- if dr == 0 and abs(dc) == 1 and r1 <= 4: # 过河后才能横走
- return True, ""
- if dr == -1 and dc == 0: # 前进一步
- return True, ""
- return False, "兵走法错误"
- else:
- # 前进
- if dr == -1 and dc == 0:
- return True, ""
- # 过河后可以横走
- if r1 <= 4 and dr == 0 and abs(dc) == 1:
- return True, ""
- return False, "兵走法错误"
- else:
- # 黑兵向下走(增加行号)
- if dr <= 0:
- if dr == 0 and abs(dc) == 1 and r1 >= 5: # 过河后才能横走
- return True, ""
- if dr == 1 and dc == 0:
- return True, ""
- return False, "卒走法错误"
- else:
- if dr == 1 and dc == 0:
- return True, ""
- if r1 >= 5 and dr == 0 and abs(dc) == 1:
- return True, ""
- return False, "卒走法错误"
- return False, "未知棋子"
- # ------------------ GUI 类 ------------------
- class ChessGame:
- def __init__(self, root):
- self.root = root
- self.root.title("中国象棋")
- self.board = init_board()
- self.current_player = 'red' # 'red' 或 'black'
- self.selected = None # (r, c) 当前选中棋子
- self.history = [] # 用于悔棋,存储 (棋子, 源r, 源c, 目标r, 目标c, 被吃子)
- # 画布
- self.canvas = tk.Canvas(root, width=BOARD_RIGHT+50, height=BOARD_BOTTOM+80, bg='#f0d9b5')
- self.canvas.pack()
- self.canvas.bind("<Button-1>", self.on_click)
- # 状态栏
- self.status_var = tk.StringVar()
- self.status_var.set("红方先行")
- status_bar = tk.Label(root, textvariable=self.status_var, bd=1, relief=tk.SUNKEN, anchor=tk.W)
- status_bar.pack(fill=tk.X, side=tk.BOTTOM)
- # 按钮框架
- btn_frame = tk.Frame(root)
- btn_frame.pack(side=tk.BOTTOM, pady=5)
- tk.Button(btn_frame, text="悔棋", command=self.undo_move).pack(side=tk.LEFT, padx=5)
- tk.Button(btn_frame, text="重新开始", command=self.reset_game).pack(side=tk.LEFT, padx=5)
- self.draw_board()
- self.draw_pieces()
- self.update_status()
- def draw_board(self):
- """绘制棋盘线条"""
- self.canvas.delete("board")
- # 画横线
- for r in range(SIZE_ROW):
- y = OFFSET_Y + r * CELL_SIZE
- self.canvas.create_line(OFFSET_X, y, BOARD_RIGHT, y, tags="board")
- # 画竖线(注意两侧边线完整,中间列有断开)
- for c in range(SIZE_COL):
- x = OFFSET_X + c * CELL_SIZE
- if c == 0 or c == SIZE_COL-1:
- self.canvas.create_line(x, OFFSET_Y, x, BOARD_BOTTOM, tags="board")
- else:
- # 上半部分 0-4行
- self.canvas.create_line(x, OFFSET_Y, x, OFFSET_Y + 4*CELL_SIZE, tags="board")
- # 下半部分 5-9行
- self.canvas.create_line(x, OFFSET_Y + 5*CELL_SIZE, x, BOARD_BOTTOM, tags="board")
- # 九宫格斜线
- # 左上角 0,3 -> 2,5
- self.canvas.create_line(OFFSET_X + 3*CELL_SIZE, OFFSET_Y,
- OFFSET_X + 5*CELL_SIZE, OFFSET_Y + 2*CELL_SIZE, tags="board")
- self.canvas.create_line(OFFSET_X + 5*CELL_SIZE, OFFSET_Y,
- OFFSET_X + 3*CELL_SIZE, OFFSET_Y + 2*CELL_SIZE, tags="board")
- # 右下角 7,3 -> 9,5
- self.canvas.create_line(OFFSET_X + 3*CELL_SIZE, OFFSET_Y + 7*CELL_SIZE,
- OFFSET_X + 5*CELL_SIZE, OFFSET_Y + 9*CELL_SIZE, tags="board")
- self.canvas.create_line(OFFSET_X + 5*CELL_SIZE, OFFSET_Y + 7*CELL_SIZE,
- OFFSET_X + 3*CELL_SIZE, OFFSET_Y + 9*CELL_SIZE, tags="board")
- # 楚河汉界文字
- self.canvas.create_text(OFFSET_X + 4*CELL_SIZE, OFFSET_Y + 4.5*CELL_SIZE,
- text="楚 河 漢 界", font=('楷体', 16, 'bold'), fill='#333', tags="board")
- def draw_pieces(self):
- """绘制所有棋子"""
- self.canvas.delete("piece")
- for r in range(SIZE_ROW):
- for c in range(SIZE_COL):
- p = self.board[r][c]
- if p == '.':
- continue
- x = OFFSET_X + c * CELL_SIZE
- y = OFFSET_Y + r * CELL_SIZE
- color = "red" if is_red(p) else "black"
- # 画圆
- self.canvas.create_oval(x-PIECE_RADIUS, y-PIECE_RADIUS,
- x+PIECE_RADIUS, y+PIECE_RADIUS,
- fill='#ffe0b2', outline=color, width=3, tags="piece")
- # 显示汉字
- char = PIECE_NAMES.get(p, p)
- self.canvas.create_text(x, y, text=char, fill=color, font=('楷体', 20, 'bold'), tags="piece")
- # 高亮选中的棋子
- if self.selected:
- r, c = self.selected
- x = OFFSET_X + c * CELL_SIZE
- y = OFFSET_Y + r * CELL_SIZE
- self.canvas.create_rectangle(x-PIECE_RADIUS-3, y-PIECE_RADIUS-3,
- x+PIECE_RADIUS+3, y+PIECE_RADIUS+3,
- outline='yellow', width=3, tags="highlight")
- else:
- self.canvas.delete("highlight")
- def update_status(self):
- if self.current_player == 'red':
- self.status_var.set("红方走棋")
- else:
- self.status_var.set("黑方走棋")
- def on_click(self, event):
- """鼠标点击事件"""
- # 将像素坐标转换为棋盘行列
- c = round((event.x - OFFSET_X) / CELL_SIZE)
- r = round((event.y - OFFSET_Y) / CELL_SIZE)
- if not is_inside(r, c):
- self.status_var.set("点击位置无效")
- return
- # 检测是否点击在棋子附近(精确度放宽)
- x = OFFSET_X + c * CELL_SIZE
- y = OFFSET_Y + r * CELL_SIZE
- if abs(event.x - x) > PIECE_RADIUS+5 or abs(event.y - y) > PIECE_RADIUS+5:
- self.status_var.set("请点击棋子中心区域")
- return
- # 如果当前没有选中棋子
- if self.selected is None:
- piece = self.board[r][c]
- if piece == '.':
- self.status_var.set("该位置无棋子")
- return
- # 检查是否是当前玩家的棋子
- if (self.current_player == 'red' and not is_red(piece)) or \
- (self.current_player == 'black' and not is_black(piece)):
- self.status_var.set("这不是你的棋子")
- return
- self.selected = (r, c)
- self.draw_pieces()
- self.status_var.set("选中棋子,请点击目标位置")
- else:
- # 已选中棋子,尝试移动
- r1, c1 = self.selected
- # 如果点击的是同一个棋子,取消选中
- if (r, c) == (r1, c1):
- self.selected = None
- self.draw_pieces()
- self.status_var.set("已取消选中")
- return
- piece = self.board[r1][c1]
- # 检查走法合法性
- valid, msg = can_move(self.board, r1, c1, r, c)
- if not valid:
- self.status_var.set(msg)
- return
- # 执行移动(保存历史用于悔棋)
- target = self.board[r][c]
- self.history.append((piece, r1, c1, r, c, target))
- self.board[r][c] = piece
- self.board[r1][c1] = '.'
- # 检查是否吃掉将/帅
- if target in ('k', 'K'):
- winner = "红方" if is_red(piece) else "黑方"
- self.show_winner(winner)
- return
- # 切换玩家
- self.current_player = 'black' if self.current_player == 'red' else 'red'
- self.selected = None
- self.draw_pieces()
- self.update_status()
- self.status_var.set("移动成功")
- def show_winner(self, winner):
- """显示胜利信息并询问是否重玩"""
- msg = "{0}获胜!\n是否重新开始?".format(winner)
- ret = tk.messagebox.askyesno("游戏结束", msg)
- if ret:
- self.reset_game()
- else:
- self.root.quit()
- def undo_move(self):
- """悔棋:撤销上一步"""
- if not self.history:
- self.status_var.set("没有可以悔棋的步骤")
- return
- piece, r1, c1, r2, c2, target = self.history.pop()
- self.board[r1][c1] = piece
- self.board[r2][c2] = target
- # 切换回合(因为悔棋后应该是上一个玩家走棋)
- self.current_player = 'black' if self.current_player == 'red' else 'red'
- self.selected = None
- self.draw_pieces()
- self.update_status()
- self.status_var.set("已悔棋")
- def reset_game(self):
- """重新开始游戏"""
- self.board = init_board()
- self.current_player = 'red'
- self.selected = None
- self.history.clear()
- self.draw_pieces()
- self.update_status()
- self.status_var.set("新游戏开始,红方先行")
- # ------------------ 主程序 ------------------
- if __name__ == "__main__":
- root = tk.Tk()
- game = ChessGame(root)
- root.mainloop()
复制代码
### 使用说明
1. 运行代码,弹出窗口。
2. 红方先手,点击红色棋子选中(高亮),再点击目标位置移动。
3. 规则已实现:车、马、象(不过河)、士、将(九宫内)、炮(须隔子)、兵/卒(过河后可横走)。
4. 吃掉对方将/帅时弹出胜利提示,可选择重新开始或退出。
5. 点击“悔棋”可撤销上一步。
6. 状态栏会显示当前走方及操作提示。
所有功能使用 Python 3.4 兼容写法,无 f-string。如果字体显示不清,可在 `PIECE_NAMES` 中替换为自己的汉字(如 '帅' 等)。
以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。 |
|