Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
245 changes: 189 additions & 56 deletions GUI.py

Large diffs are not rendered by default.

15 changes: 12 additions & 3 deletions LAN.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ def identify(self) -> None:
""" 身份确认 """
self.send(msg='OK')
code = self.recv()['msg']
# code 末尾加上当前优先保护将设置
import GUI
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

一个建议:尽量不要在代码中间引入模块

protect_flag = '1' if getattr(GUI.Global, 'protect_king_when_check', True) else '0'
if len(code) <= 13:
code += protect_flag
modechange('LAN', code)
self.toplevel.destroy()
Thread(target=GUI.LANmove, daemon=True).start()
Expand Down Expand Up @@ -193,7 +198,7 @@ def modify() -> None:
self.again.set_live(True)

def connect(self) -> None:
""" 连接 """
""" ���接 """
address = self.combobox.get()
if not address:
return messagebox.showwarning('中国象棋', '请选择可用的目标地址!')
Expand All @@ -210,12 +215,16 @@ def identify(self) -> None:
""" 身份确认 """
if self.recv()['msg'] == 'OK':
code = [str(v.get()) for v in self.toplevel.var_list]
modechange('LAN', ''.join(code))
import GUI
protect_flag = '1' if getattr(GUI.Global, 'protect_king_when_check', True) else '0'
code_str = ''.join(code) + protect_flag
modechange('LAN', code_str)
for i in 1, 5, 9:
code[i], code[i+3] = code[i+3], code[i]
code[i+1], code[i+2] = code[i+2], code[i+1]
code[0] = '0' if code[0] == '1' else '1'
self.send(msg=''.join(code))
code_str2 = ''.join(code) + protect_flag
self.send(msg=code_str2)
self.toplevel.destroy()
Thread(target=GUI.LANmove, daemon=True).start()

