📌  相关文章
📜  组合博弈论|套装2(尼姆游戏)

📅  最后修改于: 2021-04-29 08:33:07             🧑  作者: Mango

我们强烈建议您参考以下文章作为此操作的先决条件。

组合博弈论|设置1(简介)

在这篇文章中,讨论了《尼姆游戏》。 Nim的游戏由以下规则描述:

给定许多堆,其中每堆包含一定数量的石头/硬币。在每一回合中,玩家只能选择一个堆,并从该堆中清除任意数量的石头(至少一个)。不能移动的玩家被认为输掉了比赛(即,拿到最后一块石头的人是赢家)。

例如,假设有两个玩家AB ,并且最初有三堆硬币,最初每个堆中都有3、4、5个硬币,如下所示。我们假设第一步是A。请参阅下图,以清楚地了解整个游戏过程。

Set2_Nim_Game_start_with_A
A赢得比赛(注意:A采取了第一步)

那么A在此游戏中拥有强大的专业知识吗?还是他/她先开始比B有优势?

现在让我们再次进行游戏,使用与上述相同的桩组配置,但是这次B开始而不是A开始

gameofnim11
B赢得比赛(注意:B采取了第一步)

B Y形如上图所示,必须明确的是,比赛取决于一个重要因素-谁先开始游戏?

那么,先发球员会每次赢吗?
让我们再次从A开始玩游戏,这次是使用不同的桩初始配置。堆最初有1、4、5个硬币。

A会因为他先开始而再次获胜吗?让我们来看看。
gameofnim4
A采取了第一步,但输掉了比赛。

因此,结果很明显。 A已经输了。但是如何?我们知道这个游戏很大程度上取决于哪个玩家首先开始。因此,必须有另一个因素来主导这个简单有趣的游戏的结果。该因素是堆/堆的初始配置。这次的初始配置与先前的配置不同。

因此,我们可以得出结论,该游戏取决于两个因素:

  1. 首先开始的玩家。
  2. 堆/堆的初始配置。

实际上,我们甚至可以在玩游戏之前就预测游戏的赢家!

Nim-Sum:在游戏的任何一点上,每堆/堆中硬币/石头的数量的累积XOR值在该点称为Nim-Sum。

“如果AB的比赛都达到最佳状态(即,他们没有犯任何错误),那么如果游戏开始时的Nim-Sum不为零,则保证先发动的玩家获胜。否则,如果Nim-Sum计算为零,则玩家A肯定会输。”

有关上述定理的证明,请参见-https://en.wikipedia.org/wiki/Nim#Proof_of_the_winning_formula

最佳策略:

    理解最佳策略所必需的关于按位XOR的几个推论:
  • 如果“ n”个数的XOR总和已经为零,则不可能通过对数进行一次约简来使XOR总和为零。
  • 如果“ n”个数字的XOR总和不为零,那么至少有一种方法可以减少一个数字,即XOR总和为零。

最初可能存在两种情况。

情况1:初始Nim Sum为零
众所周知,在这种情况下,如果B获胜,则B总是希望A的回合的Nim总和为零。
因此,由于Nim Sum最初为零,因此无论A删除新Nim Sum的项目数量为多少(如上所述)。另外,由于BA的转弯中希望Nim的总和为零,因此他将随后移动,以使Nim Sum的总和再次为零(如上所述,这始终是可能的)。
只要堆中有任何物品,游戏就会继续进行,并且在它们各自的回合中, A将使Nim总和为非零, B将使其再次为零,最终将不剩任何元素,而B为其中一个选择最后的胜利游戏。

通过以上解释可以明显看出,每个玩家的最佳策略是使对手的Nim Sum在每个回合中为零,如果已经为零,则不可能。

情况2:初始Nim Sum不为零
现在通过最佳方法A将使Nim Sum现在为零(如上所述,这是可能的,因为初始Nim总和为非零)。现在,在B“轮到作为NIM总和已经是零时什么号B选秀权,净息差总和将是非零和A可以选择一个号码,使净息差和零一次。只要有任何可用的物品堆,这将继续进行。
A将是选择最后一项的人。

因此,正如上述情况所讨论的,对于任何玩家来说,显而易见的最佳策略是使尼姆总和为零(如果非零),并且如果该数字已经为零,那么无论玩家现在采取什么行动,都可以对其进行反击。 。

