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

Nura Othello - 一个基于 WTL 的棋盘游戏

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.40/5 (13投票s)

2006年12月29日

CPOL

23分钟阅读

viewsIcon

84561

downloadIcon

2360

在人工智能游戏中使用 WTL 库的示例。

目录

  1. 引言
    1. Nura Othello 的名称
    2. Nura Othello 的规则
    3. Nura Othello 的特点
    4. Nura Othello 的用法
  2. 背景
  3. 源代码
  4. 环境
  5. 使用代码
    1. 整体结构
    2. CMainDlg 类
    3. OthelloWindowWTL 类
    4. OthelloWindowWTL 的用户定义消息 NM_OTHELLO (= UM_OTHELLO)
    5. OthelloCore、OthelloCore0、OthelloCore1、OthelloCore2 和 OthelloCore3 类
      1. 继承结构
      2. 人工智能策略
        1. OthelloCore 策略
        2. OthelloCore0 策略
        3. OthelloCore1 策略
        4. OthelloCore2 策略
        5. OthelloCore3 策略
  6. 关注点
  7. Othello 自定义工具
  8. 致谢

引言

Nura Othello 的名称

Nura Othello 是一款用户可以与电脑对弈的电脑棋盘游戏。奥赛罗游戏有时也称为黑白棋。Nura 是我妻子的名字。因此,我将我的程序命名为 Nura Othello。Nur 在阿拉伯语中意为光明,而 -a 是一个阴性后缀,因此 Nura 可以表示“光明的女士”。这是中东地区非常流行的女性阿拉伯名字。

Nura Othello 的规则

  1. Nura Othello 在 8 X 8 的棋盘上进行游戏。它看起来像一个国际象棋棋盘。
  2. 玩家应将棋子放在方格单元上,而不是放在线上或线的交叉点上。
  3. 游戏开始时,每位玩家在棋盘中央放置两枚棋子。
  4. 如果我的棋子水平、垂直或对角线包围了对手的棋子,则被包围的对手棋子将变为我的棋子。
  5. 玩家只能将棋子放在可以包围对手棋子的地方;
  6. 玩家应交替放置棋子。
  7. 在游戏过程中,如果任何一方玩家没有地方放置棋子,则轮到另一方。
  8. 最终,拥有更多棋子的玩家将赢得游戏。
  9. 然而,在游戏过程中,如果双方玩家都没有地方放置棋子,游戏将结束,拥有更多棋子的玩家将赢得游戏。

Nura Othello 的特点

  1. 它会自动检测 Windows 的语言,并以韩语或英语显示所有说明。如果您的 Windows 是韩语 Windows,它将处于韩语模式,但如果它是其他语言的 Windows,它将处于英语模式。但是,您可以通过使用 NuraOthell.ini 将 Nura Othello 自定义为您自己的语言。您可以使用 OthelloCustomizer.exe 生成 NuraOthell.ini。我稍后会介绍 OthelloCustomizer.exe
  2. 根据游戏所需人工智能的智商,用户可以选择从级别 0 到级别 3。
  3. 游戏开始时,四个棋子的初始位置将随机确定。它有六种可能性,如下所示

    Screenshot - NuraOthello2.gif Screenshot - NuraOthello3.gif

  4. 它通过图表显示当前谁正在获胜。
  5. 它有选项让电脑先走或后走。
  6. 它有选项设置时间限制,如果用户不放棋子,用户将输掉游戏。
  7. 在开始游戏之前,如果您按下“F1”键,将弹出一个“关于”消息框。您可以在那里看到关于 Nura Othello 的简单信息。

Nura Othello 的用法

  1. 要开始游戏,点击“开始”按钮。
  2. 游戏过程中,如果您想取消游戏,点击“取消”按钮。
  3. 在游戏开始之前,如果您希望您的电脑先走棋,请勾选“电脑先走”按钮。
  4. 在游戏开始之前,如果您想体验时间限制的压力,请勾选“时间限制”按钮。
  5. 在游戏开始之前,您可以选择 Nura Othello 人工智能的智商。级别 3 最难,级别 0 最简单。

背景

