├── Code ├── AI.c ├── IO.h ├── VCX.c ├── VCX.h ├── game.c ├── game.h ├── init.h ├── main.c ├── record.h ├── search.c ├── search.h ├── utils.h ├── checker.c ├── checker.h ├── counting.c ├── counting.h ├── evaluator.c ├── evaluator.h ├── mt19937.h ├── zobrist.h ├── init.c ├── AI.h ├── record.c ├── zobrist.c ├── mt19937.c └── IO.c ├── Structure.jpg └── README.md /Code/AI.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usercjh123/Renju-UCAS-C-programming/HEAD/Code/AI.c -------------------------------------------------------------------------------- /Code/IO.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usercjh123/Renju-UCAS-C-programming/HEAD/Code/IO.h -------------------------------------------------------------------------------- /Code/VCX.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usercjh123/Renju-UCAS-C-programming/HEAD/Code/VCX.c -------------------------------------------------------------------------------- /Code/VCX.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usercjh123/Renju-UCAS-C-programming/HEAD/Code/VCX.h -------------------------------------------------------------------------------- /Code/game.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usercjh123/Renju-UCAS-C-programming/HEAD/Code/game.c -------------------------------------------------------------------------------- /Code/game.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usercjh123/Renju-UCAS-C-programming/HEAD/Code/game.h -------------------------------------------------------------------------------- /Code/init.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usercjh123/Renju-UCAS-C-programming/HEAD/Code/init.h -------------------------------------------------------------------------------- /Code/main.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usercjh123/Renju-UCAS-C-programming/HEAD/Code/main.c -------------------------------------------------------------------------------- /Code/record.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void record(Pos const* pos); 4 | int save(char* filedir); -------------------------------------------------------------------------------- /Code/search.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usercjh123/Renju-UCAS-C-programming/HEAD/Code/search.c -------------------------------------------------------------------------------- /Code/search.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usercjh123/Renju-UCAS-C-programming/HEAD/Code/search.h -------------------------------------------------------------------------------- /Code/utils.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usercjh123/Renju-UCAS-C-programming/HEAD/Code/utils.h -------------------------------------------------------------------------------- /Structure.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usercjh123/Renju-UCAS-C-programming/HEAD/Structure.jpg -------------------------------------------------------------------------------- /Code/checker.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usercjh123/Renju-UCAS-C-programming/HEAD/Code/checker.c -------------------------------------------------------------------------------- /Code/checker.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usercjh123/Renju-UCAS-C-programming/HEAD/Code/checker.h -------------------------------------------------------------------------------- /Code/counting.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usercjh123/Renju-UCAS-C-programming/HEAD/Code/counting.c -------------------------------------------------------------------------------- /Code/counting.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usercjh123/Renju-UCAS-C-programming/HEAD/Code/counting.h -------------------------------------------------------------------------------- /Code/evaluator.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usercjh123/Renju-UCAS-C-programming/HEAD/Code/evaluator.c -------------------------------------------------------------------------------- /Code/evaluator.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usercjh123/Renju-UCAS-C-programming/HEAD/Code/evaluator.h -------------------------------------------------------------------------------- /Code/mt19937.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usercjh123/Renju-UCAS-C-programming/HEAD/Code/mt19937.h -------------------------------------------------------------------------------- /Code/zobrist.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usercjh123/Renju-UCAS-C-programming/HEAD/Code/zobrist.h -------------------------------------------------------------------------------- /Code/init.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "init.h" 3 | #include "game.h" 4 | #include "mt19937.h" 5 | 6 | void init_all(Game* game) 7 | { 8 | initialize_game(game); 9 | mt_initialize((uint64)time(0)); 10 | zobrist_initialize(); 11 | } 12 | -------------------------------------------------------------------------------- /Code/AI.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "game.h" 3 | 4 | #define LEVEL_EASY 0 5 | #define LEVEL_MID 1 6 | #define LEVEL_HARD 2 7 | 8 | #define MAX_N_THREAD 12 9 | 10 | Pos AI_play(Game* game, int const level, int const width, int const threshold, int output, int rnd); -------------------------------------------------------------------------------- /Code/record.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "game.h" 3 | 4 | static Pos move_lst[BOARD_SIZE * BOARD_SIZE]; 5 | static int rnd = 0; 6 | 7 | void record(Pos const* pos) { 8 | move_lst[rnd++] = *pos; 9 | } 10 | 11 | int save(char* filedir) { 12 | FILE* fp = fopen(filedir, "w"); 13 | if (fp == NULL) return -1; 14 | fprintf(fp, "%d\n%d\n%d\n", BOARD_SIZE, BOARD_SIZE, rnd); 15 | for (int i = 0; i < rnd; ++i) { 16 | fprintf(fp, "%d %d\n", move_lst[i].row, move_lst[i].col); 17 | } 18 | fclose(fp); 19 | return 0; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /Code/zobrist.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "zobrist.h" 3 | #include "mt19937.h" 4 | #include "game.h" 5 | 6 | static uint64 zobrist_black[BOARD_SIZE][BOARD_SIZE]; 7 | static uint64 zobrist_white[BOARD_SIZE][BOARD_SIZE]; 8 | 9 | void zobrist_initialize() 10 | { 11 | //mt_initialize((uint64)time(0)); 12 | for (int row = 0; row < BOARD_SIZE; ++row) { 13 | for (int col = 0; col < BOARD_SIZE; ++col) { 14 | zobrist_black[row][col] = mt_rand(); 15 | zobrist_white[row][col] = mt_rand(); 16 | } 17 | } 18 | } 19 | 20 | void zobrist_change(uint64* hash, int r, int c, int player) 21 | { 22 | *hash ^= (player == BLACK) ? zobrist_black[r][c] : zobrist_white[r][c]; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /Code/mt19937.c: -------------------------------------------------------------------------------- 1 | #include "mt19937.h" 2 | 3 | static uint64 mt[MT_n]; 4 | static uint index; 5 | 6 | void mt_initialize(uint64 seed) 7 | { 8 | mt[0] = seed; 9 | for (uint i = 1; i < MT_n; ++i) { 10 | mt[i] = MT_f * (mt[i - 1] ^ (mt[i - 1] >> (MT_w - 2))) + i; 11 | } 12 | index = MT_n; 13 | } 14 | 15 | static void twist() 16 | { 17 | uint64 x, y; 18 | for (uint i = 0; i < MT_n; ++i) { 19 | x = (uint64)((mt[i] & MT_UPPER_MASK) + (mt[(i + 1) % MT_n] & MT_LOWER_MASK)); 20 | y = (x >> 1); 21 | if (x & 1) y = (y ^ MT_a); 22 | mt[i] = (mt[(i + MT_m) % MT_n] ^ y); 23 | } 24 | index = 0; 25 | } 26 | 27 | uint64 mt_rand() 28 | { 29 | uint64 x; 30 | if (index >= MT_n) twist(); 31 | uint i = index++; 32 | x = mt[i]; 33 | x ^= (x >> MT_u) & MT_d; 34 | x ^= (x << MT_s) & MT_b; 35 | x ^= (x << MT_t) & MT_c; 36 | x ^= (x >> MT_l); 37 | return x; 38 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Renju-UCAS-C-programming 2 | 3 | 中国科学院大学本科程序设计基础与实验(C语言)期末大作业:五子棋(部分禁手)AI的实现(2022级杨力祥班比赛第一名) 4 | 5 | **说明** 6 | 1. 文件采用GBK编码保存,使用GBK编码才能看到正确的注释! 7 | 2. 本程序不使用旧版控制台!如果必须使用旧版控制台,应做如下修改: 8 | - 把`print_with_color`函数改为`printf`(直接修改`io.c`中的`print_with_color`函数即可) 9 | - 把`io.c`中的`display`函数中棋盘多余的横线去掉(如,把“┬─”改为“┬”)以显示正常的棋盘 10 | - 如还有其他问题可以简单修改`display`函数以实现效果 11 | 3. 本程序使用了`windows.h`,利用Win32 API实现多线程,必须在Windows系统上编译运行! 12 | 4. 程序框架图见`Structure.jpg`,图中的箭头仅表示逻辑上的依赖关系,不表示代码的引用关系。 13 | 5. 编译本程序时,最好用较高版本的编译器: 14 | - 为了适应VS 2022必须使用`_getch()`函数代替`getch()`函数,在VS 2017以前的版本中应换为`getch()`函数 15 | - VS开发环境自动在每个头文件前增加`#pragma once`,这在低版本的编译器中可能不支持 16 | 6. 如果编译器对`scanf`报错,请在项目项目选项中的预处理器命令中添加`#define _CRT_SECURE_NO_WARNINGS`,或手动在每个文件前添加 17 | 7. 为了提高走子速度,建议使用-O2优化指令。 18 | 8. 由于时间问题,没有实现对保存的棋盘文件进行复盘的操作。但是,保存的棋盘文件的格式按照常用的[YIXIN BOARD](https://www.aiexp.info/pages/yixin.html)的保存格式,因此可以利用YIXIN BOARD打开 19 | 9. 程序创作过程花费大约1个半月,主要是每周六上午写一点。在开始创作程序之前,使用了Python测试了Min-Max(alpha-beta)搜索算法和朴素的MCTS算法,最终决定选择Min-Max(alpha-beta)搜索算法 20 | 10. 在禁手实现上,本程序不考虑十分复杂的禁手(忽略禁手之间的相互影响),这能够应对大部分情况 21 | 11. 程序创作过程中,主要参考: 22 | - [GitHub上某大佬使用JS的五子棋教程](https://github.com/lihongxun945/myblog/labels/五子棋AI教程第二版),整体的思路参考了这个教程,但在搜索方面我采用了先用较低层的搜索树进行预搜索再用较深层的搜索树进行深度搜索的方式,这是与该教程最大的不同 23 | - [2020级武成岗班第一名程序](https://github.com/MingZwhy/UCAS-C_programming) 24 | 25 | 在此表示感谢! 26 | 27 | --- 28 | 29 | 一些碎碎念: 30 | 31 | > 本人并非计算机相关专业的学生,想到此后不一定再有机会去尝试用C语言去从零开始写一个不算小的项目,遂决定将最终的成果上传到网络。 32 | > 33 | > 这样做一来是纪念这一个学期的努力,二则是为后来者提供一定的参考(我的程序整体上有一定的可读性)。 34 | > 35 | > 回想起最初面对这样一个大工程时的手足无措,到后来查阅了不少论文资料并经过一定的“预实验”选择了最终的思路,再到最终经过一个月多的时间反复修改重构调整最终在比赛当天交上了作业,这期间遇到了许多困难,但最终都逐一克服。这一整个过程中,虽然我的专业知识并没有特别多的增长,但个人解决问题和结构思维的能力却得到了不小的锻炼。 36 | > 37 | > 我想感谢许多人: 38 | > 39 | > 首先感谢杨力祥老师提供这样一个难得的机会。我向来喜欢挑战一些困难的问题,完成这项大作业的过程中的一次次失败后的突破都让我感到欣喜与激动。 40 | > 41 | > 其次感谢我高中信息竞赛的教练赵老师,虽然只在您的指导下学习了一年多,但我在图书馆或宿舍敲代码时总能想起当时在机房备赛的快乐的时光。 42 | > 43 | > 最后,感谢自己,没有因为这件事情不重要就随便对付一下,而是仍以十分的热忱去应对。 44 | 45 | 46 | -------------------------------------------------------------------------------- /Code/IO.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "IO.h" 8 | #include "record.h" 9 | #include "game.h" 10 | 11 | static int getline(char str[], int max_len, int ignore_line_break) { 12 | char c; int i; 13 | for (i = 0; i < max_len - 1 && (c = getchar()) != EOF && c != '\n'; ++i) str[i] = c; 14 | if (!ignore_line_break && c == '\n') { 15 | str[i] = c; 16 | ++i; 17 | } 18 | str[i] = '\0'; 19 | return i; 20 | } 21 | 22 | static void generate_prompt(Game const* game) { 23 | printf("当前的玩家是"); 24 | if (game->current_player == BLACK) print_with_color("黑方. ", IO_RED, IO_BLACK, 0); 25 | else print_with_color("白方. ", IO_RED, IO_BLACK, 0); 26 | printf("请输入走子位置(输入'?'查看输入规则): "); 27 | } 28 | 29 | static int get_input(Pos* pos) { // 输入必须为问号或“字母+数字”形式 30 | char str[MAX_LEN]; getline(str, MAX_LEN, 0); 31 | int i = 0; 32 | int col = 0, row = 0; 33 | for (; str[i] != '\0' && str[i] == ' '; ++i); 34 | if (str[i] == '?') return INPUT_HELP; 35 | if (isalpha(str[i])) { 36 | col = tolower(str[i]) - 'a'; 37 | if (col >= 15) return INPUT_ERR; 38 | for (++i; str[i] != '\0' && isdigit(str[i]); ++i) { 39 | row = row * 10 + str[i] - '0'; 40 | } 41 | if (row == 0 || row > 15) return INPUT_ERR; 42 | pos->col = col; 43 | pos->row = 15 - row; 44 | return INPUT_SUCC; 45 | } 46 | else return INPUT_ERR; 47 | } 48 | 49 | void display(Game const* game, Pos const* pos, int player) 50 | { 51 | // 后两个参数是为了在不改变棋盘的情况下新增棋子 52 | for (int row = 0; row < BOARD_SIZE; ++row) { 53 | printf("%2d ", 15 - row); 54 | for (int col = 0; col < BOARD_SIZE; ++col) { 55 | // 棋子输出 56 | if (player != 0 && row == pos->row && col == pos->col) { 57 | printf(player == WHITE ? "△": "▲"); 58 | } 59 | else if (game->board[row][col] != 0) { 60 | if (player == 0 && row == game->recent_play.row && col == game->recent_play.col) { 61 | printf(game->current_player == WHITE ? "▲" : "△"); 62 | } 63 | else printf(game->board[row][col] == BLACK ? "●" : "○"); 64 | } 65 | // 空白格输出 66 | else { 67 | if (row == 0) { 68 | if (col == 0) printf("┌─"); 69 | else if (col == BOARD_SIZE - 1) printf("┐"); 70 | else printf("┬─"); 71 | } 72 | else if (row == BOARD_SIZE - 1) { 73 | if (col == 0) printf("└─"); 74 | else if (col == BOARD_SIZE - 1) printf("┘"); 75 | else printf("┴─"); 76 | } 77 | else { 78 | if (col == 0) printf("├─"); 79 | else if (col == BOARD_SIZE - 1) printf("┤"); 80 | else printf("┼─"); 81 | } 82 | } 83 | } 84 | printf("\n"); 85 | } 86 | printf(" "); 87 | for (int col = 0; col < BOARD_SIZE; ++col) { 88 | printf("%c ", 'A' + col); 89 | } 90 | printf("\n"); 91 | } 92 | 93 | 94 | 95 | // 工具函数 96 | int valid_char(char c) { return c == '?' || isdigit(c) || isalpha(c); } 97 | 98 | 99 | 100 | void print_with_color(char const message[], int font_color, int bg_color, int new_line) 101 | { 102 | printf("\033[1;%d;%dm%s\033[0m", 30 + font_color, 40 + bg_color, message); 103 | if (new_line) printf("\n"); 104 | } 105 | 106 | void IO_loop(Game const* game, Pos* input_pos) 107 | { 108 | int input_state, undo = 1; 109 | char line[10]; 110 | do { 111 | display(game, input_pos, 0); 112 | while ((generate_prompt(game), (input_state = get_input(input_pos)) != INPUT_SUCC)) { 113 | if (input_state == INPUT_HELP) printf("输入必须是\"字母(大小写均可)+ 数字\"(如:C3、a6),字符前的空格会被忽略,在字母和数字之间不能有空格!\n"); 114 | else print_with_color("输入错误!", IO_RED, IO_BLACK, 1); 115 | } 116 | if (game->board[input_pos->row][input_pos->col] != 0) { 117 | print_with_color("该位置已有棋子!", IO_RED, IO_BLACK, 1); 118 | continue; 119 | } 120 | display(game, input_pos, game->current_player); 121 | printf("是否悔棋(Y,y/任意字符):"); 122 | getline(line, 10, 0); 123 | if (line[0] == 'Y' || line[0] == 'y') undo = 1; 124 | else undo = 0; 125 | } while (undo); 126 | 127 | } 128 | 129 | void welcome_screen(int* mode, int* level, int* color) { 130 | printf("\033[0m\n"); 131 | print_with_color(" 五子棋对战", IO_RED, IO_BLACK, 1); 132 | printf("----------------------------------------------------------------------------------------------------\n"); 133 | printf("本程序不需要使用旧版控制台!\n如在本界面出现显示问题,请取消\"使用旧版控制台\"选项!\n"); 134 | printf("\n重要提醒:\n输入棋盘位置坐标时,采用\"字母(大小写均可)+ 数字\"(如:C3、a6)的形式,在字母和数字之间不能有空格!\n"); 135 | printf("----------------------------------------------------------------------------------------------------\n"); 136 | printf("游戏模式:\n"); 137 | printf("1. 人人对战\n2. 人机对战\n"); 138 | printf("-------------------------------------------------\n"); 139 | *mode = 0; 140 | while (*mode != 1 && *mode != 2) { 141 | printf("请选择游戏模式:"); 142 | scanf("%d", mode); 143 | getchar(); // 这是个大坑!scanf不读入\n,因此需要用getchar把缓冲区的\n读出 144 | if (*mode != 1 && *mode != 2) printf("输入错误!请重新选择!\n"); 145 | } 146 | if (*mode == 1) { 147 | printf("你选择人人对战模式!按任意键继续...\n"); 148 | _getch(); 149 | Sleep(500); 150 | system("cls"); 151 | fflush(stdin); 152 | return; 153 | } 154 | printf("-------------------------------------------------\n"); 155 | printf("AI强度:\n"); 156 | printf("1. 较弱\n2. 中等\n3. 较强\n"); 157 | *level = 0; 158 | while (*level != 1 && *level != 2 && *level != 3) { 159 | printf("请选择AI强度:"); 160 | scanf("%d", level); 161 | getchar(); 162 | if (*level != 1 && *level != 2 && *level != 3) printf("输入错误!请重新选择!\n"); 163 | } 164 | printf("-------------------------------------------------\n"); 165 | *color = 0; 166 | while (*color != 1 && *color != 2) { 167 | printf("请选择玩家所执棋子(1. 黑子先手 2. 白子后手):"); 168 | scanf("%d", color); 169 | getchar(); 170 | if (*color != 1 && *color != 2) printf("输入错误!请重新选择!\n"); 171 | } 172 | printf("你选择人机对战模式!\nAI强度:"); 173 | switch (*level) 174 | { 175 | case 1: 176 | printf("较弱\n"); 177 | break; 178 | case 2: 179 | printf("中等\n"); 180 | break; 181 | case 3: 182 | printf("较强\n"); 183 | break; 184 | } 185 | *level -= 1; 186 | printf("玩家执"); 187 | switch (*color) { 188 | case 1: 189 | printf("黑子\n"); 190 | break; 191 | case 2: 192 | printf("白子\n"); 193 | break; 194 | } 195 | *color = (*color == 1) ? 1 : -1; 196 | printf("按任意键继续...\n"); 197 | _getch(); 198 | Sleep(500); 199 | system("cls"); 200 | return; 201 | } 202 | 203 | void save_board() { 204 | while (1) { 205 | char line[10]; 206 | printf("是否保存局面(Y,y/任意字符):"); 207 | getline(line, 10, 0); 208 | if (line[0] != 'Y' && line[0] != 'y') break; 209 | printf("请输入保存文件名(保存在当前目录,不需要加扩展名):\n"); 210 | char filename[100], filedir[100] = "./"; 211 | getline(filename, 100, 1); 212 | strcat(filedir, filename); 213 | strcat(filedir, ".sav"); 214 | if (save(filedir) == 0) { 215 | print_with_color("保存成功!", IO_GREEN, IO_BLACK, 1); 216 | break; 217 | } 218 | else print_with_color("保存失败!", IO_RED, IO_BLACK, 1); 219 | } 220 | 221 | 222 | 223 | } --------------------------------------------------------------------------------