Expand Down
17 changes: 10 additions & 7 deletions config.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
{
"scale": 1.404,
"auto_scale": true,
"virtual": true,
"level": 4,
"peace": 60,
"algo": 0
{
"scale": 0.5,
"auto_scale": false,
"virtual": true,
"level": 5,
"peace": 60,
"algo": 0,
"protect_king_when_check": false,
"ai_max_time": 1,
"allow_skip": true
}
11 changes: 5 additions & 6 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@
2. help.md: 帮助文本
3. statistic.json: 统计数据
4. audio/*.wav: 音效文件
5. data 棋局库
5. data/.../*.fen 棋局库

"""

# 版本号
__version__ = '1.8'
__version__ = '1.9.8'
# 作者
__author__ = '小康2022'
__author__ = '非主刘Non_main_liu'
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

你这是什么意思呢?

# 更新日期
__update__ = '2024/04/25'
__update__ = '2025/06/28'

if __name__ == '__main__':
from winsound import Beep
Expand All @@ -36,7 +37,5 @@

# 更新统计数据
statistic(Launch=1)
# 启动一下winsound模块
Beep(37, 0)
# 启动主窗口
Window()
154 changes: 144 additions & 10 deletions rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,38 @@
from configure import config, statistic
from tools import virtual

# 新增:将军时优先保护将的开关(可从config读取,默认True)
protect_king_when_check = config.get('protect_king_when_check', True)


def get_protect_king_when_check():
try:
import GUI
return getattr(GUI.Global, 'protect_king_when_check', config.get('protect_king_when_check', True))
except Exception:
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

一个建议:尽量减少使用 Exception 类,异常的捕获范围太大

return config.get('protect_king_when_check', True)


def rule(chesses: list[list], chess, flag_: bool = False) -> list[tuple[bool, int, int]]:
""" 返回可走位置 """
pos: list[tuple[bool, int, int]] = []

def ifappend(step: tuple[bool, int, int]) -> bool:
""" 应将判定 """
if not get_protect_king_when_check():
return True
if flag_:
color = '#FF0000' if chess.color == '#000000' else '#000000'
if color in virtual(chesses, chess, step, warn):
if color in virtual(chesses, chess, list(step), warn):
return False
return True

def append(x: int, y: int, flag: bool | None = None) -> None:
""" 添加位置 """
if flag and ifappend(step := (flag, x, y)):
pos.append(step)
elif chess_ := chesses[chess.y+y][chess.x+x]:
(step := (True, x, y))
elif (chess_ := chesses[chess.y+y][chess.x+x]):
step = (True, x, y)
if chess_.color != chess.color and ifappend(step):
pos.append(step)
elif ifappend(step := (False, x, y)):
Expand Down Expand Up @@ -110,6 +123,18 @@ def warn(chesses: list[list], color: str | None = None) -> list[str]:
def peace() -> bool:
""" 和棋判定 """
import GUI
# 新增:判定双方是否都没��能过河的棋子(马、车、炮、兵)
chesses = GUI.Global.chesses if hasattr(GUI.Global, 'chesses') else None
if chesses:
def has_cross_river_piece(color):
for line in chesses:
for chess in line:
if chess and chess.color == color and chess.name in '马馬车車炮砲兵卒':
# 红方(#FF0000)过河:y<=4,黑方(#000000)过河:y>=5
if (color == '#FF0000' and ((chess.name in '兵' and chess.y <= 4) or (chess.name in '马車炮' and chess.y <= 4))) or \
(color == '#000000' and ((chess.name in '卒' and chess.y >= 5) or (chess.name in '馬車砲' and chess.y >= 5))):
return True
return False
if GUI.Global.count >= config['peace']*2:
return True
if (ind := GUI.Global.index) >= 11:
Expand All @@ -120,11 +145,45 @@ def peace() -> bool:

def dead(chesses: list[list], color: str) -> str | None:
""" 绝杀判定(接收攻击者,返回攻击者) """
# 死将或将被吃都判负
# 1. 检查己方将/帅是否还在
king_alive = False
for line in chesses:
for chess in line:
if chess and chess.color == color and chess.name in '将帥':
king_alive = True
break
if king_alive:
break
if not king_alive:
# 己方将/帅已被吃,返回对方颜色
for line in chesses:
for chess in line:
if chess and chess.color != color:
return chess.color
return '#000000' if color == '#FF0000' else '#FF0000' # 兜底
# 2. 检查己方将/帅是否被对方吃掉(被攻击)
king_pos = None
for line in chesses:
for chess in line:
if chess and chess.color == color and chess.name in '将帥':
king_pos = (chess.x, chess.y)
break
if king_pos:
break
for line in chesses:
for chess in line:
if chess and chess.color != color:
for step in rule(chesses, chess):
tx, ty = chess.x + step[1], chess.y + step[2]
if king_pos and (tx, ty) == king_pos:
return chess.color
# 原有绝杀判定逻辑
for line in chesses:
for chess in line:
if chess and chess.color != color:
for step in rule(chesses, chess):
if not virtual(chesses, chess, step, warn, color):
if not virtual(chesses, chess, list(step), warn, color):
return
return color

Expand All @@ -138,7 +197,8 @@ def gameover(color: str | None = None) -> None:
who = '你'
if not color:
statistic(Peace=1)
return messagebox.showinfo('游戏结束', '本局和棋!\t')
messagebox.showinfo('游戏结束', '本局和棋!\t')
return
if GUI.Global.mode in 'LOCAL TEST':
tone, win = '', '获胜!'
who = '红方' if color == '#FF0000' else '黑方'
Expand Down Expand Up @@ -173,7 +233,7 @@ def switch() -> None:
elif GUI.Global.mode == 'LAN':
GUI.Global.player = '我方' if GUI.Global.player == '对方' else '对方'
else:
GUI.Global.player = '红方' if GUI.Global.player == '黑方' else '黑方'
GUI.Global.player = '红���' if GUI.Global.player == '黑方' else '黑方'
else:
if GUI.Global.first:
if GUI.Global.mode in 'LAN COMPUTER END':
Expand All @@ -188,7 +248,13 @@ def gameset(code: str | None = None) -> None:
""" 游戏设定 """
if code:
import GUI
GUI.Global.first = bool(int(code[0]))
# 兼容旧code长度
if len(code) > 13:
protect_king_flag = code[-1]
code = code[:-1]
GUI.Global.protect_king_when_check = (protect_king_flag == '1')
# 修复:根据code[0]����置Global.first,确保先手选择生效
GUI.Global.first = (code[0] == '1')
lis = [(0, 9), (8, 9), (0, 0), (8, 0), (1, 7), (7, 7),
(1, 2), (7, 2), (1, 9), (7, 9), (1, 0), (7, 0)]
for i, v in enumerate(code):
Expand All @@ -201,6 +267,18 @@ def gameset(code: str | None = None) -> None:
def modechange(mode: str, code: str | None = None) -> None:
""" 改变模式 """
import GUI
# 机机自弈参数解析
if mode == 'AIVS' and code and '|' in code:
# code: ai1_algo|ai1_depth|ai1_time|ai2_algo|ai2_depth|ai2_time
ai1_algo, ai1_depth, ai1_time, ai2_algo, ai2_depth, ai2_time = code.split('|')
GUI.Global.ai_vs_ai_conf = {
'red': {'algo': ai1_algo, 'depth': int(ai1_depth), 'max_time': int(ai1_time)},
'black': {'algo': ai2_algo, 'depth': int(ai2_depth), 'max_time': int(ai2_time)}
}
code = None # 不再传递给gameset
else:
if hasattr(GUI.Global, 'ai_vs_ai_conf'):
delattr(GUI.Global, 'ai_vs_ai_conf')
if mode != 'END':
GUI.Window.chess()
GUI.Global.cache.clear()
Expand All @@ -210,19 +288,24 @@ def modechange(mode: str, code: str | None = None) -> None:
GUI.Global.choose = None
gameset(code)
statistic(**{'Play': 1, mode: 1})
mode = '双人对弈' if mode == 'LOCAL' else '联机对抗' if mode == 'LAN' else '人机对战' if mode in 'COMPUTER' else '残局挑战' if mode == 'END' else 'AI测试'
GUI.Window.root.title('中国象棋 - %s' % mode)
mode_title = '双人对弈' if mode == 'LOCAL' else '联机对抗' if mode == 'LAN' else '人机对战' if mode in 'COMPUTER' else '残局挑战' if mode == 'END' else 'AI测试' if mode == 'TEST' else '机机自弈' if mode == 'AIVS' else mode
GUI.Window.root.title('中国象棋 - %s' % mode_title)
GUI.Global.player = None
GUI.Window.tip('— 提示 —\n游戏模式已更新\n为“%s”模式' % mode)
GUI.Window.tip('— 提示 —\n游戏模式已更新\n为“%s”模式' % mode_title)
switch()
if GUI.Global.mode in 'COMPUTER END' and not GUI.Global.first:
GUI.Window.root.after(
500, Thread(target=lambda: GUI.Window.AImove('#000000'), daemon=True).start)
# 机机自弈自动开始
if GUI.Global.mode == 'AIVS':
GUI.Window.root.after(
500, Thread(target=lambda: GUI.Window.AImove('#FF0000', True), daemon=True).start)


def revoke(flag: bool = False) -> None:
""" 撤销(悔棋) """
import GUI
import LAN
if flag or (GUI.Global.player and GUI.Global.mode in 'LOCAL' and GUI.Global.index >= 0):
if GUI.Global.choose:
GUI.Global.choose.virtual_delete()
Expand All @@ -243,6 +326,21 @@ def revoke(flag: bool = False) -> None:
revoke(True)
GUI.Window.root.after(600, revoke, True)
statistic(Revoke=-1)
elif GUI.Global.mode == 'LAN' and not flag:
# 联机模式下,主动方请求悔棋
LAN.API.send(type='revoke_request')
GUI.Window.tip('— 提示 —\n已向对方请求悔棋,等待同意...')
def wait_reply():
reply = LAN.API.recv()
if reply.get('type') == 'revoke_reply':
if reply.get('agree'):
revoke(True)
GUI.Window.tip('— 提示 —\n对方同意悔棋')
else:
GUI.Window.tip('— 提示 —\n对方拒绝悔棋')
else:
GUI.Window.tip('— 提示 —\n网络异常,悔棋失败')
GUI.Window.root.after(100, wait_reply)
else:
GUI.Window.tip('— 提示 —\n当前模式或状态下\n无法进行悔棋操作!')
GUI.Window.root.bell()
Expand All @@ -251,6 +349,7 @@ def revoke(flag: bool = False) -> None:
def recovery(flag: bool = False) -> None:
""" 恢复(悔棋) """
import GUI
import LAN
if flag or (GUI.Global.player and GUI.Global.mode == 'LOCAL' and -1 <= GUI.Global.index < len(GUI.Global.cache)-1):
if GUI.Global.choose:
GUI.Global.choose.virtual_delete()
Expand All @@ -267,6 +366,41 @@ def recovery(flag: bool = False) -> None:
recovery(True)
GUI.Window.root.after(600, recovery, True)
statistic(Recovery=-1)
elif GUI.Global.mode == 'LAN' and not flag:
# 联机模式下,主动方请求���复
LAN.API.send(type='recovery_request')
GUI.Window.tip('— 提示 —\n已向对方请求恢复,等待同意...')
def wait_reply():
reply = LAN.API.recv()
if reply.get('type') == 'recovery_reply':
if reply.get('agree'):
recovery(True)
GUI.Window.tip('— 提示 —\n对方同意恢复')
else:
GUI.Window.tip('— 提示 —\n对方拒绝恢复')
else:
GUI.Window.tip('— 提示 —\n网络异常,恢复失败')
GUI.Window.root.after(100, wait_reply)
else:
GUI.Window.tip('— 提示 —\n当前模式或状态下\n无法进行撤销悔棋操作!')
GUI.Window.root.bell()


def skip_turn() -> None:
"""停一手(跳过当前回合)"""
import GUI
if not config.get('allow_skip', False):
GUI.Window.tip('— 提示 —\n未开启停一手功能,请在设置中开启。')
return
if GUI.Global.mode not in ('LOCAL', 'COMPUTER', 'LAN') or not GUI.Global.player:
GUI.Window.tip('— 提示 —\n当前模式或状态下\n无法进行停一手操作!')
GUI.Window.root.bell()
return
# 清除选中状态
if GUI.Global.choose:
GUI.Global.choose.virtual_delete()
GUI.Global.choose.highlight(False, inside=False)
GUI.Global.choose = None
switch()
statistic(Skip=1)
GUI.Window.tip('— 提示 —\n已成功停一手,轮到对方。')
39 changes: 20 additions & 19 deletions statistic.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
{
"Launch": 0,
"Play": 0,
"First": 0,
"Time": 0,
"LAN": 0,
"LOCAL": 0,
"COMPUTER": 0,
"END": 0,
"TEST": 0,
"Win": 0,
"Lose": 0,
"Peace": 0,
"Eat": 0,
"Move": 0,
"Revoke": 0,
"Recovery": 0,
"Warn": 0,
"AI": 0
{
"Launch": 47,
"Play": 29,
"First": 14,
"Time": 357,
"LAN": 0,
"LOCAL": 5,
"COMPUTER": 23,
"END": 1,
"TEST": 0,
"Win": 0,
"Lose": 3,
"Peace": 0,
"Eat": 26,
"Move": 0,
"Revoke": 0,
"Recovery": 0,
"Warn": 10,
"AI": 0,
"Skip": 1
Comment on lines +1 to +20
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

数据没有清空

}
Loading