Git Product home page Git Product logo

5sonchess's Introduction

5SonChess

This is a gobang C program with AI. This program is programmed according to the C99 standard, and tested under the Microsoft Visual Studio 2017 Community IDE. This project is the coursework of the C Program Language Course in UCAS.


五子棋

这是一个带有AI的C语言五子棋程序,按照C99标准编写,在 Microsoft Visual Studio 2017 Community 集成开发环境中编译并测试通过,作为UCAS的C语言大作业。

特性

  1. 键盘操作交互,大部分情况可以通过方向键来选择选项和移动棋盘光标,无需输入坐标或者选项编号,操作方式更加友好。

  2. 完全改变了传统的cls刷新方式。显示引擎通过缓冲器读写、新旧缓冲器对比和定点覆盖输出的方式避免了频繁使用cls命令导致的屏幕闪烁,并提高了程序的运行速度。 注:传统方法使用cls进行屏幕刷新时,如果改变的内容不多而刷新频率较高,则很容易出现闪烁的现象,影响视觉体验。同时,如果显示内容需要经过较为复杂的运算,反复清屏刷新会耗费较长运行时间,影响程序效率,并可能出现显示加载到一半的不美观画面。

  3. AI模块采用dll动态链接库的方式进行动态加载、显式链接,与主程序独立。方便使用不同方法、不同版本的AI算法进行调试。

  4. 支持本地PVP、人机对战和双AI调试模式。双AI调试功能提供调试统计模式,可以令两个AI程序快速进行多局游戏并自动统计胜负情况,有利于对AI的棋力进行对比。也可以采用单局调试,对AI的每一步进行观看。

用法

双AI调试功能可以快速评判两个AI模组的棋力。使用双AI调试功能需要按照主程序推荐的方法来编写AI模组。具体说明如下:

  1. 主程序采用显式调用的方法加载AI模组。运行主程序并选择人机对战或者双AI调试功能后,主程序会调用文件管理器来要求用户选择AI模组。

  2. 主程序加载AI模组时,会首先调用GetProcAddress()函数与AI握手。AI中应当导出函数PrintVersionInfo(),其定义如下:

    int PrintVersionInfo()
    {
    	printf("DLL Name: xxxx.dll\n"); // dll文件名
    	printf("Version: x.x.x_xxxx\n"); // dll版本号
    	printf("Application Interface Type: xxxxxxxxxx\n"); // dll接口类型(随便起一个名字)
    	printf("Release Note: xxxxxxxxxxx\n"); // 版本注记
    	printf("........"); // 其他要说明的内容
    	printf("AI已经准备就绪。\n\n");
    	return x;
    	// 注意:返回值非常重要,这是主程序识别dll接口类型的唯一方式。
    	// 如果你使用了主程序中已经定义的接口,则应该返回对应的值,并且在上面dll接口类型中说明。
    	// 如果你的接口是主程序中未定义的,你需要定义一个不同于现有接口类型的返回值,并针对该dll更改主程序以添加功能。
    }
    
    

    主程序如果不能在dll中找到符合规范的这个函数,就会报错并且要求用户重新选择dll模组。

  3. AI的应该导出一个函数API_Main()来作为与主程序的接口。我们强烈建议AI返回一个place型的变量,即落子位置。place型的定义如下:

    // 该结构体的行、列均相对Chess[][]数组而言
    typedef struct place
    {
    	int x; // 行
    	int y; // 列
    }place;		
    

    如果你想返回其他类型的内容,则需要对主程序进行较大改动。AI的传入值可以有很多个,包括AI的各个参数设置、棋局等情况。主程序不应该调用除此之外的其他函数来获得或者计算落子位置。dll中的其他函数都应该由这个函数来调用。

  4. 主程序如何修改:如果你定义了新的接口,则需要修改主程序以支持这种接口。

    注:符合标准的PrintVersionInfo()是必要的,你不应该为了支持不标准的PrintVersionInfo()而修改主程序中的相关内容。

    你首先应该使用以下语句定义一个类型:

    typedef place(*tFuncpXXXX)(type, type, type,...)
    

    这是一个指向“返回值为place型,参数为(type, type, type,...)型的函数”的指针类型。"t"代表类型名,"Func"代表函数,"p"代表指针,"XXXX"可以自定义。参数表(type, type, type,...)根据你的接口定义,将type改成相应的类型。

    之后,你应该修改对AI接口的判断语句,添加一个if分支来识别你的接口类型。在这个分支里,你需要以下两个语句:

    tFuncpXXXX funcpXXXX = (tFuncpXXXX)GetProcAddress(hDllLib, "API_Main");
    AIPlace = (*funcpXXXX)(Arg1, Arg2, Arg3,...);	
    

    第一个语句调用GetProcAddress()函数来查找API_Main()函数,并将指针funcpXXXX指向该函数。第二个语句则通过指针调用该函数获得AI计算出的落子位置。

  5. 你应该新建一个Win 32动态链接库应用程序项目来生成AI模组。别忘了在int PrintVersionInfo()和place API_Main()函数前面,加上以下导出说明:

    extern "C" _declspec(dllexport)
    

    你也可以定义一个宏来少打几个字母,实现同样的功能。