它包括三样东西:人工智能引擎、一个类似控件的窗口和一个皮肤窗口。我将在本文中简要介绍这些东西。我使用了一些设计模式:备忘录模式桥接模式(确切地说,是桥接模式的修改版本),但我不会在这里解释设计模式。它使用多线程和临界区进行同步。如果您对临界区了解不多,可以从这里了解一些关于临界区的想法。此外,您可以在 Google 上轻松搜索奥赛罗游戏的策略。

源代码

此源代码使用 WTL 库。为了比较,我还冗余地使用 MFC 开发了它。如果您也熟悉 MFC,可以将 WTL 源代码与 MFC 源代码进行比较。但是,我不会解释 MFC 源代码,因为 MFC 源代码与 WTL 源代码非常相似,如果您理解 WTL 源代码,您可以轻松理解 MFC 源代码。源代码包含六个项目:OthelloCore、OthelloWindowWTL、OthelloWindowMFC、NuraOthello(在 OthelloWTL 文件夹中)、OthelloMFC 和 OthelloCustomizer。OthelloCore 项目是奥赛罗引擎(包括人工智能)的库。OthelloWindowWTL 和 OthelloWindowMFC 项目分别是 WTL 和 MFC 的奥赛罗窗口的类似控件的库。NuraOthello 和 OthelloMFC 项目分别是使用 WTL 和 MFC 编写的可执行项目。但是,它们不使用奥赛罗窗口库,而是直接使用奥赛罗窗口和奥赛罗核心的源代码。最后,OthelloCustomizer 项目是使用 WTL 编写的可执行项目。

环境

Nura Othello 是用 VC++ .NET 2003 标准版 SP1(以下简称 VC++ 7.1)和 WTL 8.0 创建的。如果网站没有更改,您可以从这里获取 WTL 8.0。它主要在 Windows XP SP2 和 Windows 2000 SP4 下进行测试,但我发现它在 Windows 98 SE 下无法工作。要编译源代码,您需要安装 WTL。此外,它可以用 VC++ .NET 2005 Express Edition(以下简称 VC++ 8.0X)编译。当然,在编译它之前您必须采取一些步骤。关于使用 VC++ 8.0X 进行 Windows 编程,这里有介绍,关于 VC++ 8.0X 的 WTL 安装,这里有介绍。为了使其能够使用 ATL 3.0 的 VC++ 8.0X 进行编译,我创建了 ATLVersion.h 并将其包含在 stdafx.h 中。Othello.sln 用于 VC++ 7.1,而 Othello2005.sln 用于 VC++ 8.0X。OthelloWTL.vcproj 用于 VC++ 7.1,而 OthelloWTL2005.vcproj 用于 VC++ 8.0X。所有解决方案文件(*.sln)和项目文件(*.vcproj)的文件名约定都与上述示例相同。

Using the Code

整体结构

我已经提到 Nura Othello 由三部分组成:人工智能引擎、一个类似控件的窗口和一个皮肤窗口。皮肤窗口本身是一个基于对话框的框架,并包含类似控件的窗口作为其子窗口。皮肤窗口由类 CMainDlg 实现。类似控件的窗口是奥赛罗游戏窗口本身,是皮肤窗口的子窗口。我称此窗口为类似控件的窗口的原因是此窗口实际上不是控件,但它像控件一样工作。类似控件的窗口包含人工智能引擎,由类 OthelloWindowWTL 实现。人工智能引擎由类 OthelloCoreOthelloCore0OthelloCore1OthelloCore2OthelloCore3 实现。

CMainDlg 类

CMainDlg 由 WTL-App Wizard 提供,我对其进行了编码。所有布局均使用 VC++ 7.1 的资源编辑器制作。它从 OthelloWindowWTL 接收用户定义消息 UM_OTHELLO。其成员函数 OnOthelloMessage 将处理消息 UM_OTHELLO。每当它收到 UM_OTHELLO 时,它都会更改显示谁正在获胜的进度条,以及如果时间限制选项打开时显示剩余时间的计时图。以下代码与处理用户定义消息 UM_OTHELLO 相关。

