中国象棋

点击下载-> V4.2 (下载0 )

点击此处下载旧版本

点击此处下载-> V4.0 (下载0 )

点击此处下载-> V3.1 (下载0 )

点击此处下载-> 3.0版本 (下载1 )

点击此处下载-> 2.0版本 (下载0 )

点击此处查看旧版本公告

======1.0版本======
游戏的初版
======1.1版本======
1.修复了在绝杀判断时,可遮挡的情况被误判为不可遮挡
2.新增窗口标题变化。
3.悔棋按钮样式修改
======1.2版本======
1.新增存档功能,未被绝杀的存档可再次游玩(目前暂只支持本地游玩)
======1.3版本======
1.修复在绝杀判断时,敌方将军吃掉正将军子,但会出现面将,被误判未合法落点的情况
======1.4版本======
1.修复在联机模式下,将军时双方音效不一致的错误
2.修复在联机模式下,一方悔棋,另一方无法看见悔棋后的状态,并双方都显示轮到对方落子的错误
3.修复在判断是否送将时逻辑判断的前提条件的错误。
4.修复旧版本系统电脑下终端显示乱码问题。
======1.5版本======
1.修复了在将军情况下,选中可进行遮挡棋子后,正常绘制落点,但第二次点击非合法落点被直接误判为绝杀的错误
2.修复在联机模式下,悔棋时出现的复活不存在棋子的bug
3.更改日志保存方式
======1.6版本======
1.取消了调试时所用的右键退出功能
2.新增悔棋按钮右侧退出按钮(右键退出按钮即可关闭游戏)
3.在取消选择棋子时,建议使用右键点击非退出按钮的位置取消,计算量会小很多
4.美化悔棋按钮的交互
======1.7版本======
1。修改了联机模式下,棋子移动未同步的错误
======2.0版本======
———重大更新———
1.修改联机方式,采用与服务器直连的方式实现联机
2.修复在将军时,找到反吃解法却误判为绝杀的错误

======3.0版本======
1.性能优化
2.新增根据屏幕大小显示棋盘大小
3.修复在判断绝杀时误判的错误
4.优化了棋子的样式

======3.1版本======
1.修复特定条件下误判绝杀的错误

======4.0版本======
1.优化界面交互
2.增加催促落子按钮
3.修改退出按钮点击方式为左键单击

======4.2版本======
1.修复退出游戏后,棋盘未重置的bug
2.修复走子后棋子原位置显示逻辑错误的错误
3.优化性能

点击此处查看最新版本完整代码(旧版本代码不再提供,若有需要请联系我)
#define _CRT_SECURE_NO_WARNINGS
#include <winsock2.h>
#include <stdio.h>
#include <stdlib.h>
#include <easyx.h>
#include <graphics.h>
#include <windows.h>
#include <mmsystem.h>
#include <process.h>
#include <stdarg.h> // 用于处理可变参数
#include <time.h>   // 用于获取时间
#pragma  comment(lib, "winmm.lib")
#pragma  comment(lib, "ws2_32.lib")
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
#define printf LogToConsoleAndFile
// --- 日志功能结束 ---
#define RESET   "\033[0m"
#define ANSI_RED     "\033[31m"     /* 红色 */
#define ANSI_GREEN   "\033[32m"     /* 绿色 */
#define ANSI_YELLOW  "\033[33m"     /* 黄色 */
#define ANSI_BLUE    "\033[34m"     /* 蓝色 */

//服务器公网IP
#define SERVER_IP "139.155.134.179" 
//访问端口
#define SERVER_PORT 9999
//窗口
#define DESIGN_W 900 //设计时窗口宽度
#define DESIGN_H 1000//设计时窗口高度

#define MAX_INPUT_LEN 20


// --- 定义网络传输协议 ---
// 我们通过传输“数组下标”来确定是哪颗棋子,比传输坐标更精准
struct MovePacket {
    int pieceIndex; // 0-31,对应 all_qizi 数组的下标
    int toX;        // 落点的屏幕 X 坐标
    int toY;        // 落点的屏幕 Y 坐标
};
typedef struct qizi
{
    int x;//x坐标值
    int y;//y坐标值
    int sign;//棋子特征
}Qizi ;//棋子结构体
typedef struct point 
{
    int x;
    int y;
}Point;//坐标点结构体
typedef struct node
{
    int data_x;
    int data_y;
    struct node *next;
}Node;//链表节点结构体
typedef struct luodian_list
{
    Point arr[120];
    int top;
}List;

int judge(int sign, Qizi *p,List* list);//判断走子函数
int drawqizi(int x1,int y1,int sign);//绘制棋子函数
void AddLuodian(int x,int y,List* list);//增加落点
int Delete(Qizi *p);//删除棋子函数
void seconddown(Qizi *p);//第二次点击函数
void shuaxinqipan(void);//刷新棋盘函数
void duijuiformation(void);//对局信息显示函数
void free_linked_list(int mode);//释放链表函数 1全部释放 2只释放head链表 3只释放对方棋子的走法链表 4只释放判断送将时的
int this_point(int t,int j,int sign2);//检测该点是否有棋子(己方/敌方/无)//传的棋盘坐标
void HuiQi(void);
void ZanCun(Qizi *p);
void ZanCun1(Qizi *p);
bool in_QiziArea(int i);
void playJiangJunSound(void);
void songjiang(Qizi *p,List *list);//检测落点中是否有送将行为
void drawluodian(void);
int JueSha(void);//绝杀判断函数//1绝杀0未绝杀
int juesha_fanchi(int x1,int y1,int sign);//0可被吃1不可被吃
int juesha_bikai(void);//1可避开0无法避开
int jiancha_jiangjun(void);
unsigned __stdcall RecvThread(void* pParam);
Point zuobiao(Qizi *p);
void drawlsatstep(int x,int y);


int heng[] = {59,157,254,352,450,548,646,744,841};//横坐标数组
int shu[] = {59,156,254,353,450,547,647,745,843,941};//纵坐标数组

Qizi all_qizi[32];

int cnt=0;//0红方走子 1黑方走子
int lable = 0;//初始为零代表做的是非吃子悔棋

int ispao = 0;//1当前落子是炮,0非炮,用于炮判断,如果没有跳板,能否移动到该位置
int ische = 0;
int valid_move_found = 0;// 标记是否找到了合法移动
List *host;
List *duifang;
List *fanchi;
List *zhedang;
List *juesha;
List *temp;

Qizi *huiqi = NULL;
Qizi zancun = {0,0,0};
Qizi *huiqi1 = NULL;
Qizi zancun1 = {0,0,0};

ExMessage msg = { 0 };
Qizi *hoverQizi = NULL;  // 记录当前悬停的棋子
bool needRedraw = false; // 标记是否需要重绘
bool isUndoHover = false; // 记录鼠标是否悬浮在悔棋按钮
bool isExitHover = false; // 记录鼠标是否悬浮在退出按钮
bool isCuiCuHover = false;// 记录鼠标是否悬浮在催促按钮

bool isSingleHover = false;    //记录鼠标是否悬浮在单人模式按钮
bool isCreatRoomHover = false; //记录鼠标是否悬浮在创建房间按钮
bool isJoinRoomHover = false; //记录鼠标是否悬浮在加入房间按钮
bool isDebugHover = false;

Qizi *lastMovedPiece = NULL; // 记录上一步移动的棋子
int jiangjun_i;

int original_x = -1;
int original_y = -1;

int btnW = 300, btnH = 80;
int cx; 
int startY;

float scaleX = 1.0f;//x轴缩放比例
float scaleY = 1.0f;//y轴缩放比例

IMAGE backimage2;

// --- 定义游戏模式和网络变量 ---
enum GameMode { MODE_LOCAL, MODE_HOST, MODE_CLIENT };
GameMode current_mode = MODE_LOCAL; // 默认为本地双人
SOCKET g_sock = INVALID_SOCKET;     // 通信套接字

Point pt;//seconddown里用来检测第二次点击的位置
int is_network_mode = 0; // 0:单机模式 1:联机模式
SOCKET sock;             // 用于通信的套接字
// --- 线程安全锁 ---
CRITICAL_SECTION cs; // 定义一把锁
volatile bool g_isRemoteUndo = false; // 标记是否收到了对方的悔棋
bool g_showConsole = false;
void InitBoard(void)
{
    Qizi *qizi = all_qizi;
    Qizi init_data[32] = 
    {{heng[0],shu[0],1},//红左车
    {heng[8],shu[0],1},//红右车
    {heng[1],shu[0],2},//红左马
    {heng[7],shu[0],2},//红右马
    {heng[2],shu[0],3},//红左相
    {heng[6],shu[0],3},//红右相
    {heng[3],shu[0],4},//红左仕
    {heng[5],shu[0],4},//红右仕
    {heng[4],shu[0],5},//红将军
    {heng[1],shu[2],6},//红左炮                  
    {heng[7],shu[2],6},//红右炮
    {heng[0],shu[3],7},//红1兵
    {heng[2],shu[3],7},//红2兵
    {heng[4],shu[3],7},//红3兵
    {heng[6],shu[3],7},//红4兵
    {heng[8],shu[3],7},//红5兵
    {heng[0],shu[9],-1},//黑左车
    {heng[8],shu[9],-1},//黑右车
    {heng[1],shu[9],-2},//黑左马
    {heng[7],shu[9],-2},//黑右马
    {heng[2],shu[9],-3},//黑左相
    {heng[6],shu[9],-3},//黑右相
    {heng[3],shu[9],-4},//黑左仕
    {heng[5],shu[9],-4},//黑右仕
    {heng[4],shu[9],-5},//黑将军
    {heng[1],shu[7],-6},//黑左炮
    {heng[7],shu[7],-6},//黑右炮
    {heng[0],shu[6],-7},//黑1兵
    {heng[2],shu[6],-7},//黑2兵
    {heng[4],shu[6],-7},//黑3兵
    {heng[6],shu[6],-7},//黑4兵
    {heng[8],shu[6],-7}//黑5兵
    };
    for(int i=0;i<32;i++)
    {
        all_qizi[i] = init_data[i];
    }
    //所有变量初始化
    cnt = 0;
    lable = 0;
    ispao = 0;
    ische = 0;
    valid_move_found = 0;
    huiqi = NULL;
    zancun = {0,0,0};
    huiqi1 = NULL;
    zancun1 = {0,0,0};
    hoverQizi = NULL;
    needRedraw = false;
    isUndoHover = false;
    isExitHover = false;
    isCuiCuHover = false;
    lastMovedPiece = NULL;
    original_x = -100;
    original_y = -100;
}
int GetX(int x)
{
    return (int)(x * scaleX);
}
int GetY(int y)
{
    return (int)(y*scaleY);
}
int GetScaleSize(int size)
{
    return (int)(size * (scaleX < scaleY ? scaleX : scaleY));
}
int GetDesignX(int screenX)
{
    return (int)(screenX / scaleX);
}
int GetDesignY(int screenY)
{
    return (int)(screenY / scaleY);
}
// --- 日志功能开始 ---
// 创建 HTML 日志文件并写入头部信息(这里本来我做的是txt文件保存日志,但是那样复盘时观感不太好,让ai把我日志换成html文件存储)
void ResetLogFile(void) 
{
    // 改为 .html 后缀
    FILE* fp = fopen("chess_log.html", "w"); 
    if (fp != NULL) {
        time_t t = time(NULL);
        struct tm* tm_info = localtime(&t);
        
        // 写入 HTML 头部,设置背景黑,字白,字体等宽
        fprintf(fp, "<html><head><meta charset='gbk'></head>"); // 防止中文乱码
        fprintf(fp, "<body style='background-color:#1e1e1e; color:#cccccc; font-family:Consolas, \"Courier New\", monospace; font-size:14px;'>\n");
        
        fprintf(fp, "<h3>=== 新对局开始 [%04d-%02d-%02d %02d:%02d:%02d] ===</h3><hr>\n", 
            tm_info->tm_year + 1900, tm_info->tm_mon + 1, tm_info->tm_mday,
            tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec);
        fclose(fp);
    }
}
// 辅助函数:将缓冲区内容写入 HTML,同时转换颜色代码
void WriteBufferToHtml(const char* buffer) {
    FILE* fp = fopen("chess_log.html", "a");
    if (fp == NULL) return;

    for (int i = 0; buffer[i] != '\0'; i++) {
        // 1. 处理换行符:HTML 需要 <br> 才能换行
        if (buffer[i] == '\n') {
            fprintf(fp, "<br>\n");
        }
        // 2. 处理颜色转义字符 (检测 \033)
        else if (buffer[i] == '\033') {
            // 检查接下来的字符来判断颜色
            if (strncmp(&buffer[i], "\033[31m", 5) == 0) {
                fprintf(fp, "<span style='color:#ff5555; font-weight:bold;'>"); // 亮红色
                i += 4; // 跳过 [31m 这4个字符
            }
            else if (strncmp(&buffer[i], "\033[32m", 5) == 0) {
                fprintf(fp, "<span style='color:#55ff55; font-weight:bold;'>"); // 亮绿色
                i += 4;
            }
            else if (strncmp(&buffer[i], "\033[33m", 5) == 0) {
                fprintf(fp, "<span style='color:#ffff55; font-weight:bold;'>"); // 亮黄色
                i += 4;
            }
            else if (strncmp(&buffer[i], "\033[34m", 5) == 0) {
                fprintf(fp, "<span style='color:#5555ff; font-weight:bold;'>"); // 亮蓝色
                i += 4;
            }
            else if (strncmp(&buffer[i], "\033[0m", 4) == 0) {
                fprintf(fp, "</span>"); // 结束颜色标签
                i += 3;
            }
            // 如果有其他颜色代码,可以在这里继续添加
        }
        // 3. 普通字符直接写入
        else {
            fputc(buffer[i], fp);
        }
    }
    fclose(fp);
}