开发目标

  1. AI的参数将可以随时自定义。每次通过一个JSON文件加载默认参数或者用户保存的参数。

  2. 极限挑战:添加对Yixin协议的支持。

Yixin protocol is a protocol derived from Gomocup protocol. It is designed because Gomocup protocol has some limitation. Firstly used by Yixin, Yixin protocol supports more commands than Gomocup protocol. Yixin protocol is fully implemented in Gomoku/Renju GUI Yixin Board.

关于弈心协议的更多内容详见弈心协议的GitHub页面

  1. 复盘文件:将单局游戏保存到xml文件中,也可以读取xml文件观看保存的比赛(针对非统计调试模式此功能可用)。

  2. 网络对战:通过TCP协议与互联网上运行本客户端的其他电脑一起游戏。输入IP地址和端口号即可连接。

  3. 界面升级:添加计时器模块,并显示在棋盘旁边。

Wentian Bu

2017-12-09



2017.12.09 修改注记 (Version 1.3.1)

好消息,好消息!本次完成了对鼠标的支持并重写了不少代码。


本次修改内容

  1. 完全消灭了system()函数,对控制台的设置一律改用控制台API进行。除了满足原来设置窗口大小和颜色的需求外,还通过控制台API实现了以下内容:

    • 隐藏闪烁的光标。隐藏后核心的刷新屏幕函数可以少一个语句,似乎提升了运行速度。
    • 设置窗口标题:五子荣耀。
    • 禁止更改窗口大小和最大化。经测试,允许用户更改窗口大小或最大化会导致显示bug。
    • 设置窗口颜色:灰色底黑色字,不那么刺眼了。
    • 禁用快速编辑模式:快速编辑模式会与鼠标产生冲突,通过API强制禁用了该模式。
  2. 添加了对鼠标操作的支持:谁说控制台只能用键盘?目前可以通过鼠标直接下棋,用户体验良好。完成了一个开发目标!!!

  3. 结构化代码:这是1.2.2版本的修改内容,由于改动不影响功能故没有单独写注记。将游戏流程的控制模块从主函数中抽出来放入game模块中,使代码模块化程度更高,可读性更强。

  4. 升级了裁判函数:长期以来借用张宁鑫的裁判函数凑数,这次终于自己写了,还添加了对黑方长连禁手的识别。这其实也是迫不得已,因为如果不在这里加,白方AI根本没机会指出禁手,游戏就已经结束了。此外,裁判函数被整合到game模块中成为了一个静态函数,减少了文件量。