LRESULT CMainDlg::OnOthelloMessage (UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (wParam)
    {
    case ON_MYSTONE:    OnOthelloMyStone();          break;
    case ON_YOURSTONE:  OnOthelloYourStone();        break;
    case ON_PASS:       OnOthelloPass(lParam);       break;
    case ON_FINISH:     OnOthelloFinish(lParam);     break;
    case ON_NOPLACE:    OnOthelloNoPlace(lParam);    break;
    }

    return 0;
}

void CMainDlg::OnOthelloUpdate (void)
{
    int     player[2], sum;
    TCHAR   str[15];

    m_pOthello->WhoWin(&player[0], &player[1]);
    sum = player[0] + player[1];
    m_Progress.SetRange(0, sum);
    m_Progress.SetPos(player[1]);
    for (int i = 0; i < 2; i++)
    {
        _stprintf(str, _T("%s: %d"), m_strPlayer[i], player[i]);
        m_Player[i].SetWindowText(str);
    }
}

void CMainDlg::OnOthelloMyStone (void)
{
    OnOthelloUpdate();
    if (!m_bTimeLimit)
        return;

    m_CountDown = LIMITATION;
    m_Elapsed.SetPos(LIMITATION);
    KillTimer(m_pOthello->GetStep() - 1);
    SetTimer(m_pOthello->GetStep(), TIMESTEP, NULL);
}

void CMainDlg::OnOthelloYourStone (void)
{
    OnOthelloMyStone();
}

void CMainDlg::OnOthelloPass (LPARAM lParam)
{
    m_CountDown = LIMITATION;
    m_Elapsed.SetPos(LIMITATION);
    KillTimer(m_pOthello->GetStep() - 1);

    switch (lParam)
    {
    case PLAYER1:    MessageBox(m_strWait);    break;
    case PLAYER2:    MessageBox(m_strPass);    break;
    }

    m_CountDown = LIMITATION;
    m_Elapsed.SetPos(LIMITATION);
    SetTimer(m_pOthello->GetStep(), TIMESTEP, NULL);
}

void CMainDlg::OnOthelloFinish (void)
{
    if (m_bTimeLimit)
        KillAllTimers();

    if (lParam)
    {
        CString     strResult, str;
        int         player1     = GETSCORE1(lParam);
        int         player2     = GETSCORE2(lParam);

        if      (player1 > player2)     strResult = m_strLose;
        else if (player1 < player2)     strResult = m_strWin;
        else                            strResult = m_strDraw;

        str.Format(strResult, player2, player1);
        MessageBox(str);
    }
}

void CMainDlg::OnOthelloNoPlace (LPARAM lParam)
{
    int         player1     = GETSCORE1(lParam);
    int         player2     = GETSCORE2(lParam);
    CString     strResult   = m_strNoPlace;
    CString     str;

    strResult += _T("\n");
    if      (player1 > player2)     strResult += m_strLose;
    else if (player1 < player2)     strResult += m_strWin;
    else                            strResult += m_strDraw;

    str.Format(strResult, player2, player1);
    MessageBox(str);
}

它还通过按钮、复选框和组合框接收用户的指令。您还可以将其编译为单线程版本。为此,请取消注释 OthelloWTL 文件夹中 stdafx.h 第 54 行的语句,如下所示

#define OTHELLO_NOT_THREAD 

我禁用了撤回功能,因为它在我的电脑上运行良好,但在其他一些电脑上运行得很糟糕。我认为这是由一些线程问题引起的,但目前我不知道确切的原因。如果您想启用撤回功能并检查它是否有效,请注释 OthelloWTL 文件夹中 stdafx.h 第 55 行的语句,如下所示

// #define NOTWITHDRAWAL

OthelloWindowWTL 类

它使用两个用户定义消息:NM_SELFNM_OTHELLO(确切地说是 m_MessageUM_OTHELLO,在这种情况下)。NM_SELF 仅供内部使用,而 NM_OTHELLO 仅供外部使用。每当放置一枚棋子时,OthelloWindowWTL 都会向 CMainDlg 发送 NM_OTHELLO。当游戏结束时,它还会向 CMainDlg 发送 NM_OTHELLO。为了避免 CMainDlg 中的消息冲突,OthelloWindowWTL 的构造函数可以接收一个不同的用户定义消息 NM_OTHELLO 的值作为参数并将其保存在 m_Message 中。OthelloWindowWTL 独立于 CMainDlg,就像 CProgressBarCtrl 独立于 CMainDlg 一样。OthelloWindowWTL 的公共成员函数如下