让我们在以上玩的游戏中应用以上定理。在第一个游戏中, A首先开始,而游戏开始时的Nim-Sum为3 XOR 4 XOR 5 = 2,这是一个非零值,因此A获胜。而在第二局中,当桩的初始配置分别为1、4、5和A时,则A注定要输,因为游戏开始时的Nim-Sum是1 XOR 4 XOR 5 = 0。

执行:

在下面的程序中,我们在计算机和人(用户)之间玩Nim游戏
下面的程序使用两个功能
knowWinnerBeforePlaying()::在播放前告知结果。
playGame():玩完整的游戏,最后宣布获胜者。函数playGame()不接受人类(用户)的输入,而是使用rand()函数随机拾取一堆并从拾取的堆中随机除去任意数量的石头。

通过删除rand()函数并插入cin或scanf()函数,可以将以下程序修改为从用户处获取输入。

C++
/* A C++ program to implement Game of Nim. The program
assumes that both players are playing optimally */
#include 
#include 
using namespace std;
  
#define COMPUTER 1
#define HUMAN 2
  
/* A Structure to hold the two parameters of a move
  
A move has two parameters-
1) pile_index = The index of pile from which stone is
                    going to be removed
2) stones_removed = Number of stones removed from the
                        pile indexed = pile_index */
struct move
{
    int pile_index;
    int stones_removed;
};
  
/*
piles[] -> Array having the initial count of stones/coins
            in each piles before the game has started.
n     -> Number of piles
  
The piles[] are having 0-based indexing*/
  
// A C function to output the current game state.
void showPiles (int piles[], int n)
{
    int i;
    cout <<"Current Game Status -> ";
    for (i=0; i 0)
                non_zero_indices [count++] = i;
  
        (*moves).pile_index = (rand() % (count));
        (*moves).stones_removed =
                1 + (rand() % (piles[(*moves).pile_index]));
        piles[(*moves).pile_index] =
        piles[(*moves).pile_index] - (*moves).stones_removed;
  
        if (piles[(*moves).pile_index] < 0)
            piles[(*moves).pile_index]=0;
    }
    return;
}
  
// A C function to play the Game of Nim
void playGame(int piles[], int n, int whoseTurn)
{
    cout <<"\nGAME STARTS\n\n";
    struct move moves;
  
    while (gameOver (piles, n) == false)
    {
        showPiles(piles, n);
  
        makeMove(piles, n, &moves);
  
        if (whoseTurn == COMPUTER)
        {
            cout <<"COMPUTER removes" << moves.stones_removed << "stones from pile at index " 
             << moves.pile_index << endl;
            whoseTurn = HUMAN;
        }
        else
        {
            cout <<"HUMAN removes"<< moves.stones_removed << "stones from pile at index " 
            << moves.pile_index << endl;
            whoseTurn = COMPUTER;
        }
    }
  
    showPiles(piles, n);
    declareWinner(whoseTurn);
    return;
}
  
void knowWinnerBeforePlaying(int piles[], int n,
                            int whoseTurn)
{
    cout <<"Prediction before playing the game -> ";
  
    if (calculateNimSum(piles, n) !=0)
    {
        if (whoseTurn == COMPUTER)
            cout <<"COMPUTER will win\n";
        else
            cout <<"HUMAN will win\n";
    }
    else
    {
        if (whoseTurn == COMPUTER)
            cout <<"HUMAN will win\n";
        else
            cout <<"COMPUTER will win\n";
    }
  
    return;
}
  
// Driver program to test above functions
int main()
{
    // Test Case 1
    int piles[] = {3, 4, 5};
    int n = sizeof(piles)/sizeof(piles[0]);
  
    // We will predict the results before playing
    // The COMPUTER starts first
    knowWinnerBeforePlaying(piles, n, COMPUTER);
  
    // Let us play the game with COMPUTER starting first
    // and check whether our prediction was right or not
    playGame(piles, n, COMPUTER);
  
    /*
    Test Case 2
    int piles[] = {3, 4, 7};
    int n = sizeof(piles)/sizeof(piles[0]);
  
    // We will predict the results before playing
    // The HUMAN(You) starts first
    knowWinnerBeforePlaying (piles, n, COMPUTER);
  
    // Let us play the game with COMPUTER starting first
    // and check whether our prediction was right or not
    playGame (piles, n, HUMAN); */
  
    return(0);
}
  