// 自定义日志函数:控制台保持原样,文件存为 HTML
int LogToConsoleAndFile(const char* format, ...) 
{
    va_list args;
    char buffer[4096]; // 缓冲区

    // 1. 格式化字符串到缓冲区
    va_start(args, format);
    vsnprintf(buffer, sizeof(buffer), format, args);
    va_end(args);

    // 2. 输出到控制台 (直接输出,保留 ANSI 颜色供黑框框显示)
    // 依然使用 fwrite 避免递归调用宏
    if (g_showConsole) 
    {
        fwrite(buffer, 1, strlen(buffer), stdout);
    }
    // 3. 输出到 HTML 文件 (转换颜色)
    WriteBufferToHtml(buffer);

    return 1;
}
// --- 存档功能开始 ---
const char* SAVE_FILE = "chess_save.dat";

void SaveGame(void) {
    FILE* fp = fopen(SAVE_FILE, "wb"); 
    if (fp != NULL) {
        // 1. 保存当前回合
        fwrite(&cnt, sizeof(int), 1, fp);
        // 2. 保存棋子数据
        fwrite(all_qizi, sizeof(Qizi), 32, fp);
        
        //保存最后落子棋子的下标
        int lastIndex = -1; // -1 代表没有棋子移动过(刚开局)
        if (lastMovedPiece != NULL) {
            // 利用指针相减计算下标 (例如: &all_qizi[5] - all_qizi = 5)
            lastIndex = (int)(lastMovedPiece - all_qizi); 
        }
        fwrite(&lastIndex, sizeof(int), 1, fp);

        fclose(fp);
    }
}

int LoadGame(void) {
    FILE* fp = fopen(SAVE_FILE, "rb"); 
    if (fp == NULL) 
    {
        return 0; 
    }
    
    // 1. 读取回合
    fread(&cnt, sizeof(int), 1, fp);
    // 2. 读取棋子数据
    fread(all_qizi, sizeof(Qizi), 32, fp);

    // 3. 读取并恢复最后落子的指针
    int lastIndex = -1;
    fread(&lastIndex, sizeof(int), 1, fp);
    
    if (lastIndex >= 0 && lastIndex < 32) 
    {
        // 根据下标恢复指针
        lastMovedPiece = &all_qizi[lastIndex]; 
    }
    else
    {
        lastMovedPiece = NULL;
    }

    fclose(fp);
    return 1;
}

void DeleteSaveFile(void) {
    remove(SAVE_FILE);
}
// --- 存档功能结束 ---
void playJiangJunSound(void)
{
    PlaySound(TEXT("sound\\jiangjun.wav"),NULL,SND_ASYNC | SND_FILENAME);
}
void playLuoZiSound(void)
{
    PlaySound(TEXT("sound\\luozi.wav"),NULL,SND_ASYNC | SND_FILENAME);
}
void playChiZiSound(void)
{
    PlaySound(TEXT("sound\\chizi.wav"),NULL,SND_ASYNC | SND_FILENAME);
}
void playKuaiDian(void)
{
    PlaySound(TEXT("sound\\kuaidian.wav"),NULL,SND_ASYNC | SND_FILENAME);
}
void checkMouseHover(int mouseX, int mouseY) 
{
    // 1. 保存上一帧的状态 (用于对比是否发生变化)
    Qizi *prevHover = hoverQizi;
    bool prevUndo = isUndoHover; // 保存悔棋按钮之前的状态
    bool prevExit = isExitHover; // 保存退出按钮之前的状态
    bool prevCuicu = isCuiCuHover;
    // 2. 重置当前帧的状态 (假设鼠标不在任何东西上)
    hoverQizi = NULL;
    // 必须重置,否则状态一旦变真就无法变回假
    isUndoHover = false; 
    isExitHover = false; 
    isCuiCuHover = false;
    // 3. 检测鼠标是否在棋子区域
    for(int i = 0; i < 32; i++) 
    {
        if(all_qizi[i].sign != 0 && 
           mouseX < all_qizi[i].x + 40 &&
           mouseX > all_qizi[i].x - 40 &&
           mouseY < all_qizi[i].y + 40 &&
           mouseY > all_qizi[i].y - 40) 
        {
            if((cnt == 0 && all_qizi[i].sign > 0) ||  
               (cnt == 1 && all_qizi[i].sign < 0))
            {  
                hoverQizi = &all_qizi[i];
            }
            break;
        }
    }

    // 4. 检测悔棋按钮 (792, 498, r=15)
    if (mouseX < 792+15 && mouseX > 792-15 && mouseY < 498+15 && mouseY > 498-15)
    {
        isUndoHover = true;
    }

    // 5. 检测退出按钮 (842, 498, r=15)
    if (mouseX < 842+15 && mouseX > 842-15 && mouseY < 498+15 && mouseY > 498-15)
    {
        isExitHover = true;
    }

    if(mouseX < 772 && mouseX > 742 && mouseY < 513 && mouseY > 483)
    {
        isCuiCuHover = true;
    }
    // 7. 检测状态是否发生变化
    // 如果棋子悬停变了,或者<按钮悬停状态变了>,才允许重绘
    if(prevHover != hoverQizi || prevUndo != isUndoHover || prevExit != isExitHover || prevCuicu != isCuiCuHover) 
    {
        needRedraw = true;
    }
}
//强制开启 Windows 控制台的 ANSI 颜色支持
void EnableANSIColor(void) 
{
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    if (hOut == INVALID_HANDLE_VALUE) return;

    DWORD dwMode = 0;
    if (!GetConsoleMode(hOut, &dwMode)) return;

    // ENABLE_VIRTUAL_TERMINAL_PROCESSING 的值是 0x0004
    // 告诉控制台:请解析转义序列(颜色代码),不要直接打印出来
    dwMode |= 0x0004; 
    
    SetConsoleMode(hOut, dwMode);
}
// ==================== 辅助工具开始 ====================

// 1. 按钮结构体
struct Button {
    int x, y, w, h;       
    const TCHAR* text;       
    COLORREF color;       
    COLORREF hoverColor; 

