Sprague Grundy Theorem

P
/**
 * Sprague Grundy Theorem for combinatorial games like Nim
 *
 * The Sprague Grundy Theorem is a fundamental concept in combinatorial game theory, commonly used to analyze
 * games like Nim. It calculates the Grundy number (also known as the nimber) for a position in a game.
 * The Grundy number represents the game's position, and it helps determine the winning strategy.
 *
 * The Grundy number of a terminal state is 0; otherwise, it is recursively defined as the minimum
 * excludant (mex) of the Grundy values of possible next states.
 *
 * For more details on Sprague Grundy Theorem, you can visit:(https://en.wikipedia.org/wiki/Sprague%E2%80%93Grundy_theorem)
 *
 * Author : [Gyandeep](https://github.com/Gyan172004)
 */

pub fn calculate_grundy_number(
    position: i64,
    grundy_numbers: &mut [i64],
    possible_moves: &[i64],
) -> i64 {
    // Check if we've already calculated the Grundy number for this position.
    if grundy_numbers[position as usize] != -1 {
        return grundy_numbers[position as usize];
    }

    // Base case: terminal state
    if position == 0 {
        grundy_numbers[0] = 0;
        return 0;
    }

    // Calculate Grundy values for possible next states.
    let mut next_state_grundy_values: Vec<i64> = vec![];
    for move_size in possible_moves.iter() {
        if position - move_size >= 0 {
            next_state_grundy_values.push(calculate_grundy_number(
                position - move_size,
                grundy_numbers,
                possible_moves,
            ));
        }
    }

    // Sort the Grundy values and find the minimum excludant.
    next_state_grundy_values.sort_unstable();
    let mut mex: i64 = 0;
    for grundy_value in next_state_grundy_values.iter() {
        if *grundy_value != mex {
            break;
        }
        mex += 1;
    }

    // Store the calculated Grundy number and return it.
    grundy_numbers[position as usize] = mex;
    mex
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn calculate_grundy_number_test() {
        let mut grundy_numbers: Vec<i64> = vec![-1; 7];
        let possible_moves: Vec<i64> = vec![1, 4];
        calculate_grundy_number(6, &mut grundy_numbers, &possible_moves);
        assert_eq!(grundy_numbers, [0, 1, 0, 1, 2, 0, 1]);
    }
}