class OthelloWindowWTL : public CWindowImpl<OthelloWindowWTL>
{
    ...
public:
    OthelloWindowWTL (UINT message = NM_OTHELLO, bool bAutoFinish = true);
    OthelloWindowWTL (UINT message, int lang, bool bAutoFinish);
    virtual ~OthelloWindowWTL ();
    ...
public:
    inline bool Start (void)        { return StartStop (true); }
    inline bool Stop (void)         { return StartStop (false); }
    inline void Initialize (void)   { Initialize(rand() % 8); }
    inline void Initialize (const char FirstPlayer, int IQ = OTHELLO_SMART)
        { Initialize(rand() % 8, FirstPlayer, IQ); }

    void Initialize (int diag, const char firstPlayer = PLAYER2, 
                     int IQ = OTHELLO_SMART);
    bool Withdraw (void);
    int WhoWin (int* pPlayer1 = 0, int* pPlayer2 = 0) const;
    int WhoseTurn (void) const;
    int GetStep (void) const;
    
    HWND Create (HWND hParentWnd, int x, int y, UINT nID);
    HWND Create (HWND hParentWnd, const POINT& pt, UINT nID);
    HWND Create (HWND hParentWnd, int x, int y, 
                 int nWidth, int nHeight, UINT nID);
    HWND Create (HWND hParentWnd, const RECT& rect, UINT nID);
    HWND Create (HWND hParentWnd, int x, int y, DWORD dwStyle, 
                 DWORD dwExStyle, UINT nID);
    HWND Create (HWND hParentWnd, const POINT& pt, 
                 DWORD dwStyle, DWORD dwExStyle, UINT nID);
    HWND Create (HWND hParentWnd, int x, int y, int nWidth, 
                 int nHeight, DWORD dwStyle, 
                 DWORD dwExStyle, UINT nID);
    HWND Create (HWND hParentWnd, const RECT& rect, 
                 DWORD dwStyle, DWORD dwExStyle, UINT nID);

    BOOL Move (int x, int y, BOOL bRepaint = TRUE);
    BOOL Move (POINT& pt, BOOL bRepaint = TRUE);
    BOOL Move (int x, int y, int nWidth, int nHeight, 
               BOOL bRepaint = TRUE);
    BOOL Move (RECT& rect, BOOL bRepaint = TRUE);
    ...
}

OthelloWindowWTL (UINT message = NM_OTHELLO, bool bAutoFinish = true);

  1. 这是一个构造函数。
  2. 第一个参数消息是 OthelloWindowWTL 将发送到父窗口的消息。为了避免消息冲突,您可以为消息提供不同的值。默认值为 NM_OTHELLO
  3. 第二个参数是它是否自行处理结束过程。如果第二个参数 bAutoFinish 设置为 true,它将不会向父窗口发送 NM_OTHELLO(或您提供的消息),它将处理结束过程。默认值为 true
  4. 语言将根据 Windows 的默认语言自动选择。

OthelloWindowWTL (UINT message, int lang, bool bAutoFinish);

  1. 这是另一个构造函数。
  2. 第一个和第三个参数与上述相同。
  3. 第二个参数 lang 用于语言。如果您将其设置为 LANG_KOREAN,它将使用韩语,无论 Windows 的默认语言如何。

inline bool Start (void);

  1. 它使奥赛罗游戏开始。
  2. 当游戏开始时,返回值为 true,当失败时,返回值为 false

inline bool Stop (void);

  1. 它使奥赛罗游戏停止。但是,当棋子正在改变时,它不会停止游戏,并立即返回而不做任何事情。
  2. 当游戏停止时,返回值为 true,当失败时,返回值为 false

void Initialize (int diag, const char firstPlayer = PLAYER2, int IQ = OTHELLO_SMART);

  1. 它初始化奥赛罗游戏。
  2. 第一个参数 diag 是四个棋子的初始位置。如下所示,蓝色为用户棋子,红色为电脑棋子。

    diag 0 或 6 1 或 7 2

    diag 3 4 5
  3. 第二个参数 firstPlayer 是将先走棋的玩家。如果 firstPlayerPLAYER2,则用户将先走棋。如果 firstPlayerPLAYER1,则电脑将先走棋。默认值为 PLAYER2
  4. IQ 是 Nura Othello 人工智能的智商。目前,IQ 设置为 0 到 3。如果 IQ 设置为 3,它将拥有最聪明的智商。如果 IQ 设置为 0,它将是最笨的。数字越高,人工智能越聪明。默认值为 3。