    void Draw(bool isHover) {
        setlinecolor(WHITE);
        setfillcolor(isHover ? hoverColor : color);
        
        fillroundrect(x, y, x + w, y + h, 10, 10);
        
        setbkmode(TRANSPARENT);
        settextcolor(WHITE);
        settextstyle(30, 0, _T("黑体"));
        
        int tx = x + (w - textwidth(text)) / 2;
        int ty = y + (h - textheight(text)) / 2;
        outtextxy(tx, ty, text);
    }
};
// 使用 EasyX 实现的数字输入框
// 返回输入的数字,如果取消则返回 -1
int GetRoomID_ByEasyX(int winW, int winH)
{
    char input[MAX_INPUT_LEN] = "";
    int inputLen = 0;
    
    int boxW = 240, boxH = 40;
    int x = (winW - boxW) / 2;
    int y = (winH - boxH) / 2;
    
    ExMessage msg;
    bool needRedraw = true;
    
    while (true)
    {
        // 只在需要时重绘
        if (needRedraw)
        {
            BeginBatchDraw();
            putimage(0,0,&backimage2);
            // 画输入框背景区域
            setfillcolor(RGB(40, 40, 40));
            fillroundrect(x - 50, y - 80, x + boxW + 50, y + 100, 15, 15);
            
            // 画标题
            setbkmode(TRANSPARENT);
            settextcolor(WHITE);
            settextstyle(30, 0, _T("黑体"));
            
            int titleW = textwidth(_T("请输入房间号"));
            outtextxy(x + (boxW - titleW) / 2, y - 60, _T("请输入房间号"));
            
            // 画输入框
            setlinecolor(RGB(0, 120, 215));
            setlinestyle(PS_SOLID, 2);
            setfillcolor(WHITE);
            fillrectangle(x, y, x + boxW, y + boxH);
            rectangle(x, y, x + boxW, y + boxH);
            
            // 画输入的文本
            settextcolor(BLACK);
            settextstyle(24, 0, _T("微软雅黑"));
            setbkmode(TRANSPARENT);
            
            // 转换 char* 到 TCHAR*
            TCHAR tInput[MAX_INPUT_LEN];
            #ifdef UNICODE
                MultiByteToWideChar(CP_ACP, 0, input, -1, tInput, MAX_INPUT_LEN);
            #else
                strcpy(tInput, input);
            #endif
            
            int textW = textwidth(tInput);
            int textH = textheight(tInput);
            outtextxy(x + 10, y + (boxH - textH) / 2, tInput);
            
            // 画光标(闪烁效果)
            static int cursorBlink = 0;
            cursorBlink++;
            if (cursorBlink % 60 < 30) // 每30帧切换一次
            {
                setlinecolor(BLACK);
                line(x + 10 + textW + 2, y + 8, x + 10 + textW + 2, y + boxH - 8);
            }
            
            // 画提示文字
            settextstyle(18, 0, _T("宋体"));
            settextcolor(RGB(180, 180, 180));
            int hintW = textwidth(_T("按 Enter 确认 / ESC 取消"));
            outtextxy(x + (boxW - hintW) / 2, y + 60, _T("按 Enter 确认 / ESC 取消"));
            
            EndBatchDraw();
            
            needRedraw = false;
        }
        
        // 处理消息
        while (peekmessage(&msg, EX_KEY))
        {
            if (msg.message == WM_KEYDOWN)
            {
                if (msg.vkcode == VK_ESCAPE)
                {
                    return -1; // 取消输入
                }
                else if (msg.vkcode == VK_RETURN)
                {
                    // 确认输入
                    if (inputLen > 0)
                    {
                        return atoi(input); // 返回转换后的数字
                    }
                    else
                    {
                        // 没有输入内容,提示一下
                        BeginBatchDraw();
                        putimage(0,0,&backimage2);
                        settextcolor(RGB(255, 100, 100));
                        settextstyle(16, 0, _T("宋体"));
                        int warnW = textwidth(_T("请输入房间号!"));
                        outtextxy(x + (boxW - warnW) / 2, y - 25, _T("请输入房间号!"));
                        EndBatchDraw();
                        Sleep(1000);
                        needRedraw = true;
                    }
                }
                else if (msg.vkcode == VK_BACK && inputLen > 0)
                {
                    // 退格
                    input[--inputLen] = '\0';
                    needRedraw = true;
                }
                else if (inputLen < MAX_INPUT_LEN - 1)
                {
                    // 检查是否为数字键
                    char c = msg.vkcode;
                    if (c >= '0' && c <= '9')
                    {
                        input[inputLen++] = c;
                        input[inputLen] = '\0';
                        needRedraw = true;
                    }
                    else if (c >= VK_NUMPAD0 && c <= VK_NUMPAD9)
                    {
                        // 小键盘数字
                        input[inputLen++] = (c - VK_NUMPAD0) + '0';
                        input[inputLen] = '\0';
                        needRedraw = true;
                    }
                }
            }
        }
        
        // 即使不需要完全重绘,也要让光标闪烁
        if (!needRedraw)
        {
            Sleep(16); // 约60FPS
            needRedraw = true; // 强制重绘以实现光标闪烁
        }
        Sleep(16);
    }
}
int main(void)
{
    // --- 1. 基础初始化 ---
    EnableANSIColor();
    InitializeCriticalSection(&cs);
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    // --- 2. 窗口初始化 ---
    int screenW = GetSystemMetrics(SM_CXSCREEN);
    int screenH = GetSystemMetrics(SM_CYSCREEN);
    int winH = (int)(screenH * 0.9);
    int winW = (int)(winH * (900.0/1000.0));
    scaleX = (float)winW / DESIGN_W;
    scaleY = (float)winH / DESIGN_H;

    initgraph(winW, winH);
    IMAGE backimage1;
    loadimage(&backimage1,_T("./image/backimage1.PNG"),getwidth(),getheight());
    loadimage(&backimage2,_T("./image/backimage2.PNG"),getwidth(),getheight());
    putimage(0,0,&backimage1);
    setbkmode(TRANSPARENT);
    
    HWND hwnd = GetHWnd(); 
    MoveWindow(hwnd, 0, 0, winW, winH, TRUE);
    
    SetWindowText(hwnd, _T("中国象棋 - 游戏大厅"));

    // --- 3. 准备菜单按钮 ---
    cx = (winW - btnW) / 2; 
    startY = winH / 2 - 150;

    Button btnLocal  = { cx, startY,       btnW, btnH, _T("本地双人对战"), RGB(0, 100, 200), RGB(0, 59, 120) };
    Button btnCreate = { cx, startY + 100, btnW, btnH, _T("创建房间 (先手)"), RGB(0, 180, 100), RGB(0, 108, 59) };
    Button btnJoin   = { cx, startY + 200, btnW, btnH, _T("加入房间 (后手)"), RGB(200, 100, 0),  RGB(120, 60, 0) };
    Button btnDebug = { 20, 20, 180, 50, _T("显示调试: 关"), RGB(80, 80, 80), RGB(0, 150, 0) };
    btnLocal.Draw(isSingleHover);
    btnCreate.Draw(isCreatRoomHover);
    btnJoin.Draw(isJoinRoomHover);
    btnDebug.Draw(isDebugHover); 
    int gameState = 0; // 0:菜单, 1:游戏中
    
    // 初始化链表
    host = (List*)malloc(sizeof(List));    if (host) host->top = 0;
    duifang = (List*)malloc(sizeof(List)); if (duifang) duifang->top = 0;
    fanchi = (List*)malloc(sizeof(List));  if (fanchi) fanchi->top = 0;
    zhedang = (List*)malloc(sizeof(List)); if (zhedang) zhedang->top = 0;
    juesha = (List*)malloc(sizeof(List));  if (juesha) juesha->top = 0;
    temp = (List*)malloc(sizeof(List));
    SetForegroundWindow(hwnd);
    // ==================== 主循环 ====================
    while (true)
    {
       // ------------------ 状态 0: 游戏大厅 ------------------
        if (gameState == 0)
        {
            InitBoard();
            ExMessage m;
            // === 1. 计算当前鼠标处于什么状态 ===
            // 0: 空白处
            // 1: 在"本地"按钮里
            // 2: 在"创建"按钮里
            // 3: 在"加入"按钮里
            while (peekmessage(&m, EX_MOUSE)) 
            {
                isSingleHover = false;
                isCreatRoomHover = false;
                isJoinRoomHover = false;
                isDebugHover = false;

                int logicX = m.x;
                int logicY = m.y;
                if(m.message == WM_MOUSEMOVE) 
                {
                    if(logicX>=cx&&logicX<=cx+btnW)
                    {
                        if(logicY>=startY&&logicY<=startY+btnH)
                        {
                            isSingleHover = true;
                        }
                        else if (logicY>=startY+100&&logicY<=startY+100+btnH)
                        {
                            isCreatRoomHover = true;
                        }
                        else if(logicY>=startY+200&&logicY<=startY+200+btnH)
                        {
                            isJoinRoomHover = true;
                        }
                    }
                    if (g_showConsole) 
                    {
                        btnDebug.text = _T("显示调试: 开");
                        btnDebug.color = RGB(0, 150, 0); // 开启时变绿
                        btnDebug.hoverColor = RGB(60, 140, 60);
                    }
                    else 
                    {
                        btnDebug.text = _T("显示调试: 关");
                        btnDebug.color = RGB(80, 80, 80); // 关闭时灰色
                        btnDebug.hoverColor = (70, 110, 70);//设置悬浮颜色
                    }
                    if (logicX >= btnDebug.x && logicX <= btnDebug.x + btnDebug.w &&
                        logicY >= btnDebug.y && logicY <= btnDebug.y + btnDebug.h)
                    {
                        if(g_showConsole)
                        {
                            btnDebug.text = _T("显示调试: 关");
                        }
                        else
                        {
                            btnDebug.text = _T("显示调试: 开");
                        }
                        isDebugHover = true;
                    }
                }
                BeginBatchDraw();
                putimage(0,0,&backimage1);
                btnLocal.Draw(isSingleHover);
                btnCreate.Draw(isCreatRoomHover);
                btnJoin.Draw(isJoinRoomHover);
                btnDebug.Draw(isDebugHover);
                EndBatchDraw();
                // === 3. 点击逻辑 ===
                if (m.message == WM_LBUTTONDOWN)
                {
                    if (logicX >= btnDebug.x && logicX <= btnDebug.x + btnDebug.w &&
                        logicY >= btnDebug.y && logicY <= btnDebug.y + btnDebug.h)
                    {
                        g_showConsole = !g_showConsole; // 切换状态
                        if (g_showConsole) 
                        {
                            // === 开启:手动申请一个控制台 ===
                            if (AllocConsole())
                            {
                                // 因为标准输出流默认是断开的,必须重新连接
                                freopen("CONOUT$", "w", stdout); 
                                freopen("CONOUT$", "w", stderr); 

                                // 重新开启颜色支持
                                EnableANSIColor(); 
                                SetConsoleTitle(_T("象棋调试日志"));
                                
                                // 设为前台
                                HWND hC = GetConsoleWindow();
                                SetForegroundWindow(hC); 
                                
                                printf(ANSI_GREEN "调试控制台已开启 (VS Code Mode)..." RESET "\n");
                            }
                        }
                        else 
                        {
                            // === 关闭:直接销毁 ===
                            freopen("NUL", "w", stdout);
                            freopen("NUL", "w", stderr);
                            FreeConsole();
                            SetForegroundWindow(hwnd);
                        }
                    }
                    if(logicX>=cx&&logicX<=cx+btnW)
                    {
                        if (logicY>=startY&&logicY<=startY+btnH) 
                        { // 点了本地
                            current_mode = MODE_LOCAL;
                            gameState = 1; 
                        }
                        else if ((logicY>=startY+100&&logicY<=startY+100+btnH)||(logicY>=startY+200&&logicY<=startY+200+btnH)) // 点了网络
                        {
                            int role = (logicY>=startY+100&&logicY<=startY+100+btnH) ? 1 : 2; 
                            
                            // 进输入框前,把状态重置,保证回来时能重绘

                            int roomId = GetRoomID_ByEasyX(winW, winH);

                            if (roomId != -1) 
                            {
                                // 强制重绘一次背景,显示"连接中"
                                BeginBatchDraw();
                                cleardevice();
                                putimage(0,0,&backimage2);
                                settextstyle(30, 0, _T("黑体"));
                                outtextxy((winW - textwidth(_T("正在连接服务器...")))/2, winH/2, _T("正在连接服务器..."));
                                EndBatchDraw();

                                g_sock = socket(AF_INET, SOCK_STREAM, 0);
                                sockaddr_in addr;
                                addr.sin_family = AF_INET;
                                addr.sin_port = htons(SERVER_PORT);
                                addr.sin_addr.s_addr = inet_addr(SERVER_IP);
                                
                                if (connect(g_sock, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
                                    MessageBox(hwnd, _T("连接服务器失败!"), _T("错误"), MB_OK | MB_ICONERROR);
                                } 
                                else {
                                    int handshake[2] = { roomId, role };
                                    send(g_sock, (char*)handshake, sizeof(handshake), 0);
                                    
                                    BeginBatchDraw();
                                    cleardevice();
                                    putimage(0,0,&backimage2);
                                    settextcolor(RED);
                                    outtextxy((winW - textwidth(_T("已连接,等待对手加入...")))/2, winH/2, _T("已连接,等待对手加入..."));
                                    EndBatchDraw();
                                    
                                    int signal = 0;
                                    recv(g_sock, (char*)&signal, sizeof(signal), 0);
                                    
                                    if (signal == 1) {
                                        current_mode = (role == 1) ? MODE_HOST : MODE_CLIENT;
                                        _beginthreadex(NULL, 0, RecvThread, NULL, 0, NULL);
                                        gameState = 1; 
                                    } else {
                                        MessageBox(hwnd, _T("匹配失败"), _T("提示"), MB_OK);
                                        closesocket(g_sock);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        // ------------------ 状态 1: 游戏中 ------------------
        else if (gameState == 1)
        {
            static bool isGameInit = false;
            if (!isGameInit) {
                SetWindowText(hwnd, (current_mode == MODE_LOCAL) ? _T("中国象棋 - 本地对战") : _T("中国象棋 - 联机对战"));
                shuaxinqipan();
                isGameInit = true;
            }

            while(peekmessage(&msg, EX_MOUSE))
            {
                int logicX = GetDesignX(msg.x);
                int logicY = GetDesignY(msg.y);

                if(msg.message == WM_MOUSEMOVE) 
                {
                    checkMouseHover(logicX, logicY);
                }
                if(needRedraw) 
                {
                    BeginBatchDraw();
                    shuaxinqipan();
                    EndBatchDraw();
                    needRedraw = false;
                }
                
                if(msg.message == WM_LBUTTONDOWN)
                {
                    //退出
                    if( logicX < 842+15 && logicX > 842-15 && logicY < 498+15 && logicY > 498-15)
                    {
                       if (current_mode != MODE_LOCAL && g_sock != INVALID_SOCKET) 
                        {
                            MovePacket packet;
                            packet.pieceIndex = -100; // 【约定】 -100 代表退出
                            packet.toX = 0; 
                            packet.toY = 0;
                            // 发送告别包
                            send(g_sock, (const char*)&packet, sizeof(packet), 0);
                            
                            // 稍微等一下,确保数据发出去后再关socket(可选,但推荐)
                            Sleep(50); 
                            
                            // 关闭 socket
                            closesocket(g_sock);
                            g_sock = INVALID_SOCKET; // 标记为无效,防止线程里再误报
                        }
                        gameState = 0;
                        isGameInit = false;
                        SetWindowText(hwnd, _T("中国象棋 - 游戏大厅"));
                    }
                    // 悔棋
                    if(logicX<792+15 && logicX>792-15 && logicY<498+15 && logicY>498-15) {
                        BeginBatchDraw();
                        HuiQi();
                        EndBatchDraw();
                    }
                    if(logicX < 772 &&  logicX> 742 && logicY < 513 && logicY > 483)
                    {
                        playKuaiDian();
                        if (current_mode != MODE_LOCAL && g_sock != INVALID_SOCKET) 
                        {
                            MovePacket packet;
                            packet.pieceIndex = -98; // 【约定】 -98 代表催促
                            packet.toX = 0; 
                            packet.toY = 0;
                            send(g_sock, (const char*)&packet, sizeof(packet), 0);
                            printf(ANSI_YELLOW "网络发送: 催促对方快点!" RESET "\n");
                        }
                    }
                    // 棋子逻辑
                    for(int i=0;i<32;i++)
                    {
                        if(logicX<all_qizi[i].x+40 && logicX>all_qizi[i].x-40 &&
                           logicY<all_qizi[i].y+40 && logicY>all_qizi[i].y-40)
                        {
                            if (current_mode == MODE_HOST && all_qizi[i].sign < 0) break;
                            if (current_mode == MODE_CLIENT && all_qizi[i].sign > 0) break;
                            if((cnt==0 && all_qizi[i].sign<0) || (cnt==1 && all_qizi[i].sign>0)) break;

                            BeginBatchDraw();
                            int original_x = all_qizi[i].x;
                            int original_y = all_qizi[i].y;
                            if(i==9||i==10||i==25||i==26) ispao = 1;
                            if(i==0||i==1||i==16||i==17) ische = 1;
                            
                            judge(all_qizi[i].sign,&(all_qizi[i]),host);
                            ispao = 0; ische = 0;
                            songjiang(&all_qizi[i],host);
                            drawluodian();
                            
                            valid_move_found = 0;
                            seconddown(&all_qizi[i]); 
                            free_linked_list(1);

                            if(pt.x==-1 && pt.y==-1) {
                                shuaxinqipan();
                                EndBatchDraw();
                                break;
                            }

                            if(jiancha_jiangjun()==1) {
                                playJiangJunSound();
                                if(JueSha()==1) {
                                    DeleteSaveFile();
                                    MessageBox(hwnd, _T("绝杀!游戏结束!"), _T("提示"), MB_OK);
                                    
                                    // 游戏结束回到菜单
                                    gameState = 0;
                                    isGameInit = false;
                                    SetWindowText(hwnd, _T("中国象棋 - 游戏大厅"));
                                }
                            }
                            free_linked_list(1);
                            break;
                        }
                    }
                }
            }
        }
        Sleep(16);
    }
    
    closegraph();
    return 0;
}
// 处理接收到的网络走法
// 找到原有的 ApplyRemoteMove 函数,替换为如下内容:
void ApplyRemoteMove(MovePacket packet) {
    //  --- 处理悔棋指令 ---
    if (packet.pieceIndex == -99) {
        printf(ANSI_YELLOW "网络同步: 对方进行了悔棋操作。" RESET "\n");
        g_isRemoteUndo = true;
        if(huiqi!=NULL)
        {
            if(lable==0) {
                huiqi->x = zancun.x; huiqi->y = zancun.y; huiqi->sign = zancun.sign;
            } else if(lable==1) {
                huiqi->x = zancun.x; huiqi->y = zancun.y; huiqi->sign = zancun.sign;
                huiqi1->x = zancun1.x; huiqi1->y = zancun1.y; huiqi1->sign = zancun1.sign;
                lable = 1 - lable;
            }
            cnt = 1 - cnt;
            zancun.x = 0; zancun.y = 0; zancun.sign = 0;
            zancun1.x = 0; zancun1.y = 0; zancun1.sign = 0;
            huiqi = NULL;
            huiqi1 = NULL;
            lastMovedPiece = NULL;
            SaveGame();
            needRedraw = true; // 触发主循环重绘
        }
        return; 
    }
    if (packet.pieceIndex == -98) 
    {
        printf(ANSI_YELLOW "网络消息: 对方正在催促你!" RESET "\n");
        
        // 播放音效
        playKuaiDian();
        return;
    }
    if (packet.pieceIndex == -100) 
    {
        printf(ANSI_RED "网络消息: 对方已退出游戏。" RESET "\n");
        
        // 1. 关闭 socket,防止继续接收报错
        if (g_sock != INVALID_SOCKET) {
            closesocket(g_sock);
            g_sock = INVALID_SOCKET; 
        }

        // 2. 弹窗提示 B
        MessageBox(GetHWnd(), _T("对方已退出游戏!"), _T("提示"), MB_OK | MB_ICONWARNING);
        
        return; 
    }
    // -----------------------------

    if (packet.pieceIndex < 0 || packet.pieceIndex >= 32) return;
    
    Qizi* p = &all_qizi[packet.pieceIndex];
    lable = 0;
    // 1. 检测是否有吃子
    int miusic_lable1 = 0;//判断是否播放过吃子音效,若播放过则不再播放落子音效(落子音效已包含在吃子音效内)
    for (int k = 0; k < 32; k++) {
        if (all_qizi[k].sign != 0 && all_qizi[k].x == packet.toX && all_qizi[k].y == packet.toY) {
            ZanCun1(&all_qizi[k]); 
            all_qizi[k].x = 0;
            all_qizi[k].y = 0;
            all_qizi[k].sign = 0; 
            playChiZiSound();
            miusic_lable1 = 1; 
            printf("网络同步: 对方吃掉了棋子ID[%d]\n", k);
            break;
        }
    }

    // 2. 移动棋子
    ZanCun(p); 
    original_x = p->x; 
    original_y = p->y;
    p->x = packet.toX;
    p->y = packet.toY;
    
    
    // 3. 播放音效与切换回合
    // 注意:如果有吃子,playChiZiSound 已经在上面播放了
    if (miusic_lable1 == 0) {
        playLuoZiSound();
    }
    miusic_lable1 = 0;
    
    lastMovedPiece = p;
    cnt = 1 - cnt; // 切换回合

    // 4. --- 检测是否将军 ---
    // 此时棋子已经移动,cnt 已经切换,正好符合 jiancha_jiangjun 的检测时机
    if (jiancha_jiangjun() == 1) {
        // 如果检测到将军,播放将军音效
        // 由于 EasyX 的 PlaySound 默认是 SND_ASYNC (异步),
        // 后播放的声音会打断前播放的。
        // 所以这里播放将军音效,会覆盖掉刚才可能播放的落子/吃子音效,这正是我们想要的。
        playJiangJunSound(); 
        printf(ANSI_RED "网络同步: 检测到对方将军!" RESET "\n");
    }
    // ------------------------------

    SaveGame(); 
    
    // 5. 重绘
    needRedraw = true; 
    
    printf(ANSI_BLUE "网络同步: 对方将棋子ID[%d] 移动到了 (%d,%d)" RESET "\n", packet.pieceIndex, packet.toX, packet.toY);
}

// 接收线程
unsigned __stdcall RecvThread(void* pParam) {
    MovePacket packet;
    while (true) 
    {
        if (g_sock == INVALID_SOCKET) break;
        int ret = recv(g_sock, (char*)&packet, sizeof(packet), 0);
        if (g_sock == INVALID_SOCKET) break;
        if (ret > 0) {
            // 收到数据,进行处理
            // 为了线程安全,这里简单处理,实际上 EasyX 绘图最好在主线程
            // 但只要逻辑数据不冲突,在此处更新数据,主线程刷新是可行的
            EnterCriticalSection(&cs);
            ApplyRemoteMove(packet);
            LeaveCriticalSection(&cs);
        }
        else
        {
            printf(ANSI_RED "与对方断开连接。" RESET "\n");
            if (g_sock != INVALID_SOCKET)
            {
                closesocket(g_sock);
                // 弹出提示框 (使用 EasyX 提供的 GetHWnd 获取窗口句柄)
                MessageBox(GetHWnd(), _T("对方已退出游戏或网络断开连接!"), _T("游戏提示"), MB_OK | MB_ICONWARNING);
            }
            break;
        }
        Sleep(16);
    }
    return 0;
}
int Delete(Qizi *p)
{
    ZanCun1(p);
    p->x=-1;
    p->y=-1;
    p->sign=0;
    return 1;
}
void seconddown(Qizi *p)
{
    printf(ANSI_RED "seconddown..." RESET "\n");
    g_isRemoteUndo = false;
    while(1)
    {
        if (g_isRemoteUndo) 
        {
            // 收到悔棋,强制中断当前操作
            printf(ANSI_YELLOW "检测到对方悔棋,中断当前落子操作!" RESET "\n");
            pt.x = -1;
            pt.y = -1;
            g_isRemoteUndo = false; // 重置标志
            
            // 强制刷新一下棋盘,把刚才拿起来的棋子“放回去”
            shuaxinqipan(); 
            return; // 直接退出函数,取消本次移动
        }
        EndBatchDraw();
        BeginBatchDraw();
        while(peekmessage(&msg,EX_MOUSE))
        {
            int logicX = GetDesignX(msg.x);
            int logicY = GetDesignY(msg.y);
            if(msg.message==WM_LBUTTONDOWN)//左键按下
            {
                for(int i=0;i<9;i++)                                                    
                {
                    for(int j=0;j<10;j++)
                    {
                        if(logicX<heng[i]+40&&
                            logicX>heng[i]-40&&
                            logicY<shu[j]+40&&
                            logicY>shu[j]-40)
                        {                             
                            pt.x=heng[i];
                            pt.y=shu[j];
                            if(heng[i]==p->x&&shu[j]==p->y)//点击原位置,重新选择
                            {
                                pt.x=-1;
                                pt.y=-1;
                                return;
                            }
                            goto second;
                        }
                    }
                }
            } 
            else if(msg.message==WM_RBUTTONDOWN)//右键按下取消选择 
            {
                pt.x=-1;
                pt.y=-1;
                return;
            }  
            
        }
        Sleep(16);
    }
    second:
    int miusic_lable1 = 0;//判断是否播放过吃子音效,若播放过则不再播放落子音效(落子音效已包含在吃子音效内)
    if(pt.x!=-1&&pt.y!=-1)
    {
        
        for(int i=0;i<host->top;i++)
        {
            if(pt.x==host->arr[i].x&&pt.y==host->arr[i].y)
            {
                int k=p->sign>0?16:0;
                int k1=(k==16)?32:16; 
                for(;k<k1;k++)
                {
                    if(all_qizi[k].x==pt.x&&all_qizi[k].y==pt.y)
                    {
                        Delete(&all_qizi[k]);
                        playChiZiSound();
                        miusic_lable1 = 1;
                        break;
                    }
                    else 
                    {
                        lable =0;
                    }
                }
                ZanCun(p);
                if(miusic_lable1==0)
                {
                    playLuoZiSound();
                }
                miusic_lable1 = 0;
                //更新位置
                original_x = p->x;
                original_y = p->y;
                p->x=pt.x;
                p->y=pt.y;
                // 2. --- 发送网络数据 ---
                if (current_mode != MODE_LOCAL && g_sock != INVALID_SOCKET) {
                    MovePacket packet;
                    // 计算当前棋子在数组中的下标 (利用指针减法)
                    packet.pieceIndex = (int)(p - all_qizi); 
                    packet.toX = p->x;
                    packet.toY = p->y;
                    
                    send(g_sock, (const char*)&packet, sizeof(packet), 0);
                    printf("网络发送: 移动了棋子ID[%d] 到 (%d,%d)\n", packet.pieceIndex, packet.toX, packet.toY);
                }
                // ------------------------------

                printf(ANSI_YELLOW"----------------------%s方移动了棋子到(%d,%d)----------------------" RESET "\n",p->sign>0?"红":"黑",p->x,p->y);
                cnt=1-cnt;
                lastMovedPiece = p;//记录上一步移动的棋子
                SaveGame(); // 保存游戏
                valid_move_found = 1; // 标记找到了合法移动
                break;
            }
        }
        if (valid_move_found == 0) 
        {
        pt.x = -1;
        pt.y = -1;
        }
    }
    shuaxinqipan();
    EndBatchDraw();
}
int this_point(int t,int j,int sign2)//检测该点是否有棋子(己方/敌方/无)传的棋盘坐标
{
    printf("正在检测点(%d,%d)\n",heng[t],shu[j]);
    if(t<0||t>8)//横坐标出界
    {
        printf("该点横坐标出界\n");
        return -1;
    }
    if(j<0||j>9)//纵坐标出界
    {
        printf("该点纵坐标出界\n");
        return -1;
    }
    int k;
    for(k=0;k<32;k++)
    {
        if(all_qizi[k].x==heng[t]&&all_qizi[k].y==shu[j]&&sign2*all_qizi[k].sign>0)//己方棋子
        {
            printf("发现己方棋子(%d,%d)\n",heng[t],shu[j]);
            return -1;//发现己方棋子
        }else if(all_qizi[k].x==heng[t]&&all_qizi[k].y==shu[j]&&sign2*all_qizi[k].sign<0)//敌方棋子
        { if(k==8||k==24)
            {
                printf("发现对方将军棋子(%d,%d)\n",heng[t],shu[j]);
                return 2;//发现将军!
            }
            printf("发现对方棋子(%d,%d)\n",heng[t],shu[j]);
            return 1;//发现对方棋子
        }
    }
    printf("该点无棋子(%d,%d)\n",heng[t],shu[j]);
    return 0;//无棋子
}
Point zuobiao(Qizi *p)//得到棋子在棋盘上的坐标
{
    Point ij;
    if(p->x==0)
    {
        ij.x = -1;
        ij.y = -1;
        return ij;
    }
    for(ij.x=0;ij.x<9;ij.x++)
    {
        if(heng[ij.x]==p->x)
        {
            break;
        }
    }
    for(ij.y=0;ij.y<10;ij.y++)
    {
        if(shu[ij.y]==p->y)
        {
            break;
        }
    }
    return ij;
}
void jiangjun(Qizi *p,List* list)//将军的走法//mode不等于0
{
    Point ij = zuobiao(p);
    int i = ij.x;
    int j = ij.y;
    BeginBatchDraw();
    if(p->sign==5)//红方
    {
        if(i>=3 && i<=5 && j>=0 && j<=2)
        {   
            if(i+1>=3 && i+1<=5)//右
            {
                int s;
                if(heng[i-1]==all_qizi[24].x)
                {
                    for(int t=j+1;t<=9;t++)
                    {
                        s =this_point(i-1,t,5);
                        if(s==2||s==1||s==-1)
                        {
                            break;
                        }
                    }
                }
                //右方是否有己方棋子(是-1)
                if(s!=2 && this_point(i+1,j,p->sign) != -1)
                {
                    printf(ANSI_RED"右方落点合法,添加落点(%d,%d)" RESET "\n",heng[i+1],shu[j]);
                    AddLuodian(heng[i+1],shu[j],list);
                }
            } 
            if(i-1>=3 && i-1<=5)//左
            {
                int s1;
                if(heng[i+1]==all_qizi[24].x)
                {
                    for(int t=j+1;t<=9;t++)
                    {
                        s1 =this_point(i+1,t,5);
                        if(s1==2||s1==1||s1==-1)
                        {
                            break;
                        }
                    }
                }
                if(s1!=2 && this_point(i-1,j,p->sign) != -1)
                {
                    AddLuodian(heng[i-1],shu[j],list);
                }
            }
            if(j-1>=0 && j-1<=2)//上
            {
                if(this_point(i,j-1,p->sign) != -1)
                {
                    AddLuodian(heng[i],shu[j-1],list);
                }
            }
            if(j+1>=0 && j+1<=2)//下
            {
                if(this_point(i,j+1,p->sign) != -1)
                {
                    AddLuodian(heng[i],shu[j+1],list);
                }
            }
        }
    }
    else //黑方
    {
        if(i>=3&&i<=5&&j>=7&&j<=9)
        {
            if(i+1>=3 && i+1<=5)//右
            {
                int s3;
                if(heng[i+1]==all_qizi[8].x)
                {
                    for(int t=j-1;t>=0;t--)
                    {
                        s3 =this_point(i+1,t,-5);
                        if(s3==2||s3==1||s3==-1)
                        {
                            break;
                        }
                    }
                }
                if(s3!=2 && this_point(i+1,j,p->sign) != -1)
                {
                    AddLuodian(heng[i+1],shu[j],list);
                }
            }
            if(i-1>=3 && i-1<=5)//左
            {
                int s4;
                if(heng[i-1]==all_qizi[8].x)//左下
                {
                    for(int t=j-1;t>=0;t--)
                    {
                        s4 =this_point(i-1,t,-5);
                        if(s4==2||s4==1||s4==-1)
                        {
                            break;
                        }
                    }
                }
                if(s4!=2 && this_point(i-1,j,p->sign) != -1)
                {
                    AddLuodian(heng[i-1],shu[j],list);
                }
            }
            
            if(j-1>=7 && j-1<=9)//下
            {
                if(this_point(i,j-1,p->sign) != -1)
                {
                    AddLuodian(heng[i],shu[j-1],list);
                }
            }
            if(j+1>=7 && j+1<=9)//上
            {
                if(this_point(i,j+1,p->sign) != -1)
                {
                    AddLuodian(heng[i],shu[j+1],list);
                }
            }
        }
    }
    
    shuaxinqipan();
    //在其他棋子攻击范围内时移除落点
    BeginBatchDraw();
    if(p->sign==5)
    {
        for(int i=16;i<32;i++)//遍历对方棋子攻击范围
        {
            if (i!=24)
            {
                judge(all_qizi[i].sign,&all_qizi[i],duifang);
            }
        }
    }
    else if(p->sign==-5)
    {
        for(int i=0;i<16;i++)
        {
            if (i!=8)
            {
                judge(all_qizi[i].sign,&all_qizi[i],duifang);
            }
        }
    }
    for(int i=0;i<duifang->top;i++)
    {
        for(int k=0;k<host->top;k++)
        {
            if(host->arr[k].x==duifang->arr[i].x && host->arr[k].y==duifang->arr[i].y)
            {
                printf(ANSI_GREEN"移除head链表落点:(%d,%d)" RESET "\n",host->arr[k].x,host->arr[k].y);
                host->arr[k].x=-1;
                host->arr[k].y=-1;
            }
        }
    }
}

void shi(Qizi *p,List* list)//仕/士的走法
{
    Point ij = zuobiao(p);
    int i = ij.x;
    int j = ij.y;
    if(p->sign>0)
    {
        if(i>=3&&i<=5&&j>=0&&j<=2)
        {
            if(i+1>=3 && i+1<=5 && j+1>=0 && j+1<=2)//右上
            {
                if(this_point(i+1,j+1,p->sign) != -1)
                {
                    AddLuodian(heng[i+1],shu[j+1],list);
                }
            }
            if(i-1>=3 && i-1<=5 && j+1>=0 && j+1<=2)//左上
            {
                if(this_point(i-1,j+1,p->sign) != -1)
                {
                    AddLuodian(heng[i-1],shu[j+1],list);
                }
            }
            if(i+1>=3 && i+1<=5 && j-1>=0 && j-1<=2)//右下
            {
                if(this_point(i+1,j-1,p->sign) != -1)
                {
                    AddLuodian(heng[i+1],shu[j-1],list);
                }
            }
            if(i-1>=3 && i-1<=5 && j-1>=0 && j-1<=2)//左下
            {
                if(this_point(i-1,j-1,p->sign) != -1)
                {
                    AddLuodian(heng[i-1],shu[j-1],list);
                }
            }
        }
    }
    else 
    {
        if(i>=3&&i<=5&&j>=7&&j<=9)
        {
            if(i+1>=3 && i+1<=5 && j+1>=7 && j+1<=9)
            {
                if(this_point(i+1,j+1,p->sign) != -1)
                {
                    AddLuodian(heng[i+1],shu[j+1],list);
                }
            }
            if(i-1>=3 && i-1<=5 && j+1>=7 && j+1<=9)
            {
                if(this_point(i-1,j+1,p->sign) != -1)
                {
                    AddLuodian(heng[i-1],shu[j+1],list);
                }
            }
            if(i+1>=3 && i+1<=5 && j-1>=7 && j-1<=9)
            {
                if(this_point(i+1,j-1,p->sign) != -1)
                {
                    AddLuodian(heng[i+1],shu[j-1],list);
                }
            }
            if(i-1>=3 && i-1<=5 && j-1>=7 && j-1<=9)
            {
                if(this_point(i-1,j-1,p->sign) != -1)
                {
                    AddLuodian(heng[i-1],shu[j-1],list);
                }
            }
        }
    }
}
void pao(Qizi *p,List* list)//炮的走法
{
    Point ij = zuobiao(p);
    int i = ij.x;
    int j = ij.y;
    int t=i;
    for(t=i+1;t<9;t++)//右
    {
        int s=this_point(t,j,p->sign);
        if(s==0)//无棋子
        {
            if(ispao)
            {
                AddLuodian(heng[t],p->y,list);
            }
        }
        else//对方棋子或己方棋子
        {
            for(int h=t+1;h<9;h++)
            {
                int s1 = this_point(h,j,p->sign);
                if(s1==1||s1==2)//发现对方棋子
                {
                    AddLuodian(heng[h],p->y,list);
                    if(s1==1)
                    {
                        break;
                    }
                }
                else if(s1==0)
                {
                    if(!ispao)//
                    {
                        AddLuodian(heng[h],p->y,list);
                    }
                }
                else if(s1==-1)
                {
                    if(!ispao)
                    {
                        AddLuodian(heng[h],p->y,list);
                    }
                    break;
                }
            }
            break;
        }
    }
    t = i-1;
    for(t;t>=0;t--)//左
    {
       int s=this_point(t,j,p->sign);
        if(s==0)//无棋子
        {
            if(ispao==1)
            {
                AddLuodian(heng[t],p->y,list);
            }
        }
        else//对方棋子或己方棋子
        {
            for(int h=t-1;h>=0;h--)
            {
                int s2 = this_point(h,j,p->sign);
                if(s2==1||s2==2)//发现对方棋子
                {
                    AddLuodian(heng[h],p->y,list);
                    if(s2==1)
                    {
                        break;
                    }
                }
                else if(s2==0)
                {
                    if(!ispao)
                    {
                        AddLuodian(heng[h],p->y,list);
                    }
                }
                else if(s2==-1)
                {
                    if(!ispao)
                    {
                        AddLuodian(heng[h],p->y,list);
                    }
                    break;
                }
            }
            break;
        } 
    }
    t = j+1;
    for(t;t<=9;t++)//下
    {
        int s=this_point(i,t,p->sign);
        if(s==0)//无棋子
        {
            if(ispao==1)
            {
                AddLuodian(p->x,shu[t],list);
            }
        }
        else//对方棋子或己方棋子
        {
            for(int h=t+1;h<=9;h++)
            {
                int s3 = this_point(i,h,p->sign);
                if(s3==1||s3==2)//发现对方棋子
                {
                    AddLuodian(p->x,shu[h],list);
                    if(s3==1)
                    {
                        break;
                    }
                }
                else if(s3==0)
                {
                    if(!ispao)
                    {
                        AddLuodian(p->x,shu[h],list);
                    }
                }
                else if(s3==-1)
                {
                    if(!ispao)
                    {
                        AddLuodian(p->x,shu[h],list);
                    }
                    break;
                }
            }
            break;
        } 
    }
    t = j-1;
    for(t;t>=0;t--)//上
    {
        int s=this_point(i,t,p->sign);
        if(s==0)//无棋子
        {
            if(ispao)
            {
                AddLuodian(p->x,shu[t],list);//遇到棋子前是否可移动
            }
        }
        else//对方棋子或己方棋子
        {
            for(int h=t-1;h>=0;h--)
            {
                int s4 = this_point(i,h,p->sign);
                if(s4==1||s4==2)//发现对方棋子
                {
                    AddLuodian(p->x,shu[h],list);
                    if(s4==1)
                    {
                        break;
                    }
                }
                else if(s4==0)
                {
                    if(!ispao)
                    {
                        AddLuodian(p->x,shu[h],list);
                    }
                }
                else if(s4==-1)
                {
                    if(!ispao)
                    {
                        AddLuodian(p->x,shu[h],list);
                    }
                    break;
                }
            }
            break;
        } 
    }
}
void xiang(Qizi *p,List* list)//相/象的走法
{
    Point ij = zuobiao(p);
    int i = ij.x;
    int j = ij.y;
    
    // 检查象是否在己方区域
    if((j <= 4 && p->sign > 0) || (j >= 5 && p->sign < 0))
    {
        // 右上 (i+2, j-2)
        int s1 = this_point(i+1, j-1, p->sign); // 象眼
        if(s1 == 0)
        {
            if(this_point(i+2, j-2, p->sign) != -1) // 落点无己方棋子
            {
                // 边界检查:红方 j-2 >= 0 && j-2 <= 4;黑方 j-2 >= 5 && j-2 <= 9
                if((p->sign > 0 && j - 2 >= 0) || (p->sign < 0 && j - 2 >= 5))
                {
                    AddLuodian(heng[i+2], shu[j-2],list);
                }
            }
        }
        
        // 左上 (i-2, j-2)
        int s2 = this_point(i-1, j-1, p->sign);
        if(s2 == 0)
        {
            if(this_point(i-2, j-2, p->sign) != -1)
            {
                if((p->sign > 0 && j - 2 >= 0) || (p->sign < 0 && j - 2 >= 5))
                {
                    AddLuodian(heng[i-2], shu[j-2],list);
                }
            }
        }
        
        // 左下 (i-2, j+2)
        int s3 = this_point(i-1, j+1, p->sign);
        if(s3 == 0)
        {
            if(this_point(i-2, j+2, p->sign) != -1)
            {
                // 边界检查:红方 j+2 <= 4;黑方 j+2 <= 9
                if((p->sign > 0 && j + 2 <= 4) || (p->sign < 0 && j + 2 <= 9))
                {
                    AddLuodian(heng[i-2], shu[j+2],list);
                }
            }
        }
        
        // 右下 (i+2, j+2)
        int s4 = this_point(i+1, j+1, p->sign);
        if(s4 == 0)
        {
            if(this_point(i+2, j+2, p->sign) != -1)
            {
                if((p->sign > 0 && j + 2 <= 4) || (p->sign < 0 && j + 2 <= 9))
                {
                    AddLuodian(heng[i+2], shu[j+2],list);
                }
            }
        }
    }
}
void ma(Qizi *p,List* list)//马的走法
{
    Point ij = zuobiao(p);
    int i = ij.x;
    int j = ij.y;
    int s1 = this_point(i+1,j,p->sign);//右侧
    if(s1==0)
    {
        if(this_point(i+2,j+1,p->sign) != -1)
        {
            AddLuodian(heng[i+2],shu[j+1],list);
        }
        if(this_point(i+2,j-1,p->sign) != -1)
        {
            AddLuodian(heng[i+2],shu[j-1],list);
        }
    }
    int s2 = this_point(i,j-1,p->sign);//上方
    if(s2==0)
    {
        if(this_point(i+1,j-2,p->sign) != -1)
        {
            AddLuodian(heng[i+1],shu[j-2],list);
        }
        if(this_point(i-1,j-2,p->sign) != -1)
        {
            AddLuodian(heng[i-1],shu[j-2],list);
        }
    }
    int s3 = this_point(i-1,j,p->sign);//左方
    if(s3==0)
    {
        if(this_point(i-2,j+1,p->sign) != -1)
        {
            AddLuodian(heng[i-2],shu[j+1],list);
        }
        if(this_point(i-2,j-1,p->sign) != -1)
        {
            AddLuodian(heng[i-2],shu[j-1],list);
        }
    }
    int s4 = this_point(i,j+1,p->sign);//下方
    if(s4==0)
    { 
        if(this_point(i-1,j+2,p->sign) != -1)
        {
            AddLuodian(heng[i-1],shu[j+2],list);
        }
        if(this_point(i+1,j+2,p->sign) != -1)
        {
            AddLuodian(heng[i+1],shu[j+2],list);
        }
    }
}
void bing(Qizi *p,List* list)//兵的走法
{
    Point ij = zuobiao(p);
    int i = ij.x;
    int j = ij.y;
    if(p->sign>0)//红兵
    {
        if(j<=4)//未过河
        {
            int s=this_point(i,j+1,p->sign);
            if(s==0)
            {
                AddLuodian(p->x,shu[j+1],list);
            }else if(s==1||s==2)
            {
                AddLuodian(p->x,shu[j+1],list);
            }
        }else if(j>4)//过河
        {
            int s=this_point(i,j+1,p->sign);//前方
            if(s==0)
            {
                AddLuodian(p->x,shu[j+1],list);
            }else if(s==1||s==2)
            {
                AddLuodian(p->x,shu[j+1],list);
            }
            int s1=this_point(i+1,j,p->sign);//右方
            if(s1==0)
            {
                AddLuodian(heng[i+1],p->y,list);
            }else if(s1==1||s1==2)
            {
                AddLuodian(heng[i+1],p->y,list);
            }
            int s2=this_point(i-1,j,p->sign);//左方
            if(s2==0)
            {
                AddLuodian(heng[i-1],p->y,list);
            }else if(s2==1||s2==2)
            {
                AddLuodian(heng[i-1],p->y,list);
            }
        }
    }else if(p->sign<0)//黑卒
    {
        if(j>=5)//未过河
        {
            int s=this_point(i,j-1,p->sign);
            if(s==0)
            {
                AddLuodian(p->x,shu[j-1],list);
            }else if(s==1||s==2)
            {
                AddLuodian(p->x,shu[j-1],list);
            }
        }else if(j<5)//过河
        {
            int s=this_point(i,j-1,p->sign);//前方
            if(s==0)
            {
                AddLuodian(p->x,shu[j-1],list);
            }else if(s==1||s==2)
            {
                AddLuodian(p->x,shu[j-1],list);
            }
            int s1=this_point(i+1,j,p->sign);//右方
            if(s1==0)
            {
                AddLuodian(heng[i+1],p->y,list);
            }else if(s1==1||s1==2)
            {
                AddLuodian(heng[i+1],p->y,list);
            }
            int s2=this_point(i-1,j,p->sign);//左方
            if(s2==0)
            {
                AddLuodian(heng[i-1],p->y,list);
            }else if(s2==1||s2==2)
            {
                AddLuodian(heng[i-1],p->y,list);
            }
        }
    }
}
void che(Qizi *p,List* list)//车的走法
{
    Point ij = zuobiao(p);
    int i = ij.x;
    int j = ij.y;
    int t=i;
    for(t=i+1;t<9;t++)//右
    {
        int s=this_point(t,j,p->sign);
        if(s==0)
        {
            AddLuodian(heng[t],p->y,list);
        }else if(s==1||s==2)
        {
            AddLuodian(heng[t],p->y,list);
            if(s==2)
            {
                AddLuodian(heng[t+1],p->y,list);
            }
            break;
        }else if(s==-1)
        {
            if(!ische)
            {
                AddLuodian(heng[t],p->y,list);
            }
            break;
        }
    }
    t=i;
    for(t=i-1;t>=0;t--)//左
    {
        int s=this_point(t,j,p->sign);
        if(s==0)
        {
            AddLuodian(heng[t],p->y,list);
        }else if(s==1||s==2)
        {
            AddLuodian(heng[t],p->y,list);
            if(s==2)
            {
                AddLuodian(heng[t-1],p->y,list);
            }
            break;
        }else if(s==-1)
        {
            if(!ische)
            {
                AddLuodian(heng[t],p->y,list);
            }
            break;
        }
    }
    t=j;
    for(t=j+1;t<10;t++)//下
    {
        int s=this_point(i,t,p->sign);
        if(s==0)
        {
            AddLuodian(p->x,shu[t],list);
        }else if(s==1||s==2)
        {
            AddLuodian(p->x,shu[t],list);
            if(s==2)
            {
                AddLuodian(p->x,shu[t+1],list);
            }
            break;
        }else if(s==-1)
        {
            if(!ische)
            {
                AddLuodian(p->x,shu[t],list);
            }
            break;
        }
    }
    t=j;
    for(t=j-1;t>=0;t--)//上
    {
        int s=this_point(i,t,p->sign);
        if(s==0)
        {
            AddLuodian(p->x,shu[t],list);
        }else if(s==1||s==2)
        {
            AddLuodian(p->x,shu[t],list);
            if(s==2)
            {
                AddLuodian(p->x,shu[t-1],list);
            }
            break;
        }else if(s==-1)
        {
            if(!ische)
            {
                AddLuodian(p->x,shu[t],list);
            }
            break;
        }
    }
}
void AddLuodian(int x,int y,List* list)
{
    if (list == NULL) {
        printf(ANSI_RED "Error: List pointer is NULL!" RESET "\n");
        return;
    }
    if (list->top >= 120) {
        printf(ANSI_RED "Error: List is full! list->top=%d Cannot add (%d,%d)" RESET "\n",list->top,x,y);
        return; 
    }

    list->arr[list->top].x = x;
    list->arr[list->top].y = y;
    (list->top)++;
    printf(ANSI_YELLOW "top=%d" RESET "\n",list->top);
}

void drawluodian(void)
{
    for(int i=0;i<host->top;i++)
    {
        setlinecolor(GREEN);
        setfillcolor(GREEN);
        printf(ANSI_GREEN"绘制落点:(%d,%d)" RESET "\n",host->arr[i].x,host->arr[i].y);
        fillcircle(GetX(host->arr[i].x),GetY(host->arr[i].y),GetScaleSize(5));
    }
}
void shuaxinqipan(void)
{
    EnterCriticalSection(&cs);
    cleardevice();
    // 载入并显示背景图片   
    IMAGE qipan;
    loadimage(&qipan,_T("./image/qipan.PNG"),getwidth(),getheight());
    putimage(0,0,&qipan);
    setbkmode(TRANSPARENT);
    //悔棋按钮
    if(huiqi!=NULL)
    {
        if(isUndoHover) 
        {
            setfillcolor(RGB(0, 128, 0)); // 悬停变深绿
        }
        else 
        {
            setfillcolor(GREEN); // 平时亮绿
        }
    }
    else
    {
        setfillcolor(RED);
    }
    fillroundrect(GetX(777),GetY(483),GetX(807),GetY(513),GetScaleSize(4),GetScaleSize(4));
    settextstyle(GetScaleSize(20),0,_T("黑体"));
    settextcolor(BLACK);
    outtextxy(GetX(772)+(textwidth(_T("悔")))/2,GetY(480)+(textheight(_T("悔")))/2,_T("悔"));
    // --- 2. 退出按钮 (红色, 右侧, x坐标+50) --
    if(isExitHover)
    {
        setfillcolor(RGB(0, 128, 0));
        fillcircle(GetX(842), GetY(498),GetScaleSize(15)); 
        settextcolor(RED);
        outtextxy(GetX(833)+(textwidth(_T("X")))/2,GetY(479)+(textheight(_T("X")))/2,_T("X"));
    }
    else
    {
        setfillcolor(GREEN);
        fillcircle(GetX(842),GetY(498),GetScaleSize(15)); 
    }

    // ---3. 催促按钮 (蓝色,悔棋按钮左侧) --
    if(isCuiCuHover)
    {
        setfillcolor(RGB(173, 216, 230));
    }
    else
    {
        setfillcolor(RGB(70, 130, 180));
    }
    fillroundrect(GetX(742),GetY(483),GetX(772),GetY(513),GetScaleSize(4),GetScaleSize(4));
    settextstyle(GetScaleSize(20),0,_T("黑体"));
    settextcolor(BLACK);
    outtextxy(GetX(737)+(textwidth(_T("催")))/2,GetY(480)+(textheight(_T("催")))/2,_T("催"));

    settextcolor(YELLOW);
    for(int i=0;i<32;i++)
    {
        drawqizi(all_qizi[i].x,all_qizi[i].y,all_qizi[i].sign);
    }
    if(lastMovedPiece != NULL && lastMovedPiece->sign != 0)//在绘制完所有棋子后,再绘制上一步移动的标记
    {
        setlinecolor(GREEN);
        setlinestyle(PS_SOLID, 3); // 使用更粗的线条
        circle(GetX(lastMovedPiece->x),GetY( lastMovedPiece->y),GetScaleSize( 45));
        setlinestyle(PS_SOLID, 1); // 恢复默认线条粗细
    }
    drawlsatstep(original_x,original_y);
    duijuiformation();
    LeaveCriticalSection(&cs);
}
int judge(int sign, Qizi *p, List* list)
{
    if (sign == 0) return 0;

    int type = abs(sign);
    switch (type)
    {
    case 1:
        che(p, list);
        break;
    case 2:
        ma(p, list);
        break;
    case 3:
        xiang(p, list);
        break;
    case 4:
        shi(p, list);
        break;
    case 5:
        jiangjun(p, list);
        break;
    case 6:
        pao(p, list);
        break;
    case 7:
        bing(p, list);
        break;
    default:
        break;
    }
    return 0;
}
// 绘制棋子

int drawqizi(int x1, int y1, int sign)
{
    if (sign == 0) return 0;

    int type = abs(sign); // 获取棋子类型 (1-7)
    if (type > 7) return 0; // 防止越界

    // === 1. 定义名字查表 (静态数组,只初始化一次) ===
    // 下标对应: 0(空), 1(车), 2(马), 3(相/象), 4(仕/士), 5(帅/将), 6(炮), 7(兵/卒)
    static const TCHAR* red_names[]   = { _T(""), _T("车"), _T("马"), _T("相"), _T("仕"), _T("帥"), _T("炮"), _T("兵") };
    static const TCHAR* black_names[] = { _T(""), _T("车"), _T("马"), _T("象"), _T("士"), _T("将"), _T("炮"), _T("卒") };

    // === 2. 准备绘制参数 ===
    bool isRed = (sign > 0);
    bool isHover = (hoverQizi != NULL && hoverQizi->x == x1 && hoverQizi->y == y1);
    
    int screenX = GetX(x1);
    int screenY = GetY(y1);
    int radius = GetScaleSize(40);
    int fontSize = GetScaleSize(40);

    // === 3. 绘制棋子实体 (圆形) ===
    // 填充色:悬停时稍微变亮,增加交互感
    if (isHover) {
        setfillcolor(RGB(255, 200, 100)); 
    } else {
        setfillcolor(RGB(255, 175, 55)); // 经典的木头黄
    }
    
    // 边框色:黑色
    setlinecolor(BLACK);
    setlinestyle(PS_SOLID, 2); // 线条稍微加粗一点点,更有质感
    fillcircle(screenX, screenY, radius);

    // === 4. 绘制内部装饰环 (让棋子更好看) ===
    setlinecolor(RGB(230, 150, 40)); // 比底色深一点的橙色
    setlinestyle(PS_SOLID, 1);
    circle(screenX, screenY, radius - 5); // 内部画一个小圈,模仿真实棋子边缘

    // === 5. 绘制文字 ===
    setbkmode(TRANSPARENT); // 透明背景
    settextstyle(fontSize, 0, _T("黑体"));

    // 根据阵营设置文字颜色
    if (isRed) {
        settextcolor(isHover ? RGB(255, 50, 50) : RED); // 红色
    } else {
        settextcolor(isHover ? RGB(80, 80, 80) : BLACK); // 黑色
    }

    // 根据阵营和类型获取文字
    const TCHAR* text = isRed ? red_names[type] : black_names[type];

    // 居中输出文字
    // 技巧:直接算好偏移量,不用每行都写一遍
    int tx = screenX - textwidth(text) / 2;
    int ty = screenY - textheight(text) / 2;
    outtextxy(tx, ty, text);

    return 1;
}
void drawlsatstep(int x,int y)
{
    int screenX = GetX(x);
    int screenY = GetY(y);
    int radius = GetScaleSize(10);
    setlinecolor(RGB(250,251,253));
    setfillcolor(RGB(250,251,253));
    fillcircle(screenX,screenY,radius);
}
void duijuiformation(void)
{
    if (cnt==0)
    {
        settextstyle(40,0,_T("黑体"));
        settextcolor(RED);
        outtextxy(290+(textwidth(_T("红方走子")))/2,458+(textheight(_T("红方走子")))/2,_T("红方走子"));
    }
    else if(cnt==1)
    {
        settextstyle(40,0,_T("黑体"));
        settextcolor(BLACK);
        outtextxy(290+(textwidth(_T("黑方走子")))/2,458+(textheight(_T("黑方走子")))/2,_T("黑方走子"));
    }
    else
    {
        settextstyle(60,0,_T("黑体"));
        settextcolor(RED);
        outtextxy(325+(textwidth(_T("绝杀")))/2,435+(textheight(_T("绝杀")))/2,_T("绝杀"));
    }
}
void free_linked_list(int mode)
{
    // 既然是指针,先判断非空是个好习惯
    if ((mode == 1 || mode == 2) && host)    host->top = 0;
    if ((mode == 1 || mode == 3) && duifang) duifang->top = 0;
    if ((mode == 1 || mode == 4) && fanchi)  fanchi->top = 0;
    if ((mode == 1 || mode == 5) && juesha)  juesha->top = 0;
    if ((mode == 1 || mode == 6) && zhedang) zhedang->top = 0;
    if ((mode == 1 || mode == 7) && temp)    temp->top = 0;

}
void HuiQi(void)
{
    if(huiqi!=NULL)
    {
        // 1. --- 网络模式下发送悔棋指令 ---
        // 我们约定 pieceIndex = -99 代表悔棋操作
        if (current_mode != MODE_LOCAL && g_sock != INVALID_SOCKET) {
            MovePacket packet;
            packet.pieceIndex = -99; 
            packet.toX = 0; 
            packet.toY = 0;
            send(g_sock, (const char*)&packet, sizeof(packet), 0);
            printf("网络发送: 请求悔棋\n");
        }
        // ----------------------------------------

        if(lable==0)
        {
            huiqi->x = zancun.x;
            huiqi->y = zancun.y;
            huiqi->sign = zancun.sign;
        }
        else if(lable==1)
        {
            huiqi->x = zancun.x;
            huiqi->y = zancun.y;
            huiqi->sign = zancun.sign;
            huiqi1->x = zancun1.x;
            huiqi1->y = zancun1.y;
            huiqi1->sign = zancun1.sign;
            lable = 1 - lable;
        }
        cnt=1-cnt;
        zancun.x = 0; zancun.y = 0; zancun.sign = 0; // 修正结构体赋值语法
        zancun1.x = 0; zancun1.y = 0; zancun1.sign = 0;
        huiqi = NULL;
        huiqi1 = NULL;
        lastMovedPiece = NULL;

        SaveGame(); 

        shuaxinqipan();
    }
}
void ZanCun(Qizi *p)//非吃子存储
{
    zancun.x = p->x;
    zancun.y = p->y;
    zancun.sign = p->sign;
    huiqi = p;
}
void ZanCun1(Qizi *p)//吃子存储
{
    zancun1.x = p->x;
    zancun1.y = p->y;
    zancun1.sign = p->sign;
    huiqi1 = p;
    lable = 1;
}
void songjiang(Qizi *p,List *list)
{
    temp->top = 0;
    printf(ANSI_RED "送将检测" RESET "\n");
    // 保存原始信息 
    int original_x = p->x;
    int original_y = p->y;
    
    
    int h = (p->sign > 0) ? 8 : 24; // 己方将帅索引
    int i=0;
    while(i<list->top)
    {
        // 1. 模拟走子
        p->x = list->arr[i].x;
        p->y = list->arr[i].y;

        // 2. 临时移除被吃掉的棋子 
        int captured_index = -1;       
        int captured_original_sign = 0; 
        
        for(int k = 0; k < 32; k++)
        {
            // 如果落点有活着的敌方棋子(排除自己)
            if(all_qizi[k].x == p->x && all_qizi[k].y == p->y && &all_qizi[k] != p && all_qizi[k].sign != 0)
            {
                captured_index = k;
                captured_original_sign = all_qizi[k].sign;
                all_qizi[k].sign = 0; // 标记为死亡
                break; 
            }
        }

        // ---------------------------------------------------------
        // 3. 手动检测“将帅照面” (飞将规则)
        //    因为 judge 函数不包含飞将攻击范围,必须手动查
        // ---------------------------------------------------------
        bool flying_general_detected = false;
        
        // 获取双方将帅的最新位置(注意:如果移动的是帅,p->x/y 已经是新位置)
        // all_qizi[8] 是红帅, all_qizi[24] 是黑将
        int r_x = all_qizi[8].x;
        int r_y = all_qizi[8].y;
        int b_x = all_qizi[24].x;
        int b_y = all_qizi[24].y;

        // 只有在同一列 (x坐标相同) 才可能照面
        if(r_x == b_x) 
        {
            int min_y = (r_y < b_y) ? r_y : b_y;
            int max_y = (r_y > b_y) ? r_y : b_y;
            int obstacle_count = 0;

            // 遍历所有棋子,看有没有在两个老将中间的
            for(int k = 0; k < 32; k++)
            {
                if(all_qizi[k].sign != 0 && // 必须是活棋 (被吃掉的 sign 已为 0)
                   all_qizi[k].x == r_x &&  // 在同一列
                   all_qizi[k].y > min_y && // 在两者之间
                   all_qizi[k].y < max_y)
                {
                    obstacle_count++;
                }
            }

            // 如果中间没有阻挡,说明照面了 -> 送将 -> 非法移动
            if(obstacle_count == 0)
            {
                flying_general_detected = true;
            }
        }

        if(flying_general_detected)
        {
            printf("发现飞将(照面)! 落点无效\n");
            list->arr[i].x=-1;
            list->arr[i].y=-1;
        }
        else 
        {
            // 4. 常规检查:遍历对方棋子攻击范围 (车马炮卒)
            if(p->sign > 0) // 我是红方,检查黑方攻击
            {
                for(int i = 16; i < 32; i++)
                {
                    if (all_qizi[i].sign != 0 && i != 24) // 跳过黑将(已手动查过)
                    {
                        judge(all_qizi[i].sign, &all_qizi[i],temp); 
                    }
                }
            }
            else // 我是黑方,检查红方攻击
            {
                for(int i = 0; i < 16; i++)
                {
                    if (all_qizi[i].sign != 0 && i != 8) // 跳过红帅(已手动查过)
                    {
                        judge(all_qizi[i].sign, &all_qizi[i],temp); 
                    }
                }
            }

            // 检查是否落入敌方常规攻击范围
            for(int k=0;k<temp->top;k++)
            {                                   
                if(temp->arr[k].x == all_qizi[h].x && temp->arr[k].y == all_qizi[h].y)
                {
                    list->arr[i].x=-1;
                    list->arr[i].y=-1;
                    break;
                }
            }
        }
        // 5. 恢复现场 (恢复被吃掉的棋子)
        if(captured_index != -1)
        {
            all_qizi[captured_index].sign = captured_original_sign;
        }

        free_linked_list(7);
        i++;
    }

    // 恢复 p 的位置和全局状态
    p->x = original_x;
    p->y = original_y;
}
int juesha_fanchi_dang()//0可被吃/可解将 1不可被吃/无解
{
    printf(ANSI_RED "进入绝杀 反吃/挡 检查,传入棋子坐标:" RESET "\n");
    // 保存全局状态
    
    BeginBatchDraw();

    // 确定防御方范围
    // p->sign是红方(>0) -> 防御方是黑方(16-32)
    // p->sign是黑方(<0) -> 防御方是红方(0-16)
    int start_index = (cnt==1) ? 16 : 0; 
    int end_index = (cnt==1) ? 32 : 16;
    
    int target_x = all_qizi[jiangjun_i].x;
    int target_y = all_qizi[jiangjun_i].y;
    int target_sign = all_qizi[jiangjun_i].sign;
    //得到最新发现的将军子坐标
    int temp_cnt = 1;
    // ---------------------- 1. 反吃检查 ----------------------
    for(int i = start_index; i < end_index; i++) 
    {
        printf(ANSI_YELLOW "进入反吃检查,当前为第%d轮反吃检查,当前检测被将方棋子:all_qizi[%d]" RESET "\n",temp_cnt,i);
        Qizi *q = &all_qizi[i];
        if(q->sign == 0) continue; 
        judge(q->sign, q,fanchi);
        songjiang(q,fanchi);
        for(int i=0;i<fanchi->top;i++)
        {
            if(fanchi->arr[i].x==target_x && fanchi->arr[i].y==target_y)
            {
                printf(ANSI_GREEN "绝杀检测: 发现反吃解法! %s方棋子可吃掉将军子 (%d,%d)" RESET "\n",
                (target_sign>0)?"黑":"红", target_x, target_y);
                all_qizi[jiangjun_i].sign = 0;
                //注意我这里没有将可以反吃将军棋子的棋子移动,不知后续会不会出现问题
                if(jiancha_jiangjun() == 1)
                {
                    printf(ANSI_YELLOW"但反吃后仍然将军,继续检查其他解法" RESET "\n");
                    all_qizi[jiangjun_i].sign = target_sign;
                    goto fanchi_next;
                }
                free_linked_list(4); // 释放 fanchi
                all_qizi[jiangjun_i].sign = target_sign;
                EndBatchDraw();
                // 恢复全局状态
                return 0; // 0代表可被吃,未绝杀 
            }
            fanchi_next:;
        }
        free_linked_list(4); // 释放 fanchi
        temp_cnt++;
    }
    printf("反吃检查完毕,无反吃解法\n");
    printf("进入遮挡检查\n");
    // ---------------------- 2. 遮挡检查 ----------------------
    if(abs(target_sign)==7)
    {
        printf("兵将军无法阻挡");
        return 1;
    }
    int h = (target_sign > 0) ? 24 : 8; // 受保护的老将索引
    for(int i = start_index; i < end_index; i++) 
    {
        Qizi *q = &all_qizi[i];
        printf(ANSI_YELLOW"进入遮挡检查,当前正检查被将方棋子:all_qizi[%d]" RESET "\n",i);
        if (q->sign == 0 || abs(q->sign) == 5 || abs(q->sign) == 7) continue; 

        int q_original_x = q->x;
        int q_original_y = q->y;

        judge(q->sign, q,zhedang);
        songjiang(q,zhedang);
        printf("遍历 zhednag 进行遮挡检查\n");
        for(int i_temp =0;i_temp<zhedang->top;i_temp++)
        {
            if(zhedang->arr[i_temp].x!=-1)
            {
                q->x = zhedang->arr[i_temp].x;
                q->y = zhedang->arr[i_temp].y;

                // 检查攻击子 p 是否仍然能将军
                printf(ANSI_YELLOW"模拟遮挡走子到 %d,%d 后,检查攻击子 all_qizi[i].sign = %d 是否仍然将军,期望调用链表juesha_head存储攻击子范围" RESET "\n", all_qizi[i].x,all_qizi[i].y, target_sign);
                bool still_checking = false;
                if(!still_checking) 
                {
                    printf(ANSI_GREEN"发现遮挡解法!落点 %d,%d 可阻挡正在将军棋子 %d,%d" RESET "\n", zhedang->arr[i_temp].x, zhedang->arr[i_temp].y, target_x, target_y);
                    free_linked_list(6); // 释放 zancun_head
                    EndBatchDraw();
                    if(jiancha_jiangjun() == 1)
                    {
                        printf(ANSI_YELLOW"但阻挡后仍然将军,继续检查其他解法" RESET "\n");
                        goto zhedang_next;
                    }
                    // 恢复棋子位置
                    q->x = q_original_x;
                    q->y = q_original_y;
                    return 0; // 可阻挡
                }
                zhedang_next:;
                // 恢复棋子位置
                q->x = q_original_x;
                q->y = q_original_y;
            }
        }        
        free_linked_list(4); // 释放 zancun_head
    }
    EndBatchDraw();
    printf("反吃/挡 检查完毕,无解\n");
    return 1; // 无法解救
}
int juesha_bikai(void)
{
    //将军自己可避开
    printf("进入绝杀 避开 检查\n");
    free_linked_list(5);
    printf("期望使用主链表 head 存储老将可走位置\n");
    if(cnt == 1) 
    {
        jiangjun(&all_qizi[24],host); // 黑帅
        printf(ANSI_RED "1" RESET "\n");
        for(int i_temp=0;i_temp<host->top;i_temp++)
        {
            if(host->arr[i_temp].x != -1)
            {
                printf(ANSI_GREEN "发现避开解法!落点 %d,%d 可避开将军" RESET "\n", host->arr[i_temp].x, host->arr[i_temp].y);
                return 0; // 有路可走,直接跳出
            }
        }
        printf("无路可走避开将军\n");
        return 1;// 无路可走
    }
    else // cnt == 0 代表当前是红方回合(红方被将军),应检测红帅(8)
    {
        jiangjun(&all_qizi[8],host); // 红帅
        for(int i_temp=0;i_temp<host->top;i_temp++)
        {
            if(host->arr[i_temp].x != -1)
            {
                printf("发现避开解法!落点 %d,%d 可避开将军\n", host->arr[i_temp].x, host->arr[i_temp].y);
                return 0; // 有路可走,直接跳出
            }
        }
        printf("无路可走避开将军\n");
        return 1;// 无路可走
    }
}
int JueSha()//传入刚下的棋子当前位置
{
    printf(ANSI_YELLOW "进入绝杀检查" RESET "\n");
    // 1. 尝试反吃或阻挡
    if(juesha_fanchi_dang() == 0)
    {
        return 0;//未被绝杀
    }
    // 2. 尝试让老帅避开 
    if(juesha_bikai() == 0)
    {
        return 0;//老帅可以跑,未被绝杀
    }
    printf(ANSI_YELLOW "绝杀确认!" RESET "\n");
    return 1;// 既不能吃,也不能挡,还跑不掉 -> 绝杀
}
int jiancha_jiangjun(void)
{
    free_linked_list(3);
    printf(ANSI_RED"=========检测将军情况========" RESET "\n");
    int i;

    int is_jiangjun = 0; // 标记是否检测到将军

    if(cnt==1)//红方刚落子(cnt已切换为1),检测黑方是否被红方将军
    {
        // 遍历红方棋子(0-16),看是否攻击黑将(24)
        for(i=0;i<16;i++)
        {
            if(all_qizi[i].sign!=0&&
                all_qizi[i].sign!=3&& // 相不能将军
                all_qizi[i].sign!=4&& // 仕不能将军
                all_qizi[i].sign!=5)  // 帅不能将军
            {
                judge(all_qizi[i].sign,&all_qizi[i],duifang);//走法存入duifang_head
            }
            // 检查攻击范围是否包含黑将
            for(int i_temp=0;i_temp<duifang->top;i_temp++)
            {                                   
                if(duifang->arr[i_temp].x==all_qizi[24].x&&duifang->arr[i_temp].y==all_qizi[24].y)
                {
                    printf(ANSI_RED"红方将军!" RESET "\n");
                    jiangjun_i = i;
                    free_linked_list(3);
                    printf(ANSI_YELLOW"发现将军方棋子位置(%d,%d)" RESET "\n",all_qizi[i].x,all_qizi[i].y);
                    return 1;
                }
            }
            free_linked_list(3); // 释放 duifang_head,准备检查下一个棋子
        }
    }
    else if(cnt==0)//黑方刚落子(cnt已切换为0),检测红方是否被黑方将军
    {
        // 遍历黑方棋子(16-32),看是否攻击红帅(8)
        for(i=16;i<32;i++)
        {
            if(all_qizi[i].sign!=0&&
                all_qizi[i].sign!=-3&&
                all_qizi[i].sign!=-4&&
                all_qizi[i].sign!=-5)
            {
                judge(all_qizi[i].sign,&all_qizi[i],duifang);//走法存入duifang_head
            }
            for(int i_temp=0;i_temp<duifang->top;i_temp++)
            {                                   
                if(duifang->arr[i_temp].x==all_qizi[8].x&&duifang->arr[i_temp].y==all_qizi[8].y)
                {
                    printf(ANSI_RED"黑方将军!" RESET "\n");
                    
                    jiangjun_i = i;
                    free_linked_list(3);
                    printf(ANSI_YELLOW"发现将军方棋子位置(%d,%d)" RESET "\n",all_qizi[i].x,all_qizi[i].y);
                    return 1;
                }
            }
            free_linked_list(3);
        }
    }
    printf(ANSI_RED"无将军情况" RESET "\n");
    return 0;//无人将军
}

1.须知:由于是独立制作,大概率会有bug,若在游玩过程中发现bug,非常欢迎以及感谢反馈意见和遇到的问题:

反馈请提供日志文件,即:文件夹内名为 chess_log.html 的文件(注意:若遇到闪退,请不要再次运行程序,避免日志重置)(若在联机模式下游玩,双方日志都请提供)联系方式:guyann595@163.com

关于游戏:

(一)游戏是基于Easyx图形库制作的,由于年代久远,再加上象棋游戏的大量运算(实则是本人目前实力还不足),游戏运行中会有些许的卡顿(特别是在显示炮的可用落点时),还请谅解,耐心等待即可。目前绝杀音效并未制作(因为没有找到喜欢的)。

(二)游玩:在鼠标移动至走子方棋子时,会有棋子颜色的变化提示,单击选中棋子后,可看到该棋子的可用落点。刚被移动的棋子会有绿圈提示。

建议取消选择该棋子时,采用右键键盘任意位置(退出按钮除外)的方式,会大大减小取消选择的计算量,增加游玩流畅度。

棋盘上的绿圆角矩形为悔棋按钮,双方均可按下,绝杀后程序退出,故无法悔棋。中途退出点击窗口右上角的x即可,或是左键(4.0版本及以上)右键(4.0版本以下)单击退出按钮(悔棋右侧圆形按钮)。

2.制作该游戏的原因:在本人学习到二维数组时,制作了一个简单的井字棋游戏,但我哥以及朋友反馈不知道如何游玩,之前未见过这样的游戏,没有体验感,我也认同他们的观点,所以将编程的学习变得更有趣,不再是以前的那个黑黑的看腻的窗口。而是更希望能真的有种玩游戏的感觉。

3.制作游戏的过程:于25年11月18日开始接触Easyx图形库,截至现在(1.0发布)已有10天,期间也经历过多次的segmentation fault,一遍遍的找错误,后来想找ai辅助,但发现除了时间浪费掉以外,也完全是在帮倒忙(幽默一下)。网络编程的这一部分由于我还没有学习,所以有着ai的帮忙,不能说一下弄好,但花费的时间对象棋本身的代码实现而言不值一提。

最后,再次感谢向我提供反馈以及建议的大家。

@我哥

@袁海人(一起参与测试了的小伙伴)

1人评论了“中国象棋”

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部