|
|
- # -*- coding: utf-8 -*-
- import tkinter as tk
- from tkinter import messagebox
- import random
- SIZE_ROW = 10
- SIZE_COL = 9
- CELL_SIZE = 55
- RED_PIECES = {'帥','仕','相','馬','車','炮','兵'}
- BLACK_PIECES = {'將','士','象','馬','車','砲','卒'}
- ALL_PIECES = RED_PIECES | BLACK_PIECES
- class ChessApp:
- def __init__(self, root):
- self.root = root
- self.root.title("中国象棋 - 人机对战")
- self.canvas = tk.Canvas(root, width=CELL_SIZE*SIZE_COL+40, height=CELL_SIZE*SIZE_ROW+80, bg='#f5deb3')
- self.canvas.pack(side=tk.LEFT, padx=10, pady=10)
- self.status_label = tk.Label(root, text="红方走棋", font=('Arial',14))
- self.status_label.pack()
- self.move_log = tk.Text(root, height=15, width=30)
- self.move_log.pack(side=tk.RIGHT, padx=10)
- self.restart_btn = tk.Button(root, text="重新开始", command=self.reset_game)
- self.restart_btn.pack()
- self.board = [['.' for _ in range(SIZE_COL)] for _ in range(SIZE_ROW)]
- self.selected = None
- self.turn = 'red' # 'red' or 'black'
- self.move_history = []
- self.setup_board()
- self.draw_board()
- self.canvas.bind("<Button-1>", self.on_click)
- def setup_board(self):
- # 黑方(上方0-4行)
- black_pieces = ['車','馬','象','士','將','士','象','馬','車']
- for c in range(9):
- self.board[0][c] = black_pieces[c]
- self.board[2][1] = '砲'
- self.board[2][7] = '砲'
- for c in [0,2,4,6,8]:
- self.board[3][c] = '卒'
- # 红方(下方5-9行)
- red_pieces = ['車','馬','相','仕','帥','仕','相','馬','車']
- for c in range(9):
- self.board[9][c] = red_pieces[c]
- self.board[7][1] = '炮'
- self.board[7][7] = '炮'
- for c in [0,2,4,6,8]:
- self.board[6][c] = '兵'
- def draw_board(self):
- self.canvas.delete("all")
- # 画网格线
- for r in range(SIZE_ROW):
- for c in range(SIZE_COL):
- x0 = 20 + c * CELL_SIZE
- y0 = 20 + r * CELL_SIZE
- x1 = x0 + CELL_SIZE
- y1 = y0 + CELL_SIZE
- # 画格子边框
- if r < 4 or r >= 5: # 非河界部分画完整十字线
- self.canvas.create_line(x0, y0, x1, y0, fill='black')
- self.canvas.create_line(x0, y0, x0, y1, fill='black')
- if r == 4: # 河界区域只画左右边界
- self.canvas.create_line(x0, y0, x0, y1, fill='black')
- if r == 5:
- self.canvas.create_line(x0, y0, x0, y1, fill='black')
- # 画最右侧边界
- if c == 8:
- self.canvas.create_line(x1, y0, x1, y1, fill='black')
- # 画下边界
- if r == 9:
- self.canvas.create_line(x0, y1, x1, y1, fill='black')
- # 画楚河汉界
- self.canvas.create_text(20+4*CELL_SIZE, 20+4.5*CELL_SIZE, text="楚河 汉界", font=('Arial',14), fill='black')
- # 画九宫格斜线
- for (r,c) in [(0,3),(0,5),(7,3),(7,5)]:
- x0 = 20 + c * CELL_SIZE
- y0 = 20 + r * CELL_SIZE
- self.canvas.create_line(x0, y0, x0+2*CELL_SIZE, y0+2*CELL_SIZE, fill='black')
- self.canvas.create_line(x0+2*CELL_SIZE, y0, x0, y0+2*CELL_SIZE, fill='black')
- # 画棋子
- for r in range(SIZE_ROW):
- for c in range(SIZE_COL):
- piece = self.board[r][c]
- if piece == '.':
- continue
- x = 20 + c * CELL_SIZE + CELL_SIZE//2
- y = 20 + r * CELL_SIZE + CELL_SIZE//2
- color = 'red' if piece in RED_PIECES else 'black'
- self.canvas.create_oval(x-20, y-20, x+20, y+20, fill='#f5deb3', outline='black', width=2)
- self.canvas.create_text(x, y, text=piece, font=('Arial',18,'bold'), fill=color)
- # 画选中高亮
- if self.selected:
- r,c = self.selected
- x = 20 + c * CELL_SIZE + CELL_SIZE//2
- y = 20 + r * CELL_SIZE + CELL_SIZE//2
- self.canvas.create_oval(x-22, y-22, x+22, y+22, outline='gold', width=3)
- # 更新状态
- player = "红方" if self.turn == 'red' else "黑方"
- self.status_label.config(text="{}走棋".format(player))
- def is_inside(self, r, c):
- return 0 <= r < SIZE_ROW and 0 <= c < SIZE_COL
- def is_legal_move(self, r1, c1, r2, c2):
- piece = self.board[r1][c1]
- target = self.board[r2][c2]
- if target != '.' and ((piece in RED_PIECES and target in RED_PIECES) or (piece in BLACK_PIECES and target in BLACK_PIECES)):
- return False
- dr = r2 - r1
- dc = c2 - c1
- p = piece
- # 将/帅
- if p in ('將','帥'):
- if not (0 <= c2 <= 8 and 0 <= r2 <= 2 if p == '將' else 7 <= r2 <= 9):
- return False
- if abs(dr) > 1 or abs(dc) > 1 or (abs(dr) + abs(dc) != 1):
- return False
- # 将帅对面
- if target in ('帥','將'):
- return True
- return True
- # 士/仕
- if p in ('士','仕'):
- if not (0 <= c2 <= 8 and 0 <= r2 <= 2 if p == '士' else 7 <= r2 <= 9):
- return False
- if abs(dr) != 1 or abs(dc) != 1:
- return False
- return True
- # 象/相
- if p in ('象','相'):
- if r2 <= 4 if p == '象' else r2 >= 5: # 不能过河
- return False
- if abs(dr) != 2 or abs(dc) != 2:
- return False
- # 象眼
- eye_r = (r1 + r2) // 2
- eye_c = (c1 + c2) // 2
- if self.board[eye_r][eye_c] != '.':
- return False
- return True
- # 馬/馬
- if p in ('馬','馬'):
- if (abs(dr), abs(dc)) not in ((2,1), (1,2)):
- return False
- if abs(dr) == 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 self.board[block_r][block_c] != '.':
- return False
- return True
- # 車/車
- if p in ('車','車'):
- if dr != 0 and dc != 0:
- return False
- step_r = 0 if dr == 0 else (1 if dr > 0 else -1)
- step_c = 0 if dc == 0 else (1 if dc > 0 else -1)
- r, c = r1 + step_r, c1 + step_c
- while (r,c) != (r2,c2):
- if self.board[r][c] != '.':
- return False
- r += step_r
- c += step_c
- return True
- # 炮/砲
- if p in ('炮','砲'):
- if dr != 0 and dc != 0:
- return False
- step_r = 0 if dr == 0 else (1 if dr > 0 else -1)
- step_c = 0 if dc == 0 else (1 if dc > 0 else -1)
- r, c = r1 + step_r, c1 + step_c
- between = 0
- while (r,c) != (r2,c2):
- if self.board[r][c] != '.':
- between += 1
- r += step_r
- c += step_c
- if target == '.':
- return between == 0
- else:
- return between == 1
- # 兵/卒
- if p in ('兵','卒'):
- if piece in RED_PIECES: # 红兵向上
- if dr >= 0:
- return False
- if abs(dc) + abs(dr) != 1:
- return False
- if r1 > 4: # 未过河
- if dc != 0:
- return False
- # 过河可直可横
- return True
- else: # 黑卒向下
- if dr <= 0:
- return False
- if abs(dc) + abs(dr) != 1:
- return False
- if r1 < 5: # 未过河
- if dc != 0:
- return False
- return True
- return False
- def is_in_check(self, board, color):
- # 查找己方将/帅位置
- king_piece = '帥' if color == 'red' else '將'
- king_pos = None
- for r in range(SIZE_ROW):
- for c in range(SIZE_COL):
- if board[r][c] == king_piece:
- king_pos = (r,c)
- break
- if king_pos:
- break
- if not king_pos:
- return True # 被吃将
- # 检查对方能否攻击到将
- enemy = 'black' if color == 'red' else 'red'
- for r in range(SIZE_ROW):
- for c in range(SIZE_COL):
- piece = board[r][c]
- if piece == '.':
- continue
- if (piece in RED_PIECES and color == 'red') or (piece in BLACK_PIECES and color == 'black'):
- continue
- # 对方棋子,判断是否能攻击到将
- if self.is_legal_move(r, c, king_pos[0], king_pos[1]):
- return True
- # 将帅对面
- red_king = None
- black_king = None
- for r in range(SIZE_ROW):
- for c in range(SIZE_COL):
- if board[r][c] == '帥':
- red_king = (r,c)
- elif board[r][c] == '將':
- black_king = (r,c)
- if red_king and black_king and red_king[1] == black_king[1]:
- between = False
- for r in range(min(red_king[0], black_king[0])+1, max(red_king[0], black_king[0])):
- if board[r][red_king[1]] != '.':
- between = True
- break
- if not between:
- return True
- return False
- def make_move(self, r1, c1, r2, c2):
- piece = self.board[r1][c1]
- captured = self.board[r2][c2]
- self.board[r2][c2] = piece
- self.board[r1][c1] = '.'
- self.move_history.append((r1,c1,r2,c2,piece,captured,self.turn))
- def undo_move(self):
- if not self.move_history:
- return
- r1,c1,r2,c2,piece,captured,turn = self.move_history.pop()
- self.board[r1][c1] = piece
- self.board[r2][c2] = captured
- self.turn = turn
- def on_click(self, event):
- if self.turn != 'red':
- return
- if self.selected is None:
- # 选择己方棋子
- c = (event.x - 20) // CELL_SIZE
- r = (event.y - 20) // CELL_SIZE
- if not self.is_inside(r,c):
- return
- piece = self.board[r][c]
- if piece == '.':
- return
- if piece in BLACK_PIECES:
- return
- self.selected = (r,c)
- self.draw_board()
- else:
- # 目标位置
- r1,c1 = self.selected
- c = (event.x - 20) // CELL_SIZE
- r = (event.y - 20) // CELL_SIZE
- if not self.is_inside(r,c):
- self.selected = None
- self.draw_board()
- return
- if (r,c) == self.selected:
- # 取消选择
- self.selected = None
- self.draw_board()
- return
- # 检查是否合法
- if not self.is_legal_move(r1,c1,r,c):
- # 可能想选其他己方棋子
- piece = self.board[r][c]
- if piece != '.' and piece in RED_PIECES:
- self.selected = (r,c)
- self.draw_board()
- return
- self.selected = None
- self.draw_board()
- return
- # 模拟走棋
- old_board = [row[:] for row in self.board]
- self.make_move(r1,c1,r,c)
- # 检查是否自将
- if self.is_in_check(self.board, 'red'):
- self.undo_move()
- self.selected = None
- self.draw_board()
- return
- # 检查是否吃掉对方将/帅
- if self.board[r][c] == '將':
- # 红胜
- self.draw_board()
- if messagebox.askyesno("胜利", "红方获胜!是否重新开始?"):
- self.reset_game()
- return
- # 切换回合
- self.turn = 'black'
- self.selected = None
- self.draw_board()
- # 记录棋谱
- move_str = self.format_move(r1,c1,r,c)
- self.move_log.insert(tk.END, "红方: {}\n".format(move_str))
- # 检查黑方是否被将死或困毙(这里简单处理,随机AI会响应)
- self.root.after(100, self.ai_move)
- def format_move(self, r1, c1, r2, c2):
- piece = self.board[r2][c2] # 实际移动后
- # 简化处理:显示源和目标行列
- return "({},{})->({},{})".format(r1,c1,r2,c2)
- def ai_move(self):
- if self.turn != 'black':
- return
- # 收集所有黑方合法走法
- moves = []
- for r in range(SIZE_ROW):
- for c in range(SIZE_COL):
- piece = self.board[r][c]
- if piece == '.' or piece in RED_PIECES:
- continue
- for dr in range(-9,10):
- for dc in range(-9,10):
- nr = r + dr
- nc = c + dc
- if not self.is_inside(nr,nc):
- continue
- if self.board[nr][nc] != '.' and self.board[nr][nc] in BLACK_PIECES:
- continue
- if self.is_legal_move(r,c,nr,nc):
- # 模拟走棋,检查是否自将
- old_board = [row[:] for row in self.board]
- captured = self.board[nr][nc]
- self.board[nr][nc] = piece
- self.board[r][c] = '.'
- incheck = self.is_in_check(self.board, 'black')
- self.board[r][c] = piece
- self.board[nr][nc] = captured
- if not incheck:
- moves.append((r,c,nr,nc))
- if not moves:
- # 无合法走法,黑方输
- if messagebox.askyesno("胜利", "红方获胜!是否重新开始?"):
- self.reset_game()
- return
- # 随机选择一步
- r1,c1,r2,c2 = random.choice(moves)
- # 记录棋谱
- piece = self.board[r1][c1]
- move_str = "({},{})->({},{})".format(r1,c1,r2,c2)
- self.make_move(r1,c1,r2,c2)
- self.turn = 'red'
- self.selected = None
- self.draw_board()
- self.move_log.insert(tk.END, "黑方: {}\n".format(move_str))
- # 检查红帅是否被吃
- if self.board[r2][c2] == '帥':
- if messagebox.askyesno("胜利", "黑方获胜!是否重新开始?"):
- self.reset_game()
- return
- def reset_game(self):
- self.board = [['.' for _ in range(SIZE_COL)] for _ in range(SIZE_ROW)]
- self.selected = None
- self.turn = 'red'
- self.move_history.clear()
- self.move_log.delete(1.0, tk.END)
- self.setup_board()
- self.draw_board()
- if __name__ == "__main__":
- root = tk.Tk()
- app = ChessApp(root)
- root.mainloop()
复制代码
优化下,怎么提醒中了飞刀,人机对战 |
|