diff --git "a/C\350\257\255\350\250\200\345\255\246\344\271\240\347\254\224\350\256\260.md" "b/C\350\257\255\350\250\200\345\255\246\344\271\240\347\254\224\350\256\260.md" index 5f40c20..533ffbe 100755 --- "a/C\350\257\255\350\250\200\345\255\246\344\271\240\347\254\224\350\256\260.md" +++ "b/C\350\257\255\350\250\200\345\255\246\344\271\240\347\254\224\350\256\260.md" @@ -2,4 +2,753 @@ # 记录你学习过程中的所见所思!酸甜苦辣! -# 看什么看! 赶紧填坑啊! \ No newline at end of file +# 看什么看! 赶紧填坑啊! + +首先,我想出了大概的框架,就是三步走——初始化、绘制内容、更新界面。 +接着,我细化了所要完成的内容,先再用户图形界面里放上我选好的棋盘图作为背景图,然后仔细调整,保证每个落子点的位置与绘制棋子的坐标点尽可能地吻合。 +然后,我还要考虑鼠标的问题,我查询了相关资料,发现message函数使可以获取鼠标信息的,于是我在此基础上,通过确认鼠标指针位于哪个格子的范围内,进而确认此时如果点击鼠标,棋子该在哪个坐标点落下。为了使游戏界面更加美观,我还添加了鼠标提示框。 +为了满足多次的参数调用时代码的简洁性,避免出现大量的参数,扰乱思路,也为了方便函数灵活地更改棋局信息,我定义了一个名为Game的结构体,存放关于棋局的各种信息。(根据需求随时添加变量) +//游戏结构 +struct Game { + //0表示没有棋子,1表示黑棋,-1表示白棋 + int map[ROWS][COLS]; + //游戏是否在运行 + bool isRunning; + //定义消息变量 + ExMessage msg;//消息结构体,负责鼠标键盘 + //鼠标当前所在下标 + int row = 0; + int col = 0; + //上一次下的棋子的位置坐标 + int currentchess_row = 0; + int currentchess_col = 0; + //当前棋手 + ChessType currentChessType; + Player player; + //电脑下棋的坐标 + int robotRow = 1; + int robotCol = 1; +}; +以下是我设计游戏界面的具体思路和代码。 +(1)先初始化游戏界面 +void init(Game* pthis, int w, int h) { + srand(time(NULL)); + //创建一个窗口 + initgraph(w, h, EX_SHOWCONSOLE); + pthis->isRunning = true; + pthis->row = -1; + pthis->col = -1; + pthis->currentChessType = White; + //创建棋盘 + //0表示没有棋子,1表示黑棋,-1表示白棋 + int map[ROWS][COLS] = { }; + memcpy(pthis->map, map, sizeof(int) * ROWS * COLS); +} + + +(2)根据棋盘(map)绘制棋子,根据换算后的鼠标坐标绘制鼠标所在提示框 +void render(Game* pthis) { + //绘制棋子 + for (int i = 0; i < ROWS; i++) { + for (int k = 0; k < COLS; k++) { + + + if (pthis->map[i][k] != None) { + //求每个格子左上角的坐标 + int x = k * GRID_SIZE + XOFFSET; + int y = i * GRID_SIZE + YOFFSET; + //绘制棋子 + if (pthis->map[i][k] == White) { + setfillcolor(WHITE); + } + else if (pthis->map[i][k] == Black) { + setfillcolor(BLACK); + } + + //绘制棋子 + solidcircle(x, y, 20); + } + } + } + //绘制当前鼠标所在的提示框 + if (pthis->row != -1 && pthis->col != -1) { + setlinecolor(BLUE);//设置提示框线条颜色 + int x = pthis->col * GRID_SIZE + XOFFSET; + int y = pthis->row * GRID_SIZE + YOFFSET; + circle(x, y, 25); + } +} +(3)更新界面 +void update(Game* pthis) { + + //鼠标移动,判断鼠标指针位于哪个格子的区域 + if (pthis->msg.message == WM_MOUSEMOVE) { + for (int i = 0; i < ROWS; i++) { + for (int k = 0; k < COLS; k++) { + int cx = k * GRID_SIZE + XOFFSET; + int cy = i * GRID_SIZE + YOFFSET; + //判断鼠标指针位于棋盘哪个格子的区域 + if (abs(pthis->msg.x - cx) < GRID_SIZE / 2 && abs(pthis->msg.y - cy) < GRID_SIZE / 2) { + pthis->row = i; + pthis->col = k; + goto END_LOOP; + } + } + } + END_LOOP:;//跳转于此 + } + + //下出棋子,并在pthis->map上记录棋子位置和颜色 + else if (pthis->msg.message == WM_LBUTTONDOWN//鼠标左键点击 + && pthis->row != -1 && pthis->col != -1 &&//点击了合法的位置 + pthis->map[pthis->row][pthis->col] == None)//当前位置没有棋子 + { + pthis->map[pthis->row][pthis->col] = pthis->currentChessType; + //记录本次下棋棋子的位置 + pthis->currentchess_row = pthis->row; + pthis->currentchess_col = pthis->col; + //切换棋手 + pthis->currentChessType = (ChessType)-pthis->currentChessType; + shiftplayer(pthis);//人机切换 + + } +} +为了保证代码的简洁性,我把若干操作打包在了一个函数里: +//创建、更新游戏界面 +void creatUI(Game* pthis, IMAGE* theimage) { + BeginBatchDraw();//开始批量绘图(双缓冲) + cleardevice();//清屏 + putimage(0, 0, theimage);//放背景图 + render(pthis);//绘制棋子和提示框 + EndBatchDraw();//结束双缓冲绘图 +} +2. 实现游戏规则:定义五子棋的游戏规则,如如何判断胜负、如何轮流落子等。 +(1)判断胜负,需要横向纵向斜向分析是否有五子连珠。 +以下代码是负责分析横向是否五子连珠,其他方向的分析同理。 +bool horizontal(Game* pthis, int chess) { + int counter = 1; // 假设当前位置已经是该棋子的颜色 + int start = pthis->currentchess_col - 1; // 从当前列的前一列开始检查 + int end = pthis->currentchess_col + 1; // 从当前列的后一列开始检查 + + // 向左检查 + for (int i = start; i >= 0 && pthis->map[pthis->currentchess_row][i] == chess; i--) { + counter++; + } + // 向右检查 + for (int i = end; i < 15 && pthis->map[pthis->currentchess_row][i] == chess; i++) { + counter++; + } + + return counter >= 5; + + + + +3.构建博弈树:实现一个用于生成博弈树的函数,该函数应能递归地生成所有可能的走法。 +这一步的代码构建已经具有一定的挑战性,我经历了多次的试错与尝试,不断的对其进行改进。先附上我最初行不通的代码,之所以放弃这种思路是因为实在难以插入剪枝算法。 +//失败的方案 +void generate_moves(State* state, int depth, bool maximizing_player, State* best_move) { + if (depth == 0 || is_game_over(state)) { + best_move->x = state->x; + best_move->y = state->y; + return; + } + + if (maximizing_player) { + int max_eval = -10000; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + if (state->board[i][j] == 0) { + state->board[i][j] = 1; + state->x = i; + state->y = j; + generate_moves(state, depth - 1, false, best_move); + int eval = evaluate(state); + if (eval > max_eval) { + max_eval = eval; + best_move->x = state->x; + best_move->y = state->y; + } + state->board[i][j] = 0; + } + } + } + } + else { + int min_eval = 10000; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + if (state->board[i][j] == 0) { + state->board[i][j] = -1; + state->x = i; + state->y = j; + generate_moves(state, depth - 1, true, best_move); + int eval = evaluate(state); + if (eval < min_eval) { + min_eval = eval; + best_move->x = state->x; + best_move->y = state->y; + } + state->board[i][j] = 0; + } + } + } + } +} +设计评分函数是很重要的一步,经过优化,我把evaluate(state)函数改为int score(State* state, ChessType a),以便区分我方和对方的棋子。 +经过改进,我把评分函数拆分成了多个函数,分别具有不同的功能: +int score(State* state, ChessType a) { + int MAP[ROWS][COLS]; + memcpy(MAP, state->board, sizeof(int) * ROWS * COLS); + DirectionMap directionMap[ROWS][COLS] = {}; + for (int i = 0; i < ROWS; i++) { + for (int j = 0; j < COLS; j++) { + if (MAP[i][j] == 0) { + directionMap[i][j].horizontal = 2; + directionMap[i][j].vertical = 2; + directionMap[i][j].leftOblique = 2; + directionMap[i][j].rightOblique = 2;//将没有棋子的位置标记,遍历时不经过这些点 + } + } + } + int Point = detect_succession(MAP, Black, directionMap); + + return Point; + +} +//棋子阵型分为活、死、冲 +//如果连子两侧没有被堵死,则称为活,如果只堵死一侧,称为死,如果都被堵死,则没有分数。 +int detect_succession(int MAP[][COLS], ChessType a, DirectionMap directionMap[][COLS]) { + int point = 0;//记录得分 + int x, y; + int NumOfSame, NumOfDifference,NumOfjump = 0; + + + //1.纵向分析 + for (int j = 0; j < COLS; j++) { + for (int i = 0; i < ROWS; i++) { + x = i, y = j; + NumOfSame, NumOfDifference = 0; + //如果相应点不是棋子类型a,则直接跳过 + if (MAP[x][y] != a || directionMap[x][y].horizontal != 0) { + continue; + } + + while (MAP[x][y] == a && directionMap[x][y].horizontal == 0 && x < ROWS) { + NumOfSame = 1; + NumOfDifference = 0; + + directionMap[x][y].horizontal = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + x++;//继续分析下一个棋子 + } + if (x= 0) { + directionMap[x][y].horizontal = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + x--;//继续分析下一个棋子 + + } + if (x>0&&MAP[x][y] == -a) { + NumOfDifference++;//记录撞上的第二个异色棋子 + + } + x--; + if (x >= 0 && MAP[x][y] == a) { + NumOfjump++; + } + x++; + point += countPoint(NumOfSame, NumOfDifference); + } + } + + //2.横向分析 +(…) + //3.斜向右下&&左上 +(…) + //4.斜向左下&右上 + (…) + return point; +} +但是,经过多次测试,发现这个评分机制还不够完善,不能很明确的区分特定的棋型,比如:1个连4和1个连2,以及2个连3,这两种情况无法通过这个算法区分开来,于是我对此进行了优化,采用数组的方法,明确了每种特定的棋型。优化后的代码如下: +typedef struct { +//记录每种棋形出现的次数 + int NumOf_2Dif[5], NumOf_1Dif[5], NumOf_0dif[5],NumOf_0jump[5],NumOf_1jump[5], NumOf_2jump[5]; +}Count; +int score(State* state, ChessType a) { + int MAP[ROWS][COLS]; + memcpy(MAP, state->board, sizeof(int) * ROWS * COLS); + DirectionMap directionMap[ROWS][COLS] = {}; + for (int i = 0; i < ROWS; i++) { + for (int j = 0; j < COLS; j++) { + if (MAP[i][j] == 0) { + directionMap[i][j].horizontal = 2; + directionMap[i][j].vertical = 2; + directionMap[i][j].leftOblique = 2; + directionMap[i][j].rightOblique = 2;//将没有棋子的位置标记,遍历时不经过这些点 + } + } + } + + int Point = countPoint(MAP, Black, directionMap)-countPoint(MAP,White,directionMap); + //棋局得分为双方得分的差值 + + return Point; + +} +//如果连子两侧没有被堵死,则称为活,如果只堵死一侧,称为死,如果都被堵死,则没有分数。 +Count detect_succession(int MAP[][COLS], ChessType a, DirectionMap directionMap[][COLS]) +{ + int point = 0;//记录得分 + int x, y; + int NumOfSame, NumOfDifference,NumOfjump = 0; + Count count = { 0 }; + + + //1.纵向分析 + for (int j = 0; j < COLS; j++) + { + for (int i = 0; i < ROWS; i++) + { + x = i, y = j; + NumOfSame, NumOfDifference = 0; + //如果相应点不是棋子类型a,则直接跳过 + if (MAP[x][y] != a || directionMap[x][y].horizontal != 0) { + continue; + } + + while (MAP[x][y] == a && directionMap[x][y].horizontal == 0 && x < ROWS) { + NumOfSame = 1; + NumOfDifference = 0; + + directionMap[x][y].horizontal = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + x++;//继续分析下一个棋子 + } + if (x= 0) { + directionMap[x][y].horizontal = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + x--;//继续分析下一个棋子 + + } + if (x>0&&MAP[x][y] == -a) { + NumOfDifference++;//记录撞上的第二个异色棋子 + + } + else { + x--; + if (x >= 0 && MAP[x][y] == a) { + NumOfjump++; + } + x++; + } + if (NumOfDifference == 0)count.NumOf_0dif[NumOfSame-1]++; + if (NumOfDifference == 1)count.NumOf_1Dif[NumOfSame-1]++; + if (NumOfDifference == 2) count.NumOf_2Dif[NumOfSame - 1]++; + if (NumOfjump == 0)count.NumOf_0jump[NumOfSame - 1]++; + if (NumOfjump == 1)count.NumOf_1jump[NumOfSame - 1]++; + if (NumOfjump == 2)count.NumOf_2jump[NumOfSame - 1]++; + NumOfSame = 0; + } + } + + + //2.横向分析 + for (int i = 0; i < ROWS; i++) +{ + for (int j = 0; j < COLS; j++) + { + x = i, y = j; + //如果相应点不是棋子类型a,则直接跳过 + if (MAP[x][y] != a || directionMap[x][y].vertical != 0) + { + continue; + } + + while (MAP[x][y] == a && directionMap[x][y].vertical == 0 && y < ROWS) + { + NumOfSame = 1; + NumOfDifference = 0; + + directionMap[x][y].horizontal = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + y++;//继续分析下一个棋子 + } + if (y= 0) + { + directionMap[x][y].vertical = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + y--;//继续分析下一个棋子 + + } + if (MAP[x][y] == -a) + { + NumOfDifference++;//记录撞上的第二个异色棋子 + + } + else { + y--; + if (y >= 0 && MAP[x][y] == a) { + NumOfjump++; + } + y++; + } + if (NumOfDifference == 0)count.NumOf_0dif[NumOfSame - 1]++; + if (NumOfDifference == 1)count.NumOf_1Dif[NumOfSame - 1]++; + if (NumOfDifference == 2)count.NumOf_2Dif[NumOfSame - 1]++; + if (NumOfjump == 0)count.NumOf_0jump[NumOfSame - 1]++; + if (NumOfjump == 1)count.NumOf_1jump[NumOfSame - 1]++; + if (NumOfjump == 2)count.NumOf_2jump[NumOfSame - 1]++; + NumOfSame = 0; + } +} + + //3.斜向右下&&左上 + for (int j = 0; j < COLS; j++) + { + for (int i = 0; i < ROWS; i++) + { + x = i, y = j; + //如果相应点不是棋子类型a,则直接跳过 + if (MAP[x][y] != a || directionMap[x][y].leftOblique != 0) + { + continue; + } + + while (MAP[x][y] == a && directionMap[x][y].leftOblique == 0 && y < ROWS && x < ROWS) + { + NumOfSame = 1; + NumOfDifference = 0; + + directionMap[x][y].leftOblique = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + x++; + y++;//继续分析下一个棋子 + } + if (MAP[x][y] == -a) + { + NumOfDifference++;//记录往右下方向遍历时撞上的第一个异色棋子 + + }else { + x++; + y++; + if (x < ROWS && y < COLS && MAP[x][y] == a) { + NumOfjump++; + } + x--; + y--; + } + x -= NumOfSame; + y -= NumOfSame;//回到出发点 + + //开始往左上方向遍历 + while (MAP[x][y] == a && directionMap[x][y].leftOblique == 0 && x >= 0 && y >= 0) + { + directionMap[x][y].leftOblique = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + x--; + y--;//继续分析下一个棋子 + + } + if (MAP[x][y] == -a) + { + NumOfDifference++;//记录撞上的第二个异色棋子 + + } + else { + x--; + y--; + if (x >= 0 && y >= 0 && MAP[x][y] == a) { + NumOfjump++; + } + x++; + y++; + } + if (NumOfDifference == 0)count.NumOf_0dif[NumOfSame - 1]++; + if (NumOfDifference == 1)count.NumOf_1Dif[NumOfSame - 1]++; + if (NumOfDifference == 2) count.NumOf_2Dif[NumOfSame - 1]++; + if (NumOfjump == 0)count.NumOf_0jump[NumOfSame - 1]++; + if (NumOfjump == 1)count.NumOf_1jump[NumOfSame - 1]++; + if (NumOfjump == 2)count.NumOf_2jump[NumOfSame - 1]++; + NumOfSame = 0; + } + } + + //4.斜向左下&右上 + for (int i = 0; i < ROWS; i++) + { + for (int j = COLS - 1; j >= 0; j--) + { + x = i, y = j; + //如果相应点不是棋子类型a,则直接跳过 + if (MAP[x][y] != a || directionMap[x][y].rightOblique != 0) + { + continue; + } + + while (MAP[x][y] == a && directionMap[x][y].rightOblique == 0 && y < COLS && x >= 0) + { + NumOfSame = 1; + NumOfDifference = 0; + + directionMap[x][y].rightOblique = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + x--; + y++;//继续分析下一个棋子 + } + if (MAP[x][y] == -a) + { + NumOfDifference++;//记录往右下方向遍历时撞上的第一个异色棋子 + + } + else { + x--; + y++; + if (x >= 0 && y < COLS && MAP[x][y] == a) { + NumOfjump++; + } + x++; + y--; + } + x += NumOfSame; + y -= NumOfSame;//回到出发点 + //开始往左上方向遍历 + while (MAP[x][y] == a && directionMap[x][y].rightOblique == 0 && y >= 0 && x < ROWS) + { + directionMap[x][y].rightOblique = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + x++; + y--;//继续分析下一个棋子 + + } + if (MAP[x][y] == -a) + { + NumOfDifference++;//记录撞上的第二个异色棋子 + + } + else { + x++; + y--; + if (x < ROWS && y >= 0 && MAP[x][y] == a) { + NumOfjump++; + } + x--; + y++; + } + if (NumOfDifference == 0)count.NumOf_0dif[NumOfSame - 1]++; + if (NumOfDifference == 1)count.NumOf_1Dif[NumOfSame - 1]++; + if (NumOfDifference == 2) count.NumOf_2Dif[NumOfSame - 1]++; + if (NumOfjump == 0)count.NumOf_0jump[NumOfSame - 1]++; + if (NumOfjump == 1)count.NumOf_1jump[NumOfSame - 1]++; + if (NumOfjump == 2)count.NumOf_2jump[NumOfSame - 1]++; + NumOfSame = 0; + } + } + return count; +} +//单次搜索评估分数 +//采用指数函数来拟合权重 +int countPoint(int MAP[][COLS], ChessType a, DirectionMap directionMap[][COLS]) { + int point; + Count count=detect_succession(MAP, a, directionMap); + for (int i = 0; i < 5; i++) { + point += count.NumOf_0dif[i] * pow(10, i) + count.NumOf_1Dif[i] * pow(9, i) + count.NumOf_2Dif[i] * 0 + + count.NumOf_0jump[i] * pow(9, i) + count.NumOf_1jump[i] * pow(10,i)+ count.NumOf_2jump[i]*(pow(10,i)+pow(8,i)); +} return point; + +} +完善了评分算法后,我又苦恼于如何用代码实现博弈树。我意识到棋局的数据需要经过层与层之间的传递,因此必须要想办法把数据暂存下来,起初我的想法是定义若干数组,但与大神交流后,我认为这样实现起来过于复杂和冗余,于是觉得采用类似于链表的树状结构来实现数据的传递。 +我定义了一个新的结构体,记录预想中的棋局信息,(不在Game结构体里定义是为了区分开现在的局面和预测的局面,防止混淆),使用结构指针prev,采用类似于链表的数据结构,方便存储相关数据,也方便回溯和遍历。 +我先把可能会用得上的变量都在结构体State里面定义一下,不管这些结构体里的定义的变量是否真的能用得上,方便后续设计出更高效的算法。 +typedef struct { + int board[ROWS][COLS];//这一步的棋盘 + int val;//这一步的评分 + ChessType chesstype;//这一步的棋子类型 + int x, y;//下棋的坐标 + State* prev;//上一步棋局 +} State; +博弈树的代码构建要与极大极小值算法紧密结合在一起,这是下一步我需要做的内容。 +4.实现极大极小值算法:使用极大极小值算法评估博弈树的每个节点,并选择一个最优的走法。 +当位于max层时(即电脑一方出棋时),需要记录该层所有节点得分的最大值,以获得最有利于自己的出棋策略,代码如下 + int max_eval = -10000; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + if (state->board[i][j] == 0) { + state->board[i][j] = 1; + state->x = i; + state->y = j; + generate_moves(state, depth - 1, false, best_move); + int eval = evaluate(state); + if (eval > max_eval) { + max_eval = eval; + best_move->x = state->x; + best_move->y = state->y; + } + state->board[i][j] = 0; + } +同理,当位于min层时(即人类一方出棋时),需要记录该层所有节点得分的最小值,以尽可能使人类一方的利益最小,代码略。 +5.实现α-β剪枝算法:在极大极小值算法的基础上,加入α-β剪枝以提高搜索效率。 +结合之前的博弈树的大致结构,我设计出以下的思路,代码如下: +成功的方案: +int search(State* StepNode, int depth, int alpha, int beta, bool is_max) { + if (depth == 0 || is_win(StepNode)) { + int s = score(StepNode, is_max ? Black : White); + return s; + } + + if (is_max) { + int maxEval = -INFINITY; // 假设负无穷为最小值 + for (int i = 0; i < ROWS; i++) { + for (int j = 0; j < COLS; j++) { + if (isNextchess) { + State* NewstepNode = (State*)malloc(sizeof(State)); + memcpy(NewstepNode, StepNode, sizeof(State)); + NewstepNode->board[i][j] = Black; // 下棋 + NewstepNode->prev = StepNode; + NewstepNode->x = i; + NewstepNode->y = j; + int s = search(NewstepNode, depth - 1, alpha, beta, is_max); + NewstepNode->val = s; + maxEval = max(maxEval, s); + alpha = max(alpha, s); // 更新alpha值 + if (beta <= alpha) { // alpha-beta剪枝 + free(NewstepNode); + return maxEval; // 返回当前最大值 + } + } + } + } + return maxEval; // 返回最大值 + } + else { // is_min + int minEval = INFINITY; // 假设正无穷为最大值 + for (int i = 0; i < ROWS; i++) { + for (int j = 0; j < COLS; j++) { + if (isNextchess) { + State* NewstepNode = (State*)malloc(sizeof(State)); + memcpy(NewstepNode, StepNode, sizeof(State)); + NewstepNode->board[i][j] = White; // 下棋 + NewstepNode->prev = StepNode; + NewstepNode->x = i; + NewstepNode->y = j; + int s = search(NewstepNode, depth - 1, alpha, beta, is_max); + NewstepNode->val = s; + minEval = min(minEval, s); + beta = min(beta, s); // 更新beta值 + if (beta <= alpha) { // alpha-beta剪枝 + free(NewstepNode); + return minEval; // 返回当前最小值 + } + } + } + } + return minEval; // 返回最小值 + } +} +typedef struct { + int x; + int y; +} Coordinate; + +Coordinate get_best_move(State* state, int depth) { + Coordinate best_move = { -1, -1 }; // 初始化最佳坐标为无效值 + int best_score = -INFINITY; // 初始化最佳得分为负无穷 + + for (int i = 0; i < ROWS; i++) { + for (int j = 0; j < COLS; j++) { + if (isNextchess(state, i, j,2)) { // 判断该位置是否可以落子 + State* new_state = (State*)malloc(sizeof(State)); + memcpy(new_state, state, sizeof(State)); + new_state->board[i][j] = Black; // 电脑落子为黑色 + new_state->prev = state; + new_state->x = i; + new_state->y = j; + + int score = search(new_state, depth, -INFINITY, INFINITY, true); // 搜索该落子后的局面得分 + if (score > best_score) { // 更新最佳得分和最佳坐标 + best_score = score; + best_move.x = i; + best_move.y = j; + } + free(new_state); // 释放内存 + } + } + } + return best_move; // 返回最佳落子坐标 +} +6.游戏逻辑整合:将以上步骤整合到一个主循环中,实现游戏的完整逻辑。 + +int main() { + Game game; + init(&game, 1000, 1000); + IMAGE img_bg;//定义图片 + loadimage(&img_bg, L"D:\\五子棋游戏\\bk.jpg");//输入想要的图片 + putimage(0, 0, &img_bg); + + //处理鼠标操作 + + //游戏主循环 + + + + + //默认人是先手 + game.player = human; + while (game.isRunning) { + //获取消息 + if (judge(&game)) { + MessageBox(GetHWnd(), L"结束了", L"XXX ", MB_OK); + game.isRunning = false; + } + if (game.player == human) { + if (peekmessage(&game.msg)) { + update(&game);//鼠标点击后生成白色棋子 + } + creatUI(&game, &img_bg);//生成、更新游戏界面 + } + else if (game.player == robot) { + + robotplay(&game); + creatUI(&game, &img_bg);//生成、更新游戏界面 + shiftplayer(&game);//人机切换 + } + } + return 0; +} diff --git a/level1/p01_running_letter/main.c b/level1/p01_running_letter/main.c index f84d224..64a06e6 100644 --- a/level1/p01_running_letter/main.c +++ b/level1/p01_running_letter/main.c @@ -1,6 +1,60 @@ -#include +#include +#include + +#define RIGHT 00 +#define LEFT 1 + + +HANDLE hd; +int width,dir=RIGHT; +void move(int x,int y) +{ + COORD pos={x,y}; + SetConsoleCursorPosition(hd,pos); + return; +} +void GetSize() +{ + CONSOLE_SCREEN_BUFFER_INFO info; + GetConsoleScreenBufferInfo(hd,&info); + width=info.srWindow.Right; + return; +} +int ne(int now) +{ + if(dir==RIGHT) + { + if(now!=width) return now+1; + else + { + dir=LEFT; + return now-1; + } + } + else + { + if(now) return now-1; + else + { + dir=RIGHT; + return now+1; + } + } +} int main() { - printf("hello world!\n"); + int now; + hd= GetStdHandle(STD_OUTPUT_HANDLE); + GetSize(); + printf("a"); + while(1) + { + move(now,0); + printf(" "); + now=ne(now); + move(now,0); + printf("a"); + Sleep(25); + } return 0; } \ No newline at end of file diff --git a/level1/p02_is_prime/main.c b/level1/p02_is_prime/main.c index f84d224..1dd004a 100644 --- a/level1/p02_is_prime/main.c +++ b/level1/p02_is_prime/main.c @@ -1,6 +1,27 @@ -#include - -int main() { - printf("hello world!\n"); +#include +int main() +{ + int m,n; + int i; + int cnt=0; + int sum=0; + scanf("%d %d",&m,&n); + if(m==1){ + m=2;} + for(i=m;i<=n;i++){ + int isprime=1; + int k; + for(k=2;k +#include int main() { - printf("hello world!\n"); + int i,x,cnt,num=0; + int a[100]; + for(i=2;i<=100;i++){ + cnt=0; + for(x=2;x<=i;x++){ + cnt++; + if(i%x==0){ + break; + } + } + if(cnt==i-1) { + printf("%d\n", i); + num++; + a[num-1]=i; + } + else + continue; + } + printf("num=%d\n",num); + int t,k,j,m,n,CNT=0; + for(t=6;t<=100;t++){ + // printf("%d->%d\n",t,CNT); + for(i=0;i +#include int main() { - printf("hello world!\n"); + int i,x,cnt,num=0; + int a[100]; + for(i=2;i<=100;i++){ + cnt=0; + for(x=2;x<=i;x++){ + cnt++; + if(i%x==0){ + break; + } + } + if(cnt==i-1) { + printf("%d\n", i); + num++; + a[num-1]=i; + } + else + continue; + } + printf("num=%d\n",num); + int t,k,j,m,n,CNT=0; + for(t=6;t<=100;t++){ + // printf("%d->%d\n",t,CNT); + for(i=0;i - -int main() { - printf("hello world!\n"); +#include +int main(){ + char a[26]; + a[0]='a'; + int i,x; + i=1; + while(i<26){ + a[i]=a[i-1]+1; + } + for(i=0;i<26;i++) + printf("%c",a[i]); return 0; -} \ No newline at end of file +} diff --git a/level1/p06_hanoi/main.c b/level1/p06_hanoi/main.c index f84d224..81bbca0 100644 --- a/level1/p06_hanoi/main.c +++ b/level1/p06_hanoi/main.c @@ -1,6 +1,28 @@ -#include +#include -int main() { - printf("hello world!\n"); - return 0; +//定义move函数,将一个盘子从x座移到y座上 +void move(char x, char y) +{ + printf("%c-->%c\n",x,y); //输出移盘方案。x,y代表A,B,C座之一,根据每次的不同情况分别取A,B,C代入 +} + +void hanoi(int n, char one, char two, char three) //将n个盘从one座借助two座,移到three座 +{ + if(n==1) + move(one, three); + else + { + hanoi(n-1, one, three, two); //将 A 座上 n-1 个盘子移到 B 座上(借助 C) + move(one, three);//将 A 座上剩下的 1 个盘子移到 C 座上 + hanoi(n-1, two, one, three);//将 B 座上 n-1 个盘子移到 C 座上(借助 A) + } +} + +main() +{ + int m; + printf("input the number of disks:"); + scanf("%d",&m); //盘子的数量 + printf("The step to moving %d disks:\n",m); + hanoi(m,'A','B','C'); } \ No newline at end of file diff --git a/level1/p07_maze/main.c b/level1/p07_maze/main.c index f84d224..86deaf2 100644 --- a/level1/p07_maze/main.c +++ b/level1/p07_maze/main.c @@ -1,6 +1,230 @@ -#include +#include +#include +#include -int main() { - printf("hello world!\n"); +using namespace std; + +#define MAZE_WIDTH 110 +#define MAZE_HIGHTH 35 +#define RAND_RANGE 8 +#define UP 72 +#define DOWN 80 +#define LEFT 75 +#define RIGHT 77 +#define SP_SIGN -32 + +typedef struct Pos +{ + int x; + int y; +}pos; + +HANDLE handle_out; +bool mp[MAZE_WIDTH][MAZE_HIGHTH]={}; +pos start,End,usr; + +void MoveCursor(int,int); +void InitMaze(); +int random(int,int); +void gPath(pos,pos); +void pave(int,int); +pos Point(int,int); +bool move(int); +void win(); + +int main() +{ + handle_out=GetStdHandle(STD_OUTPUT_HANDLE); + InitMaze(); + usr=start; + usr.y--; + MoveCursor(usr.x,usr.y); + printf("!"); + while(1) + { + if(kbhit()) + { + char input; + input=getch(); + if(input==SP_SIGN) + { + int res=move(getch()); + if(res) break; + } + } + } + win(); return 0; +} + + + + + +void win() +{ + system("cls"); + printf("You win the game!!!"); + return; +} +bool move(int op) +{ + switch(op) + { + case UP: + if(usr.y) + { + if(mp[usr.x][usr.y-1]) + { + MoveCursor(usr.x,usr.y); + printf(" "); + usr.y--; + MoveCursor(usr.x,usr.y); + printf("!"); + } + } + break; + case LEFT: + if(mp[usr.x-1][usr.y]) + { + MoveCursor(usr.x,usr.y); + printf(" "); + usr.x--; + MoveCursor(usr.x,usr.y); + printf("!"); + } + break; + case RIGHT: + if(mp[usr.x+1][usr.y]) + { + MoveCursor(usr.x,usr.y); + printf(" "); + usr.x++; + MoveCursor(usr.x,usr.y); + printf("!"); + } + break; + case DOWN: + if(usr.y==MAZE_HIGHTH-1) return true; + if(mp[usr.x][usr.y+1]) + { + MoveCursor(usr.x,usr.y); + printf(" "); + usr.y++; + MoveCursor(usr.x,usr.y); + printf("!"); + } + break; + } + return false; +} +void pave(int x,int y) +{ + MoveCursor(x,y); + printf(" "); + mp[x][y]=true; + return; +} +void MoveCursor(int x,int y) +{ + COORD position={x,y}; + SetConsoleCursorPosition(handle_out,position); + return; +} +void InitMaze() +{ + for(int y=0;yL.y) swap(H,L); + + while(L.y-now>RAND_RANGE/2) + { + do + { + _y=random(1,RAND_RANGE/2); + _x=random(-RAND_RANGE,RAND_RANGE); + }while(H.x+_x<=0||H.x+_x>=MAZE_WIDTH-1||now+_y<=0||now+_y>=MAZE_HIGHTH-1); + if(_x>0) m=1; + else m=-1; + for(int i=H.x;i!=H.x+_x;i+=m) + { + pave(i,now); + pave(i,now+_y); + } + for(int i=now;i<=now+_y;++i) pave(H.x+_x,i); + now=now+_y+1; + pave(H.x,now); + ++now; + } + for(;now<=L.y;++now) pave(H.x,now); + + now=left; + while(right-now>RAND_RANGE/2) + { + do + { + _y=random(-(RAND_RANGE-2),RAND_RANGE-2); + _x=random(1,RAND_RANGE/2); + }while(now+_x<=0||now+_x>=MAZE_WIDTH-1||L.y+_y<=0||L.y+_y>=MAZE_HIGHTH-1); + if(_y>0) m=1; + else m=-1; + for(int i=L.y;i!=L.y+_y&&i>0;i+=m) + { + pave(now,i); + pave(now+_x,i); + } + for(int i=now;i<=now+_x;++i) pave(i,max(L.y+_y,0)); + now=now+_x+1; + pave(now,L.y); + ++now; + } + for(;now<=right;++now) pave(now,L.y); + return; +} +int random(int Min,int Max) +{ + static int times=0; + int ans; + times++; + srand(time(0)+times); + times%=1000; + ans=rand()%(Max-Min+1)+Min; + return ans; } \ No newline at end of file diff --git a/level1/p08_push_boxes/main.c b/level1/p08_push_boxes/main.c index f84d224..fe3d5e1 100644 --- a/level1/p08_push_boxes/main.c +++ b/level1/p08_push_boxes/main.c @@ -1,6 +1,82 @@ -#include - -int main() { - printf("hello world!\n"); +#include +#include +#include +#include +int main(void) +{ + char map[50][50]={ + "######", + "#O # ", + "# ## #", + "# # #", + "## #", + "######", + }; + int i,x,y,p,q; + int ch; + x=1;y=1;p=1;q=5; + printf("\t\t\t\t迷宫游戏\n"); + printf("操作方式:\n\tw为往上走\n\ts为往下走\n\ta为往左走\n\td为往右走\n"); + printf("\n\n"); + for (i=0;i<6;i++){ + printf("\t\t\t\t"); + puts(map[i]); + } + while(x!=p||y!=q) + { + ch=getch(); + switch(ch) + { + case 's': + { + if(map[x+1][y]!='#') + { + map[x][y]=' '; + x++; + map[x][y]='O'; + } + } + break; + case 'w': + { + if(map[x-1][y]!='#') + { + map[x][y]=' '; + x--; + map[x][y]='O'; + } + } + break; + case 'a': + { + if(map[x][y-1]!='#') + { + map[x][y]=' '; + y--; + map[x][y]='O'; + } + } + break; + case 'd': + { + if(map[x][y+1]!='#') + { + map[x][y]=' '; + y++; + map[x][y]='O'; + } + } + } + system("cls"); + printf("\t\t\t\t迷宫游戏\n"); + printf("操作方式:\n\tw为往上走\n\ts为往下走\n\ta为往左走\n\td为往右走\n"); + printf("\n\n"); + for (i=0;i<6;i++){ + printf("\t\t\t\t"); + puts(map[i]); + } + } + system("cls"); + printf("恭喜你,成功了!"); return 0; -} \ No newline at end of file +} diff --git a/level1/p09_linked_list/main.c b/level1/p09_linked_list/main.c index f84d224..881f44d 100644 --- a/level1/p09_linked_list/main.c +++ b/level1/p09_linked_list/main.c @@ -1,6 +1,87 @@ -#include - +#include +//作业p09 +#include int main() { - printf("hello world!\n"); + //创建64个数组构成的链表,顺序由小到大 + struct Node{ + int x; + struct Node* next; + //struct Node* past; + }; + typedef struct Node node; + node *pre; + pre=NULL; + //node *r; + //r=NULL; + node begin; + pre=&begin; + + int i=1; + int num; + + int a[]={2,45,2,54,23,56,35,23,56,34,5, + 3,6,3,6,7,4,6,4,23,3,3,5,34,4,7,4,5,3}; + num=sizeof(a)/sizeof(a[0]); + + //做个单链表 + for(i=0;inext=pre; + //pre->past=back; + pre->x=a[i]; + pre->next=NULL; + } + //打印 + pre=&begin; + for(pre=&begin;pre!=NULL;pre=pre->next){ + printf("%d\n",pre->x); + } + + + pre=&begin; + node* r=pre->next; + node* p=r->next; + r->next=pre; + pre->next=NULL; + do{//链表反序 + pre=r; + r=p; + p=p->next; + r->next=pre; + }while(p->next!=NULL); + p->next=r; + pre=p; + + //打印 + printf("----------\n"); + while(pre->next!=NULL){ + printf("%d\n",pre->x); + pre=pre->next; + } + + //搜索指定数字5 + int cnt=0; + for(pre=p;pre!=NULL;pre=pre->next,cnt++){ + if(pre->x==5){ + printf("find at location %d\n",cnt); + + }else{ + continue;} + + } + if(cnt==num){ + printf("NOT FIND"); + } + + //清除内存 + pre=p; + while(pre!=NULL) + { + free(pre); + pre=pre->next; + } + + return 0; -} \ No newline at end of file +} diff --git a/level1/p10_warehouse/main.c b/level1/p10_warehouse/main.c index f84d224..ad117cb 100644 --- a/level1/p10_warehouse/main.c +++ b/level1/p10_warehouse/main.c @@ -1,6 +1,106 @@ -#include +#include +#include +#include + +// 定义货物结构体 +typedef struct { + char model[50]; // 型号 + int quantity; // 数量 +} Item; + +// 定义库存结构体 +typedef struct { + Item items[100]; // 假设最多100种货物 + int size; // 当前货物数量 +} Inventory; + +// 从文件中读取库存数据 +void loadInventory(Inventory *inv, const char *filename) { + FILE *file = fopen(filename, "r"); + if (file == NULL) { + printf("无法打开文件:%s\n", filename); + return; + } + inv->size = 0; + while (fscanf(file, "%s %d", inv->items[inv->size].model, &inv->items[inv->size].quantity) != EOF) { + inv->size++; + } + fclose(file); +} + +// 将库存数据保存到文件 +void saveInventory(const Inventory *inv, const char *filename) { + FILE *file = fopen(filename, "w"); + if (file == NULL) { + printf("无法打开文件:%s\n", filename); + return; + } + for (int i = 0; i < inv->size; i++) { + fprintf(file, "%s %d\n", inv->items[i].model, inv->items[i].quantity); + } + fclose(file); +} + +// 显示存货列表 +void showInventory(const Inventory *inv) { + printf("存货列表:\n"); + for (int i = 0; i < inv->size; i++) { + printf("%s: %d\n", inv->items[i].model, inv->items[i].quantity); + } +} + +// 入库操作 +void addItem(Inventory *inv) { + if (inv->size >= 100) { + printf("库存已满,无法添加新货物。\n"); + return; + } + printf("请输入货物型号:"); + scanf("%s", inv->items[inv->size].model); + printf("请输入货物数量:"); + scanf("%d", &inv->items[inv->size].quantity); + inv->size++; +} + +// 出库操作 +void removeItem(Inventory *inv) { + char model[50]; + printf("请输入要出库的货物型号:"); + scanf("%s", model); + for (int i = 0; i < inv->size; i++) { + if (strcmp(inv->items[i].model, model) == 0) { + if (inv->items[i].quantity > 0) { + inv->items[i].quantity--; + printf("成功出库 %s 一件。\n", model); + return; + } else { + printf("库存不足,无法出库 %s。\n", model); + return; + } + } + } + printf("未找到货物:%s\n", model); +} int main() { - printf("hello world!\n"); - return 0; -} \ No newline at end of file + Inventory inv; + loadInventory(&inv, "inventory.txt"); // 从文件读取库存数据,假设文件名为inventory.txt,格式为“型号 数量”每行一种货物。如果文件不存在,则创建一个空库存。 + int choice; + do { + printf("\n菜单:\n"); + printf("1. 显示存货列表\n"); + printf("2. 入库\n"); + printf("3. 出库\n"); + printf("4. 退出程序\n"); + printf("请选择菜单功能(1-4):"); + scanf("%d", &choice); + switch (choice) { + case 1: showInventory(inv);break; + case 2:addItem(inv);break; + case 3: removeItem(inv);break; + case 4:return 0; + + } + } + } + diff --git a/level2/GA/GA.maze.c b/level2/GA/GA.maze.c new file mode 100644 index 0000000..a677ed2 --- /dev/null +++ b/level2/GA/GA.maze.c @@ -0,0 +1,95 @@ +#include +#include +#include +#include +#include"maze.c" + +#define ROWS 10 +#define COLS 10 +#define POP_SIZE 100 +#define MAX_GEN 500 +#define MUTATION_RATE 0.01 + +// 迷宫地图 +int maze[ROWS][COLS] = {}; //进行初始化 +maze[ROWS][COLS]=Maze[][10]; +typedef struct { + int x, y; +} Point; + +typedef struct { + Point path[ROWS*COLS]; + int length; + double fitness; +} Individual; + +Individual population[POP_SIZE]; + +// 计算适应度 +double calculate_fitness(Individual* ind) { + return 1.0 / (ind->length + 1); // 加1是为了避免除数为0的情况 +} + +// 初始化种群 +void initialize_population() { + for (int i = 0; i < POP_SIZE; i++) { + population[i].length = 0; + population[i].fitness = 0; + } +} + +// 选择操作 +int select_index() { + double total_fitness = 0; + for (int i = 0; i < POP_SIZE; i++) { + total_fitness += population[i].fitness; + } + double r = (double)rand() / RAND_MAX * total_fitness; + double sum = 0; + for (int i = 0; i < POP_SIZE; i++) { + sum += population[i].fitness; + if (sum >= r) return i; + } + return POP_SIZE - 1; // 为了确保返回一个有效的索引 +} + +// 交叉操作 +void crossover(Individual* parent1, Individual* parent2, Individual* child1, Individual* child2) { + int index1 = rand() % (parent1->length - 1) + 1; // 保证至少有一个点被交叉 + int index2 = rand() % (parent1->length - index1) + index1; // 保证index2 > index1 + for (int i = 0; i < index1; i++) { + child1->path[i] = parent1->path[i]; + child2->path[i] = parent2->path[i]; + } + for (int i = index1; i < index2; i++) { + child1->path[i] = parent2->path[i]; + child2->path[i] = parent1->path[i]; + } + for (int i = index2; i < parent1->length; i++) { + child1->path[i] = parent1->path[i]; + child2->path[i] = parent2->path[i]; + } + child1->length = child2->length = parent1->length; + child1->fitness = calculate_fitness(child1); + child2->fitness = calculate_fitness(child2); +} + +// 变异操作 +void mutate(Individual* ind) { + if (rand() / (double)RAND_MAX < MUTATION_RATE) { + int index = rand() % ind->length; + // 未完待续 + } +} + +int main() { + srand(time(NULL)); // 设置随机种子 + initialize_population(); // 初始化种群,可以将起点加入每个个体的路径中,并计算适应度等。 + for (int gen = 0; gen<1000;gen++){ + crossover(parent1, parent2, child1, child2); + mutate(ind) + calculate_fitness(Individual* ind); + select_index() ; +}//未完善route的换算 +return 0; + } diff --git a/level2/NeuralNetworks/NEURALNETWORK.c b/level2/NeuralNetworks/NEURALNETWORK.c new file mode 100644 index 0000000..0c33019 --- /dev/null +++ b/level2/NeuralNetworks/NEURALNETWORK.c @@ -0,0 +1,143 @@ +#include +#include +#include + +#define INPUT_NODES 784 // 28x28像素的图像(minist图片集给的尺寸是这个,8*8的貌似找不到合适的图片输入,除非手打) +#define HIDDEN_NODES 100 +#define OUTPUT_NODES 10 // 对应0-9的数字 +#define LEARNING_RATE 0.1 + +double inputLayer[INPUT_NODES]; +double hiddenLayer[HIDDEN_NODES]; +double outputLayer[OUTPUT_NODES]; +double weightsIH[INPUT_NODES][HIDDEN_NODES]; +double weightsHO[HIDDEN_NODES][OUTPUT_NODES]; + +// Sigmoid激活函数 +double sigmoid(double x) { + return 1.0 / (1.0 + exp(-x)); +} + +// 前馈网络 +void feedForward(double input[INPUT_NODES], double target[OUTPUT_NODES], + double hidden[HIDDEN_NODES], double output[OUTPUT_NODES], + double weightsIH[INPUT_NODES][HIDDEN_NODES], double weightsHO[HIDDEN_NODES][OUTPUT_NODES]) { + // 输入层到隐藏层 + for (int i = 0; i < HIDDEN_NODES; i++) { + hiddenLayer[i] = 0.0; + for (int j = 0; j < INPUT_NODES; j++) { + hiddenLayer[i] += inputLayer[j] * weightsIH[j][i]; + } + hiddenLayer[i] = sigmoid(hiddenLayer[i]); + } + + // 隐藏层到输出层 + for (int i = 0; i < OUTPUT_NODES; i++) { + outputLayer[i] = 0.0; + for (int j = 0; j < HIDDEN_NODES; j++) { + outputLayer[i] += hiddenLayer[j] * weightsHO[j][i]; + } + outputLayer[i] = sigmoid(outputLayer[i]); + } +} + +// 反向传播和权重更新 +void backpropagation(double input[INPUT_NODES], double target[OUTPUT_NODES], + double hidden[HIDDEN_NODES], double output[OUTPUT_NODES], + double weightsIH[INPUT_NODES][HIDDEN_NODES], double weightsHO[HIDDEN_NODES][OUTPUT_NODES]) { + // 计算输出层的误差和梯度 + double outputErrors[OUTPUT_NODES]; + double gradientsHO[HIDDEN_NODES][OUTPUT_NODES]; + for (int i = 0; i < OUTPUT_NODES; i++) { + outputErrors[i] = target[i] - output[i]; // 误差计算 + gradientsHO[i][i] = outputErrors[i] * sigmoid_derivative(output[i]); // 梯度计算 + } + + // 计算隐藏层的误差和梯度,并更新权重HO和权重IH + double hiddenErrors[HIDDEN_NODES]; + double gradientsIH[INPUT_NODES][HIDDEN_NODES]; + for (int i = 0; i < HIDDEN_NODES; i++) { + hiddenErrors[i] = 0; + for (int j = 0; j < OUTPUT_NODES; j++) { + hiddenErrors[i] += weightsHO[i][j] * outputErrors[j]; // 误差计算 + } + gradientsIH[i][i] = hiddenErrors[i] * sigmoid_derivative(hidden[i]); // 梯度计算 + for (int j = 0; j < OUTPUT_NODES; j++) { // 更新权重HO + weightsHO[i][j] += LEARNING_RATE * gradientsHO[i][j]; + } +// 更新权重IH + } +} + + +int main() { + // 初始化权重(此处使用随机值) + for (int i = 0; i < INPUT_NODES; i++) { + for (int j = 0; j < HIDDEN_NODES; j++) { + weightsIH[i][j] = (rand() % 2000 - 1000) / 1000.0; // -1到1之间的随机数 + } + } + for (int i = 0; i < HIDDEN_NODES; i++) { + for (int j = 0; j < OUTPUT_NODES; j++) { + weightsHO[i][j] = (rand() % 2000 - 1000) / 1000.0; // -1到1之间的随机数 + } + } + + // 假设我们有一个手写数字的图像数据存储在inputLayer数组中 + // ... 加载或处理输入数据的代码 ... + feedForward(input[INPUT_NODES], target[OUTPUT_NODES], + hidden[HIDDEN_NODES], output[OUTPUT_NODES], + weightsIH[INPUT_NODES][HIDDEN_NODES], weightsHO[HIDDEN_NODES][OUTPUT_NODES]); // 前馈网络,得到输出结果在outputLayer数组中 + backpropagation(input[INPUT_NODES], target[OUTPUT_NODES], + hidden[HIDDEN_NODES], output[OUTPUT_NODES], + weightsIH[INPUT_NODES][HIDDEN_NODES], weightsHO[HIDDEN_NODES][OUTPUT_NODES]); // 根据输出结果进行反向传播和权重更新 + // ... 可以根据outputLayer数组中的值来判断识别的数字 ... + return 0; +} + + + + +#define OUTPUT_ERROR_THRESHOLD 0.01 // 输出层误差阈值 +#define HIDDEN_ERROR_THRESHOLD 0.01 // 隐藏层误差阈值 +#define LEARNING_RATE 0.1 // 学习率 + +// Sigmoid函数的导数 +double sigmoidDerivative(double x) { + return x * (1.0 - x); +} + +// 反向传播和权重更新 +void backpropagation() { + // 计算输出层的误差 + double outputErrors[OUTPUT_NODES]; + for (int i = 0; i < OUTPUT_NODES; i++) { + double target = (i == targetLabel) ? 1.0 : 0.0; // 目标值为1或0,取决于当前样本的标签 + double error = target - outputLayer[i]; + outputErrors[i] = error; + } + + // 计算隐藏层的误差 + double hiddenErrors[HIDDEN_NODES]; + for (int i = 0; i < HIDDEN_NODES; i++) { + hiddenErrors[i] = 0.0; + for (int j = 0; j < OUTPUT_NODES; j++) { + hiddenErrors[i] += outputErrors[j] * weightsHO[i][j]; + } + hiddenErrors[i] *= sigmoidDerivative(hiddenLayer[i]); + } + + // 更新权重 + for (int i = 0; i < HIDDEN_NODES; i++) { + for (int j = 0; j < OUTPUT_NODES; j++) { + double gradient = outputErrors[j] * sigmoidDerivative(outputLayer[j]); + weightsHO[i][j] += LEARNING_RATE * hiddenLayer[i] * gradient; + } + } + for (int i = 0; i < INPUT_NODES; i++) { + for (int j = 0; j < HIDDEN_NODES; j++) { + double gradient = hiddenErrors[j] * sigmoidDerivative(hiddenLayer[j]); + weightsIH[i][j] += LEARNING_RATE * inputLayer[i] * gradient; + } + } +} diff --git a/level2/NeuralNetworks/anothermethod.c b/level2/NeuralNetworks/anothermethod.c new file mode 100644 index 0000000..c12d9d4 --- /dev/null +++ b/level2/NeuralNetworks/anothermethod.c @@ -0,0 +1,237 @@ +#include +#include +#include +#include + +typedef const int cint; +typedef const char cchar; + +/*一个手写数字的结构体*/ +typedef struct +{ + int pixel[1024];//像素点 + int label;// +}Digit; + +/*一个有label的距离结构体*/ +typedef struct +{ + float distance; + int label; +}Distance; + +/*文件路径+名称*/ +cchar* trainingFile = "C:\\Users\86147\Desktop\mnist_dataset\train-labels.txt"; +cchar* testingFile = "C:\\Users\86147\Desktop\mnist_dataset\t10k-labels.txt"; +cchar* predictFile = "C:\\Users\86147\Desktop\mnist_dataset\t10k-images.idx3-ubyte"; + +/*每个数据集的数字个数*/ +cint ntrain = 3;//943 +cint ntest = 6;//196 +cint npredict = 1;//9 + +float calDistance(Digit digit1, Digit digit2) +/*求距离*/ +{ + int i, squareSum = 0.0; + for (i = 0; i < 1024; i++) + { + squareSum += pow(digit1.pixel[i] - digit2.pixel[i], 2.0); + } + return sqrtf(squareSum); +} + +int loadDigit(Digit* digit, FILE* fp, int* labels) +/*读取digit*/ +{ + int index = 0; + for (index = 0; index < 1024; index++) + { + if (!fscanf_s(fp, "%d", &(digit->pixel[index]))) + { + printf("FILE already read finish.\n"); + return -1; + } + } + fscanf_s(fp, "%d", &(digit->label)); + *labels = digit->label; + + return 1; +} + +void showDigit(Digit digit) +/*显示一个Digit 结构体*/ +{ + int i, j, id; + for (i = 0; i < 32; i++) + { + for (j = 0; j < 32; j++) + { + printf("%d", digit.pixel[i * 32 + j]); + } + printf("\n"); + } + printf(" %d \n", digit.label); +} + +void exchange(Distance* in, int index1, int index2) +/*交换字符串两项*/ +{ + Distance tmp = (Distance)in[index1]; + in[index1] = in[index2]; + in[index2] = tmp; +} + +void selectSort(Distance* in, int length) +/*选择排序*/ +{ + int i, j, min; + int N = length; + for (i = 0; i < N - 1; i++) + { + min = i; + for (j = i + 1; j < N; j++) + { + if (in[j].distance < in[min].distance) min = j; + } + exchange(in, i, min); + } +} +int prediction(int K, Digit in, Digit* train, int nt) +/*利用训练数据预测一个数据digit*/ +{ + int i, it; + Distance* distance=(Distance*)malloc(nt*sizeof(Distance));//定义一个数组 + /*求取输入digit与训练数据的距离*/ + for (it = 0; it < nt; it++) + { + distance[it].distance = calDistance(in, train[it]); + distance[it].label = train[it].label; + } + /*给计算的距离排序(选择排序)*/ + int predict = 0; + selectSort(distance, nt); + for (i = 0; i < K; i++)/*在已排序好的数据中取前K个数据(长得最像in的K个数据)*/ + { + predict += distance[i].label; + } + return (int)(predict / K);//?待改进 +} +void knn_classifiy(int K) +/*用测试数据集进行测试*/ +{ + printf(".knn_classifiy.\n"); + + int i; + FILE* fp; + + /*读入训练数据*/ + int trainLabels[ntrain]; + int trainCount[10] = { 0 }; + Digit* Dtrain = (Digit*)malloc(ntrain * sizeof(Digit)); + fp = fopen(trainingFile, "r"); + printf("..load training digits.\n"); + for (i = 0; i < ntrain; i++) + { + loadDigit(&Dtrain[i], fp, &trainLabels[i]); + trainCount[Dtrain[i].label]++; + } + fclose(fp); + printf("..Done.\n"); + + /*读入测试数据*/ + int testLabels[ntest]; + int testCount[10] = { 0 }; + Digit* Dtest = (Digit*)malloc(ntest * sizeof(Digit)); + fp = fopen(testingFile, "r"); + printf("..load testing digits.\n"); + for (i = 0; i < ntest; i++) + { + loadDigit(&Dtest[i], fp, &testLabels[i]); + testCount[Dtest[i].label]++; + } + fclose(fp); + printf("..Done.\n"); + + /*求测试数据与训练数据之间的距离*/ + printf("..Cal Distance begin.\n"); + Distance Distance2Train[ntrain]; + int CorrectCount[10] = { 0 }; + int itrain, itest, predict; + for (itest = 0; itest < ntest; itest++) + { + predict = prediction(K, Dtest[itest], Dtrain, ntrain); + //printf("%d-%d\n",predict, Dtest[itest].label); + + /*给预测准确的进行计数*/ + if (predict == Dtest[itest].label) + { + CorrectCount[predict]++; + } + } + + /*输出测试数据的准确率*/ + printf(" Correct radio: \n\n"); + for (i = 0; i < 10; i++) + { + printf("%d: ( %2d / %2d ) = %.2f%%\n", + i, + CorrectCount[i], + testCount[i], + (float)(CorrectCount[i] * 1.0 / testCount[i] * 100)); + } +} +void knn_predict(int K) +/*预测数据*/ +{ + int i; + FILE* fp; + + /*读入训练数据*/ + int trainLabels[ntrain]; + int trainCount[10] = { 0 }; + Digit* Dtrain = (Digit*)malloc(ntrain * sizeof(Digit)); + fp = fopen(trainingFile, "r"); + printf("..load training digits.\n"); + for (i = 0; i < ntrain; i++) + { + loadDigit(&Dtrain[i], fp, &trainLabels[i]); + trainCount[Dtrain[i].label]++; + } + fclose(fp); + printf("..Done.\n"); + + /*读入需要预测的数据*/ + int predictLabels[npredict]; + int predictCount[10] = { 0 }; + Digit* Dpredict = (Digit*)malloc(npredict * sizeof(Digit)); + fp = fopen(predictFile, "r"); + printf("..load predict digits.\n"); + for (i = 0; i < npredict; i++) + { + loadDigit(&Dpredict[i], fp, &predictLabels[i]); + predictCount[Dpredict[i].label]++; + } + fclose(fp); + printf("..Done.\n"); + + /*求输入数据与训练数据之间的距离*/ + printf("..Cal Distance begin.\n"); + Distance Distance2Train[ntrain]; + int itrain, ipredict, predict; + for (ipredict = 0; ipredict < npredict; ipredict++) + { + predict = prediction(K, Dpredict[ipredict], Dtrain, ntrain); + printf("%d\n", predict); + } +} +int main(int argc, char** argv) +{ + int K = 1; + /*对已知数据进行测试,统计预测的正确率*/ + knn_classifiy(K); + /*对位置数据进行预测*/ + knn_predict(K); + + return 1; +} diff --git a/level2/PI/PI.c b/level2/PI/PI.c new file mode 100644 index 0000000..48a8f56 --- /dev/null +++ b/level2/PI/PI.c @@ -0,0 +1,87 @@ +#include +#include + +#define MAX_DIGITS 10000 +#define SCALE 150000 +#define ARRINIT(x) memset(x, 0, sizeof(x)) + +typedef struct { + int len; + int data[MAX_DIGITS]; +} BigInt; + +void multiply(BigInt* a, int b, BigInt* result) { + int carry = 0; + for (int i = 0; i < a->len; i++) { + int temp = a->data[i] * b + carry; + result->data[i] = temp % SCALE; + carry = temp / SCALE; + } + if (carry != 0) { + result->data[a->len] = carry; + result->len = a->len + 1; + } else { + result->len = a->len; + } +} + +void divide(BigInt* a, int b, BigInt* quotient, int* remainder) { + ARRINIT(quotient->data); + quotient->len = 0; + *remainder = 0; + + for (int i = 0; i < a->len; i++) { + int temp = *remainder * SCALE + a->data[i]; + quotient->data[i] = temp / b; + *remainder = temp % b; + } + quotient->len = a->len; + while (quotient->len > 1 && quotient->data[quotient->len - 1] == 0) { + quotient->len--; + } +} + +void subtract(BigInt* a, BigInt* b, BigInt* result) { + int borrow = 0; + for (int i = 0; i < a->len || i < b->len || borrow != 0; i++) { + if (i < a->len) { + a->data[i] -= borrow; + } + if (i < b->len) { + a->data[i] -= b->data[i]; + } + borrow = (a->data[i] < 0) ? 1 : 0; + a->data[i] = (a->data[i] + SCALE) % SCALE; + } + result->len = a->len; + while (result->len > 1 && result->data[result->len - 1] == 0) { + result->len--; + } + memcpy(result->data, a->data, sizeof(int) * result->len); +} + +void output(BigInt* num) { + printf("%d.", num->data[0]); + for (int i = 1; i < num->len; i++) { + printf("%04d", num->data[i]); + } + printf("\n"); +} + +int main() { + BigInt a, b, ar, br, t1, t2, t3; + int k = 1; + a.len = 1; + a.data[0] = SCALE - 1; // Initialize a with 9999...9999 (SCALE-1 digits of 9) + b.len = 1; + b.data[0] = 2 * SCALE + 3; // Initialize b with 20003 (2 * SCALE + 3) + arrinit(&ar); // Temporary variables for multiplication and division operations. Initialize them to zero. + arrinit(&br); // Temporary variables for multiplication and division operations. Initialize them to zero. + BigInt piResult; // To store the calculated value of pi. Initialize it to zero. piResult is our final result. It is printed at the end. + arrinit(&piResult); // Temporary variables for subtraction operation. Initialize them to zero. + arrinit(&t1); arrinit(&t2); arrinit(&t3); + while (k <= MAX_DIGITS) { multiply(&a, k, &t1); + } + return 0; + } + diff --git a/level2/PI/README.md b/level2/PI/README.md index c19ad35..3ca1155 100755 --- a/level2/PI/README.md +++ b/level2/PI/README.md @@ -6,6 +6,93 @@ 1. 打印总时间 1. 打印内存消耗的最大值 -### 参考: -[2014级柱神作品](https://github.com/holdzhu/uestc2015ccpp/tree/master/Pi) (做完本题后再看啊!!!) \ No newline at end of file + + +#include +#include + +#define MAX_DIGITS 10000 +#define SCALE 150000 +#define ARRINIT(x) memset(x, 0, sizeof(x)) + +typedef struct { + int len; + int data[MAX_DIGITS]; +} BigInt; + +void multiply(BigInt* a, int b, BigInt* result) { + int carry = 0; + for (int i = 0; i < a->len; i++) { + int temp = a->data[i] * b + carry; + result->data[i] = temp % SCALE; + carry = temp / SCALE; + } + if (carry != 0) { + result->data[a->len] = carry; + result->len = a->len + 1; + } else { + result->len = a->len; + } +} + +void divide(BigInt* a, int b, BigInt* quotient, int* remainder) { + ARRINIT(quotient->data); + quotient->len = 0; + *remainder = 0; + + for (int i = 0; i < a->len; i++) { + int temp = *remainder * SCALE + a->data[i]; + quotient->data[i] = temp / b; + *remainder = temp % b; + } + quotient->len = a->len; + while (quotient->len > 1 && quotient->data[quotient->len - 1] == 0) { + quotient->len--; + } +} + +void subtract(BigInt* a, BigInt* b, BigInt* result) { + int borrow = 0; + for (int i = 0; i < a->len || i < b->len || borrow != 0; i++) { + if (i < a->len) { + a->data[i] -= borrow; + } + if (i < b->len) { + a->data[i] -= b->data[i]; + } + borrow = (a->data[i] < 0) ? 1 : 0; + a->data[i] = (a->data[i] + SCALE) % SCALE; + } + result->len = a->len; + while (result->len > 1 && result->data[result->len - 1] == 0) { + result->len--; + } + memcpy(result->data, a->data, sizeof(int) * result->len); +} + +void output(BigInt* num) { + printf("%d.", num->data[0]); + for (int i = 1; i < num->len; i++) { + printf("%04d", num->data[i]); + } + printf("\n"); +} + +int main() { + BigInt a, b, ar, br, t1, t2, t3; + int k = 1; + a.len = 1; + a.data[0] = SCALE - 1; // Initialize a with 9999...9999 (SCALE-1 digits of 9) + b.len = 1; + b.data[0] = 2 * SCALE + 3; // Initialize b with 20003 (2 * SCALE + 3) + arrinit(&ar); // Temporary variables for multiplication and division operations. Initialize them to zero. + arrinit(&br); // Temporary variables for multiplication and division operations. Initialize them to zero. + BigInt piResult; // To store the calculated value of pi. Initialize it to zero. piResult is our final result. It is printed at the end. + arrinit(&piResult); // Temporary variables for subtraction operation. Initialize them to zero. + arrinit(&t1); arrinit(&t2); arrinit(&t3); + while (k <= MAX_DIGITS) { multiply(&a, k, &t1); + } + return 0; + } + diff --git a/level2/SkipList/create.c b/level2/SkipList/create.c new file mode 100644 index 0000000..69595a3 --- /dev/null +++ b/level2/SkipList/create.c @@ -0,0 +1,44 @@ +#include"标头.h" +#include +#include +/*动态申请跳表节点*/ +static skip_list_node* skip_list_node_creat(int level, int key, int value) { + struct skip_list_node* node = NULL; + /*节点空间大小为节点数据大小+level层索引所占用的大小*/ + node = (skip_list_node*)SKIP_LIST_MALLOC(sizeof(*node) + level * sizeof(node)); + if (node == NULL) { + printf("fail to malloc node"); + return NULL; + } + + /*把申请空间初始化为0*/ + memset(node, 0, sizeof(*node) + level * sizeof(node)); + node->key = key; + node->value = value; + node->max_level = level; + + return node; +} +/*创建跳表头节点*/ +struct skip_list* skip_list_creat(int max_level) { + struct skip_list* list = NULL; + list = (skip_list*)SKIP_LIST_MALLOC(sizeof(*list)); + if (list == NULL) { + printf("fail to malloc list"); + return NULL; + } + + list->level = 1; + list->num = 0; + list->head = skip_list_node_creat(max_level, 0, 0); + if (list->head = NULL) { + SKIP_LIST_FREE(list); + printf("fail to malloc list"); + return NULL; + } + return list; +} + + + + diff --git a/level2/SkipList/delete.c b/level2/SkipList/delete.c new file mode 100644 index 0000000..81d6af6 --- /dev/null +++ b/level2/SkipList/delete.c @@ -0,0 +1,74 @@ +#include"标头.h" +#include +int skip_list_delete(struct skip_list* list, int key) +{ + struct skip_list_node** update = NULL; /*用来更新每层索引指针,存放插入位置的前驱各层节点索引*/ + struct skip_list_node* cur = NULL; + struct skip_list_node* prev = NULL; + int i = 0; + + if (list == NULL && list->num == 0) + return -1; + + /*申请update空间用于保存每层的节点索引指针*/ + update = (struct skip_list_node**)SKIP_LIST_MALLOC(sizeof(list->level * sizeof(struct skip_list_node*))); + if (update == NULL) + return -2; + + /*逐层查询,查找删除位置的前驱各层节点索引 + *update[0] 存放第一层的删除位置前驱节点,update[0]->next[0]表示删除位置的前驱节点的下一节点(update[0]->next[0])的第一层索引值 + *update[1] 存放第二层的删除位置前驱节点,update[1]->next[1]表示删除位置的前驱节点的下一节点(update[1]->next[0])的第二层索引值 + *update[n] 存放第一层的删除位置前驱节点,update[n]->next[n]表示删除位置的前驱节点的下一节点(update[n]->next[0])的第n层索引值 + */ + prev = list->head; /*从第一个节点开始的最上层开始找*/ + i = list->level - 1; + for (; i >= 0; i--) + { + /* 各层每个节点的下一个节点不为空 && 下个节点的key小于要插入的key */ + while (((cur = prev->next[i]) != NULL) && (cur->key < key)) + { + prev = cur; /* 向后移动 */ + } + update[i] = prev; /* 各层要删除节点的前驱节点 */ + } + + /* 当前key存在 */ + if ((cur != NULL) && (cur->key == key)) + { + /*逐层删除*/ + for (i = 0; i < list->level; i++) + { + if (update[i]->next[i] == cur) + { + update[i]->next[i] = cur->next[i]; + } + } + + SKIP_LIST_FREE(cur); + cur = NULL; + + /*更新索引的层数*/ + for (i = list->level - 1; i >= 0; i--) + { + /*如果删除节点后,某层的头结点后驱节点为空,则说明该层无索引指针,索引层数需要减1*/ + if (list->head->next[i] == NULL) + { + list->level--; + } + else + { + break; + } + } + + list->num--; /*节点数减1*/ + } + else + { + return -3; + } + + return 0; +} + + diff --git a/level2/SkipList/destroy.c b/level2/SkipList/destroy.c new file mode 100644 index 0000000..bae85ab --- /dev/null +++ b/level2/SkipList/destroy.c @@ -0,0 +1,21 @@ +#include"标头.h" +#include +int skip_list_destroy(struct skip_list* list) +{ + struct skip_list_node* cur = NULL; + + if (list == NULL && list->head == NULL) + return -1; + + while ((cur = list->head->next[0]) != NULL) + { + list->head->next[0] = cur->next[0]; + SKIP_LIST_FREE(cur); + cur = NULL; + } + + SKIP_LIST_FREE(list->head); + SKIP_LIST_FREE(list); + + return 0; +} diff --git a/level2/SkipList/insert.c b/level2/SkipList/insert.c new file mode 100644 index 0000000..80b977a --- /dev/null +++ b/level2/SkipList/insert.c @@ -0,0 +1,62 @@ +#include"标头.h" +#include"create.cpp" +#include +#include +/*以50%的概率产生插入元素的索引层数*/ +static int skip_list_level(struct skip_list* list) { + int i = 0, level = 1; + for (i = 1; i < list->head->max_level; i++) + { + if ((rand() % 2) == 1) + { + level++; + } + } + return level; +} + +/*插入跳表节点*/ +int skip_list_insert(struct skip_list* list, int key, int value) { + struct skip_list_node** update = NULL; /*用来更新每层索引指针,存放插入位置的前驱各层节点索引*/ + struct skip_list_node* cur = NULL; + struct skip_list_node* prev = NULL; + struct skip_list_node* insert = NULL; + int i = 0, level = 0; + if (list == NULL)return -1; + + /*申请update空间用于保存每层的索引指针*/ + update = (struct skip_list_node**)SKIP_LIST_MALLOC(sizeof(list->head->max_level)); + if (update == NULL)return -2; + + /*逐层查询,查找插入位置的前驱各层节点索引 + *update[0] 存放第一层的插入位置前驱节点,update[0]->next[0]表示插入位置的前驱节点的下一节点(update[0]->next[0])的第一层索引值 + *update[1] 存放第二层的插入位置前驱节点,update[1]->next[1]表示插入位置的前驱节点的下一节点(update[1]->next[0])的第二层索引值 + *update[n] 存放第一层的插入位置前驱节点,update[n]->next[n]表示插入位置的前驱节点的下一节点(update[n]->next[0])的第n层索引值 + */ + prev = list->head;/*从第一个节点开始的最上层开始找*/ + for (i = list->level - 1; i >= 0; i--) { + while (((cur = prev->next[i]) != NULL) && (cur->key < key)) { + prev = cur;/*向后移动*/ + } + update[i] = prev;/*各层要插入的前驱节点*/ + } + /*当前key已存在,返回错误*/ + if ((cur != NULL) && (cur->key == key))return -3; + + /*获取插入元素的随机层数,并更新跳表的最大层数*/ + level = skip_list_level(list); + /*创建当前节点*/ + insert = skip_list_node_creat(level, key, value); + if (level > list->level) { + for (i = list->level; i < level; i++) { + update[i] = list->head; + } + list->level = level; + } + + for (i = 0; i < level; i++) { + insert->next[i] = update[i]->next[i]; + update[i]->next[i] = insert; + } + list->num++; +} diff --git a/level2/SkipList/modify.c b/level2/SkipList/modify.c new file mode 100644 index 0000000..bd3005e --- /dev/null +++ b/level2/SkipList/modify.c @@ -0,0 +1,35 @@ +#include"标头.h" +#include +int skip_list_modify(struct skip_list* list, int key, int value) +{ + struct skip_list_node* cur = NULL; + struct skip_list_node* prev = NULL; + int i = 0; + + if (list == NULL && list->num == 0) + return -1; + + /*逐层查找,查找查询位置原始链表的节点*/ + prev = list->head; /*从第一个节点开始的最上层开始找*/ + i = list->level - 1; + for (; i >= 0; i--) + { + /* 各层每个节点的下一个节点不为空 && 下个节点的key小于要插入的key */ + while (((cur = prev->next[i]) != NULL) && (cur->key < key)) + { + prev = cur; /* 向后移动 */ + } + } + + /* 当前key存在 */ + if ((cur != NULL) && (cur->key == key)) + { + cur->value = value; + } + else + { + return -3; + } + + return 0; +} diff --git a/level2/SkipList/search.c b/level2/SkipList/search.c new file mode 100644 index 0000000..605b045 --- /dev/null +++ b/level2/SkipList/search.c @@ -0,0 +1,35 @@ +#include"标头.h" +#include +int skip_list_search(struct skip_list* list, int key, int* value) +{ + struct skip_list_node* cur = NULL; + struct skip_list_node* prev = NULL; + int i = 0; + + if (list == NULL && value == NULL && list->num == 0) + return -1; + + /*逐层查找,查找查询位置原始链表的节点*/ + prev = list->head; /*从第一个节点开始的最上层开始找*/ + i = list->level - 1; + for (; i >= 0; i--) + { + /* 各层每个节点的下一个节点不为空 && 下个节点的key小于要插入的key */ + while (((cur = prev->next[i]) != NULL) && (cur->key < key)) + { + prev = cur; /* 向后移动 */ + } + } + + /* 当前key存在 */ + if ((cur != NULL) && (cur->key == key)) + { + *value = cur->value; + } + else + { + return -3; + } + + return 0; +} diff --git a/level2/SkipList/skiplist.c b/level2/SkipList/skiplist.c new file mode 100644 index 0000000..39b6724 --- /dev/null +++ b/level2/SkipList/skiplist.c @@ -0,0 +1,62 @@ +#include"create.cpp" +#include"insert.cpp" +#include"delete.cpp" +#include"modify.cpp" +#include"search.cpp" +#include"distroy.cpp" +#include +#include +int main(){ + skip_list* Mylist = skip_list_creat(6); + int ch; + printf("input'a'to insert elements,\ninput'b'to modify elements,\ninput'c'to search elements,\ninput'd'to delete elements,input'e'to finish\n"); + while (ch = getch()) + { + + switch (ch) + { + + case'a': + { + int key, value = 0; + printf("insert//input key:\n"); + scanf("%d", &key); + printf("input value:\n"); + scanf("%d", &value); + skip_list_insert(Mylist, key, value); + } + case'b': { + int key, value = 0; + printf("modify//input key:\n"); + scanf("%d", &key); + printf("input value:\n"); + scanf("%d", &value); + skip_list_modify(Mylist, key, value); + } + case'c': { + int key, value = 0; + printf("search//input key:\n"); + scanf("%d", &key); + printf("input value:\n"); + scanf("%d", &value); + skip_list_search(Mylist, key, value); + } + case'd': { + int key= 0; + printf("delete//input key:\n"); + scanf("%d", &key); + skip_list_delete(Mylist, key); + } + case'e': { + printf("destroy!\n"); + skip_list_destroy(Mylist); + printf("Bye") + } + default: + break; + + } + + } + return 0; +} diff --git "a/level2/SkipList/\346\240\207\345\244\264.h" "b/level2/SkipList/\346\240\207\345\244\264.h" new file mode 100644 index 0000000..fd84c79 --- /dev/null +++ "b/level2/SkipList/\346\240\207\345\244\264.h" @@ -0,0 +1,24 @@ +#include +#define SKIP_LIST_MALLOC(size) malloc(size); +#define SKIP_LIST_CALLOC(n,size) calloc(n,size); +#define SKIP_LIST_FREE(p) free(p); + +struct skip_list_node +{ + int key;// + int value;//存储的内容 + int max_level;//当前节点最大层数 + struct skip_list_node* next[];//柔性数组 + //根据该节点层数的不同指向大小不同的数组 +}; +struct skip_list { + int level;//跳表的索引层数 + int num;//节点数目 + struct skip_list_node* head; +}; +extern struct skip_list* skip_list_creat(int max_level); +extern int skip_list_insert(struct skip_list* list, int key, int value); +extern int skip_list_delete(struct skip_list* list, int key); +extern int skip_list_modify(struct skip_list* list, int key, int value); +extern int skip_list_search(struct skip_list* list, int key, int* value); +extern int skip_list_destroy(struct skip_list* list); diff --git a/levfing_path_GA.c b/levfing_path_GA.c new file mode 100644 index 0000000..127b023 --- /dev/null +++ b/levfing_path_GA.c @@ -0,0 +1,99 @@ + + + + +#include +#include +#include +#include +#include"maze.c" + +#define ROWS 10 +#define COLS 10 +#define POP_SIZE 100 +#define MAX_GEN 500 +#define MUTATION_RATE 0.01 + +// 迷宫地图 +int maze[ROWS][COLS] = {}; //进行初始化 +maze[ROWS][COLS]=Maze[][10]; +typedef struct { + int x, y; +} Point; + +typedef struct { + Point path[ROWS*COLS]; + int length; + double fitness; +} Individual; + +Individual population[POP_SIZE]; + +// 计算适应度 +double calculate_fitness(Individual* ind) { + return 1.0 / (ind->length + 1); // 加1是为了避免除数为0的情况 +} + +// 初始化种群 +void initialize_population() { + for (int i = 0; i < POP_SIZE; i++) { + population[i].length = 0; + population[i].fitness = 0; + } +} + +// 选择操作 +int select_index() { + double total_fitness = 0; + for (int i = 0; i < POP_SIZE; i++) { + total_fitness += population[i].fitness; + } + double r = (double)rand() / RAND_MAX * total_fitness; + double sum = 0; + for (int i = 0; i < POP_SIZE; i++) { + sum += population[i].fitness; + if (sum >= r) return i; + } + return POP_SIZE - 1; // 为了确保返回一个有效的索引 +} + +// 交叉操作 +void crossover(Individual* parent1, Individual* parent2, Individual* child1, Individual* child2) { + int index1 = rand() % (parent1->length - 1) + 1; // 保证至少有一个点被交叉 + int index2 = rand() % (parent1->length - index1) + index1; // 保证index2 > index1 + for (int i = 0; i < index1; i++) { + child1->path[i] = parent1->path[i]; + child2->path[i] = parent2->path[i]; + } + for (int i = index1; i < index2; i++) { + child1->path[i] = parent2->path[i]; + child2->path[i] = parent1->path[i]; + } + for (int i = index2; i < parent1->length; i++) { + child1->path[i] = parent1->path[i]; + child2->path[i] = parent2->path[i]; + } + child1->length = child2->length = parent1->length; + child1->fitness = calculate_fitness(child1); + child2->fitness = calculate_fitness(child2); +} + +// 变异操作 +void mutate(Individual* ind) { + if (rand() / (double)RAND_MAX < MUTATION_RATE) { + int index = rand() % ind->length; + // 未完待续 + } +} + +int main() { + srand(time(NULL)); // 设置随机种子 + initialize_population(); // 初始化种群,可以将起点加入每个个体的路径中,并计算适应度等。 + for (int gen = 0; gen<1000;gen++){ + crossover(parent1, parent2, child1, child2); + mutate(ind) + calculate_fitness(Individual* ind); + select_index() ; +}//未完善route的换算 +return 0; + } diff --git "a/\347\250\213\345\272\217\350\256\276\350\256\241\350\257\276\347\273\223\350\257\276ppt.pptx" "b/\347\250\213\345\272\217\350\256\276\350\256\241\350\257\276\347\273\223\350\257\276ppt.pptx" new file mode 100644 index 0000000..e69de29 diff --git "a/\350\257\276\347\250\213\350\256\276\350\256\241/AIchess.c" "b/\350\257\276\347\250\213\350\256\276\350\256\241/AIchess.c" new file mode 100644 index 0000000..5e9be63 --- /dev/null +++ "b/\350\257\276\347\250\213\350\256\276\350\256\241/AIchess.c" @@ -0,0 +1,889 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define ROWS 15//棋盘格子尺寸 +#define MAX_ROW 15 +#define COLS 15 +#define GRID_SIZE 65//像素尺寸 +#define XOFFSET 40//偏移量 +#define YOFFSET 40 + + +enum ChessType {//棋盘格子的状态 + None = 0, + Black = 1, + White = -1 +}; +enum Player {//棋手是人还是电脑 + robot = -1, + human = 1, + +}; + + + + + +//游戏结构 +struct Game { + //0表示没有棋子,1表示黑棋,-1表示白棋 + int map[ROWS][COLS]; + //游戏是否在运行 + bool isRunning; + //定义消息变量 + ExMessage msg;//消息结构体,负责鼠标键盘 + //鼠标当前所在下标 + int row = 0; + int col = 0; + //上一次下的棋子的位置坐标 + int currentchess_row = 0; + int currentchess_col = 0; + //当前棋手 + ChessType currentChessType; + Player player; + //电脑下棋的坐标 + int robotRow = 1; + int robotCol = 1; + int specialRow, specialCol = 0; + bool is_warning; +}; +struct DirectionMap { + int horizontal = {}; + int vertical = {}; + int leftOblique = {}; + int rightOblique = {}; +}; +typedef struct { + int NumOf_2Dif[5], NumOf_1Dif[5], NumOf_0dif[5],NumOf_0jump[5],NumOf_1jump[5], NumOf_2jump[5],NumOf_warning; +}Count; + +typedef struct State { + int board[ROWS][COLS];//这一步的棋盘 + int val;//这一步的评分 + ChessType chesstype;//这一步的棋子类型 + int x, y;//下棋的坐标 + State* prev;//上一步棋局 +} State; + + + +//游戏初始化 +void init(Game* pthis, int w, int h); +//游戏绘制 +void render(Game* pthis); +//游戏更新 +void update(Game* pthis); +//生成游戏界面 +void creatUI(Game* pthis, IMAGE* theimage); +//人机切换 +void shiftplayer(Game* pthis); +//机器下棋 +void robotplay(Game* pthis); +//计算电脑下棋的坐标 +void figure_coord(Game* pthis); + +//搜索有棋子的邻居空位,只深搜这些空位 +bool isNextchess(State* state, int x, int y, int n);//判断(x,y)坐标是否为符合条件的位置。 +//n为1,判断直接相邻的空位;n为2,判断两格以内的所有空位 + +// 局部分数评估 +int score(State* state, ChessType a); +bool judge(Game* pthis, int num); + + + + + + + + + + +//判断坐标为(x,y)的棋子周围的情况 +//活一10,活二100,活三1000,活四10000,活五100000。死二10,死三100,死四1000。 +//如果连子两侧没有被堵死,则称为活,如果只堵死一侧,称为死,如果都被堵死,则没有分数。 +Count detect_succession(int MAP[][COLS], ChessType a, DirectionMap directionMap[][COLS]); +//单次搜索评估分数 +//活一10,活二100,活三1000,活四10000,活五100000。死二10,死三100,死四1000。 +int countPoint(int MAP[][COLS], ChessType a, DirectionMap directionMap[][COLS]); +//博弈树 +//void generate_moves(State* state, int depth, bool maximizing_player, State* best_move); +//State alpha_beta_search(State* state, int depth); +//void generate_moves(State* state, int depth, bool maximizing_player, State* best_move); +//bool is_game_over(State* state); +//int search(int depth,State stepNode, int alpha, int beta, bool is_max); + +bool is_win(State* state) { + int x = state->x; + int y = state->y; + int player = state->board[x][y]; + int directions[4][2] = { {1, 0}, {0, 1}, {1, 1}, {1, -1} }; + + for (int i = 0; i < 4; i++) { + int count = 1; + for (int j = 1; j < 5; j++) { + int new_x = x + directions[i][0] * j; + int new_y = y + directions[i][1] * j; + if (new_x >= 0 && new_x < 15 && new_y >= 0 && new_y < 15 && state->board[new_x][new_y] == player) { + count++; + } + else { + break; + } + } + for (int j = 1; j < 5; j++) { + int new_x = x - directions[i][0] * j; + int new_y = y - directions[i][1] * j; + if (new_x >= 0 && new_x < 15 && new_y >= 0 && new_y < 15 && state->board[new_x][new_y] == player) { + count++; + } + else { + break; + } + } + if (count >= 5) { + return true; + } + } + return false; +} +int search(State* StepNode, int depth, int alpha, int beta, bool is_max) { + if (depth == 0 || is_win(StepNode)) { + int s = score(StepNode, is_max ? Black : White); + return s; + } + + if (is_max) { + int maxEval = -INFINITY; // 假设负无穷为最小值 + for (int i = 0; i < ROWS; i++) { + for (int j = 0; j < COLS; j++) { + if (isNextchess) { + State* NewstepNode = (State*)malloc(sizeof(State)); + memcpy(NewstepNode, StepNode, sizeof(State)); + NewstepNode->board[i][j] = Black; // 下棋 + NewstepNode->prev = StepNode; + NewstepNode->x = i; + NewstepNode->y = j; + int s = search(NewstepNode, depth - 1, alpha, beta, is_max); + NewstepNode->val = s; + maxEval = max(maxEval, s); + alpha = max(alpha, s); // 更新alpha值 + if (beta <= alpha) { // alpha-beta剪枝 + free(NewstepNode); + return maxEval; // 返回当前最大值 + } + } + } + } + return maxEval; // 返回最大值 + } + else { // is_min + int minEval = INFINITY; // 假设正无穷为最大值 + for (int i = 0; i < ROWS; i++) { + for (int j = 0; j < COLS; j++) { + if (isNextchess) { + State* NewstepNode = (State*)malloc(sizeof(State)); + memcpy(NewstepNode, StepNode, sizeof(State)); + NewstepNode->board[i][j] = White; // 下棋 + NewstepNode->prev = StepNode; + NewstepNode->x = i; + NewstepNode->y = j; + int s = search(NewstepNode, depth - 1, alpha, beta, is_max); + NewstepNode->val = s; + minEval = min(minEval, s); + beta = min(beta, s); // 更新beta值 + if (beta <= alpha) { // alpha-beta剪枝 + free(NewstepNode); + return minEval; // 返回当前最小值 + } + } + } + } + return minEval; // 返回最小值 + } +} +typedef struct { + int x; + int y; +} Coordinate; + +Coordinate get_best_move(Game*pthis,State* state, int depth) { + Coordinate best_move = { -1, -1 }; // 初始化最佳坐标为无效值 + int best_score = -INFINITY; // 初始化最佳得分为负无穷 + if (judge(pthis, 4)) { + best_move.x = pthis->specialRow; + best_move.y = pthis->specialCol; + return best_move; + } + for (int i = 0; i < ROWS; i++) { + for (int j = 0; j < COLS; j++) { + if (isNextchess(state, i, j,2)) { // 判断该位置是否可以落子 + State* new_state = (State*)malloc(sizeof(State)); + memcpy(new_state, state, sizeof(State)); + new_state->board[i][j] = Black; // 电脑落子为黑色 + new_state->prev = state; + new_state->x = i; + new_state->y = j; + + int score = search(new_state, depth, -INFINITY, INFINITY, true); // 搜索该落子后的局面得分 + if (score > best_score) { // 更新最佳得分和最佳坐标 + best_score = score; + best_move.x = i; + best_move.y = j; + } + free(new_state); // 释放内存 + } + } + } + + return best_move; // 返回最佳落子坐标 +} + + + + + +//判断五子连珠 +bool horizontal(Game* pthis, int chess, int num); +bool vertical(Game* pthis, int chess,int num); +bool leftOblique(Game* pthis, int chess, int num); +bool rightOblique(Game* pthis, int chess,int num); +bool judge(Game* pthis, int num); + + + + + +int main() { + Game game; + init(&game, 1000, 1000); + IMAGE img_bg;//定义图片 + loadimage(&img_bg, L"D:\\五子棋游戏\\bk.jpg");//输入想要的图片 + putimage(0, 0, &img_bg); + + //处理鼠标操作 + + //游戏主循环 + + + + + //默认人是先手 + game.player = human; + while (game.isRunning) { + + //获取消息 + //尚未完善 + if (judge(&game,5)) { + MessageBox(GetHWnd(), L"结束了", L"XXX ", MB_OK); + game.isRunning = false; + } + if (game.player == human) { + if (peekmessage(&game.msg)) { + update(&game);//鼠标点击后生成白色棋子 + } + creatUI(&game, &img_bg);//生成、更新游戏界面 + } + else if (game.player == robot) { + + robotplay(&game); + creatUI(&game, &img_bg);//生成、更新游戏界面 + shiftplayer(&game);//人机切换 + } + } + return 0; +} +//初始化棋盘 +void init(Game* pthis, int w, int h) { + srand(time(NULL)); + //创建一个窗口 + initgraph(w, h, EX_SHOWCONSOLE); + pthis->isRunning = true; + pthis->row = -1; + pthis->col = -1; + //pthis->currentChessType=rand()%2?Black:White; + pthis->currentChessType = White; + //创建棋盘 + //0表示没有棋子,1表示黑棋,-1表示白棋 + int map[ROWS][COLS] = { }; + memcpy(pthis->map, map, sizeof(int) * ROWS * COLS); +} +//绘制棋子 +void render(Game* pthis) { + //绘制棋子 + for (int i = 0; i < ROWS; i++) { + for (int k = 0; k < COLS; k++) { + + + if (pthis->map[i][k] != None) { + //求每个格子左上角的坐标 + int x = k * GRID_SIZE + XOFFSET; + int y = i * GRID_SIZE + YOFFSET; + //绘制棋子 + if (pthis->map[i][k] == White) { + setfillcolor(WHITE); + } + else if (pthis->map[i][k] == Black) { + setfillcolor(BLACK); + } + + //绘制棋子 + solidcircle(x, y, 20); + } + } + } + //绘制当前鼠标所在的提示框 + if (pthis->row != -1 && pthis->col != -1) { + setlinecolor(BLUE);//设置提示框线条颜色 + int x = pthis->col * GRID_SIZE + XOFFSET; + int y = pthis->row * GRID_SIZE + YOFFSET; + circle(x, y, 25); + } +} +void update(Game* pthis) { + + //鼠标移动,判断鼠标指针位于哪个格子的区域 + if (pthis->msg.message == WM_MOUSEMOVE) { + for (int i = 0; i < ROWS; i++) { + for (int k = 0; k < COLS; k++) { + int cx = k * GRID_SIZE + XOFFSET; + int cy = i * GRID_SIZE + YOFFSET; + //判断鼠标指针位于棋盘哪个格子的区域 + if (abs(pthis->msg.x - cx) < GRID_SIZE / 2 && abs(pthis->msg.y - cy) < GRID_SIZE / 2) { + pthis->row = i; + pthis->col = k; + goto END_LOOP; + } + } + } + END_LOOP:;//跳转于此 + } + + //下出棋子,并在pthis->map上记录棋子位置和颜色 + else if (pthis->msg.message == WM_LBUTTONDOWN//鼠标左键点击 + && pthis->row != -1 && pthis->col != -1 &&//点击了合法的位置 + pthis->map[pthis->row][pthis->col] == None)//当前位置没有棋子 + { + pthis->map[pthis->row][pthis->col] = pthis->currentChessType; + //记录本次下棋棋子的位置 + pthis->currentchess_row = pthis->row; + pthis->currentchess_col = pthis->col; + //切换棋手 + pthis->currentChessType = (ChessType)-pthis->currentChessType; + shiftplayer(pthis);//人机切换 + + } +} +//判断胜负 +bool judge(Game* pthis,int num) { + int who = -pthis->currentChessType; + if (horizontal(pthis, who, num))return true; + if (vertical(pthis, who,num))return true; + if (leftOblique(pthis, who,num))return true; + if (rightOblique(pthis, who,num))return true; + return false; +} +//判断五子连珠 +//未做完 +bool horizontal(Game* pthis, int chess,int num) { + int counter = 1; // 假设当前位置已经是该棋子的颜色 + int start = pthis->currentchess_col - 1; // 从当前列的前一列开始检查 + int end = pthis->currentchess_col + 1; // 从当前列的后一列开始检查 + + // 向左检查 + for (int i = start; i >= 0 && pthis->map[pthis->currentchess_row][i] == chess; i--) { + counter++; + + if (pthis->map[pthis->currentchess_row][i] == 0) { + + pthis->specialRow = pthis->currentchess_row; + pthis->specialCol = i; + } + + } + // 向右检查 + for (int i = end; i < 15 && pthis->map[pthis->currentchess_row][i] == chess; i++) { + counter++; + + if (pthis->map[pthis->currentchess_row][i] == 0) { + + pthis->specialRow = pthis->currentchess_row; + pthis->specialCol = i; + } + } + + return counter >= num; +} + +bool vertical(Game* pthis, int chess, int num) { + int counter = 1; // 假设当前位置已经是该棋子的颜色 + int start = pthis->currentchess_row - 1; // 从当前行的前一行开始检查 + int end = pthis->currentchess_row + 1; // 从当前行的后一行开始检查 + + // 向上检查 + for (int i = start; i >= 0 && pthis->map[i][pthis->currentchess_col] == chess; i--) { + counter++; + if (pthis->map[i][pthis->currentchess_col] == 0) { + pthis->specialRow = i; + pthis->specialCol = pthis->currentchess_col; + } + } + // 向下检查 + for (int i = end; i < 15 && pthis->map[i][pthis->currentchess_col] == chess; i++) { + counter++; + if (pthis->map[i][pthis->currentchess_col] == 0) { + pthis->specialRow = i; + pthis->specialCol = pthis->currentchess_col; + } + } + + return counter >= num; +} + +bool leftOblique(Game* pthis, int chess, int num) { + int counter = 1; // 假设当前位置已经是该棋子的颜色 + int startRow = pthis->currentchess_row - 1; + int startCol = pthis->currentchess_col - 1; + + // 向左上检查 + for (int i = startRow, j = startCol; i >= 0 && j >= 0 && pthis->map[i][j] == chess; i--, j--) { + counter++; + if (pthis->map[i][j] == 0) { + pthis->specialRow = i; + pthis->specialCol = j; + } + } + + // 向右下检查 + for (int i = pthis->currentchess_row + 1, j = pthis->currentchess_col + 1; i < 15 && j < 15 && pthis->map[i][j] == chess; i++, j++) { + counter++; + if (pthis->map[i][j] == 0) { + pthis->specialRow = i; + pthis->specialCol = j; + } + } + + return counter >= num; +} + +bool rightOblique(Game* pthis, int chess, int num) { + int counter = 1; // 假设当前位置已经是该棋子的颜色 + int startRow = pthis->currentchess_row - 1; + int startCol = pthis->currentchess_col + 1; + + // 向右上检查 + for (int i = startRow, j = startCol; i >= 0 && j < 15 && pthis->map[i][j] == chess; i--, j++) { + counter++; + if (pthis->map[i][j] == 0) { + pthis->specialRow = i; + pthis->specialCol = j; + } + } + // 向左下检查 + for (int i = pthis->currentchess_row + 1, j = pthis->currentchess_col - 1; i < 15 && j >= 0 && pthis->map[i][j] == chess; i++, j--) { + counter++; + if (pthis->map[i][j] == 0) { + pthis->specialRow = i; + pthis->specialCol = j; + } + } + + return counter >= num; +} + +void shiftplayer(Game* pthis) { + if (pthis->player == human) { + pthis->player = robot; + } + else if (pthis->player == robot) { + pthis->player = human; + } +} +void robotplay(Game* pthis) { + figure_coord(pthis); + printf("(%d,%d)\n", pthis->robotRow, pthis->robotCol); + pthis->map[pthis->robotRow][pthis->robotCol] = pthis->currentChessType; + pthis->currentChessType = (ChessType)-pthis->currentChessType; + +} +//创建、更新游戏界面 +void creatUI(Game* pthis, IMAGE* theimage) { + BeginBatchDraw();//开始批量绘图(双缓冲) + cleardevice();//清屏 + putimage(0, 0, theimage);//放背景图 + render(pthis);//绘制棋子和提示框 + EndBatchDraw();//结束双缓冲绘图 +} +//算法核心——计算电脑下棋的坐标 +//未完善 +void figure_coord(Game* pthis) { + + State* state =(State*)malloc(sizeof(State)); + memcpy(state->board, pthis->map, sizeof(int) * ROWS * COLS); + Coordinate best_move= get_best_move(pthis,state, 5); + pthis->robotRow = best_move.x; + pthis->robotCol = best_move.y; + pthis->currentchess_row = pthis->robotRow; + pthis->currentchess_col = pthis->robotCol; + +} + +//搜索有棋子的邻居空位,只深搜这些空位 +//判断(x,y)坐标是否为符合条件的位置。 +//n为1,判断直接相邻的空位;n为2,判断两格以内的所有空位 +bool isNextchess(State* state, int x, int y, int n) { + int MAP[ROWS][COLS]; + memcpy(MAP, state->board, sizeof(int) * ROWS * COLS); + int i, j=0; + //printf("b1"); + if (MAP[x][y] == 0) + { + + if (n == 1) + { + for (i = x - 1; i < x + 2; i++) { + for (j = y - 1; j < y + 2; j++) { + if ((i > 0 && j > 0 && i < ROWS && j < COLS) && (MAP[i][j] == 1 || MAP[i][j] == -1)) + return true; + } + } + } + else + { + for (i = x - 2; i < x + 3; i += 2) { + for (j = y - 2; j < y + 3; j += 2) { + if ((i > 0 && j > 0 && i < ROWS && j < COLS) && (MAP[i][j] == 1 || MAP[i][j] == -1)) + return true; + } + } + } + } + return false; +} +//棋局分数评估 +//活一10,活二100,活三1000,活四10000,活五100000。死二10,死三100,死四1000。 +//如果连子两侧没有被堵死,则称为活,如果只堵死一侧,称为死,如果都被堵死,则没有分数。 +int score(State* state, ChessType a) { + int MAP[ROWS][COLS]; + memcpy(MAP, state->board, sizeof(int) * ROWS * COLS); + DirectionMap directionMap[ROWS][COLS] = {}; + for (int i = 0; i < ROWS; i++) { + for (int j = 0; j < COLS; j++) { + if (MAP[i][j] == 0) { + directionMap[i][j].horizontal = 2; + directionMap[i][j].vertical = 2; + directionMap[i][j].leftOblique = 2; + directionMap[i][j].rightOblique = 2;//将没有棋子的位置标记,遍历时不经过这些点 + } + } + } + + int Point = countPoint(MAP, Black, directionMap)-countPoint(MAP,White,directionMap); + + + return Point; + +} + +//活一10,活二100,活三1000,活四10000,活五100000。死二10,死三100,死四1000。 +//如果连子两侧没有被堵死,则称为活,如果只堵死一侧,称为死,如果都被堵死,则没有分数。 +Count detect_succession(int MAP[][COLS], ChessType a, DirectionMap directionMap[][COLS]) +{ + int point = 0;//记录得分 + int x, y; + int NumOfSame, NumOfDifference,NumOfjump = 0; + Count count = { 0 }; + + + //1.纵向分析 + for (int j = 0; j < COLS; j++) + { + for (int i = 0; i < ROWS; i++) + { + x = i, y = j; + NumOfSame, NumOfDifference = 0; + //如果相应点不是棋子类型a,则直接跳过 + if (MAP[x][y] != a || directionMap[x][y].horizontal != 0) { + continue; + } + + while (MAP[x][y] == a && directionMap[x][y].horizontal == 0 && x < ROWS) { + NumOfSame = 1; + NumOfDifference = 0; + + directionMap[x][y].horizontal = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + x++;//继续分析下一个棋子 + } + if (x= 0) { + directionMap[x][y].horizontal = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + x--;//继续分析下一个棋子 + + } + if (x>0&&MAP[x][y] == -a) { + NumOfDifference++;//记录撞上的第二个异色棋子 + + } + else { + x--; + if (x >= 0 && MAP[x][y] == a) { + NumOfjump++; + } + x++; + } + if (NumOfDifference == 0)count.NumOf_0dif[NumOfSame-1]++; + if (NumOfDifference == 1)count.NumOf_1Dif[NumOfSame-1]++; + if (NumOfDifference == 2) count.NumOf_2Dif[NumOfSame - 1]++; + if (NumOfjump == 0)count.NumOf_0jump[NumOfSame - 1]++; + if (NumOfjump == 1)count.NumOf_1jump[NumOfSame - 1]++; + if (NumOfjump == 2)count.NumOf_2jump[NumOfSame - 1]++; + if (a == White && NumOfSame >= 3)count.NumOf_warning+=NumOfSame; + NumOfSame = 0; + } + } + + + //2.横向分析 + for (int i = 0; i < ROWS; i++) + { + for (int j = 0; j < COLS; j++) + { + x = i, y = j; + //如果相应点不是棋子类型a,则直接跳过 + if (MAP[x][y] != a || directionMap[x][y].vertical != 0) + { + continue; + } + + while (MAP[x][y] == a && directionMap[x][y].vertical == 0 && y < ROWS) + { + NumOfSame = 1; + NumOfDifference = 0; + + directionMap[x][y].horizontal = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + y++;//继续分析下一个棋子 + } + if (y= 0) + { + directionMap[x][y].vertical = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + y--;//继续分析下一个棋子 + + } + if (MAP[x][y] == -a) + { + NumOfDifference++;//记录撞上的第二个异色棋子 + + } + else { + y--; + if (y >= 0 && MAP[x][y] == a) { + NumOfjump++; + } + y++; + } + if (NumOfDifference == 0)count.NumOf_0dif[NumOfSame - 1]++; + if (NumOfDifference == 1)count.NumOf_1Dif[NumOfSame - 1]++; + if (NumOfDifference == 2)count.NumOf_2Dif[NumOfSame - 1]++; + if (NumOfjump == 0)count.NumOf_0jump[NumOfSame - 1]++; + if (NumOfjump == 1)count.NumOf_1jump[NumOfSame - 1]++; + if (NumOfjump == 2)count.NumOf_2jump[NumOfSame - 1]++; + if (a == White && NumOfSame >= 3)count.NumOf_warning += NumOfSame; + NumOfSame = 0; + } + } + + + //3.斜向右下&&左上 + for (int j = 0; j < COLS; j++) + { + for (int i = 0; i < ROWS; i++) + { + x = i, y = j; + //如果相应点不是棋子类型a,则直接跳过 + if (MAP[x][y] != a || directionMap[x][y].leftOblique != 0) + { + continue; + } + + while (MAP[x][y] == a && directionMap[x][y].leftOblique == 0 && y < ROWS && x < ROWS) + { + NumOfSame = 1; + NumOfDifference = 0; + + directionMap[x][y].leftOblique = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + x++; + y++;//继续分析下一个棋子 + } + if (MAP[x][y] == -a) + { + NumOfDifference++;//记录往右下方向遍历时撞上的第一个异色棋子 + + }else { + x++; + y++; + if (x < ROWS && y < COLS && MAP[x][y] == a) { + NumOfjump++; + } + x--; + y--; + } + x -= NumOfSame; + y -= NumOfSame;//回到出发点 + + //开始往左上方向遍历 + while (MAP[x][y] == a && directionMap[x][y].leftOblique == 0 && x >= 0 && y >= 0) + { + directionMap[x][y].leftOblique = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + x--; + y--;//继续分析下一个棋子 + + } + if (MAP[x][y] == -a) + { + NumOfDifference++;//记录撞上的第二个异色棋子 + + } + else { + x--; + y--; + if (x >= 0 && y >= 0 && MAP[x][y] == a) { + NumOfjump++; + } + x++; + y++; + } + if (NumOfDifference == 0)count.NumOf_0dif[NumOfSame - 1]++; + if (NumOfDifference == 1)count.NumOf_1Dif[NumOfSame - 1]++; + if (NumOfDifference == 2) count.NumOf_2Dif[NumOfSame - 1]++; + if (NumOfjump == 0)count.NumOf_0jump[NumOfSame - 1]++; + if (NumOfjump == 1)count.NumOf_1jump[NumOfSame - 1]++; + if (NumOfjump == 2)count.NumOf_2jump[NumOfSame - 1]++; + if (a == White && NumOfSame >= 3)count.NumOf_warning += NumOfSame; + NumOfSame = 0; + } + } + + //4.斜向左下&右上 + for (int i = 0; i < ROWS; i++) + { + for (int j = COLS - 1; j >= 0; j--) + { + x = i, y = j; + //如果相应点不是棋子类型a,则直接跳过 + if (MAP[x][y] != a || directionMap[x][y].rightOblique != 0) + { + continue; + } + + while (MAP[x][y] == a && directionMap[x][y].rightOblique == 0 && y < COLS && x >= 0) + { + NumOfSame = 1; + NumOfDifference = 0; + + directionMap[x][y].rightOblique = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + x--; + y++;//继续分析下一个棋子 + } + if (MAP[x][y] == -a) + { + NumOfDifference++;//记录往右下方向遍历时撞上的第一个异色棋子 + + } + else { + x--; + y++; + if (x >= 0 && y < COLS && MAP[x][y] == a) { + NumOfjump++; + } + x++; + y--; + } + x += NumOfSame; + y -= NumOfSame;//回到出发点 + //开始往左上方向遍历 + while (MAP[x][y] == a && directionMap[x][y].rightOblique == 0 && y >= 0 && x < ROWS) + { + directionMap[x][y].rightOblique = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + x++; + y--;//继续分析下一个棋子 + + } + if (MAP[x][y] == -a) + { + NumOfDifference++;//记录撞上的第二个异色棋子 + + } + else { + x++; + y--; + if (x < ROWS && y >= 0 && MAP[x][y] == a) { + NumOfjump++; + } + x--; + y++; + } + if (NumOfDifference == 0)count.NumOf_0dif[NumOfSame - 1]++; + if (NumOfDifference == 1)count.NumOf_1Dif[NumOfSame - 1]++; + if (NumOfDifference == 2) count.NumOf_2Dif[NumOfSame - 1]++; + if (NumOfjump == 0)count.NumOf_0jump[NumOfSame - 1]++; + if (NumOfjump == 1)count.NumOf_1jump[NumOfSame - 1]++; + if (NumOfjump == 2)count.NumOf_2jump[NumOfSame - 1]++; + if (a == White && NumOfSame >= 3)count.NumOf_warning += NumOfSame; + NumOfSame = 0; + } + } + return count; +} +//单次搜索评估分数 +//活一10,活二100,活三1000,活四10000,活五100000。死二10,死三100,死四1000。 +int countPoint(int MAP[][COLS], ChessType a, DirectionMap directionMap[][COLS]) { + int point = 0; + Count count=detect_succession(MAP, a, directionMap); + for (int i = 0; i < 5; i++) { + point += count.NumOf_0dif[i] * pow(10, i) + count.NumOf_1Dif[i] * pow(9, i) + count.NumOf_2Dif[i] * 0 + + count.NumOf_0jump[i] * pow(9, i) + count.NumOf_1jump[i] * pow(10, i)+count.NumOf_2jump[i]*(pow(10,i)+pow(8,i)); + } + if (count.NumOf_warning >= 2)point += 1000000; + return point; + +} diff --git "a/\350\257\276\347\250\213\350\256\276\350\256\241/\345\256\236\351\252\214\346\212\245\345\221\212" "b/\350\257\276\347\250\213\350\256\276\350\256\241/\345\256\236\351\252\214\346\212\245\345\221\212" new file mode 100644 index 0000000..797fce5 --- /dev/null +++ "b/\350\257\276\347\250\213\350\256\276\350\256\241/\345\256\236\351\252\214\346\212\245\345\221\212" @@ -0,0 +1,783 @@ +五子棋人机对战游戏的程序设计实验 +实验者:曹思瀚 2023080903002 +一、实验目的 +本实验的主要目的是设计并实现一个五子棋人机对战游戏。通过这个项目,我们希望达到以下目标: +1. 理解和掌握博弈树、极大极小值算法以及α-β剪枝算法在五子棋游戏中的应用。 +2. 熟悉C/C++编程语言,并提升编程能力。 +3. 了解游戏设计的基本原理和实现过程,包括游戏界面设计、游戏规则制定、游戏逻辑实现等。 +二、实验环境 +本实验在Windows 11操作系统下进行,使用的编程语言是C/C++,开发环境为Visual Studio。 +三、实验原理 +五子棋人机对战程序主要依赖于以下几个核心概念: +1. 博弈树:五子棋游戏中的所有可能走法构成了一个树状结构,称为博弈树。每个节点代表一个局面,而边代表一个走法。博弈树可以用来评估和选择最优策略。 +2. 极大极小值算法:这是一种用于零和游戏的搜索算法,适用于五子棋这类二人对弈游戏。该算法通过递归地搜索博弈树,寻找对己方最有利(极大值)且对对方最不利(极小值)的走法。 +用估值函数可以为每一个局面的优劣评分。极大极小策略是考虑双方对弈若干步之后,从可能的步中选一步相对好的步法来走,即在有限的搜索深度范围内进行求解。定义一个估值函数f,以便对棋局的态势做出优劣评估。 +规定:max和min代表对弈双方; +p代表一个棋局(即一个状态);有利于MAX的态势,f§取正值;有利于MIN的态势,f§取负值;态势均衡,f§取零值; +MINMAX的基本思想:(1)当轮到MIN走步时,MAX应该考虑最坏的情况(即f§取极小值)(2)当轮到MAX走步时,MAX应该考虑最好的情况(即f§取极大值)(3)相应于两位棋手的对抗策略,交替使用(1)和(2)两种方法传递倒推值。 + +3. α-β剪枝算法:为了提高搜索效率,我们可以使用α-β剪枝来减少需要评估的节点数量。该算法通过维护两个值α和β,分别表示当前局面下己方的最小和最大收益,来提前终止对不利局面的搜索。 +αβ剪枝算法是对Minimax方法的优化αβ剪枝算法的基本思想是:边生成博弈树边计算评估各节点的倒推值,根据倒推值范围及时减掉那些没有必要再扩展的子节点,从而减少搜索节点的数量,节约了机器开销,提高搜索效率。其内容如下: +α剪枝:如果当前节点的某子节点的值不比当前节点的前兄弟节点中的最大值大,则舍弃该子节点和该子节点的所有后兄弟节点。 +β剪枝:如果当前节点的某子节点的值不比当前节点的前兄弟节点中的最小值小,则舍弃该子节点和该子节点的所有后兄弟节点。 +αβ剪枝算法搜索过程: + +四、实验步骤 +1. 设计游戏界面:使用EaxyX库创建五子棋游戏的界面,包括棋盘、棋子等。 +首先,我想出了大概的框架,就是三步走——初始化、绘制内容、更新界面。 +接着,我细化了所要完成的内容,先再用户图形界面里放上我选好的棋盘图作为背景图,然后仔细调整,保证每个落子点的位置与绘制棋子的坐标点尽可能地吻合。 +然后,我还要考虑鼠标的问题,我查询了相关资料,发现message函数使可以获取鼠标信息的,于是我在此基础上,通过确认鼠标指针位于哪个格子的范围内,进而确认此时如果点击鼠标,棋子该在哪个坐标点落下。为了使游戏界面更加美观,我还添加了鼠标提示框。 +为了满足多次的参数调用时代码的简洁性,避免出现大量的参数,扰乱思路,也为了方便函数灵活地更改棋局信息,我定义了一个名为Game的结构体,存放关于棋局的各种信息。(根据需求随时添加变量) +//游戏结构 +struct Game { + //0表示没有棋子,1表示黑棋,-1表示白棋 + int map[ROWS][COLS]; + //游戏是否在运行 + bool isRunning; + //定义消息变量 + ExMessage msg;//消息结构体,负责鼠标键盘 + //鼠标当前所在下标 + int row = 0; + int col = 0; + //上一次下的棋子的位置坐标 + int currentchess_row = 0; + int currentchess_col = 0; + //当前棋手 + ChessType currentChessType; + Player player; + //电脑下棋的坐标 + int robotRow = 1; + int robotCol = 1; +}; +以下是我设计游戏界面的具体思路和代码。 +(1)先初始化游戏界面 +void init(Game* pthis, int w, int h) { + srand(time(NULL)); + //创建一个窗口 + initgraph(w, h, EX_SHOWCONSOLE); + pthis->isRunning = true; + pthis->row = -1; + pthis->col = -1; + pthis->currentChessType = White; + //创建棋盘 + //0表示没有棋子,1表示黑棋,-1表示白棋 + int map[ROWS][COLS] = { }; + memcpy(pthis->map, map, sizeof(int) * ROWS * COLS); +} + + +(2)根据棋盘(map)绘制棋子,根据换算后的鼠标坐标绘制鼠标所在提示框 +void render(Game* pthis) { + //绘制棋子 + for (int i = 0; i < ROWS; i++) { + for (int k = 0; k < COLS; k++) { + + + if (pthis->map[i][k] != None) { + //求每个格子左上角的坐标 + int x = k * GRID_SIZE + XOFFSET; + int y = i * GRID_SIZE + YOFFSET; + //绘制棋子 + if (pthis->map[i][k] == White) { + setfillcolor(WHITE); + } + else if (pthis->map[i][k] == Black) { + setfillcolor(BLACK); + } + + //绘制棋子 + solidcircle(x, y, 20); + } + } + } + //绘制当前鼠标所在的提示框 + if (pthis->row != -1 && pthis->col != -1) { + setlinecolor(BLUE);//设置提示框线条颜色 + int x = pthis->col * GRID_SIZE + XOFFSET; + int y = pthis->row * GRID_SIZE + YOFFSET; + circle(x, y, 25); + } +} +(3)更新界面 +void update(Game* pthis) { + + //鼠标移动,判断鼠标指针位于哪个格子的区域 + if (pthis->msg.message == WM_MOUSEMOVE) { + for (int i = 0; i < ROWS; i++) { + for (int k = 0; k < COLS; k++) { + int cx = k * GRID_SIZE + XOFFSET; + int cy = i * GRID_SIZE + YOFFSET; + //判断鼠标指针位于棋盘哪个格子的区域 + if (abs(pthis->msg.x - cx) < GRID_SIZE / 2 && abs(pthis->msg.y - cy) < GRID_SIZE / 2) { + pthis->row = i; + pthis->col = k; + goto END_LOOP; + } + } + } + END_LOOP:;//跳转于此 + } + + //下出棋子,并在pthis->map上记录棋子位置和颜色 + else if (pthis->msg.message == WM_LBUTTONDOWN//鼠标左键点击 + && pthis->row != -1 && pthis->col != -1 &&//点击了合法的位置 + pthis->map[pthis->row][pthis->col] == None)//当前位置没有棋子 + { + pthis->map[pthis->row][pthis->col] = pthis->currentChessType; + //记录本次下棋棋子的位置 + pthis->currentchess_row = pthis->row; + pthis->currentchess_col = pthis->col; + //切换棋手 + pthis->currentChessType = (ChessType)-pthis->currentChessType; + shiftplayer(pthis);//人机切换 + + } +} +为了保证代码的简洁性,我把若干操作打包在了一个函数里: +//创建、更新游戏界面 +void creatUI(Game* pthis, IMAGE* theimage) { + BeginBatchDraw();//开始批量绘图(双缓冲) + cleardevice();//清屏 + putimage(0, 0, theimage);//放背景图 + render(pthis);//绘制棋子和提示框 + EndBatchDraw();//结束双缓冲绘图 +} +2. 实现游戏规则:定义五子棋的游戏规则,如如何判断胜负、如何轮流落子等。 +(1)判断胜负,需要横向纵向斜向分析是否有五子连珠。 +以下代码是负责分析横向是否五子连珠,其他方向的分析同理。 +bool horizontal(Game* pthis, int chess) { + int counter = 1; // 假设当前位置已经是该棋子的颜色 + int start = pthis->currentchess_col - 1; // 从当前列的前一列开始检查 + int end = pthis->currentchess_col + 1; // 从当前列的后一列开始检查 + + // 向左检查 + for (int i = start; i >= 0 && pthis->map[pthis->currentchess_row][i] == chess; i--) { + counter++; + } + // 向右检查 + for (int i = end; i < 15 && pthis->map[pthis->currentchess_row][i] == chess; i++) { + counter++; + } + + return counter >= 5; + + + + +3.构建博弈树:实现一个用于生成博弈树的函数,该函数应能递归地生成所有可能的走法。 +这一步的代码构建已经具有一定的挑战性,我经历了多次的试错与尝试,不断的对其进行改进。先附上我最初行不通的代码,之所以放弃这种思路是因为实在难以插入剪枝算法。 +//失败的方案 +void generate_moves(State* state, int depth, bool maximizing_player, State* best_move) { + if (depth == 0 || is_game_over(state)) { + best_move->x = state->x; + best_move->y = state->y; + return; + } + + if (maximizing_player) { + int max_eval = -10000; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + if (state->board[i][j] == 0) { + state->board[i][j] = 1; + state->x = i; + state->y = j; + generate_moves(state, depth - 1, false, best_move); + int eval = evaluate(state); + if (eval > max_eval) { + max_eval = eval; + best_move->x = state->x; + best_move->y = state->y; + } + state->board[i][j] = 0; + } + } + } + } + else { + int min_eval = 10000; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + if (state->board[i][j] == 0) { + state->board[i][j] = -1; + state->x = i; + state->y = j; + generate_moves(state, depth - 1, true, best_move); + int eval = evaluate(state); + if (eval < min_eval) { + min_eval = eval; + best_move->x = state->x; + best_move->y = state->y; + } + state->board[i][j] = 0; + } + } + } + } +} +设计评分函数是很重要的一步,经过优化,我把evaluate(state)函数改为int score(State* state, ChessType a),以便区分我方和对方的棋子。 +经过改进,我把评分函数拆分成了多个函数,分别具有不同的功能: +int score(State* state, ChessType a) { + int MAP[ROWS][COLS]; + memcpy(MAP, state->board, sizeof(int) * ROWS * COLS); + DirectionMap directionMap[ROWS][COLS] = {}; + for (int i = 0; i < ROWS; i++) { + for (int j = 0; j < COLS; j++) { + if (MAP[i][j] == 0) { + directionMap[i][j].horizontal = 2; + directionMap[i][j].vertical = 2; + directionMap[i][j].leftOblique = 2; + directionMap[i][j].rightOblique = 2;//将没有棋子的位置标记,遍历时不经过这些点 + } + } + } + int Point = detect_succession(MAP, Black, directionMap); + + return Point; + +} +//棋子阵型分为活、死、冲 +//如果连子两侧没有被堵死,则称为活,如果只堵死一侧,称为死,如果都被堵死,则没有分数。 +int detect_succession(int MAP[][COLS], ChessType a, DirectionMap directionMap[][COLS]) { + int point = 0;//记录得分 + int x, y; + int NumOfSame, NumOfDifference,NumOfjump = 0; + + + //1.纵向分析 + for (int j = 0; j < COLS; j++) { + for (int i = 0; i < ROWS; i++) { + x = i, y = j; + NumOfSame, NumOfDifference = 0; + //如果相应点不是棋子类型a,则直接跳过 + if (MAP[x][y] != a || directionMap[x][y].horizontal != 0) { + continue; + } + + while (MAP[x][y] == a && directionMap[x][y].horizontal == 0 && x < ROWS) { + NumOfSame = 1; + NumOfDifference = 0; + + directionMap[x][y].horizontal = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + x++;//继续分析下一个棋子 + } + if (x= 0) { + directionMap[x][y].horizontal = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + x--;//继续分析下一个棋子 + + } + if (x>0&&MAP[x][y] == -a) { + NumOfDifference++;//记录撞上的第二个异色棋子 + + } + x--; + if (x >= 0 && MAP[x][y] == a) { + NumOfjump++; + } + x++; + point += countPoint(NumOfSame, NumOfDifference); + } + } + + //2.横向分析 +(…) + //3.斜向右下&&左上 +(…) + //4.斜向左下&右上 + (…) + return point; +} +但是,经过多次测试,发现这个评分机制还不够完善,不能很明确的区分特定的棋型,比如:1个连4和1个连2,以及2个连3,这两种情况无法通过这个算法区分开来,于是我对此进行了优化,采用数组的方法,明确了每种特定的棋型。优化后的代码如下: +typedef struct { +//记录每种棋形出现的次数 + int NumOf_2Dif[5], NumOf_1Dif[5], NumOf_0dif[5],NumOf_0jump[5],NumOf_1jump[5], NumOf_2jump[5]; +}Count; +int score(State* state, ChessType a) { + int MAP[ROWS][COLS]; + memcpy(MAP, state->board, sizeof(int) * ROWS * COLS); + DirectionMap directionMap[ROWS][COLS] = {}; + for (int i = 0; i < ROWS; i++) { + for (int j = 0; j < COLS; j++) { + if (MAP[i][j] == 0) { + directionMap[i][j].horizontal = 2; + directionMap[i][j].vertical = 2; + directionMap[i][j].leftOblique = 2; + directionMap[i][j].rightOblique = 2;//将没有棋子的位置标记,遍历时不经过这些点 + } + } + } + + int Point = countPoint(MAP, Black, directionMap)-countPoint(MAP,White,directionMap); + //棋局得分为双方得分的差值 + + return Point; + +} +//如果连子两侧没有被堵死,则称为活,如果只堵死一侧,称为死,如果都被堵死,则没有分数。 +Count detect_succession(int MAP[][COLS], ChessType a, DirectionMap directionMap[][COLS]) +{ + int point = 0;//记录得分 + int x, y; + int NumOfSame, NumOfDifference,NumOfjump = 0; + Count count = { 0 }; + + + //1.纵向分析 + for (int j = 0; j < COLS; j++) + { + for (int i = 0; i < ROWS; i++) + { + x = i, y = j; + NumOfSame, NumOfDifference = 0; + //如果相应点不是棋子类型a,则直接跳过 + if (MAP[x][y] != a || directionMap[x][y].horizontal != 0) { + continue; + } + + while (MAP[x][y] == a && directionMap[x][y].horizontal == 0 && x < ROWS) { + NumOfSame = 1; + NumOfDifference = 0; + + directionMap[x][y].horizontal = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + x++;//继续分析下一个棋子 + } + if (x= 0) { + directionMap[x][y].horizontal = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + x--;//继续分析下一个棋子 + + } + if (x>0&&MAP[x][y] == -a) { + NumOfDifference++;//记录撞上的第二个异色棋子 + + } + else { + x--; + if (x >= 0 && MAP[x][y] == a) { + NumOfjump++; + } + x++; + } + if (NumOfDifference == 0)count.NumOf_0dif[NumOfSame-1]++; + if (NumOfDifference == 1)count.NumOf_1Dif[NumOfSame-1]++; + if (NumOfDifference == 2) count.NumOf_2Dif[NumOfSame - 1]++; + if (NumOfjump == 0)count.NumOf_0jump[NumOfSame - 1]++; + if (NumOfjump == 1)count.NumOf_1jump[NumOfSame - 1]++; + if (NumOfjump == 2)count.NumOf_2jump[NumOfSame - 1]++; + NumOfSame = 0; + } + } + + + //2.横向分析 + for (int i = 0; i < ROWS; i++) +{ + for (int j = 0; j < COLS; j++) + { + x = i, y = j; + //如果相应点不是棋子类型a,则直接跳过 + if (MAP[x][y] != a || directionMap[x][y].vertical != 0) + { + continue; + } + + while (MAP[x][y] == a && directionMap[x][y].vertical == 0 && y < ROWS) + { + NumOfSame = 1; + NumOfDifference = 0; + + directionMap[x][y].horizontal = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + y++;//继续分析下一个棋子 + } + if (y= 0) + { + directionMap[x][y].vertical = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + y--;//继续分析下一个棋子 + + } + if (MAP[x][y] == -a) + { + NumOfDifference++;//记录撞上的第二个异色棋子 + + } + else { + y--; + if (y >= 0 && MAP[x][y] == a) { + NumOfjump++; + } + y++; + } + if (NumOfDifference == 0)count.NumOf_0dif[NumOfSame - 1]++; + if (NumOfDifference == 1)count.NumOf_1Dif[NumOfSame - 1]++; + if (NumOfDifference == 2)count.NumOf_2Dif[NumOfSame - 1]++; + if (NumOfjump == 0)count.NumOf_0jump[NumOfSame - 1]++; + if (NumOfjump == 1)count.NumOf_1jump[NumOfSame - 1]++; + if (NumOfjump == 2)count.NumOf_2jump[NumOfSame - 1]++; + NumOfSame = 0; + } +} + + //3.斜向右下&&左上 + for (int j = 0; j < COLS; j++) + { + for (int i = 0; i < ROWS; i++) + { + x = i, y = j; + //如果相应点不是棋子类型a,则直接跳过 + if (MAP[x][y] != a || directionMap[x][y].leftOblique != 0) + { + continue; + } + + while (MAP[x][y] == a && directionMap[x][y].leftOblique == 0 && y < ROWS && x < ROWS) + { + NumOfSame = 1; + NumOfDifference = 0; + + directionMap[x][y].leftOblique = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + x++; + y++;//继续分析下一个棋子 + } + if (MAP[x][y] == -a) + { + NumOfDifference++;//记录往右下方向遍历时撞上的第一个异色棋子 + + }else { + x++; + y++; + if (x < ROWS && y < COLS && MAP[x][y] == a) { + NumOfjump++; + } + x--; + y--; + } + x -= NumOfSame; + y -= NumOfSame;//回到出发点 + + //开始往左上方向遍历 + while (MAP[x][y] == a && directionMap[x][y].leftOblique == 0 && x >= 0 && y >= 0) + { + directionMap[x][y].leftOblique = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + x--; + y--;//继续分析下一个棋子 + + } + if (MAP[x][y] == -a) + { + NumOfDifference++;//记录撞上的第二个异色棋子 + + } + else { + x--; + y--; + if (x >= 0 && y >= 0 && MAP[x][y] == a) { + NumOfjump++; + } + x++; + y++; + } + if (NumOfDifference == 0)count.NumOf_0dif[NumOfSame - 1]++; + if (NumOfDifference == 1)count.NumOf_1Dif[NumOfSame - 1]++; + if (NumOfDifference == 2) count.NumOf_2Dif[NumOfSame - 1]++; + if (NumOfjump == 0)count.NumOf_0jump[NumOfSame - 1]++; + if (NumOfjump == 1)count.NumOf_1jump[NumOfSame - 1]++; + if (NumOfjump == 2)count.NumOf_2jump[NumOfSame - 1]++; + NumOfSame = 0; + } + } + + //4.斜向左下&右上 + for (int i = 0; i < ROWS; i++) + { + for (int j = COLS - 1; j >= 0; j--) + { + x = i, y = j; + //如果相应点不是棋子类型a,则直接跳过 + if (MAP[x][y] != a || directionMap[x][y].rightOblique != 0) + { + continue; + } + + while (MAP[x][y] == a && directionMap[x][y].rightOblique == 0 && y < COLS && x >= 0) + { + NumOfSame = 1; + NumOfDifference = 0; + + directionMap[x][y].rightOblique = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + x--; + y++;//继续分析下一个棋子 + } + if (MAP[x][y] == -a) + { + NumOfDifference++;//记录往右下方向遍历时撞上的第一个异色棋子 + + } + else { + x--; + y++; + if (x >= 0 && y < COLS && MAP[x][y] == a) { + NumOfjump++; + } + x++; + y--; + } + x += NumOfSame; + y -= NumOfSame;//回到出发点 + //开始往左上方向遍历 + while (MAP[x][y] == a && directionMap[x][y].rightOblique == 0 && y >= 0 && x < ROWS) + { + directionMap[x][y].rightOblique = 1;//标记,已经遍历过 + NumOfSame++;//连起来的棋子数+1 + x++; + y--;//继续分析下一个棋子 + + } + if (MAP[x][y] == -a) + { + NumOfDifference++;//记录撞上的第二个异色棋子 + + } + else { + x++; + y--; + if (x < ROWS && y >= 0 && MAP[x][y] == a) { + NumOfjump++; + } + x--; + y++; + } + if (NumOfDifference == 0)count.NumOf_0dif[NumOfSame - 1]++; + if (NumOfDifference == 1)count.NumOf_1Dif[NumOfSame - 1]++; + if (NumOfDifference == 2) count.NumOf_2Dif[NumOfSame - 1]++; + if (NumOfjump == 0)count.NumOf_0jump[NumOfSame - 1]++; + if (NumOfjump == 1)count.NumOf_1jump[NumOfSame - 1]++; + if (NumOfjump == 2)count.NumOf_2jump[NumOfSame - 1]++; + NumOfSame = 0; + } + } + return count; +} +//单次搜索评估分数 +//采用指数函数来拟合权重 +int countPoint(int MAP[][COLS], ChessType a, DirectionMap directionMap[][COLS]) { + int point; + Count count=detect_succession(MAP, a, directionMap); + for (int i = 0; i < 5; i++) { + point += count.NumOf_0dif[i] * pow(10, i) + count.NumOf_1Dif[i] * pow(9, i) + count.NumOf_2Dif[i] * 0 + + count.NumOf_0jump[i] * pow(9, i) + count.NumOf_1jump[i] * pow(10,i)+ count.NumOf_2jump[i]*(pow(10,i)+pow(8,i)); +} return point; + +} +完善了评分算法后,我又苦恼于如何用代码实现博弈树。我意识到棋局的数据需要经过层与层之间的传递,因此必须要想办法把数据暂存下来,起初我的想法是定义若干数组,但与大神交流后,我认为这样实现起来过于复杂和冗余,于是觉得采用类似于链表的树状结构来实现数据的传递。 +我定义了一个新的结构体,记录预想中的棋局信息,(不在Game结构体里定义是为了区分开现在的局面和预测的局面,防止混淆),使用结构指针prev,采用类似于链表的数据结构,方便存储相关数据,也方便回溯和遍历。 +我先把可能会用得上的变量都在结构体State里面定义一下,不管这些结构体里的定义的变量是否真的能用得上,方便后续设计出更高效的算法。 +typedef struct { + int board[ROWS][COLS];//这一步的棋盘 + int val;//这一步的评分 + ChessType chesstype;//这一步的棋子类型 + int x, y;//下棋的坐标 + State* prev;//上一步棋局 +} State; +博弈树的代码构建要与极大极小值算法紧密结合在一起,这是下一步我需要做的内容。 +4.实现极大极小值算法:使用极大极小值算法评估博弈树的每个节点,并选择一个最优的走法。 +当位于max层时(即电脑一方出棋时),需要记录该层所有节点得分的最大值,以获得最有利于自己的出棋策略,代码如下 + int max_eval = -10000; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + if (state->board[i][j] == 0) { + state->board[i][j] = 1; + state->x = i; + state->y = j; + generate_moves(state, depth - 1, false, best_move); + int eval = evaluate(state); + if (eval > max_eval) { + max_eval = eval; + best_move->x = state->x; + best_move->y = state->y; + } + state->board[i][j] = 0; + } +同理,当位于min层时(即人类一方出棋时),需要记录该层所有节点得分的最小值,以尽可能使人类一方的利益最小,代码略。 +5.实现α-β剪枝算法:在极大极小值算法的基础上,加入α-β剪枝以提高搜索效率。 +结合之前的博弈树的大致结构,我设计出以下的思路,代码如下: +成功的方案: +int search(State* StepNode, int depth, int alpha, int beta, bool is_max) { + if (depth == 0 || is_win(StepNode)) { + int s = score(StepNode, is_max ? Black : White); + return s; + } + + if (is_max) { + int maxEval = -INFINITY; // 假设负无穷为最小值 + for (int i = 0; i < ROWS; i++) { + for (int j = 0; j < COLS; j++) { + if (isNextchess) { + State* NewstepNode = (State*)malloc(sizeof(State)); + memcpy(NewstepNode, StepNode, sizeof(State)); + NewstepNode->board[i][j] = Black; // 下棋 + NewstepNode->prev = StepNode; + NewstepNode->x = i; + NewstepNode->y = j; + int s = search(NewstepNode, depth - 1, alpha, beta, is_max); + NewstepNode->val = s; + maxEval = max(maxEval, s); + alpha = max(alpha, s); // 更新alpha值 + if (beta <= alpha) { // alpha-beta剪枝 + free(NewstepNode); + return maxEval; // 返回当前最大值 + } + } + } + } + return maxEval; // 返回最大值 + } + else { // is_min + int minEval = INFINITY; // 假设正无穷为最大值 + for (int i = 0; i < ROWS; i++) { + for (int j = 0; j < COLS; j++) { + if (isNextchess) { + State* NewstepNode = (State*)malloc(sizeof(State)); + memcpy(NewstepNode, StepNode, sizeof(State)); + NewstepNode->board[i][j] = White; // 下棋 + NewstepNode->prev = StepNode; + NewstepNode->x = i; + NewstepNode->y = j; + int s = search(NewstepNode, depth - 1, alpha, beta, is_max); + NewstepNode->val = s; + minEval = min(minEval, s); + beta = min(beta, s); // 更新beta值 + if (beta <= alpha) { // alpha-beta剪枝 + free(NewstepNode); + return minEval; // 返回当前最小值 + } + } + } + } + return minEval; // 返回最小值 + } +} +typedef struct { + int x; + int y; +} Coordinate; + +Coordinate get_best_move(State* state, int depth) { + Coordinate best_move = { -1, -1 }; // 初始化最佳坐标为无效值 + int best_score = -INFINITY; // 初始化最佳得分为负无穷 + + for (int i = 0; i < ROWS; i++) { + for (int j = 0; j < COLS; j++) { + if (isNextchess(state, i, j,2)) { // 判断该位置是否可以落子 + State* new_state = (State*)malloc(sizeof(State)); + memcpy(new_state, state, sizeof(State)); + new_state->board[i][j] = Black; // 电脑落子为黑色 + new_state->prev = state; + new_state->x = i; + new_state->y = j; + + int score = search(new_state, depth, -INFINITY, INFINITY, true); // 搜索该落子后的局面得分 + if (score > best_score) { // 更新最佳得分和最佳坐标 + best_score = score; + best_move.x = i; + best_move.y = j; + } + free(new_state); // 释放内存 + } + } + } + return best_move; // 返回最佳落子坐标 +} +6.游戏逻辑整合:将以上步骤整合到一个主循环中,实现游戏的完整逻辑。 + +int main() { + Game game; + init(&game, 1000, 1000); + IMAGE img_bg;//定义图片 + loadimage(&img_bg, L"D:\\五子棋游戏\\bk.jpg");//输入想要的图片 + putimage(0, 0, &img_bg); + + //处理鼠标操作 + + //游戏主循环 + + + + + //默认人是先手 + game.player = human; + while (game.isRunning) { + //获取消息 + if (judge(&game)) { + MessageBox(GetHWnd(), L"结束了", L"XXX ", MB_OK); + game.isRunning = false; + } + if (game.player == human) { + if (peekmessage(&game.msg)) { + update(&game);//鼠标点击后生成白色棋子 + } + creatUI(&game, &img_bg);//生成、更新游戏界面 + } + else if (game.player == robot) { + + robotplay(&game); + creatUI(&game, &img_bg);//生成、更新游戏界面 + shiftplayer(&game);//人机切换 + } + } + return 0; +} +五、实验结果 +棋盘效果图如下: + + +六、实验分析 +该五子棋程序能够较好地完成相关运算,加入剪枝算法后落子速度显著提高,且能够判断潜在的连子情况。 +七、实验总结 +要注重理论与实践相结合,提高逻辑分析能力 +