下一步开发目标

  1. 目前程序存在一个难以理解的bug:高速移动光标并快速下棋(这种行为一般是左手疯狂瞎按WASD,右手狂按Enter下棋,或者按住鼠标左键在棋盘上拖动才会触发)时,光标会发生清除不全的bug。正常使用不会触发这个bug。有机会好好研究一下是什么原因。

  2. 界面的完善:左右侧栏添加计时、计步以及AI属性、指针位置等信息,还可随时显示和设置AI参数。下边栏作为输出窗口。

  3. AI的开发:最近好好研究了一下AI的算法,想得也差不多了,即将开始动手写AI。

很多目标实现起来还是没有时间,放假有空再完善这些零碎功能,顺便也算提升能力。

Wentian Bu

2017-12-09



2017.11.25 修改注记 (Version 1.2.0)

版本1.1.0添加功能后未写注记和发布,此次一并记录。


版本1.2.0修改内容

  1. 基本解决了历史遗留问题:计算好的缓冲器位置扩展性和灵活性极差。本次花了大功夫对这部分内容进行重写,采用宏定义的方法来控制缓冲器大小(控制台的大小将跟随缓冲器改变),彻底消灭了计算死的缓冲器位置,并且添加了自动居中的效果。现在可以根据需要自定义缓冲器大小(更改宏定义的值,需要重新编译),显示效果保持居中(由于棋盘大小限制,若缓冲器小于32*32可能导致棋盘无法正常显示),并可较容易地添加显示内容而保持显示效果基本不变。

  2. 对于玩家对战和人机对战的键盘输入部分进行了优化,将接收键盘输入的部分独立出来,提升了代码的可读性和模块化程度,更加易于维护。

版本1.1.0修改内容

  1. 添加了人机对战和双AI调试时的AI模块选择功能。

  2. 调用WIN32 API,使选择AI模块的界面可视化,方便调试和使用。

  3. 将已有代码文件统一采用UTF-8 with BOM编码格式保存,方便读取和识别。

下一步开发目标

  1. 添加记录功能。本次在优化键盘读取模块时预留了悔棋功能,将来会完善这部分代码。由于悔棋功能需要记录棋局数据,因此可以扩展成复盘保存、读取功能。可能采用第三方库libXML来实现XML文件的读写。

  2. AI开发的计划:先做一个不带禁手的AI,但是保留填子法判断棋型的算法,将来较容易扩展成带禁手的AI。

  3. 鼠标交互:控制台程序不意味着鼠标就失效了。将来可能加入对鼠标的支持,使之能够接近图形应用程序,使用鼠标进行操作。

  4. 配置文件:设置项目虽然少,但是仍然可以添加这一块功能。可以考虑用JSON格式来记录用户配置,进行一些按键的定义、界面的颜色更改之类。这算是后话了。

  5. 网络功能:通过TCP协议与网络上运行相同主程序的五子棋客户端下棋。这涉及到网络协议等知识,而且目前网络防火墙也可能产生问题。这是后话,有心情再做。

Wentian Bu

2017-11-25



2017.11.18 修改注记 (Version 1.0.1)

本次主要是少量显示内容的修改,同时思考了下一步开发计划。


本次修改内容

  1. 根据课程提供的规范对五子棋界面程序进行了少量修改,棋盘添加了天元和星点标记,棋盘下部采用AO、左侧采用115标明了坐标。

  2. 主程序添加了查阅五子棋规则的功能。课程要求的五子棋为黑方禁手规则,就此编写了rules.html。