inline void Initialize ();

它与 Initialize(rand() % 8, PLAYER2, OTHELLO_SMART) 相同。

inline void Initialize (const char FirstPlayer, int IQ = OTHELLO_SMART);

它与 Initialize(rand() % 8, FirstPlayer, IQ) 相同。

bool Withdraw (void);

  1. 它强制撤回电脑的棋子,然后撤回用户的棋子。但是,它有一个bug。我不知道哪里出错了。
  2. 当成功时返回 true,当失败时返回 false

int WhoWin (int* pPlayer1 = 0, int* pPlayer2 = 0) const;

  1. 它告诉你谁正在获胜或已经获胜。
  2. 返回值为赢家。如果是 PLAYER1,则电脑正在获胜或已经获胜。如果是 PLAYER2,则用户正在获胜或已经获胜。
  3. 第一个参数 pPlayer1 是一个整数变量的指针,其中将保存电脑的分数。如果为零,则忽略。
  4. 第二个参数 pPlayer2 是一个整数变量的指针,其中将保存用户的分数。如果为零,则忽略。

int WhoseTurn (void) const;

  1. 它告诉你下一个轮到谁。
  2. 返回值为应该放置棋子的玩家。如果是 PLAYER1,则轮到电脑。如果是 PLAYER2,则轮到用户。

int GetStep (void) const;

  • 它告诉你当前步骤的序号。
  • 返回值为当前步骤的序号。

HWND Create (HWND hParentWnd, const RECT& rect, DWORD dwStyle, DWORD dwExStyle, UINT nID);

  • 它创建奥赛罗控件式窗口。其窗口类名为 OTHELLO_CLASS,对于 ASCII 码定义为 "Othello",对于 UNICODE 定义为 L"Othello"。
  • 返回值为奥赛罗窗口的窗口句柄。
  • 第一个参数 hParentWnd 是其父窗口的窗口句柄。
  • 第二个参数 rect 是奥赛罗窗口矩形的引用。
  • 第三个参数 dwStyle 是奥赛罗窗口的窗口样式。
  • 第四个参数 dwExStyle 是奥赛罗窗口的扩展窗口样式。
  • 第五个参数 nID 是赋予奥赛罗窗口的 ID。

HWND Create (HWND hParentWnd, const POINT& pt, DWORD dwStyle, DWORD dwExStyle, UINT nID);

  • 它创建奥赛罗控件式窗口。其窗口类名为 OTHELLO_CLASS,对于 ASCII 码定义为 "Othello",对于 UNICODE 定义为 L"Othello"。
  • 返回值为奥赛罗窗口的窗口句柄。
  • 第一个参数 hParentWnd 是其父窗口的窗口句柄。
  • 第二个参数 pt 是奥赛罗窗口左上角的引用。其宽度和高度都将确定为 400 像素。
  • 第三个参数 dwStyle 是奥赛罗窗口的窗口样式。
  • 第四个参数 dwExStyle 是奥赛罗窗口的扩展窗口样式。
  • 第五个参数 nID 是赋予奥赛罗窗口的 ID。

HWND Create (HWND hParentWnd, const RECT& rect, UINT nID);

  • 它创建奥赛罗控件式窗口。其窗口类名为 OTHELLO_CLASS,对于 ASCII 码定义为 "Othello",对于 UNICODE 定义为 L"Othello"。
  • 返回值为奥赛罗窗口的窗口句柄。
  • 第一个参数 hParentWnd 是其父窗口的窗口句柄。
  • 第二个参数 rect 是奥赛罗窗口矩形的引用。
  • 第三个参数 nID 是赋予奥赛罗窗口的 ID。
  • 奥赛罗窗口的样式和扩展样式都默认为 NULL

