Prerequisites : Grundy Numbers/Numbers and Mex
We have already seen in Set 2 (https://www.geeksforgeeks.org/combinatorial-game-theory-set-2-game-nim/), that we can find who wins in a game of Nim without actually playing the game.
Suppose we change the classic Nim game a bit. This time each player can only remove 1, 2 or 3 stones only (and not any number of stones as in the classic game of Nim). Can we predict who will win?
Yes, we can predict the winner using Sprague-Grundy Theorem.
What is Sprague-Grundy Theorem?
Suppose there is a composite game (more than one sub-game) made up of N sub-games and two players, A and B. Then Sprague-Grundy Theorem says that if both A and B play optimally (i.e., they don’t make any mistakes), then the player starting first is guaranteed to win if the XOR of the grundy numbers of position in each sub-games at the beginning of the game is non-zero. Otherwise, if the XOR evaluates to zero, then player A will lose definitely, no matter what.
How to apply Sprague Grundy Theorem ?
We can apply Sprague-Grundy Theorem in any impartial game and solve it. The basic steps are listed as follows:
- Break the composite game into sub-games.
- Then for each sub-game, calculate the Grundy Number at that position.
- Then calculate the XOR of all the calculated Grundy Numbers.
- If the XOR value is non-zero, then the player who is going to make the turn (First Player) will win else he is destined to lose, no matter what.
Example Game : The game starts with 3 piles having 3, 4 and 5 stones, and the player to move may take any positive number of stones upto 3 only from any of the piles [Provided that the pile has that much amount of stones]. The last player to move wins. Which player wins the game assuming that both players play optimally?
How to tell who will win by applying Sprague-Grundy Theorem?
As, we can see that this game is itself composed of several sub-games.
First Step : The sub-games can be considered as each piles.
Second Step : We see from the below table that
Grundy(3) = 3
Grundy(4) = 0
Grundy(5) = 1

We have already seen how to calculate the Grundy Numbers of this game in the previous article.
Third Step : The XOR of 3, 0, 1 = 2
Fourth Step : Since XOR is a non-zero number, so we can say that the first player will win.
Below is the program that implements above 4 steps.
C++
#include<bits/stdc++.h>
using namespace std;
#define PLAYER1 1
#define PLAYER2 2
int calculateMex(unordered_set< int > Set)
{
int Mex = 0;
while (Set.find(Mex) != Set.end())
Mex++;
return (Mex);
}
int calculateGrundy( int n, int Grundy[])
{
Grundy[0] = 0;
Grundy[1] = 1;
Grundy[2] = 2;
Grundy[3] = 3;
if (Grundy[n] != -1)
return (Grundy[n]);
unordered_set< int > Set;
for ( int i=1; i<=3; i++)
Set.insert (calculateGrundy (n-i, Grundy));
Grundy[n] = calculateMex (Set);
return (Grundy[n]);
}
void declareWinner( int whoseTurn, int piles[],
int Grundy[], int n)
{
int xorValue = Grundy[piles[0]];
for ( int i=1; i<=n-1; i++)
xorValue = xorValue ^ Grundy[piles[i]];
if (xorValue != 0)
{
if (whoseTurn == PLAYER1)
printf ( "Player 1 will win\n" );
else
printf ( "Player 2 will win\n" );
}
else
{
if (whoseTurn == PLAYER1)
printf ( "Player 2 will win\n" );
else
printf ( "Player 1 will win\n" );
}
return ;
}
int main()
{
int piles[] = {3, 4, 5};
int n = sizeof (piles)/ sizeof (piles[0]);
int maximum = *max_element(piles, piles + n);
int Grundy[maximum + 1];
memset (Grundy, -1, sizeof (Grundy));
for ( int i=0; i<=n-1; i++)
calculateGrundy(piles[i], Grundy);
declareWinner(PLAYER1, piles, Grundy, n);
return (0);
}
|
Java
import java.util.*;
class GFG {
static int PLAYER1 = 1 ;
static int PLAYER2 = 2 ;
static int calculateMex(HashSet<Integer> Set)
{
int Mex = 0 ;
while (Set.contains(Mex))
Mex++;
return (Mex);
}
static int calculateGrundy( int n, int Grundy[])
{
Grundy[ 0 ] = 0 ;
Grundy[ 1 ] = 1 ;
Grundy[ 2 ] = 2 ;
Grundy[ 3 ] = 3 ;
if (Grundy[n] != - 1 )
return (Grundy[n]);
HashSet<Integer> Set = new HashSet<Integer>();
for ( int i = 1 ; i <= 3 ; i++)
Set.add(calculateGrundy (n - i, Grundy));
Grundy[n] = calculateMex (Set);
return (Grundy[n]);
}
static void declareWinner( int whoseTurn, int piles[],
int Grundy[], int n)
{
int xorValue = Grundy[piles[ 0 ]];
for ( int i = 1 ; i <= n - 1 ; i++)
xorValue = xorValue ^ Grundy[piles[i]];
if (xorValue != 0 )
{
if (whoseTurn == PLAYER1)
System.out.printf( "Player 1 will win\n" );
else
System.out.printf( "Player 2 will win\n" );
}
else
{
if (whoseTurn == PLAYER1)
System.out.printf( "Player 2 will win\n" );
else
System.out.printf( "Player 1 will win\n" );
}
return ;
}
public static void main(String[] args)
{
int piles[] = { 3 , 4 , 5 };
int n = piles.length;
int maximum = Arrays.stream(piles).max().getAsInt();
int Grundy[] = new int [maximum + 1 ];
Arrays.fill(Grundy, - 1 );
for ( int i = 0 ; i <= n - 1 ; i++)
calculateGrundy(piles[i], Grundy);
declareWinner(PLAYER1, piles, Grundy, n);
}
}
|
Python3
PLAYER1 = 1
PLAYER2 = 2
def calculateMex( Set ):
Mex = 0 ;
while (Mex in Set ):
Mex + = 1
return (Mex)
def calculateGrundy(n, Grundy):
Grundy[ 0 ] = 0
Grundy[ 1 ] = 1
Grundy[ 2 ] = 2
Grundy[ 3 ] = 3
if (Grundy[n] ! = - 1 ):
return (Grundy[n])
Set = set ()
for i in range ( 1 , 4 ):
Set .add(calculateGrundy(n - i,
Grundy))
Grundy[n] = calculateMex( Set )
return (Grundy[n])
def declareWinner(whoseTurn, piles, Grundy, n):
xorValue = Grundy[piles[ 0 ]];
for i in range ( 1 , n):
xorValue = (xorValue ^
Grundy[piles[i]])
if (xorValue ! = 0 ):
if (whoseTurn = = PLAYER1):
print ( "Player 1 will win\n" );
else :
print ( "Player 2 will win\n" );
else :
if (whoseTurn = = PLAYER1):
print ( "Player 2 will win\n" );
else :
print ( "Player 1 will win\n" );
if __name__ = = "__main__" :
piles = [ 3 , 4 , 5 ]
n = len (piles)
maximum = max (piles)
Grundy = [ - 1 for i in range (maximum + 1 )];
for i in range (n):
calculateGrundy(piles[i], Grundy);
declareWinner(PLAYER1, piles, Grundy, n);
|
C#
using System;
using System.Linq;
using System.Collections.Generic;
class GFG
{
static int PLAYER1 = 1;
static int calculateMex(HashSet< int > Set)
{
int Mex = 0;
while (Set.Contains(Mex))
Mex++;
return (Mex);
}
static int calculateGrundy( int n, int []Grundy)
{
Grundy[0] = 0;
Grundy[1] = 1;
Grundy[2] = 2;
Grundy[3] = 3;
if (Grundy[n] != -1)
return (Grundy[n]);
HashSet< int > Set = new HashSet< int >();
for ( int i = 1; i <= 3; i++)
Set.Add(calculateGrundy (n - i, Grundy));
Grundy[n] = calculateMex (Set);
return (Grundy[n]);
}
static void declareWinner( int whoseTurn, int []piles,
int []Grundy, int n)
{
int xorValue = Grundy[piles[0]];
for ( int i = 1; i <= n - 1; i++)
xorValue = xorValue ^ Grundy[piles[i]];
if (xorValue != 0)
{
if (whoseTurn == PLAYER1)
Console.Write( "Player 1 will win\n" );
else
Console.Write( "Player 2 will win\n" );
}
else
{
if (whoseTurn == PLAYER1)
Console.Write( "Player 2 will win\n" );
else
Console.Write( "Player 1 will win\n" );
}
return ;
}
static void Main()
{
int []piles = {3, 4, 5};
int n = piles.Length;
int maximum = piles.Max();
int []Grundy = new int [maximum + 1];
Array.Fill(Grundy, -1);
for ( int i = 0; i <= n - 1; i++)
calculateGrundy(piles[i], Grundy);
declareWinner(PLAYER1, piles, Grundy, n);
}
}
|
Javascript
<script>
let PLAYER1 = 1;
let PLAYER2 = 2;
function calculateMex(Set)
{
let Mex = 0;
while (Set.has(Mex))
Mex++;
return (Mex);
}
function calculateGrundy(n,Grundy)
{
Grundy[0] = 0;
Grundy[1] = 1;
Grundy[2] = 2;
Grundy[3] = 3;
if (Grundy[n] != -1)
return (Grundy[n]);
let Set = new Set();
for (let i = 1; i <= 3; i++)
Set.add(calculateGrundy (n - i, Grundy));
Grundy[n] = calculateMex (Set);
return (Grundy[n]);
}
function declareWinner(whoseTurn,piles,Grundy,n)
{
let xorValue = Grundy[piles[0]];
for (let i = 1; i <= n - 1; i++)
xorValue = xorValue ^ Grundy[piles[i]];
if (xorValue != 0)
{
if (whoseTurn == PLAYER1)
document.write( "Player 1 will win<br>" );
else
document.write( "Player 2 will win<br>" );
}
else
{
if (whoseTurn == PLAYER1)
document.write( "Player 2 will win<br>" );
else
document.write( "Player 1 will win<br>" );
}
return ;
}
let piles = [3, 4, 5];
let n = piles.length;
let maximum = Math.max(...piles)
let Grundy = new Array(maximum + 1);
for (let i=0;i<maximum+1;i++)
Grundy[i]=0;
for (let i = 0; i <= n - 1; i++)
calculateGrundy(piles[i], Grundy);
declareWinner(PLAYER1, piles, Grundy, n);
</script>
|
Output :
Player 1 will win
Time complexity : O(n^2), where n is the maximum number of stones in a pile.
Space complexity :O(n), as the Grundy array is used to store the results of subproblems to avoid redundant computations and it takes O(n) space.
References :
https://en.wikipedia.org/wiki/Sprague%E2%80%93Grundy_theorem
Exercise to the Readers: Consider the below game.
“A game is played by two players with N integers A1, A2, .., AN. On his/her turn, a player selects an integer, divides it by 2, 3, or 6, and then takes the floor. If the integer becomes 0, it is removed. The last player to move wins. Which player wins the game if both players play optimally?”
Hint : See the example 3 of previous article.
If you like GeeksforGeeks and would like to contribute, you can also write an article and mail your article to review-team@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.
Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above
Feeling lost in the world of random DSA topics, wasting time without progress? It's time for a change! Join our DSA course, where we'll guide you on an exciting journey to master DSA efficiently and on schedule.
Ready to dive in? Explore our Free Demo Content and join our DSA course, trusted by over 100,000 geeks!
Last Updated :
14 Feb, 2023
Like Article
Save Article