
点击下载-> V4.2 (下载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的帮忙,不能说一下弄好,但花费的时间对象棋本身的代码实现而言不值一提。
最后,再次感谢向我提供反馈以及建议的大家。
@我哥
@袁海人(一起参与测试了的小伙伴)

太帅了!