HWND Create (HWND hParentWnd, const POINT& pt, UINT nID);

  • 它创建奥赛罗控件式窗口。其窗口类名为 OTHELLO_CLASS,对于 ASCII 码定义为 "Othello",对于 UNICODE 定义为 L"Othello"。
  • 返回值为奥赛罗窗口的窗口句柄。
  • 第一个参数 hParentWnd 是其父窗口的窗口句柄。
  • 第二个参数 pt 是奥赛罗窗口左上角的引用。其宽度和高度都将确定为 400 像素。
  • 第三个参数 nID 是赋予奥赛罗窗口的 ID。
  • 奥赛罗窗口的样式和扩展样式都默认为 NULL

BOOL Move (int x, int y, BOOL bRepaint = TRUE);

  • 它将窗口移动到指定位置。
  • 如果成功,返回值为 true,如果失败,返回值为 false
  • 第一和第二个参数 xy 是奥赛罗窗口的左上角位置。
  • 第三个参数 bRepaint 与奥赛罗窗口移动时是否重绘有关。如果 bRepaint 为真,则奥赛罗窗口将被重绘。如果 bRepaint 为假,则奥赛罗窗口将不会被重绘。bRepaint 的默认值为 true
  • 奥赛罗窗口的宽度和高度将保持不变。

BOOL Move (POINT& pt, BOOL bRepaint = TRUE);

  • 它将窗口移动到指定位置。
  • 如果成功,返回值为 true,如果失败,返回值为 false
  • 第一个参数 pt 是奥赛罗窗口的左上角位置。
  • 第二个参数 bRepaint 与奥赛罗窗口移动时是否重绘有关。如果 bRepainttrue,则奥赛罗窗口将被重绘。如果 bRepaintfalse,则奥赛罗窗口将不会被重绘。bRepaint 的默认值为 true
  • 奥赛罗窗口的宽度和高度将保持不变。

BOOL Move (int x, int y, int nWidth, int nHeight, BOOL bRepaint = TRUE);

  • 它将窗口移动到指定位置。
  • 如果成功,返回值为 true,如果失败,返回值为 false
  • 第一、二、三、四个参数 xynWidthnHeight 是奥赛罗窗口的位置。
  • 第五个参数 bRepaint 与奥赛罗窗口移动时是否重绘有关。如果 bRepainttrue,则奥赛罗窗口将被重绘。如果 bRepaintfalse,则奥赛罗窗口将不会被重绘。bRepaint 的默认值为 true

BOOL Move (RECT& rect, BOOL bRepaint = TRUE);

  • 它将窗口移动到指定位置。
  • 如果成功,返回值为 true,如果失败,返回值为 false
  • 第一个参数 rect 是奥赛罗窗口位置的引用。
  • 第二个参数 bRepaint 与奥赛罗窗口移动时是否重绘有关。如果 bRepainttrue,则奥赛罗窗口将被重绘。如果 bRepaintfalse,则奥赛罗窗口将不会被重绘。bRepaint 的默认值为 true

Create(...) 和 Move(...) 的模式与 Nura Tetris 的模式相同。Nura Tetris 发布在此处。我更喜欢 Create(...) 和 Move(...) 函数的这些模式。

OthelloWindowWTL 的用户定义消息 NM_OTHELLO (= UM_OTHELLO)

用户定义消息 NM_OTHELLO(在本例中为 UM_OTHELLO)以 WPARAM 作为通知代码,以 LPARAM 作为与通知代码相关的信息。通知代码在 OthelloWindowWTL.h 中定义。通知代码如下所示

用户定义消息 NM_OTHELLO(= UM_OTHELLO)表

WPARAM(通知代码)

LPARAM

描述

ON_MYSTONE

如果对手在坐标 (x, y) 放置棋子,则 (x, y) 为 (LOWORD(lParam), HIWORD(lParam))

当对手放置其棋子时,OthelloWindowWTL 会向其父窗口发送带有通知代码 ON_MYSTONE 的消息 NM_OTHELLO (= UM_OTHELLO)。

ON_YOURSTONE

如果用户在坐标 (x, y) 放置棋子,则 (x, y) 为 (LOWORD(lParam), HIWORD(lParam))

当用户放置其棋子时,OthelloWindowWTL 会向其父窗口发送带有通知代码 ON_YOURSTONE 的消息 NM_OTHELLO (= UM_OTHELLO)。

