main.c
#include "game.h"
#include "user.h"
#include <stdio.h>
int main()
{
// 初始化用户系统
initUserSystem();
// 用户登录或注册
User currentUser;
if (!loginOrRegister(¤tUser))
{
printf("程序退出
");
return 0;
}
// 初始化游戏
initGame();
// 开始游戏
startGame(¤tUser);
// 清理资源
cleanupUserSystem();
return 0;
}
一、项目概述
本项目是一款基于传统策略对战场景的 “中国象棋” 游戏,核心支持人机对战模式(红方为玩家,黑方为 AI)。玩家通过控制红方棋子遵循象棋规则移动,目标是吃掉黑方 “将” 获胜;AI 通过 α-β 剪枝算法控制黑方棋子自动走棋。游戏具备完整的用户系统(注册、登录、用户管理)和走棋记录功能,提供兼具竞技性与传统趣味的策略对战体验。代码采用多文件模块化设计,包括主程序、棋盘管理、游戏逻辑、记录系统、用户系统等模块,实现低耦合高内聚的架构。
二、功能需求
2.1 游戏初始化功能
资源加载:
加载棋盘布局规则、棋子类型定义(红方:帅、仕、相、马、车、炮、兵;黑方:将、士、象、马、车、炮、卒)及用户数据文件(users.txt)。验证资源完整性,若用户文件缺失则自动创建;若核心规则配置错误,弹出提示 “初始化失败,请检查配置文件” 并退出。
棋盘初始化:
尺寸:采用ROW×COL网格(代码中默认为 10 行 ×9 列),以左上角为原点 (0,0),右下角为 (9,8)。
布局:横向 9 条线、纵向 10 条线,线宽 2px;中间第 4-5 行之间为 “楚河汉界” 区域,显示对应文字。
初始化:通过二维数组board[ROW][COL]存储棋子状态(0 为空位,正数为红方棋子,负数为黑方棋子),按传统规则固定摆放初始棋子(如黑方车位于 (0,0)、(0,8),红方帅位于 (9,4) 等,具体坐标见代码initBoard()函数)。
棋子初始化:
类型与属性:红方(1-7)对应帅、仕、相、马、车、炮、兵;黑方(-1 至 – 7)对应将、士、象、马、车、炮、卒(代码中pieceName()函数定义映射关系)。
显示:红方棋子以红色字体显示,黑方以白色字体显示,空位置用两空格填充(保持棋盘对齐)。
用户系统初始化:
支持 “注册”(用户名 + 密码,存储于users.txt,需校验用户名唯一性)和 “登录”(验证用户名与密码匹配)。
登录后记录用户状态(User结构体:用户名、密码、登录状态),支持游客模式(默认用户名 “红方玩家”)。
附加功能:查看用户列表、删除用户、添加用户(通过菜单交互实现)。
2.2 核心游戏流程
回合控制:
采用回合制,初始回合为红方(玩家)。每完成一步有效走棋后,自动切换至黑方(AI)回合,屏幕顶部实时显示 “当前回合:红方 / 黑方”。
棋子移动规则(基于代码isValidMove()及细分检查函数):
车:沿直线(横 / 竖)移动,路径不可有棋子阻挡(checkLineMove())。
马:走 “日” 字(坐标差 (1,2) 或 (2,1)),“蹩马腿”(日字拐角处有棋子)时不可移动(checkHorseMove())。
炮:直线移动,不吃子时路径无棋子,吃子时路径需有且仅有 1 个棋子(炮架,checkCannonMove())。
士 / 仕:沿对角线走 1 格,仅限九宫格内(红方仕:(7-9,3-5);黑方士:(0-2,3-5),checkAdvisorMove())。
象 / 相:走 “田” 字(坐标差 (2,2)),“堵象眼”(田字中心有棋子)时不可移动,且红相不超过楚河(x≤4)、黑象不超过汉界(x≥5,checkElephantMove())。
将 / 帅:九宫格内横 / 竖走 1 格,不可与对方将 / 帅直接见面(checkLineMove())。
兵 / 卒:未过河时只能前进 1 格(红兵向上,黑卒向下);过河后可左右移动 1 格(checkSoldierMove())。
合法性校验:移动需满足 “目标位置非己方棋子”“符合棋子专属规则”“不自将军”,非法操作时提示 “走法无效!请检查规则或坐标”,不切换回合。
输入控制:
玩家走棋:通过键盘输入坐标(格式 “x1 y1 x2 y2”,如 “9 0 8 0” 表示从 (9,0) 移动到 (8,0))。
功能键:q/Q退出游戏;r/R查看当前用户的走棋记录;Enter确认输入。
AI 逻辑:
基于 α-β 剪枝的 minimax 算法搜索最优走法,搜索深度由AI_DEPTH控制(代码中可配置)。
走棋流程:遍历所有黑方棋子的合法移动,通过评估函数(evaluateBoard())计算分数(棋子价值 + 将军加分),选择最高分走法。
交互:AI 思考时显示 “AI 思考中…”,走棋后输出 “AI 走棋:(x1,y1) -> (x2,y2),移动 / 吃掉红方 XX”。
胜负判定:
若玩家吃掉黑方 “将”(board[x2][y2] == B_GENERAL),判定玩家胜利。
若 AI 吃掉红方 “帅”,或玩家被 “将死”(被将军且无合法走法),判定 AI 胜利。
结果显示于屏幕中央(如 “恭喜!你赢了!”),游戏结束后返回主菜单。
2.3 记录系统功能
记录内容:
每步走棋记录包含 “时间(格式%Y-%m-%d %H:%M:%S)、走棋方(玩家 / AI)、起始坐标 (x1,y1)、目标坐标 (x2,y2)、移动棋子类型、吃掉棋子类型(若有)”。
存储与查看:
按用户存储:每个用户的记录保存至用户名_records.txt(通过saveMoveRecord()函数追加写入)。
查看方式:游戏中输入r/R键,弹出记录窗口并按时间顺序展示所有记录(showMoveRecords()函数)。
2.4 图像渲染功能
棋盘打印:采用字符界面,带蓝色边框,显示行列坐标(左侧 0-9 行,底部 0-8 列),红方棋子用红色字体、黑方用白色字体,选中状态无额外高亮(字符界面限制)。
状态显示:走棋后实时刷新棋盘;AI 思考、胜负结果等信息通过文字提示展示。
三、非功能需求
性能需求:
资源加载:用户文件、棋盘布局加载时间≤1 秒。
运行流畅:棋子移动无卡顿,AI 走棋耗时≤5 秒(简单)/8 秒(中等)/12 秒(困难,随搜索深度调整)。
兼容性需求:
支持 Windows(cls清屏)和 Linux(clear清屏)系统,适配终端窗口分辨率≥80×24 字符。
可用性需求:
操作简洁:仅需键盘输入坐标和功能键,符合用户对字符界面游戏的操作习惯。
反馈明确:非法走法、登录失败等错误有文字提示;走棋记录清晰可查,便于复盘。
四、界面需求
界面组成:
标题区:顶部显示 “中国象棋(人机对战)” 及边框。
提示区:显示输入格式(“输入格式:9 0 7 0(从 x1 y1 移动到 x2 y2)”)和功能键说明(“‘q’ 退出游戏,‘r’ 查看记录”)。
棋盘区:居中显示 10×9 网格,含 “楚河汉界”,行列坐标标注于两侧。
信息区:底部显示当前回合、AI 走棋提示、胜负结果等。
视觉风格:
字符界面 + 彩色字体(红方红色、黑方白色、边框蓝色),布局对称,文字清晰,突出棋盘核心区域。
五、数据需求
核心数据结构:
游戏状态(GameState):当前回合(红方 1 / 黑方 – 1)、棋盘状态(board[ROW][COL])、AI 搜索深度(AI_DEPTH)。
棋子(piece):用整数表示(1-7 为红方,-1 至 – 7 为黑方),映射关系见pieceName()函数。
用户(User):包含用户名(username)、密码(password)、登录状态(loggedIn)。
走棋记录(MoveRecord):时间(time)、坐标(x1,y1,x2,y2)、移动棋子(piece)、被吃棋子(capturedPiece)、是否 AI 走棋(isAI)。
常量定义:
棋盘尺寸(ROW=10,COL=9)。
棋子类型(R_GENERAL=1,B_GENERAL=-1,R_CHARIOT=5,B_CHARIOT=-5等)。
棋子价值(PIECE_VALUE数组:帅 / 将 =±10000,车 =±9000,炮 =±4500 等)。
六、总结
本游戏基于传统中国象棋规则,通过模块化代码实现了人机对战核心功能,包括棋子移动规则校验、AI 决策、用户管理及走棋记录等模块。需求严格对应代码实现,确保功能完整、逻辑严谨,兼顾策略性与易用性,适合作为字符界面象棋游戏的开发规范与参考依据。
game.h
#ifndef GAME_H
#define GAME_H
#include "user.h"
#include "board.h"
#include "record.h"
// AI搜索深度
#define AI_DEPTH 2
// 初始化游戏
void initGame();
// 游戏主循环
void startGame(const User* user);
// 检查移动是否合法
bool isValidMove(int x1, int y1, int x2, int y2, int currentPlayer);
// 检查是否将军
bool isCheck(int currentPlayer);
// 检查是否困毙
bool isStalemate(int currentPlayer);
// AI走棋
void aiMove(const User* user);
#endif
game.c
#include "game.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <limits.h>
// 棋子价值表
const int PIECE_VALUE[15] =
{
0, // 0-EMPTY
-10000, // 1-R_GENERAL
-2000, // 2-R_ADVISOR
-2000, // 3-R_ELEPHANT
-4000, // 4-R_HORSE
-9000, // 5-R_CHARIOT
-4500, // 6-R_CANNON
-1000, // 7-R_SOLDIER
10000, // 8-B_GENERAL (映射-1)
2000, // 9-B_ADVISOR (映射-2)
2000, // 10-B_ELEPHANT (映射-3)
4000, // 11-B_HORSE (映射-4)
9000, // 12-B_CHARIOT (映射-5)
4500, // 13-B_CANNON (映射-6)
1000 // 14-B_SOLDIER (映射-7)
};
// 初始化游戏
void initGame()
{
srand((unsigned int)time(NULL)); // 随机种子,为 AI 随机探索
initBoard(); // 初始化棋盘,清空,摆初始棋子
}
// 车/帅/将移动合法性检查
static bool checkLineMove(int x1, int y1, int x2, int y2, int piece)
{
// 车、帅/将必须走直线(x 或 y 坐标一致)
if (x1 != x2 && y1 != y2) return 0;
if (abs(piece) == 1)
{
// 帅/将(GENERAL)
// 帅/将有“九宫格”限制:
// 红帅(piece<0)只能在 (0-2, 3-5);黑将(piece>0)只能在 (7-9, 3-5)
if ((piece > 0 && (x2 < 7 || x2 > 9 || y2 < 3 || y2 > 5)) ||
(piece < 0 && (x2 < 0 || x2 > 2 || y2 < 3 || y2 > 5)))
return 0;
// 帅/将每次只能走 1 格
if (abs(x2 - x1) + abs(y2 - y1) != 1) return 0;
} else
{
// 车(CHARIOT)
int step = (x1 == x2) ? 1 : 0; // 1=沿 y 轴移动,0=沿 x 轴移动
int start, end;
if (step)
{
// 沿 y 轴移动,检查路径是否被阻挡
start = (y1 < y2) ? y1 + 1 : y2 + 1;
end = (y1 < y2) ? y2 : y1;
for (int y = start; y < end; y++)
if (board[x1][y] != EMPTY) return false;
} else {
// 沿 x 轴移动,检查路径是否被阻挡
start = (x1 < x2) ? x1 + 1 : x2 + 1;
end = (x1 < x2) ? x2 : x1;
for (int x = start; x < end; x++)
if (board[x][y1] != EMPTY) return false;
}
}
return true;
}
// 马移动合法性检查
static bool checkHorseMove(int x1, int y1, int x2, int y2) {
int dx = abs(x2 - x1), dy = abs(y2 - y1);
// 马走“日”:必须是 (1,2) 或 (2,1) 的坐标差
if (!((dx == 1 && dy == 2) || (dx == 2 && dy == 1))) return false;
// 检查“蹩马腿”:日字拐角处是否有棋子
if (dx == 2) {
// 沿 x 轴跨 2 格,检查中间 x 坐标
int xMid = (x1 < x2) ? x1 + 1 : x2 + 1;
if (board[xMid][y1] != EMPTY) return false;
} else {
// 沿 y 轴跨 2 格,检查中间 y 坐标
int yMid = (y1 < y2) ? y1 + 1 : y2 + 1;
if (board[x1][yMid] != EMPTY) return false;
}
return true;
}
// 象/相移动合法性检查
static bool checkElephantMove(int x1, int y1, int x2, int y2, int piece) {
int dx = abs(x2 - x1), dy = abs(y2 - y1);
// 象走“田”:坐标差必须是 (2,2)
if (dx != 2 || dy != 2) return false;
// 阵营过界限制:
// 红象(piece<0)不能过楚河(x2 > 4);黑象(piece>0)不能过汉界(x2 < 5)
if ((piece > 0 && x2 < 5) || (piece < 0 && x2 > 4)) return false;
// 检查“堵象眼”:田字中心是否有棋子
int xMid = (x1 + x2) / 2, yMid = (y1 + y2) / 2;
if (board[xMid][yMid] != EMPTY) return false;
return true;
}
// 仕/士移动合法性检查
static bool checkAdvisorMove(int x1, int y1, int x2, int y2, int piece)
{
int dx = abs(x2 - x1), dy = abs(y2 - y1);
// 士走“斜线”:坐标差必须是 (1,1)
if (dx != 1 || dy != 1) return false;
// 九宫格限制:
// 红士(piece<0)只能在 (0-2, 3-5);黑士(piece>0)只能在 (7-9, 3-5)
if ((piece > 0 && (x2 < 7 || x2 > 9 || y2 < 3 || y2 > 5)) ||
(piece < 0 && (x2 < 0 || x2 > 2 || y2 < 3 || y2 > 5)))
return false;
return true;
}
// 炮移动合法性检查
static bool checkCannonMove(int x1, int y1, int x2, int y2) {
// 炮必须走直线(x 或 y 坐标一致)
if (x1 != x2 && y1 != y2) return false;
int step = (x1 == x2) ? 1 : 0; // 1=沿 y 轴,0=沿 x 轴
int start, end, obstacle = 0;
if (step)
{
// 沿 y 轴移动,统计路径棋子数
start = (y1 < y2) ? y1 + 1 : y2 + 1;// 路径起始点(排除起始位置)
end = (y1 < y2) ? y2 : y1;// 路径终点(排除目标位置)
for (int y = start; y < end; y++) // 遍历路径上的所有位置,统计棋子数量
if (board[x1][y] != EMPTY) obstacle++; // 非空位置即算障碍物
} else
{
// 沿 x 轴移动,统计路径棋子数
start = (x1 < x2) ? x1 + 1 : x2 + 1;// 路径起始点(排除起始位置)
end = (x1 < x2) ? x2 : x1;// 路径终点(排除目标位置)
for (int x = start; x < end; x++) // 遍历路径上的所有位置,统计棋子数量
if (board[x][y1] != EMPTY) obstacle++; // 非空位置即算障碍物
}
// 逻辑:
// - 移动到空位:路径必须无棋子(obstacle == 0)
// - 吃子:路径必须有且仅有 1 个棋子(炮架,obstacle == 1)
return (board[x2][y2] == EMPTY) ? (obstacle == 0) : (obstacle == 1);
}
// 兵/卒移动合法性检查
static bool checkSoldierMove(int x1, int y1, int x2, int y2, int piece)
{
int dx = x2 - x1, dy = abs(y2 - y1);
if (piece > 0)
{
// 红兵
if (dx > 0) return false;
if (x1 > 4)
{
// 未过河
return (dx == -1 && dy == 0);
} else
{
// 过河后
return ((dx == -1 && dy == 0) || (dx == 0 && dy == 1));
}
} else
{
// 黑卒
if (dx < 0) return false;
if (x1 < 5) {
// 未过河
return (dx == 1 && dy == 0);
} else {
// 过河后
return ((dx == 1 && dy == 0) || (dx == 0 && dy == 1));
}
}
}
// 综合移动合法性检查
bool isValidMove(int x1, int y1, int x2, int y2, int currentPlayer) {
// 基本边界检查
if (!isInBoard(x1, y1) || !isInBoard(x2, y2)) return false;
if (board[x1][y1] == EMPTY) return false; // 起始位置非空
if (isAlly(board[x2][y2], currentPlayer)) return false; // 目标位置非己方
if (!isAlly(board[x1][y1], currentPlayer)) return false; // 起始位置是己方
if (x1























暂无评论内容