65.9K
CodeProject 正在变化。 阅读更多。
Home

C# / C++ CLI Micro Chess (Huo Chess)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.70/5 (104投票s)

2007年10月3日

CPOL

31分钟阅读

viewsIcon

411255

downloadIcon

24220

关于Huo Chess的一篇文章,这是一个C#迷你开源国际象棋程序,旨在成为有史以来最小的用于教育目的的国际象棋程序……

 

 

Huo Chess (GUI 版本) 

新闻

2021-03-14: 仅当着法涉及吃子时,才进行4级和5级深度思考。删除了冗余代码。通过结尾的阵地元素改进了CountScore函数。在思考过程中引入了将死和逼和的元素。

2019-02-18: Huo Chess v0.991 Java 版本发布。请参阅Harmonia Philosophica门户网站上的主Huo Chess页面,了解更多关于该版本的信息。

2019-02-12: Huo Chess v0.991 发布。算法得到许多改进。添加了开局能力。该版本还包含一个开发者版本,其中包含思考过程的详细日志。

2018-08-01: Huo Chess v0.990 开发完成。新版本解决了代码中的各种问题,并对国际象棋引擎进行了更新的逻辑处理。

2015-09-04: Huo Chess Windows 8 版本已上传。该版本具有最小的用户交互能力。新版本将很快跟进...

2015-09-04: Huo Chess v0.980 已更新:针对CheckMove上传了热修复补丁,该补丁存在一个bug,导致计算机做出非法着法。由于热修复补丁在发布新版本后不久上传,版本号未更改。感谢Peter Bateman指出此bug!(这叫做回归测试,而它却常常被忽视……)

摘要