ON_PASS

如果对手跳过其回合,lParam 为 PLAYER1。如果用户跳过其回合,lParam 为 PLAYER2。

如果用户或对手因没有可放置棋子的位置而跳过其回合,OthelloWindowWTL 会向其父窗口发送带有通知代码 ON_PASS 的消息 NM_OTHELLO (= UM_OTHELLO)。

ON_FINISH

对手分数是 LOWORD(lParam),用户分数是 HIWORD(lParam)。但是,如果 OthelloWindowWTL 之前发送了带有通知代码 ON_NOPLACENM_OTHELLO (= UM_OTHELLO),则 NM_OTHELLO (= UM_OTHELLO) 的 lparam 将为零。

当游戏结束时,OthelloWindowWTL 会向其父窗口发送带有通知代码 ON_FINISH 的消息 NM_OTHELLO (= UM_OTHELLO)。

ON_NOPLACE

对手分数是 LOWORD(lParam),用户分数是 HIWORD(lParam)

当双方玩家都没有地方放置棋子时,OthelloWindowWTL 会向其父窗口发送带有通知代码 ON_NOPLACE 的消息 NM_OTHELLO (= UM_OTHELLO)。然后,OthelloWindowWTL 接下来会自动发送带有通知代码 ON_FINISHNM_OTHELLO (= UM_OTHELLO),其中 lparam 为零。

ON_START

尚未确定

留作将来使用

ON_STOP

尚未确定

留作将来使用

ON_LEVEL

尚未确定

留作将来使用

OthelloCore、OthelloCore0、OthelloCore1、OthelloCore2 和 OthelloCore3 类

我不会解释所有的成员函数。相反,我将解释 Nura Othello 人工智能的策略。我认为这将对您理解 Nura Othello 人工智能的算法更有帮助。Nura Othello 人工智能的核心是函数 virtual bool SeekBestPoint (int* px, int* py, const int player = PLAYER1)。其余大部分是 Nura Othello 规则的实现。如何找到电脑应该放置棋子的位置,实际上是 OthelloCore 系列类的一切。因此,策略是在这个函数 SeekBestPoint (...) 中实现的。

继承结构

  • OthelloCore0 派生自 OthelloCore,具有 0 级人工智能。
  • OthelloCore1 派生自 OthelloCore,具有 1 级人工智能。
  • OthelloCore2 派生自 OthelloCore1,具有 2 级人工智能。
  • OthelloCore3 派生自 OthelloCore2,具有 3 级人工智能。

人工智能策略

OthelloCore 策略

它提供人工智能的基本功能。它几乎是一个通用类,因此所有 OthelloCore 系列类都直接或间接从它派生。实际上,它没有任何人工智能因素,因为函数 SeekBestPoint (...) 是一个纯虚函数,如下所示。

virtual bool SeekBestPoint (int* px, int* py, const int player = PLAYER1) = 0;

将来,我也会开发一个网络版本。在该版本中,SeekBestPoint (...) 将从网络上的另一个用户接收输入。这就是我将其设为虚函数的原因。

OthelloCore0 策略

它具有 0 级人工智能。它旨在帮助初学者学习 Nura Othello 的规则。电脑会随意放置棋子,无需思考。这是无脑模式。所以,它没有任何策略。

OthelloCore1 策略

它具有 1 级人工智能。它会选择电脑放置棋子后能获得最多棋子的位置。在电脑可以放置棋子的候选位置中,它会选择最贪婪的位置。通常,人类初学者会选择这种策略。

OthelloCore2 策略

它具有 2 级人工智能。电脑会首先尝试获得更有利的位置。位置的优先级如下。P1 最有利,P9 最不利。在电脑可以放置棋子的候选位置中,它会选择最有利的位置。

带优先级标记的奥赛罗棋盘

P1

P3

P2

P2

P2

P2

P3

P1

P3

P9

P7

P8

P8

P7

P9

P3

P2

P7

P4

P5

P5

P4

P7

P2

P2

P8

P5

P6

P6

P5

P8

P2

P2

P8

P5

P6

P6

P5

P8

P2

P2

P7

P4

P5

P5