下一步开发目标

  1. 解决最开始编写缓冲器模块时的遗留问题。

    最开始写缓冲器模块时,缓冲器大小固定为32*32。

     char *Buffer[32][32] = { NULL };
     char *OldBuffer[32][32] = { NULL };
    

    此外,很多显示信息的语句都是在编程时直接计算出值之后写入指定位置的缓冲器,例如:

    • 主程序界面的显示功能

      for (int i = 6; i < 26; i++) *(Buffer+160+i) = "——";
      *(Buffer + 235) = "欢迎来到 五子荣耀!";
      *(Buffer + 298) = "请使用上下方向键选择功能:";
      *(Buffer + 397) = "1. 玩家对战";
      *(Buffer + 461) = "2. 与AI对战";
      *(Buffer + 525) = "3. 双AI调试";
      *(Buffer + 589) = "4. 查看规则";
      *(Buffer + 653) = "5. 退出游戏";
      *(Buffer + 775) = "Powered By Wentian Bu    Version 1.0.1";
      for (int i = 6; i < 26; i++) *(Buffer + 800 + i) = "——";
      
    • 绘制棋盘的核心模块

      // 绘制棋盘方格
      *(Buffer + 2 * i * 32 + 2 * j) = " ";
      *(Buffer + 2 * i * 32 + 2 * j + 1) = "│";
      *(Buffer + (2 * i + 1) * 32 + 2 * j) = "—";
      *(Buffer + (2 * i + 1) * 32 + 2 * j + 1) = "┼";
      
      // 绘制棋盘边界图样
      *(Buffer + 2 * i * 32 + 1) = "┃";
      *(Buffer + 2 * i * 32 + 29) = "┃";
      *(Buffer + (2 * i + 1) * 32 + 1) = "┠";
      *(Buffer + (2 * i + 1) * 32 + 29) = "┨";
      *(Buffer + 32 + 2 * j) = "━";
      *(Buffer + 928 + 2 * j) = "━";
      *(Buffer + 32 + 2 * j + 1) = "┯";
      *(Buffer + 928 + 2 * j + 1) = "┷";
      
      *(Buffer + 33) = "┏";
      *(Buffer + 61) = "┓";
      *(Buffer + 929) = "┗";
      *(Buffer + 957) = "┛";
      
    • 刷新显示的核心函数

      for (int j = 0; j < 32; j++)
      {
      	if (*(Buffer + i * 32 + j) != *(OldBuffer + i * 32 + j))
      	{
      		gotoxy(2*j,i);
      		printf("%s", *(Buffer + i * 32 + j));
      		*(OldBuffer + i * 32 + j) = *(Buffer + i * 32 + j);
      	}
      }
      

    这些重要的核心功能模块都是预先计算好的数据,这直接导致了程序难以适应变化的需求。本次在添加了左侧数字坐标后明显觉得左侧空白不足,界面不够美观。但是由于写定的模块太多,修改极其麻烦。将来会对核心模块进行重写和扩充,使之能够适应可变的显示区域。

  2. 对于AI算法的考虑:

    目前仍然计划棋型评分的做法。结合看过的一些代码,大致可以这样操作:

    1. 对棋盘进行处理,将各种棋子之间的关系转换成易于查找和搜索的向量表供AI使用。

      注:该向量表每一步棋落子后进行更新,且一直保持到本局结束,这样可以大大减少全盘搜索棋型耗费的时间。

    2. AI搜索向量表,并给每个位置评分:根据下在该位置所得的分数,进行综合决策。AI可以纵深搜索多步,对某个位置的优劣有更完善的评估。

      注:为了节约运算时间,尽量减少重复的搜索计算过程,可在内存中保留多个版本的向量表,根据具体棋局走向进行选择保留。还可以综合利用剪枝算法等来减少分支,增加一定时间内的搜索效率。

    3. 对禁手的理解和检测:检测禁手的有效方法是 填子法 ,即在某点试着落子并根据定义检查是否存在禁手,尤其是假活三等看似会导致禁手的棋型很容易混淆。对禁手的处理麻烦在于多重禁手,但是实际下棋过程中概率极小,因此能够正确识别并处理两到三层禁手即可。

    4. 容错性:课程要求每一步棋最多计算15秒,超时判负。因此应该设计容错算法,即在15秒以内尽可能多地计算,但在其中任何时刻都应该有一个当前计算量下最佳的落子位置。

    5. 关于裁判函数:当前版本的裁判函数暂时借用了张宁鑫的函数,在各方向上检查连子数。将来完善向量表结构之后,会利用向量表来进行裁判,提高效率。

    Wentian Bu

    2017-11-18

5sonchess's People

Contributors

wentianbu avatar

Stargazers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.