Huo Chess 是一个开源国际象棋程序,大小仅为 **46 KB**(此大小指的是0.992 C# 控制台应用程序版本,不含GUI)。该程序的目标是多方面的:实现卓越的代码结构和算法效率,从而创建最小的、棋力尚可的国际象棋程序,并且(最重要的是)为那些想学习如何开发国际象棋程序的人提供教育参考。

关于该程序的教育目的,必须注意以下几点:

  • 源代码**注释非常详细**,以便读者理解他们看到的内容。变量名称完整(例如,StartingColumn、ThinkingDepth、MovingPiece),并且没有使用快捷方式来模糊代码。代码中的一切都**干净整洁,易于大家理解**思考过程。
  • 我在本文中分析并**记录了计算机的思考过程**,以便让大家都能明白。下面还列出了**未来可能的改进**之处,以便大家为新版本做出贡献。一个关于为控制台应用程序制作小型“GUI”的重要贡献已在评论中提交,现在已成为一个独立的版本(当然,会注明创建者的姓名)。
  • 程序包含一套**各种日志**,易于激活(只需在程序中的#Log或#WriteLog区域取消注释相关的行)。这些日志会生成文本文件,详细显示国际象棋引擎算法的工作方式。(前提是你有时间和耐心去阅读它们,它们确实非常长且详细)。
  • 作为项目的一部分,创建了一系列“**如何开发国际象棋程序**”。该系列旨在(顾名思义……)通过循序渐进的指导,向新手用户展示如何开发国际象棋程序。没有必要尝试从“Hello world”应用程序开始学习编程!直接深入,时间久了你就能学会游泳!(不用担心,最坏的情况就是应用程序无法编译)。你可以在Harmonia Philosophica门户网站上找到第一课“傻瓜式国际象棋程序开发指南”。你也可以在这里找到一个关于如何用Java一天编写演示国际象棋程序的非常简短的教程。这里

该程序是用Microsoft Visual Studio 2019开发的。(早期版本是用Visual Studio 2013和2015创建的)。目前有四个版本。

  1. Huo Chess 0.992 带 GUI
  2. Huo Chess 0.992 cs
  3. Huo Chess 0.992 cs 带图形界面
  4. Huo Chess 0.992 开发者版本

请参阅Harmonia Philosophica门户网站上的主Huo Chess页面,了解该程序的最新动态或关于如何开发国际象棋程序的文章(以Huo Chess为例)。

其他版本 (Java, QBasic)

我还将该程序移植到了QBasic,你可以在这里找到该版本。你还可以在这里找到v0.991版本的Java移植版。目前我正在努力将国际象棋引擎移植到Commodore 128,但这确实是一项艰巨的任务,需要一些时间。但这才是最终目标。如果不能在Commodore上享受微型国际象棋,那还有什么意义呢?这 anyway 就是我开始编程的原因……

0.992 版本亮点

Huo Chess 的新版本 (v0.992) 尺寸依然小巧,但增加了许多功能,大大提高了游戏质量。

仅当着法涉及吃子时,才进行4级和5级深度思考,从而提高了剪枝的质量。评估位置的函数(CountScore)已用结尾的阵地元素进行了丰富。

另一个重要方面是,在思考过程中增加了将死和逼和的元素。现在计算机将能够识别下一步的将死并追求它。同时,如果检测到逼和的可能性,它也会努力避免。

最后但同样重要的是,在思考算法中增加了对重复走相同着法的处罚,以避免不必要的重复三次(这是这个级别的计算机国际象棋的一大痛点)。

0.991 版本亮点

Huo Chess 的新版本 (v0.991) 尺寸小巧(和所有以前的版本一样),功能更强大。官方带 Windows Forms 用户界面的版本大小为 77 KB,带棋盘用户界面的控制台应用程序版本为 48 KB,不带棋盘用户界面的控制台应用程序版本为 45 KB。在其当前版本(Huo Chess v0.990 – C#)中,它可以思考到 3 个半步 [Kakos-Minimax 版本](例如,当计算机执白时,它会思考 2 个白方半步和 1 个黑方半步),并且具有开局库能力。

MinMax 思考算法的问题已修复,现在计算机在任何情况下都能做出逻辑性的走法(至少在它思考的 3 个半步深度下)。增加了思考 5 个半步深度的能力,但未激活(需要将 Thinking_Depth 改为 4 才能实现;注意,思考从第0步开始)。开局库能力已重新添加(此能力存在但已在0.980版本中删除)(必须在“Opening Book”文件夹中有位置数据才能使用)。

此外,还创建了一个开发者版本,其中包含思考机制的分析日志,用于调试(用于教学目的)。日志可以轻松激活或停用。(注意:请记住检查日志的大小并在需要时删除它们!它们的大小可能会显著增长)。此版本还朝着向用户界面显示计算机正在思考的变着迈出了一小步。

开局库编辑器单独分发(请参阅下载列表)。除了编辑器外,分发文件中还包含一些样本开局库位置的库。

Huo Chess 棋力尚可,并成功绘制了 Commodore 时代的首个微型国际象棋 Microchess(见下方的 Huo Chess Games Archive 部分)。其算法基于蛮力分析,同时利用 MiniMax 算法。它可以用于研究国际象棋程序底层逻辑,或作为您自己国际象棋程序的基础。正如我之前提到的,源代码**以英文注释详细,易于自定义**,因为所有变量都有清晰易懂的名称。源代码在不断改进,并根据The Code Project Open License分发。在本文中,我还分析了其编程逻辑,重点是计算机“思维”中的通用流程,从初始扫描棋盘到最终决定走哪一步。欢迎任何评论!

介绍 

编程时最重要的是什么?是程序设计吗?是的?如果是“是”,那么为什么如今每个程序都超过 123876 GB,运行需要 120987 MB 的 RAM?为什么每个新项目和程序 — 默认情况下 — 都比前一个版本更大、更慢?答案很简单:由于计算机速度的不断提升,没有人真正关心优化代码结构和软件设计,因为新的处理器肯定会让程序在最终用户看来运行良好。在 Commodore 计算机时代,内存相当有限。因此,程序员们试图使代码小型且高效。这产生了 Microchess,一个非常高效、小型(正如“micro”这个词,在希腊语中意为“小”,暗示的那样)的国际象棋程序,能够在那个时代可用的 KB 内存中进行国际象棋对弈。

我完成了什么

出于以上原因,我开始用 CLI C++ v8.0(使用 Visual Studio 2008 Express Edition,而现在最新版本是用 Visual Studio 2015/2017 开发的)开发一个下棋程序,目的是制作一个尺寸小巧(Micro)的国际象棋程序,甚至比 Commodore 时代的 Microchess 更小。现在,也有一个 C# 编程语言版本和一个带图形用户界面的版本(你可以在这里下载)。我出于个人原因将其命名为“Huo Chess”。该程序棋力尚可,但如果对阵 Garry Kasparov,恐怕还是会输。其大小目前为 **46 KB**(在 C# 控制台版本 v0.992 中,这比之前的 v0.961 版本 55 KB 有了很大改进!),而 C 语言中相应地模拟 Microchess 为 56 KB。**然而,必须指出的是,最初用纯汇编编写的 Microchess 大约是 1 KB……我估计没有人能再达到这个水平!**在与 Microchess 的比赛中,v0.4 版本成功逼平了 Microchess(见下文的 Huo Chess Games Archive 部分)。我还没有时间让 Huo Chess v0.992 再与 Microchess 对弈,但总有一天我会的……在 Chess.com 上与各种机器人对弈时,新的 v0.992 版本表现良好,轻松击败了 1000 ELO 以下的级别。我计划进行更多对弈以确定引擎的 ELO 强度,一旦确定,我将发布更新。

如何自定义 - 优化程序

该程序具有**开局库能力**,这意味着任何人都可以通过在相应的文件夹 Huo Chess Opening Book(应与可执行文件位于同一目录)中添加自己的开局走法数据,并使用 Huo Opening Book Editor 来优化程序。

您还可以通过添加新的 `AnalyzeMove` 函数(例如,每个新思考深度层的 Analyze_Move_2_ComputerMove、Analyze_Move_4_ComputerMove 等)并对所分析走法应用的**过滤器**进行必要的调整来增加**思考深度**能力(这些对于提高程序速度至关重要 – 请参阅 `FindAttackers / FindDefenders`)。

您还可以通过更改 `CountScore` 函数以及计算机(或其人类对手)**评估棋子**或**棋盘位置**的方式来优化 Huo Chess 的思考方式。例如,如果您在 `CountScore` 函数中将皇后棋子的分数从 `9` 改为 `12`,那么 HY 将积极攻击对方的皇后,同时更努力地保护自己的皇后。您还可以 — 例如 — 为车控制的开放直线位置的存在给予高分,以使计算机更多地使用其车并尝试通过它们占据直线。

任何关于开局库或 `CountScore` 函数(实际上是系统的核心!)的更好配置的**反馈**都是**受欢迎的**!

v0.992 版本的当前限制和改进设想

当前版本(v0.992)设置为 5 个半步的思考深度(代码中对应 Thinking_Depth = 4;请记住,思考从第0步开始)。这是因为完整的 MiniMax 算法非常占用内存且耗时。您可以将思考深度设置为 2(Thinking_Depth 变量需要是偶数,因为计算机的分析设计为在计算机回合“结束”),使计算机思考 3 个半步深度,然后程序将运行得更快。

CountScore 函数在某种程度上也受到限制。这主要是因为我试图将控制台版本的代码保持在 50 KB 以下,图形版本的代码保持在 100 KB 以下。我将尝试优化这一点,因为这是思考机制的核心,以及树枝剪枝(这也是需要优化的一个方面)。

内存是个问题。目前程序在思考时消耗太多内存,我真的需要在后续版本中解决这个问题。需要以某种方式平衡小尺寸(这是硬性要求,也是项目本身的目标)与改善内存使用的需求。

后续版本

下一个版本的 Huo Chess 已经有一个潜在改进列表,可以在下面找到。

  • 通过改进“外围”函数(如 CheckForBlackMate、ElegxosNomimotitas 等)来减小尺寸。
  • 通过考虑每个分支的平均分数来提高 MiniMax 算法的效率。(代码已就位,但被注释掉了)。
  • 添加神经网络能力:这将在非常初级的水平上实现,即让 MiniMax 算法的关键点(计算机走法分析中每个节点级别的不等式)依赖于外部配置文件中存储的值,并在每次游戏结束后更新这些文件(如果游戏获胜则增加“分数”,如果游戏失败则减少)。这仍处于初步阶段,但我将尝试在下一个版本中准备一个相关的开发者版本(同时不影响尺寸)。
  • 改进 CountScore 函数,以考虑棋盘位置、在开局阶段是否多次移动同一棋子等。

一个**Java 版的 Huo Chess** 也在开发中。你可以在 Harmonia Philosophica 门户网站上找到 Huo Chess v0.990 的 Java 版本。这将是该应用程序 Android 移植版的基础。

I. 国际象棋算法分析 (v0.971 - 0.992 版本)

该程序实现了 MiniMax 算法。Huo Chess 考虑物质上的得失,同时其代码中也融入了一些阵地策略的元素。更详细地说:当程序开始思考时,它会扫描棋盘以查找其棋子的位置(请参阅 ComputerMove 函数),然后尝试所有可能的走法。它分析这些走法直到我定义的思考深度(通过 ComputerMove -> Analyze_Move_1_HumanMove -> Analyze_Move_2_ComputerMove 路径),衡量所有可能的走法变体最终达到的位置的分数(请参阅 CountScore 函数),最后选择能够导向最有希望(从分数角度看)位置的走法(ComputerMove 函数)。

C# v0.990 Kakos-Minimax 算法摘要

进展的**高级摘要**如下:

  1. ComputerMove:扫描棋盘。检查危险的格子。进行所有可能的走法。(排除基于特定标准的“愚蠢”走法)。
  2. CheckMove:存储走法的初始值并进行一些附加检查(例如,检查是否将军)。
  3. 如果未达到思考深度 => 调用 Analyze_Move_1_HumanMove
  4. Analyze_Move_1_HumanMove:检查人类对手的所有可能回应。
  5. 如果未达到思考深度 => 调用 Analyze_Move_2_ComputerMove
  6. Analyze_Move_2_ComputerMove:扫描棋盘并在下一个思考级别进行所有可能的计算机走法。
  7. 如果达到思考深度 => 记录 MiniMax 算法节点中最终位置的分数。
  8. 算法继续进行,直到所有可能的走法都被扫描。
  9. 最后使用 MiniMax 算法通过分析思考树节点来计算最佳走法。

您可以**取消注释日志写入代码**(请参阅 #region WriteLog、#region NodesLogBEFORE 和 #region NodesLogAFTER),以在计算机思考过程中查看其进展,这些进展将体现在生成思考过程结果的相应 .txt 文件中。

v0.990 的完整文件包中还分发了一个非常有用的**记录 Huo Chess 思考过程的 Word 文档**(见本文顶部)。

国际象棋引擎逻辑 (v0.990)

v0.990 工作方式的更详细摘要如下:

如果人类先走 => EnterMove

(否则,ComputerMove 直接从 Main_Console() 调用)

对于人类对手输入的走法...

  • 检查走法的合法性。

  • 检查是否被将死。

  • 检查是否有将军。

  • 存储走法的坐标。

  • 存储人类移动棋子的值。

  • 存储该棋子移动到的坐标 [Human_last_move_target_rank/column]。

ComputerMove [Move_Analyzed = 0]

#region InitializeNodes       //Initialize all nodes

#region StoreInitialPosition  //Store initial position

#region OpeningBookCheck      //OPENING BOOK CHECK

#region DangerousSquares      //CHECK FOR DANGEROUS SQUARES

// Initialize variables

对于每种可能的走法

{

// 检查走法是否愚蠢

#region CheckStupidMove

If Move < 5 and you move the knight to the edges => Stupid move etc…

+ Store moving piece’s value

#endregion CheckStupidMove

如果不是愚蠢的 & 目标格子不危险 => ...

if ((ThisIsStupidMove.CompareTo("N") == 0) && (Skakiera_Dangerous_Squares[(m_FinishingColumnNumber - 1), (m_FinishingRank - 1)] == 0))...

调用 CheckMove。

CheckMove(Skakiera_Thinking, m_StartingRank, m_StartingColumnNumber, m_FinishingRank, m_FinishingColumnNumber, MovingPiece);

<Call CheckMove(Skakiera_Thinking)> 来

  • 检查走法的合法性(Gr. Nomimotita)和有效性(Gr. Orthotita)。
  • 检查是否被将死(未完成!必须添加!)。
  • 检查是否有将军。

// 如果是在第一次分析的走法:将走法存储在 ***_HY 变量中(以便保留初始走法)。

if (((m_OrthotitaKinisis == true) && (m_NomimotitaKinisis == true)) && (Move_Analyzed == 0)) => ...

如果走法有效...

if ((m_OrthotitaKinisis == true) && (m_NomimotitaKinisis == true))

// 执行走法。

ProsorinoKommati = Skakiera_Thinking[(m_FinishingColumnNumber - 1), (m_FinishingRank - 1)];

Skakiera_Thinking[(m_StartingColumnNumber - 1), (m_StartingRank - 1)] = "";

Skakiera_Thinking[(m_FinishingColumnNumber - 1), (m_FinishingRank - 1)] = MovingPiece;

检查计算机走法后的得分。

if (Move_Analyzed == 0) then...

    NodeLevel_0_count++;

    Temp_Score_Move_0 = CountScore(Skakiera_Thinking, humanDangerParameter);

if (Move_Analyzed == 2) then...

    NodeLevel_2_count++;

    Temp_Score_Move_2 = CountScore(Skakiera_Thinking, humanDangerParameter);

// Store the best move score at this level

if ((m_PlayerColor.CompareTo("Black") == 0) && (Temp_Score_Move_0 > bestScoreLevel0))

{

              bestScoreLevel0 = Temp_Score_Move_0;

}

// 检查你是否可以吃掉人类移动的棋子!

if ((m_FinishingColumnNumber == Human_last_move_target_column)

     && (m_FinishingRank == Human_last_move_target_row)

     && (ValueOfMovingPiece <= ValueOfHumanMovingPiece))

{

    Best_Move_StartingColumnNumber = m_StartingColumnNumber;

    Best_Move_StartingRank = m_StartingRank;

    Best_Move_FinishingColumnNumber = m_FinishingColumnNumber;

    Best_Move_FinishingRank = m_FinishingRank;

    possibility_to_eat_back = true;

}

检查吃子的可能性。

If there is a possibility to eat a piece of the opponent, then there is no need to analyze any further...

如果思考深度未达到,调用下一级别的分析。

if ((Move_Analyzed < Thinking_Depth) && (possibility_to_eat_back == false) && (possibility_to_eat == false))

{

    Move_Analyzed = Move_Analyzed + 1;

    for (i = 0; i <= 7; i++)

    {

        for (j = 0; j <= 7; j++)

        {

            Skakiera_Move_After[(i), (j)] = Skakiera_Thinking[(i), (j)];

        }

    }

    Who_Is_Analyzed = "Human";

     Analyze_Move_1_HumanMove(Skakiera_Move_After);

}

// 撤销走法。

Skakiera_Thinking[(m_StartingColumnNumber0 - 1), (m_StartingRank0 - 1)] = MovingPiece0;

Skakiera_Thinking[(m_FinishingColumnNumber0 - 1), (m_FinishingRank0 - 1)] = ProsorinoKommati0;

}

...

}

Analyze_Move_1_HumanMove [Move_Analyzed = 1]

分析所有可能的走法。

{

// 如果走法有效且合法...

if ((m_OrthotitaKinisis == true) && (m_NomimotitaKinisis == true)) then...

// Do the move

ProsorinoKommati = Skakiera_Human_Thinking_2[(m_FinishingColumnNumber - 1), (m_FinishingRank - 1)];

Skakiera_Human_Thinking_2[(m_StartingColumnNumber - 1), (m_StartingRank - 1)] = "";

Skakiera_Human_Thinking_2[(m_FinishingColumnNumber - 1), (m_FinishingRank - 1)] = MovingPiece;

// 衡量走法后的得分。

NodeLevel_1_count++;

Temp_Score_Move_1_human = CountScore(Skakiera_Human_Thinking_2, humanDangerParameter);

// 在此级别存储最佳走法。

if ((m_PlayerColor.CompareTo("Black") == 0) && (Temp_Score_Move_1_human < bestScoreLevel1))

{

    bestScoreLevel1 = Temp_Score_Move_1_human;

}

如果思考深度未达到,调用下一级别的思考函数。

if (Move_Analyzed < Thinking_Depth) && (Temp_Score_Move_1_human better than bestScoreLevel1)

{

    Move_Analyzed = Move_Analyzed + 1;

    Who_Is_Analyzed = "HY";

    if (Move_Analyzed == 2)

        Analyze_Move_2_ComputerMove(Skakiera_Move_After);

    else if (Move_Analyzed == 4)

        Analyze_Move_4_ComputerMove(Skakiera_Move_After);

    else if (Move_Analyzed == 6)

        Analyze_Move_6_ComputerMove(Skakiera_Move_After);

}

// 撤销走法。

Skakiera_Human_Thinking_2[(m_StartingColumnNumber1 - 1), (m_StartingRank1 - 1)] = MovingPiece1;

Skakiera_Human_Thinking_2[(m_FinishingColumnNumber1 - 1), (m_FinishingRank1 - 1)] = ProsorinoKommati1;

}

// 返回到之前的分析深度。

Move_Analyzed = Move_Analyzed - 1;

Who_Is_Analyzed = "HY";

Analyze_Move_2_ComputerMove [Move_Analyzed = 2]

检查所有可能的走法。

{

// 如果走法有效且合法...

if ((m_OrthotitaKinisis == true) && (m_NomimotitaKinisis == true))

{

// 执行走法。

ProsorinoKommati = Skakiera_Thinking_HY_2[(m_FinishingColumnNumber - 1), (m_FinishingRank - 1)];

Skakiera_Thinking_HY_2[(m_StartingColumnNumber - 1), (m_StartingRank - 1)] = "";

Skakiera_Thinking_HY_2[(m_FinishingColumnNumber - 1), (m_FinishingRank - 1)] = MovingPiece;

// 检查计算机走法后的得分。

NodeLevel_2_count++;

Temp_Score_Move_2 = CountScore(Skakiera_Thinking_HY_2, humanDangerParameter);

// 存储此级别的最佳得分。

if ((m_PlayerColor.CompareTo("Black") == 0) && (Temp_Score_Move_2 > bestScoreLevel2))

{

              bestScoreLevel2 = Temp_Score_Move_2;

}

如果思考深度未达到,调用下一级别的分析...

if (Move_Analyzed < Thinking_Depth)

{

    Move_Analyzed = Move_Analyzed + 1;

    Who_Is_Analyzed = "Human";

    // Check human move

    if (Move_Analyzed == 1)

        Analyze_Move_1_HumanMove(Skakiera_Move_After);

    else if (Move_Analyzed == 3)

        Analyze_Move_3_HumanMove(Skakiera_Move_After);

    else if (Move_Analyzed == 5)

        Analyze_Move_5_HumanMove(Skakiera_Move_After);

}

// 如果已达到定义的分析深度...

if (Move_Analyzed == Thinking_Depth)   

{

// [MiniMax algorithm - skakos]

// Record the node in the Nodes Analysis array (to use with MiniMax algorithm) skakos

NodesAnalysis0[NodeLevel_0_count, 0] = Temp_Score_Move_0;

NodesAnalysis1[NodeLevel_1_count, 0] = Temp_Score_Move_1_human;

NodesAnalysis2[NodeLevel_2_count, 0] = Temp_Score_Move_2;

NodesAnalysis3[NodeLevel_3_count, 0] = Temp_Score_Move_3_human;

// Store the parents (number of the node of the upper level)

NodesAnalysis0[NodeLevel_0_count, 1] = 0;

NodesAnalysis1[NodeLevel_1_count, 1] = NodeLevel_0_count;

NodesAnalysis2[NodeLevel_2_count, 1] = NodeLevel_1_count;

NodesAnalysis3[NodeLevel_3_count, 1] = NodeLevel_2_count;

}

注意:由于分析仅在 ComputerMove 函数中结束,因此 ThinkigDepth 必须是偶数!

// 撤销走法。

Skakiera_Thinking_HY_2[(m_StartingColumnNumber2 - 1), (m_StartingRank2 - 1)] = MovingPiece2;

Skakiera_Thinking_HY_2[(m_FinishingColumnNumber2 - 1), (m_FinishingRank2 - 1)] = ProsorinoKommati2;

}

Move_Analyzed = Move_Analyzed - 1;

Who_Is_Analyzed = "Human";

ComputerMove [Continued... - Analysis End]

检查是否被将死。

// 查找最佳走法:使用 MiniMax 算法(仅当没有机会吃掉对手棋子时)。

if (possibility_to_eat_back == false)

{

    // [MiniMax algorithm - skakos]

    // Find node 1 move with the best score via the MiniMax algorithm.

    int counter0, counter1, counter2;

    // ------------------------------------------------------

    // NodesAnalysis

    // ------------------------------------------------------

    // Nodes structure...

    // [ccc, xxx, 0]: Score of node No. ccc at level xxx

    // [ccc, xxx, 1]: Parent of node No. ccc at level xxx-1

    // ------------------------------------------------------

    int parentNodeAnalyzed = -999;

    //v0.980: Remove

    //parentNodeAnalyzed = -999;

    for (counter2 = 1; counter2 <= NodeLevel_2_count; counter2++)

    {

        if (Int32.Parse(NodesAnalysis2[counter2, 1].ToString()) != parentNodeAnalyzed)

        {

            //parentNodeAnalyzedchanged = true;

            parentNodeAnalyzed = Int32.Parse(NodesAnalysis2[counter2, 1].ToString());

            NodesAnalysis1[Int32.Parse(NodesAnalysis2[counter2, 1].ToString()), 0] = NodesAnalysis2[counter2, 0];

        }

        if (NodesAnalysis2[counter2, 0] >= NodesAnalysis1[Int32.Parse(NodesAnalysis2[counter2, 1].ToString()), 0])

            NodesAnalysis1[Int32.Parse(NodesAnalysis2[counter2, 1].ToString()), 0] = NodesAnalysis2[counter2, 0];

    }

    // Now the Node1 level is filled with the score data

    parentNodeAnalyzed = -999;

    for (counter1 = 1; counter1 <= NodeLevel_1_count; counter1++)

    {

        if (Int32.Parse(NodesAnalysis1[counter1, 1].ToString()) != parentNodeAnalyzed)

        {

            //parentNodeAnalyzedchanged = true;

            parentNodeAnalyzed = Int32.Parse(NodesAnalysis1[counter1, 1].ToString());

            NodesAnalysis0[Int32.Parse(NodesAnalysis1[counter1, 1].ToString()), 0] = NodesAnalysis1[counter1, 0];

        }

        if (NodesAnalysis1[counter1, 0] <= NodesAnalysis0[Int32.Parse(NodesAnalysis1[counter1, 1].ToString()), 0])

            NodesAnalysis0[Int32.Parse(NodesAnalysis1[counter1, 1].ToString()), 0] = NodesAnalysis1[counter1, 0];

    }

    // Choose the biggest score at the Node0 level

    // Check example at http://en.wikipedia.org/wiki/Minimax#Example_2

    // Initialize the score with the first score and move found

    double temp_score = NodesAnalysis0[1, 0];

    Best_Move_StartingColumnNumber = Int32.Parse(NodesAnalysis0[1, 2].ToString());

    Best_Move_StartingRank = Int32.Parse(NodesAnalysis0[1, 4].ToString());

    Best_Move_FinishingColumnNumber = Int32.Parse(NodesAnalysis0[1, 3].ToString());

    Best_Move_FinishingRank = Int32.Parse(NodesAnalysis0[1, 5].ToString());

    for (counter0 = 1; counter0 <= NodeLevel_0_count; counter0++)

    {

        if (NodesAnalysis0[counter0, 0] > temp_score)

        {

            temp_score = NodesAnalysis0[counter0, 0];

            Best_Move_StartingColumnNumber = Int32.Parse(NodesAnalysis0[counter0, 2].ToString());

            Best_Move_StartingRank = Int32.Parse(NodesAnalysis0[counter0, 4].ToString());

            Best_Move_FinishingColumnNumber = Int32.Parse(NodesAnalysis0[counter0, 3].ToString());

            Best_Move_FinishingRank = Int32.Parse(NodesAnalysis0[counter0, 5].ToString());

        }

    }

}

执行走法。

重绘棋盘。

如果未找到走法 => 认输。

Huo Chess 0.94 启用 huo_DEBUG:您可以查看计算机的思考过程并进行优化。

II. Huo Chess 思考流程 [Minimax 算法] (v0.971, 0.980 版本)

下面,我将分析国际象棋程序的思考流程。我将只描述主要步骤和代码段,以展示计算机扫描棋盘、进行所有可能的走法并最终找到最佳走法的方式。函数名称以粗体显示,例如 **ComputerMove** - Start 表示 `ComputerMove()` 函数的开始。请注意,某些代码段可能与分发的 ZIP 文件中的代码略有不同,因为我一直在不断修改程序。正如现在所见,“持续 beta”状态是当今的趋势。

ComputerMove (开始)

初始化节点。

存储初始位置。

检查开局库。

检查危险格子。

分析所有可能的走法。

for (...)
{

MovingPiece = Skakiera_Thinking[(iii), (jjj)];

m_StartingColumnNumber = iii + 1;
m_FinishingColumnNumber = w + 1;
m_StartingRank = jjj + 1;
m_FinishingRank = r + 1;

// Store temporary move data in local variables, so as to use them in the Undo of the move at the end of this function (MovingPiece, m_StartingColumnNumber, etc variables are changed by next functions as well, so using them leads to problems)

MovingPiece0             = MovingPiece;
m_StartingColumnNumber0  = m_StartingColumnNumber;
m_FinishingColumnNumber0 = m_FinishingColumnNumber;
m_StartingRank0          = m_StartingRank;
m_FinishingRank0         = m_FinishingRank;
ProsorinoKommati0        = Skakiera_Thinking[(m_Finishi…)];

} (check all possible moves)

调用 CheckMove(Skakiera_Thinking) 进行一些检查。

CheckMove

  • 存储走法的初始值。
  • 检查是否被将死。
  • 检查将军。
  • 检查走法的合法性和有效性(仅适用于 ComputerMove!)。

Computer move (从 CheckMove 返回)

对于每种可能的走法

  • 计算得分。

如果正在分析的走法是正确且合法的,则执行它并测量其得分。

if ((m_OrthotitaKinisis == true) && (m_NomimotitaKinisis == true))

{

DO THE MOVE

// Measure score AFTER the move

if (Move_Analyzed == 0)
{
NodeLevel_0_count++;
Temp_Score_Move_0 = CountScore(Skakie…);
}

if (Move_Analyzed == 2)
{
NodeLevel_2_count++;
Temp_Score_Move_2 = CountScore(Skakie…);
}

if (Move_Analyzed == 4)
{
NodeLevel_4_count++;
Temp_Score_Move_4 = CountScore(Skakie…);
}

if (Move_Analyzed == 6)
{
NodeLevel_6_count++;
Temp_Score_Move_6 = CountScore(Skakie…);
}

如果您尚未达到思考深度,则调用下一级别分析函数……

  • 增加节点计数。
  • 存储该节点的分数。
  • 调用下一个函数以进行更深层次的搜索……
if (Move_Analyzed < Thinking_Depth)

{
Move_Analyzed = Move_Analyzed + 1;

// Check human move (to find the best possible answer of the human
// to the move currently analyzed by the HY Thought process)

Who_Is_Analyzed = "Human";

if (Move_Analyzed == 1)
    Analyze_Move_1_HumanMove(Skakiera_Move_After);
else if (Move_Analyzed == 3)
    Analyze_Move_3_HumanMove(Skakiera_Move_After);
else if (Move_Analyzed == 5)
    Analyze_Move_5_HumanMove(Skakiera_Move_After);
}

Analyze_Move_1_HumanMove (从 ComputerMove 调用)

for ... (check all possible moves)

{

// Store temporary move data in local variables, so as to use them in the Undo of the move at the end of this function (the MovingPiece, m_StartingColumnber, etc variables are changed by next functions as well, so using them leads to problems)

MovingPiece1             = MovingPiece;
m_StartingColumnNumber1  = m_StartingColumnNumber;
m_FinishingColumnNumber1 = m_FinishingColumnNumber;
m_StartingRank1          = m_StartingRank;
m_FinishingRank1         = m_FinishingRank;
ProsorinoKommati1        = Skakiera_Human_Thinking_2[(m_Fi...)];

如果走法合法且有效……

if ((m_OrthotitaKinisis == true) && (m_NomimotitaKinisis == true))

执行走法。

测量走法**后**的得分。

if (Move_Analyzed == 1)
{
    NodeLevel_1_count++;
    Temp_Score_Move_1_human = CountScore(Skakiera_Human_Thinking_2, humanDangerParameter);
}

if (Move_Analyzed == 3)
{
    NodeLevel_3_count++;
    Temp_Score_Move_3_human = CountScore(Skakiera_Human_Thinking_2, humanDangerParameter);
}

if (Move_Analyzed == 5)
{
    NodeLevel_5_count++;
    Temp_Score_Move_5_human = CountScore(Skakiera_Human_Thinking_2, humanDangerParameter);
}

如果思考深度未达到,则调用下一级别思考函数……

if (Move_Analyzed < Thinking_Depth)
{
    // Call ComputerMove for the HY throught process to continue

    Move_Analyzed = Move_Analyzed + 1;
    Who_Is_Analyzed = "HY";

    for (i = 0; i <= 7; i++)
    {
        for (j = 0; j <= 7; j++)
        {
            Skakiera_Move_After[(i), (j)] = Skakiera_Human_Thinking_2[(i), (j)];
        }
    }


    if (Move_Analyzed == 2)
        Analyze_Move_2_ComputerMove(Skakiera_Move_After);
    else if (Move_Analyzed == 4)
        Analyze_Move_4_ComputerMove(Skakiera_Move_After);
    else if (Move_Analyzed == 6)
        Analyze_Move_6_ComputerMove(Skakiera_Move_After);

}

Analyze_Move_2_ComputerMove (从 Analyze_Move_1_HumanMove 调用)

分析所有可能的走法。

for (...)

{

 MovingPiece = Skakiera_Thinking_template[(iii), (jjj)];

 m_StartingColumnNumber = iii + 1;

 m_FinishingColumnNumber = w + 1;

 m_StartingRank = jjj + 1;

 m_FinishingRank = r + 1;

}

检查走法。

  • 是否合法?
  • 是否有效?

如果走法有效且合法……

if ((m_OrthotitaKinisis == true) && (m_NomimotitaKinisis == true))

执行走法。

检查计算机走法**后**的得分。

if (Move_Analyzed == 0)
{
    NodeLevel_0_count++;
    Temp_Score_Move_0 = CountScore(Skakiera_Thinking_HY_2, humanDangerParameter);
}

if (Move_Analyzed == 2)
{
    NodeLevel_2_count++;
    Temp_Score_Move_2 = CountScore(Skakiera_Thinking_HY_2, humanDangerParameter);
}

if (Move_Analyzed == 4)
{
    NodeLevel_4_count++;
    Temp_Score_Move_4 = CountScore(Skakiera_Thinking_HY_2, humanDangerParameter);
}

if (Move_Analyzed == 6)
{
    NodeLevel_6_count++;
    Temp_Score_Move_6 = CountScore(Skakiera_Thinking_HY_2, humanDangerParameter);
}

如果已达到思考深度,则将节点记录在 Nodes Analysis 数组中(供 MiniMax 算法使用)。

if (Move_Analyzed == Thinking_Depth)                                           
{

// [MiniMax algorithm - skakos]
// Record the node in the Nodes Analysis array (to use with MiniMax algorithm) skakos

NodesAnalysis[NodeLevel_0_count, 0, 0] = Temp_Score_Move_0;
NodesAnalysis[NodeLevel_1_count, 1, 0] = Temp_Score_Move_1_human;
NodesAnalysis[NodeLevel_2_count, 2, 0] = Temp_Score_Move_2;
NodesAnalysis[NodeLevel_3_count, 3, 0] = Temp_Score_Move_3_human;
NodesAnalysis[NodeLevel_4_count, 4, 0] = Temp_Score_Move_4;
NodesAnalysis[NodeLevel_5_count, 5, 0] = Temp_Score_Move_5_human;
NodesAnalysis[NodeLevel_6_count, 6, 0] = Temp_Score_Move_6;

// Store the prnts (number of the node of the upper level)

NodesAnalysis[NodeLevel_0_count, 0, 1] = 0;
NodesAnalysis[NodeLevel_1_count, 1, 1] = NodeLevel_0_count;
NodesAnalysis[NodeLevel_2_count, 2, 1] = NodeLevel_1_count;
NodesAnalysis[NodeLevel_3_count, 3, 1] = NodeLevel_2_count;
NodesAnalysis[NodeLevel_4_count, 4, 1] = NodeLevel_3_count;
NodesAnalysis[NodeLevel_5_count, 5, 1] = NodeLevel_4_count;
NodesAnalysis[NodeLevel_6_count, 6, 1] = NodeLevel_5_count;

}

如果未达到思考深度,则调用下一级别,依此类推……

if (Move_Analyzed < Thinking_Depth)
{
    Move_Analyzed = Move_Analyzed + 1;

    for (i = 0; i <= 7; i++)
    {
        for (j = 0; j <= 7; j++)
        {
            Skakiera_Move_After[(i), (j)] = Skakiera_Thinking[(i), (j)];
        }
    }

    Who_Is_Analyzed = "Human";
    First_Call_Human_Thought = true;

    // Check human move
    if (Move_Analyzed == 1)
        Analyze_Move_1_HumanMove(Skakiera_Move_After);
    else if (Move_Analyzed == 3)
        Analyze_Move_3_HumanMove(Skakiera_Move_After);
    else if (Move_Analyzed == 5)
        Analyze_Move_5_HumanMove(Skakiera_Move_After);
}

撤销走法。

返回到上一级别……

Move_Analyzed = Move_Analyzed - 1;

Who_Is_Analyzed = "Human";

Analyze_Move_1_HumanMove (从 Analyze_Move_2_ComputerMove 返回)

撤销走法。

返回到上一级别……

Move_Analyzed = Move_Analyzed - 1;

Who_Is_Analyzed = "HY";

ComputerMove (从 Analyze_Move_1_HumanMove 返回)

撤销走法。

Skakiera_Thinking[(m_StartingColumnNumber0 - 1), (m_StartingRank0 - 1)] = MovingPiece0;
Skakiera_Thinking[(m_FinishingColumnNumber0 - 1), (m_FinishingRank0 - 1)] = ProsorinoKommati0;

} (end of for loop)

查找是否存在将死。

执行找到的**最佳走法**!

[MiniMax 算法 – skakos - 开始]

有关该算法如何工作的,请参阅 MiniMax 算法:http://en.wikipedia.org/wiki/Minimax

// -----------------------------------------------------
// NodesAnalysis
// -----------------------------------------------------
// Nodes structure...
// [ccc, xxx, 0]: Score of node No. ccc at level xxx
// [ccc, xxx, 1]: Parent of node No. ccc at level xxx-1
// -----------------------------------------------------


prntNodeAnalyzed = -999;


for (counter6 = 1; counter6 <= NodeLevel_6_count; counter6++)
{

if (Int32.Parse(NodesAnalysis[counter6, 6, 1].ToString()) != prntNodeAnalyzed)
{
    prntNodeAnalyzed = Int32.Parse(NodesAnalysis[counter6, 6, 1].ToString());
    NodesAnalysis[Int32.Parse(NodesAnalysis[counter6, 6, 1].ToString()), 5, 0] = NodesAnalysis[counter6, 6, 0];

}

if (NodesAnalysis[counter6, 6, 0] <= NodesAnalysis[Int32.Parse(NodesAnalysis[counter6, 6, 1].ToString()), 5, 0])

    NodesAnalysis[Int32.Parse(NodesAnalysis[counter6, 6, 1].ToString()), 5, 0] = NodesAnalysis[counter6, 6, 0];

}


prntNodeAnalyzed = -999;


for (counter5 = 1; counter5 <= NodeLevel_5_count; counter5++)
{

if (Int32.Parse(NodesAnalysis[counter5, 5, 1].ToString()) != prntNodeAnalyzed)
{
    prtNodeAnalyzed = Int32.Parse(NodesAnalysis[counter5, 5, 1].ToString());
    NodesAnalysis[Int32.Parse(NodesAnalysis[counter5, 5, 1].ToString()), 4, 0] = NodesAnalysis[counter5, 5, 0];

}

if (NodesAnalysis[counter5, 5, 0] >= NodesAnalysis[Int32.Parse(NodesAnalysis[counter5, 5, 1].ToString()), 4, 0])
    NodesAnalysis[Int32.Parse(NodesAnalysis[counter5, 5, 1].ToString()), 4, 0] = NodesAnalysis[counter5, 5, 0];

}

...

[MiniMax 算法 – skakos - 结束]

重绘棋盘。

// now it is the other color's turn to play

if (m_PlayerColor.CompareTo("Black") == 0)

    m_WhichColorPlays = "Black";

else if (m_PlayerColor.CompareTo("White") == 0)

    m_WhichColorPlays = "White";


// now it is the human's turn to play

m_WhoPlays = "Human";

[CountScore]

每一步走法的得分都会被测量(如果走法合法且正确)。这些得分存储在 `NodesAnalysis` 数组中(见下文)。评分函数是程序的核心。它目前主要考虑物质价值,并在游戏开局阶段加入了一些阵地考虑(例如,如果 `Move < 11`,则不宜移动皇后,否则会施加一个小小的“评分惩罚”)。优化该函数是提高计算机棋力水平的关键。

应用 MiniMax 算法

当达到思考深度时(即,当到达我们定义为分析链中最后一个函数的 `ComputerMove` 函数时),我们会为每个思考深度级别存储思考树节点的棋盘得分(适用于0.93及以上版本)。

这些节点将用于 MiniMax 算法来找到最佳走法。

// Record the node in the Nodes Analysis array (to use with MiniMax algorithm) skakos
NodesAnalysis[NodeLevel_1_count, 1, 0] = Temp_Score_Human_before_2;
NodesAnalysis[NodeLevel_2_count, 2, 0] = Temp_Score_Human_after_2;
NodesAnalysis[NodeLevel_3_count, 3, 0] = Temp_Score_Human_before_4;
NodesAnalysis[NodeLevel_4_count, 4, 0] = Temp_Score_Human_after_4;
NodesAnalysis[NodeLevel_5_count, 5, 0] = Temp_Score_Human_before_6;
NodesAnalysis[NodeLevel_6_count, 6, 0] = Temp_Score_Human_after_6;

对于每个节点,我们还存储父节点的编号。

// Store the parents (number of the node of the upper level)
NodesAnalysis[NodeLevel_1_count, 1, 1] = 0;
NodesAnalysis[NodeLevel_2_count, 2, 1] = NodeLevel_1_count;
NodesAnalysis[NodeLevel_3_count, 3, 1] = NodeLevel_2_count;
NodesAnalysis[NodeLevel_4_count, 4, 1] = NodeLevel_3_count;
NodesAnalysis[NodeLevel_5_count, 5, 1] = NodeLevel_4_count;

这对于 MiniMax 算法的实现是必需的(请参阅 http://en.wikipedia.org/wiki/Minimax 了解该算法的工作原理):假设正在进行的游戏每个回合最多只有两个可能的走法。该算法生成所有走法和对这些走法的可能回应的树。

算法使用 `CountScore` 评估函数评估每个**叶节点**,得到所示值。使**最大化玩家**获胜的走法被赋予正无穷大,而导致**最小化玩家**获胜的走法被赋予负无穷大(这仅为说明目的 – 在当前开发的棋局中不会出现无穷大)。在第3级,算法将为每个节点选择**子节点**值中的**最小值**,并将其分配给该节点本身(例如,左侧的节点将选择“10”和“+8”之间的最小值,从而将值“10”分配给自己)。下一步,在第2级,包括为每个节点选择**子节点**值中的**最大值**。再次,值被分配给每个**父节点**。算法继续交替评估子节点的最大值和最小值,直到到达**根节点**,在那里它选择具有最大值的走法(如图中用蓝色箭头表示)。这是玩家应该采取的走法,以**最小化****最大**可能的损失。

为了让程序计算出最佳走法,应用了一系列“`for` 循环”,以便能够进行上述反向计算。

        for (counter7 = 1; counter7 <= NodeLevel_7_count; counter7++)
        {
            for (counter8 = 1; counter8 <= NodeLevel_8_count; counter8++)
            {
                if (NodesAnalysis[counter8, 8, 1] == counter7)
                {
                    if (counter8 == 1)
                        NodesAnalysis[counter7, 7, 0] = NodesAnalysis[counter8, 8, 0];
 
                    if (counter8 > 1)
                        if (NodesAnalysis[counter8, 8, 0] < NodesAnalysis[counter7, 7, 0])
                            NodesAnalysis[counter7, 7, 0] = NodesAnalysis[counter8, 8, 0];
                }
            }
        }

算法到达根节点后,选择得分最高的走法。

ComputerMove[最大思考深度] – 结束

III. Huo Chess 思考流程 (v0.95 Simple-Minimax)

ComputerMove()
{
 
DangerousSquares
MoveFilter
 
for all possible moves
{
    Temporarily make all possible moves
    Count the score of these moves
    Call ComputerMove2 (with the temporary chessboard as input)
 
    // Second level of thought
    ComputerMove2()
    {
         ...
         ComputerMove5()
         {
         Record the moves and their scores in the Nodes Analysis array
         }
    }
}
 
Do the best move (MiniMax);
}

IV. Huo Chess 思考流程 (v0.93 Kakos-Minimax 或所有 v0.84 及更早版本)

在本节中,我将分析 v0.93-Kakos-Minimax 版本或 v0.84 及更早版本的思考算法。(它们之间的主要区别在于找到所有可能走法分析完成后最佳走法的方法)。下面,我将以思考深度为 2 的情况为例,说明计算机思考的逐步过程。让我们看看“步骤”框,以理解程序的结构。

场景详情

  • 计算机级别:Maitre (ThinkingDepth = 2)

ComputerMove - 开始

步骤 1

开始

Move_Analyzed = 0

1.       If the first time called -> store initial chessboard position.
2.       if( Move_Analyzed >Thinking_Depth )
3.       Stop_Analyzing = true;
4.       if( Stop_Analyzing = false)
5.       Scan chessboard.
         for iii, jjj
6.       Scan chessboard, find a piece of the HY , conduct move, 
         check correctness and legality of move,
         and if all is OK, then call CheckMove to measure the score of the move.

调用: CheckMove

CheckMove - 开始

  1. 已分析的走法数 ++。
  2. 检查走法的正确性和合法性。
  3. 检查棋盘上是否有将死。
  4. 如果走法正确且合法,则执行它。
  5. 检查是否有兵可以升变。
  6. 将走法存储在 ***_HY 变量中,因为在多次调用 `ComputerMove` 和 `CheckMove` 函数后,已分析走法的初始值将会丢失。
  7. 如果这是分析的第一个走法,则将其记录为正确的“最佳”走法,无论它有多糟糕。

第二步

IF result: FALSE
Move_Analyzed = 0
  1. if(Move_Analyzed = Thinking_Depth)
  2. 测量走法的得分,并将其记录为“最佳”走法,如果它的得分大于到目前为止的最佳走法得分。

步骤 3

IF result: TRUE
Move_Analyzed = 1
  1. if (Move_Analyzed < Thinking_Depth)

HumanMove - 开始 [v0.93 的 HumanMove_Template]

步骤 4

找出**人类的最佳回应**(Move_Analyzed = 1)。

版本 0.93:找出**所有可能的人类走法**。

  • 扫描棋盘 -> 找到任何可能的走法。
  • 调用 CheckHumanMove。[v0.93 中冗余]。

存储人类走法**之前**和**之后**的棋盘得分。

CheckHumanMove - 开始

voidCheckHumanMove(array<String^, 2>^ CMSkakiera_Human_Thinking)

计算走法的得分,并将其记录为“最佳”走法,如果它的得分优于到目前为止的最佳走法。
在 v0.93 及更高版本中:记录人类对手走棋**之前**和**之后**的得分。
这些得分将记录在 Nodes Analysis 数组中,最终用于 MiniMax 算法(以评估最佳走法)。

CheckHumanMove - 结束

执行**人类的最佳走法**[在 v0.93 中执行**所有可能的人类走法**]。

Move_Analyzed = Move_Analyzed + 1;
Who_Is_Analyzed = "HY";
 
for(i = 0; i <= 7;i++)
{
for(j = 0; j <= 7; j++)
{
Skakiera_Move_After[(i),(j)]=Skakiera_Human_Thinking[(i),(j)];
}

步骤 5

Move_Analyzed = 2

步骤 6

调用下一个 `ComputerMove` 函数进行下一级别走法的分析。

Move_Analyzed = 2
if(Move_Analyzed == 2)
this->ComputerMove2(Skakiera_Move_After);
elseif(Move_Analyzed == 4)
this->ComputerMove4(Skakiera_Move_After);
elseif(Move_Analyzed == 6)
this->ComputerMove6(Skakiera_Move_After);
elseif(Move_Analyzed == 8)
this->ComputerMove8(Skakiera_Move_After);
 
// Call ComputerMove2 to find the best next move of the HY (deeper thinking)

步骤 7

扫描棋盘,找出**计算机的最佳走法**。

Move_Analyzed = 2
voidComputerMove2(array<STRING^, />^ Skakiera_Thinking_2)
{
// Same as…ComputerMove
 
if(Move_Analyzed has not reached thinking depth)
{
// Same as…ComputerMove: Call CheckMove -> HumanMove -> next ComputerMove etc
// (If we haven't reached the desired level of analysis, then the HumanMove
// will be called again, then again the ComputerMove function etc.)
}

步骤 8

返回到之前的 `ComputerMove`(即 `ComputerMove4` 调用 `ComputerMove2`)函数以继续分析。

Move_Analyzed = 2 (在分析结束时,此变量将等于 0,请参阅步骤 9)。

// Return to the ComputerMove function of the 'previous' thinking
// level to continue the analysis
 
Move_Analyzed = Move_Analyzed - 2;
Who_Is_Analyzed = "HY";
 
for(i = 0; i <= 7; i++)
{
for(j = 0; j <= 7; j++)
{
Skakiera_Thinking[i,j] = Skakiera_Move_0[i,j];
}
}
}

ComputerMove2 - 结束

HumanMove - 结束

CheckMove - 结束

// close for iii, jjj loop
// close if( Stop_Analyzing = false ) segment

如果找不到合法走法 => 我们将被将死!

步骤 9

版本 0.93:应用 MiniMax 算法以得出最佳走法。

执行得分最高的走法。现在轮到**人类**走棋了。

if (move_analyzed==0){
  1. 检查是否适合易位。
  2. 用最佳走法“重绘”棋盘。
  3. 如果发生易位,则移动靠近王的相对应的车。
  4. 检查是否有兵被升变(在当前版本中,计算机总是将兵升变为皇后)。
  5. 现在轮到另一位玩家(人类)走棋了!
}
20. else
21. {
22. Move_Analyzed = Move_Analyzed - 2;
23. Who_Is_Analyzed = "HY";
24.
25. for(i = 0; i <= 7; i++)
26. {
27. for(j = 0; j <= 7;j++)
28. {
29. Skakiera_Thinking[i,j] = Skakiera_Move_0[i,j];
30. }
31. }
}

ComputerMove – 结束

v0.93 Kakos-Minimax 或 v0.84 及更早版本的思考流程总结

ComputerMove()
{
for
{
    // First level of thought
    CheckMove()
    {
    if (Move_Analyzed <Thinking_Depth)
    {
        Move_Analyzed++;
        // Find the best possible human move-answer and continue the thinking tree
        Find Best Human Move (HumanMove function) / Find all possible human moves (v0.93);
         Record chessboard scores before and after the human move;
        Move_Analyzed++;
        Go to next thinking depth
 
            ComputerMove2()
            {
                for
                {
                    CheckMove();
 
                    // Think deeper if you have not reached the thinking depth
                    if (Move_Analyzed <Thinking_Depth)
                        [Think deeper, if necessary!];
 
                    // Record move analyzed as Best Move, if it has the best score
                    if (Move_Analyzed = Thinking_Depth)
                        CountScore();
                        [Record if best move];
                }
 
            // Return to the initial level of thinking to start
            // analyzing a new thread
            Move_Analyzed = Move_Analyzed – 2;
    }
    }
}
 
Do the best move;
}

V. Huo Chess GUI 版本

Huo Chess 的图形用户界面(GUI)版本包含一个简单的棋盘,显示当前棋盘状态。它支持鼠标移动,因此您无需通过键盘输入走法。将来会有更高级的 GUI 版本。必须指出的是,GUI 对程序的总大小影响不大。Huo Chess C# 0.980 GUI 版本总大小为 78 KB。**未来计划**包括开发一个兼容 PGN 的程序,该程序可以利用互联网上任何可用的 Chess GUI。

VI. Huo Chess 游戏存档

本部分包含 Huo Chess 与其他微型国际象棋程序的对弈记录。

游戏 1

日期:2007-11-11
地点:希腊雅典
白方:HuoChess v0.4 (带开局库) [由 The Code Project 分发]
黑方:Microchess (由 BenLo Park 提供)
结果:三次重复和棋

1.       d4 Nc6
2.       d5 Nb4
3.       Nc3 e5
4.       Bg5 Qxg5
5.       Nh3 Qg4
6.       e4 Qh4
7.       Be2 d6
8.       Bb5+ Kd8
9.       Nf4 Qxf4
10.      h3 Nf6
11.      f3 Qe3+
12.      Be2 Bd7
13.      f4 Qg3+
14.      Kd2 Qxf4+
15.      Ke1 Qg3+
16.      Kd2 Qf4+
17.      Ke1 Qg3+
18.      Kd2 Qf4+
19.      Ke1 Qg3+
20.      Kd2 Qf4+
21.      Ke1 [draw by threefold repetition]

游戏 2

日期:2007-11-11
地点:希腊雅典
白方:Microchess (由 BenLo Park 提供)
黑方:HuoChess v0.4 (带开局库) [由 The Code Project 分发]
结果:三次重复和棋

1.       e4 e6
2.       Qh5 d6
3.       Bb5+ Ke7
4.       Qg5+ f6
5.       Qh5 h6
6.       d4 g6
7.       Qxg6 Nd7
8.       Bf4 f5
9.       exf5 Ndf6
10.      fxe6 Bxe6
11.      a4 Bg7
12.      Qxg7+ Bf7
13.      Qxh8 Bh5
14.      Qg7+ Bf7
15.      Nc3 h5
16.      Nf3 Nh7
17.      Nd5+ Ke6
18.      c4 c6
19.      Nc7+ Qxc7
20.      d5+ cxd5
21.      Nd4+ Ke7
22.      Nf5+ Ke6
23.      Nd4+ Ke7
24.      Nf5+ Ke6
25.      Nd4+ Ke7
26.      Nf5+ Ke6
27.      Nd4+ Ke7
28.      Nf5+ [draw by threefold repetition]

游戏 3

日期:2008-01-22
地点:希腊雅典
白方:Microchess (由 BenLo Park 提供)
黑方:HuoChess v0.5 (带开局库) [由 The Code Project 分发]
结果:三次重复和棋

1.       e2-e4 e7-e6
2.       d1-h5 c7-c5
3.       f1-b5 g8-f6
4.       h5-e5 f6-g4
5.       e5-f4 g4xf2
6.       e1xf2 g7-g5
7.       f4-e5 a7-a6
8.       b5-c4 f7-f6
9.       e5-g3 d7-d6
10.      g1-h3 h7-h6
11.      h1-e1 e8-e7
12.      b1-a3 d8-b6
13.      d2-d4 b6-a5
14.      c1-d2 a5xd2+
15.      e1-e2 d2xd4+
16.      16.f2-f3 d4xb2
17.      a1-d1 b2xa3+
18.      c2-c3 a3xc3+
19.      c4-d3 a6-a5
20.      f3-g4 e7-d7
21.      d3-b5+ d7-e7
22.      g3xc3 f8-g7
23.      e2-f2 e7-f7
24.      d1xd6 a8-a7
25.      c3xc5 b8-c6
26.      f2-f3 b7-b6
27.      c5xb6 c8-d7
28.      f3-c3 e6-e5+
29.      d6xd7+ a7xd7
30.      b5-c4+ f7-e7
31.      b6-c5+ d7-d6
32.      c3-d3 c6-d4
33.      c5-c7+ d6-d7
34.      c7-c5+ d7-d6
35.      c5-c7+ d6-d7
36.      c7-c5+ d7-d6 [draw by threefold repetition]

游戏 4

日期:2008-02-22
地点:希腊雅典
白方:Microchess (由 BenLo Park 提供)
黑方:HuoChess v0.6 (不带开局库) [由 The Code Project 分发]
结果:三次重复和棋

1.       Nf3 d5
2.       Nc3 Qd6
3.       Na4 Qf4
4.       c3 Bd7
5.       d4 Qe4
6.       Ng5 Qg4
7.       f3 Qh4+
8.       g3 Qh5
9.       Nc5 Bb5
10.      b3 f6
11.      Nge6 b6
12.      e3 Bc6
13.      Nd3 Bd7
14.      Nxf8 Kxf8
15.      Nf4 Qg5
16.      Ne2 Nc6
17.      f4 Qg4
18.      h3 Qf3
19.      Rh2 Nh6
20.      Kd2 Nf5
21.      Kc2 Qe4+
22.      Kb2 Qf3
23.      Kc2 Qe4+
24.      Kb2 Qf3
25.      Kc2 Qe4+

Huo Chess(即使不带开局库)也下出了非常精彩的一局,在此期间它下出了结构良好的国际象棋。它没有做出任何错误的走法,没有放弃任何棋子,并且在游戏结束时(三次重复导致和棋)比 Microchess 处于更好的位置。

游戏 5

日期:2009-01-25
地点:希腊雅典
白方:Microchess (由 BenLo Park 提供)
黑方:HuoChess v0.82 (带开局库) [由 The Code Project 分发]
结果:三次重复和棋

1. e4   e6
2. Qh5  Qe7
3. Bc4  Kd8
4. d4   a6
5. Bf4  d5
6. exd5 f5
7. dxe6 Bxe6
8. Bxe6 g6
9. Qe2  Nd7
10. Bxc7+ Kxc7
11. Qc4+ Nc5
12. dxc5 Qg7
13. Qf4+ Kc6
14. Qf3+ Kxc5
15. Qd5+ Kb6
16. Qb3+ Kc7
17. Qc4+ Kd6
18. Qd5+ Kc7
19. Qc4+ Kd6
20. Qd5+ Kc7
21. Qc4+ Kd6
22. Qd5+  draw by threefold repetition

Huo Chess 下了一盘好棋。它没有无故放弃棋子,也没有错过抓住对手棋子的机会。

历史

  • 2007年10月3日 -- 发布初始版本
  • 2007年10月15日 -- 版本 0.2
  • 2007年10月22日 -- 版本 0.3
  • 2007年11月15日 -- 版本 0.4
  • 2008年1月25日 -- 版本 0.5
  • 2008年1月28日 -- 更新了许可证信息和下载
  • 2008年2月28日 -- 更新了文章内容和下载
  • 2008年7月3日 -- 版本 0.721 - 更新了文章内容和下载
  • 2009年1月8日 -- 版本 0.722 - 更新了文章内容和下载
  • 2009年4月22日 - 版本 0.82 - 更新了文章内容和下载
  • 2011年9月8日 – 版本 0.93 (仅 C#) – MiniMax 算法更新
  • 2012年9月19日 – 版本 0.95 (仅 C#) - 更新了危险格子检查 & CountScore
  • 2014年8月12日 - 版本 0.961 (仅 C#) - 修复了 MiniMax 算法的问题
  • 2015年8月22日 - 版本 0.971 (仅 C#,带 GUI,微型版) - 改进了算法性能,修复了“愚蠢走法”过滤器等
  • 2015年8月28日 - 版本 0.980 (仅 C#,带 GUI,微型版) - 修复了与将军和非法人类走法相关的错误,添加了“认输”功能
  • 2018年8月2日 - 版本 0.990 (仅 C#,带 GUI,微型版) - 更新了引擎,修复了错误。
  • 2019年2月12日: 发布版本 0.991 (C#),包含开发者版本。

版本控制

Huo Chess 设计巧妙,并且也在不断发展。目前,该程序版本为 0.990。版本时间线和程序代码中进行的相应更改如下所示。

版本 0.992 (C#) 改进了 CountScore 函数。在思考过程中添加了将死和逼和的概念。增加了三次重复的处罚。删除了冗余代码。
版本 0.991 (C#) 修复了 MinMax 算法的一些问题,现在它按计划执行。重新添加了开局库功能。[Visual Studio 2017]
版本 0.990 (C#) 更新了引擎。修复了错误。[Visual Studio 2015/ 2017]
版本 0.980 (C#) 修复了与将军和非法人类走法相关的错误,添加了“认输”功能 [Visual Studio 2013/ 2015]
版本 0.971 (C#) 修复了 Nodes Analysis:为每个级别创建了不同的 NodesAnalysis 数组。修复了错误,使分析正确存储了值,并且 MiniMax 算法按预期运行。修复了计算机走法的“愚蠢走法”过滤器。修复了“吃回的可能性”功能。[Visual Studio 2013/ 2015]
版本 0.961 (C#) Huo Chess 0.961 中的更改:修复了与 MiniMax 算法相关的问题和错误。[Visual Studio 2012]
版本 0.95 (C#)

Huo Chess 0.95 中的更改:

1. 将 CountScore 和 CountScore_Human 合并为一个函数。还向函数添加了参数,以便更好地控制其参数化。

2. 添加了用于在棋盘上查找危险格子的函数(请参阅 FindAttackers 和 FindDefenders 函数)。在 CountScore 函数中添加了“危险”检查。这将检查棋盘上的棋子是否受到对手一个或多个棋子的威胁(请参阅 DangerWeight 变量)。

3. 删除了未使用的变量以消除所有警告。维护指数达到 19(比以前的版本 16 提高)。

4. 修复了程序以考虑 Danger_penalty(并删除了在某些地方使用的类似 danger_penalty,这导致了混淆,实际上抵消了 Danger_penalty 在 CountScore 中的作用)。

5. 在 ComputerMove 中取消注释了“查找 Skakiera(y,u) 中棋子的值”部分,以便找到 Value_of_piece_in_square 的值。+++

大小:57 KB(无 GUI),93 KB(带 GUI)。

0.93 版本(仅 C#)

实现了 MiniMax 算法进行思考。分发了两种类型的版本:

  1. Huo Chess (Kakos-MiniMax 版):一个版本,其中使用了现有的 Kakos 深度搜索算法,并添加了 MiniMax 算法来评估最佳走法。
  2. Huo Chess (Simple-MiniMax):一个更简单的版本,深度搜索已从头开始编写(试图“清理”代码)。MiniMax 算法再次用于评估最佳走法。在这个更简单的版本中,发生了以下变化:删除了 `CheckMove`、`CountScore_Human`、`HumanMove`、`CheckHumanMove HumanMove_template` 函数。通过使用一个模板 `ComputerMove` 函数来适应所有思考深度(而不是 `ComputerMove2`、`ComputerMove4`、`ComputerMove6` 等)来减小尺寸。C++ 版本仍为 0.81 版本(仍然存在独立的 `ComputerMove` 函数),XNA / VB 版本仍为 0.82 版本,并仍为教育目的分发。C# 版本大小:52.5 KB。
版本 0.82 更改了 `ComputerMove`、`HumanMove`、`CountScore`、`ElegxosOrthotitas` 函数。将思考深度从 8 增加到 20。通过使用一个模板 `ComputerMove` 函数来适应所有思考深度(而不是 `ComputerMove2`、`ComputerMove4`、`ComputerMove6` 等)来减小尺寸。C++ 版本仍为 0.81 版本(仍然存在独立的 `ComputerMove` 函数),为教育目的。C# 版本大小:52.5 KB。
版本 0.722 XNA Huo Chess (C# 版本) 带有基于 XNA 的图形用户界面。
版本 0.722 cs Huo Chess 移植到 C# 编程语言。
版本 0.722 修复了 `HumanMove` 函数的一些问题。
版本 0.721 程序中增加了更多的思考深度分析能力。Huo Chess 使用 `ComputerMove2`、`ComputerMove4`、`ComputerMove6` 和 `ComputerMove8` 函数来思考 8 步的深度(白方 4 个半步,黑方 4 个半步)。大小:56.5 KB。
版本 0.6 (微型版) 基于版本 0.6。减少了所有字符串/文本(例如,“White Rook”=>“WR”)。减少了变量名的长度(在程序中的每个变量中,特定的字符串被替换为更短的字符串)。图标被替换为更小的图标。删除了所有不必要的文件(资源、assembly.cpp、stdafx 等)。大小:47.5 KB。
版本 0.6 移除了 line 3959 的空 try...catch 语句在 `HumanMove` 中,这导致 `ElegxosNomimotitas` 未显示。优化了 `CountScore` 函数。修复了 `ElegxosNomimotitas` 在检查 Rook-Queen-Bishop 走法部分 line 3143-32173 的 bug。在计算机的棋子靠近对手的兵时增加了惩罚。降低了对手王的价值,以避免计算机持续追逐它并为此牺牲棋子。优化了 `CountScore_Human` 函数。大小:53.5 KB。
版本 0.5 修复了 `HumanMove` 函数。优化了 `CountScore` 和 `ComputerMove` 函数。大小:51.5 KB。
版本 0.4 增加了随机走法能力。优化了 `CheckForWhiteCheck` 和 `CheckForBlackCheck` 函数。大小:51.0 KB。
版本 0.3 更强的对弈能力。增加了开局库能力。优化了 `ElegxosNomimotitas` 和 `ElegxosOrthotitas` 函数。大小:91.5 KB。
版本 0.2 修复了导致计算机走非法棋子的一些 bug。感谢所有提供反馈的人!大小:99.5 KB。
版本 0.1 初始版本。已知问题:程序有时会走非法棋子。棋力不强。49.0 KB。

 

++继续编码! 

 

© . All rights reserved.