P4

P7

P2

P3

P9

P7

P8

P8

P7

P9

P3

P1

P3

P2

P2

P2

P2

P3

P1

这是一种比1级策略更聪明的策略,尤其是在长远角度来看。通常,中级水平的人类玩家会选择这种策略。然而,每个位置的优先级会根据情况而改变。因此,电脑需要根据情况改变每个位置的优先级,但它使用的是固定的优先级表。它不够灵活,并且没有将情况反映到其决策中。

OthelloCore3 策略

我改进了2级的弱点。这是3级。它根据情况改变每个位置的优先级,并将改变的情况反映到其决策中。它模拟了一个只有一条线的线形奥赛罗,因此它可以预测几个步骤并选择最佳结果。但是,它也有一个弱点。它只使用一维的线形奥赛罗。因此,它容易受到对手的二维攻击。如果你对这个解释不太理解,当你查看源代码时你会明白。

然而,在游戏的最后和最终阶段,级别 1 和级别 2 的策略分别远优于其他策略。因此,它会根据游戏阶段改变其策略。

关注点

如前所述,我未能解决一个问题,即撤回问题。我简单地禁用了撤回功能,因为它在我的电脑上运行良好,但在其他一些电脑上运行得很糟糕。我怀疑存在一些线程问题,但目前我不知道确切的原因。如果您想启用撤回功能并检查它是否有效,请注释 OthelloWTL 文件夹中 stdafx.h 第 55 行的语句,如下所示

// #define NOTWITHDRAWAL

VC++ 8.0X 使用 ATL 3.0,而 VC++ 7.1 使用 ATL 7.1。尽管两者都使用相同的 WTL 8.0,但 VC++ 7.1 的源代码与 VC++ 8.0X 不兼容。因此,为了使其能够使用 VC++ 8.0X 编译,尽管它是用 VC++ 7.1(ATL 7.1)开发的,我还是创建了 ATLVersion.h 并将其包含在 stdafx.h 中。ATLVersion.h 文件与Nura Tetris 的文件相同。在此项目中,ATLVersion.h 运行良好,但我认为它不适用于所有用 VC++ 7.1 编写的项目都能很好地用 VC++ 8.0X 编译。如果有人对 ATL 的向下兼容性感兴趣,我希望鼓励他或她改进 ATLVersion.h

Othello 自定义工具

我不会解释 OthelloCustomizer 的源代码。相反,我将介绍如何使用这个实用程序。如果您运行此程序并在相应的编辑窗口中将所有消息翻译成您的语言并保存,您就可以得到一个 NuraOthello.ini。如果您将其放在 NuraOthello.exe 所在的同一文件夹中,NuraOthello.exe 将使用 NuraOthello.ini 以您的语言显示所有消息。此外,您可以将其另存为不同的名称,例如 NuraOthelloRussian.ini。它本身不会对 NuraOthello.exe 起作用,但您可以将其作为 NuraOthello 的特定语言包保留。

致谢

我要感谢 Michael Dunn 为 MFC 程序员撰写的关于 WTL 的优秀文章。我从他的文章中学到了很多关于 WTL 的知识。他的文章可以在这里找到。我还要感谢 Sergey Solozhentsev 在 WTL Helper 和 WTL Wizards 方面所做的出色工作。您可以在这里这里获取他的 WTL Helper,以及在这里这里获取其手册。不要混淆。他的 WTL Wizards 不同于可以例如通过 setup71.js 安装的普通 WTL App Wizard。他的 WTL Wizards 也支持分屏窗口框架。您还可以在这里获取他的 WTL Wizards 及其手册这里。它在我使用 WTL 编码时提供了很多便利。最重要的是,我真的非常感谢上帝和我的妻子 Nura。他将她赐予我,她永远在我身边,是我坚定的支持者。

历史

2006年12月29日 - NuraOthello 1.6 和 Othello Customizer 1.0 发布。
2007年1月13日 - 源代码略有更新。
2007年1月20日 - 文章和源代码略有更新。
2007年2月1日 - NuraOthello 1.7 和 Othello Customizer 1.1 发布;其规则根据国际奥赛罗规则进行了更改,并修复了一个与时间限制选项相关的错误。

© . All rights reserved.