// This code is contributed by shivanisinghss2110


C
/* A C program to implement Game of Nim. The program
   assumes that both players are playing optimally */
#include 
#include 
#include 
  
#define COMPUTER 1
#define HUMAN 2
  
/* A Structure to hold the two parameters of a move
  
 A move has two parameters-
  1) pile_index = The index of pile from which stone is
                    going to be removed
  2) stones_removed = Number of stones removed from the
                        pile indexed = pile_index  */
struct move
{
    int pile_index;
    int stones_removed;
};
  
/*
 piles[] -> Array having the initial count of stones/coins
            in each piles before the game has started.
 n       -> Number of piles
  
 The piles[] are having 0-based indexing*/
  
// A C function to output the current game state.
void showPiles (int piles[], int n)
{
    int i;
    printf ("Current Game Status -> ");
    for (i=0; i 0)
                non_zero_indices [count++] = i;
  
        (*moves).pile_index = (rand() % (count));
        (*moves).stones_removed =
                 1 + (rand() % (piles[(*moves).pile_index]));
        piles[(*moves).pile_index] =
         piles[(*moves).pile_index] - (*moves).stones_removed;
  
        if (piles[(*moves).pile_index] < 0)
            piles[(*moves).pile_index]=0;
    }
    return;
}
  
// A C function to play the Game of Nim
void playGame(int piles[], int n, int whoseTurn)
{
    printf("\nGAME STARTS\n\n");
    struct move moves;
  
    while (gameOver (piles, n) == false)
    {
        showPiles(piles, n);
  
        makeMove(piles, n, &moves);
  
        if (whoseTurn == COMPUTER)
        {
            printf("COMPUTER removes %d stones from pile "
                   "at index %d\n", moves.stones_removed,
                   moves.pile_index);
            whoseTurn = HUMAN;
        }
        else
        {
            printf("HUMAN removes %d stones from pile at "
                   "index %d\n", moves.stones_removed,
                   moves.pile_index);
            whoseTurn = COMPUTER;
        }
    }
  
    showPiles(piles, n);
    declareWinner(whoseTurn);
    return;
}
  
void knowWinnerBeforePlaying(int piles[], int n,
                             int whoseTurn)
{
    printf("Prediction before playing the game -> ");
  
    if (calculateNimSum(piles, n) !=0)
    {
        if (whoseTurn == COMPUTER)
            printf("COMPUTER will win\n");
        else
            printf("HUMAN will win\n");
    }
    else
    {
        if (whoseTurn == COMPUTER)
            printf("HUMAN will win\n");
        else
            printf("COMPUTER will win\n");
    }
  
    return;
}
  
// Driver program to test above functions
int main()
{
    // Test Case 1
    int piles[] = {3, 4, 5};
    int n = sizeof(piles)/sizeof(piles[0]);
  
    // We will predict the results before playing
    // The COMPUTER starts first
    knowWinnerBeforePlaying(piles, n, COMPUTER);
  
    // Let us play the game with COMPUTER starting first
    // and check whether our prediction was right or not
    playGame(piles, n, COMPUTER);
  
    /*
    Test Case 2
    int piles[] = {3, 4, 7};
    int n = sizeof(piles)/sizeof(piles[0]);
  
    // We will predict the results before playing
    // The HUMAN(You) starts first
    knowWinnerBeforePlaying (piles, n, COMPUTER);
  
    // Let us play the game with COMPUTER starting first
    // and check whether our prediction was right or not
    playGame (piles, n, HUMAN);    */
  
    return(0);
}


输出:在不同的运行中可能会有所不同,因为随机数用于决定下一步(对于松散的玩家)。

Prediction before playing the game -> COMPUTER will win

GAME STARTS

Current Game Status -> 3 4 5 
COMPUTER removes 2 stones from pile at index 0
Current Game Status -> 1 4 5 
HUMAN removes 3 stones from pile at index 1
Current Game Status -> 1 1 5 
COMPUTER removes 5 stones from pile at index 2
Current Game Status -> 1 1 0 
HUMAN removes 1 stones from pile at index 1
Current Game Status -> 1 0 0 
COMPUTER removes 1 stones from pile at index 0
Current Game Status -> 0 0 0 

COMPUTER won

参考 :
https://zh.wikipedia.org/wiki/尼姆