Nim
(game/nim.hpp)
- View this file on GitHub
- Last update: 2026-08-24 02:07:48+09:00
- Include:
#include "game/nim.hpp"
Overview
Helpers for ordinary Nim and misere Nim. A position is given as a range of nonnegative heap sizes. An empty or all-zero position is losing in ordinary Nim and winning under the misere convention, where the player with no move wins.
In ordinary Nim, the first player wins exactly when the xor of all heap sizes is nonzero. In misere Nim, the same rule applies while some heap has size at least two; if every nonempty heap has size one, the first player wins exactly when their count is even.
Functions
All functions are in namespace m1une::game. Heap values must support default
construction, ^=, comparison with zero, and, for misere Nim, comparison with
one.
NimMove<T> stores the zero-based heap index and its strictly smaller
new_size. Winning-move functions return std::nullopt for losing positions.
For the empty misere position they also return std::nullopt: the position is
winning because the player already has no move, so no move can be constructed.
| Function signature | Description | Complexity |
|---|---|---|
template<class Iterator>auto nim_sum(Iterator first, Iterator last)
|
Returns the xor of the heap sizes in [first, last). |
O(N) time, O(1) space |
template<class Range>auto nim_sum(const Range& heaps)
|
Range overload of nim_sum. |
O(N) time, O(1) space |
template<class Range>bool nim_first_player_wins(const Range& heaps)
|
Whether the first player wins ordinary Nim under normal play. |
O(N) time, O(1) space |
template<class Range>std::optional<NimMove<T>> nim_winning_move(const Range& heaps)
|
Returns a move to an ordinary-Nim losing position when one exists. |
O(N) time, O(1) space |
template<class Range>bool misere_nim_first_player_wins(const Range& heaps)
|
Whether the first player wins Nim when taking the last object loses. |
O(N) time, O(1) space |
template<class Range>std::optional<NimMove<T>> misere_nim_winning_move(const Range& heaps)
|
Returns a move to a misere-Nim losing position when one exists. |
O(N) time, O(1) space |
Example
#include "game/nim.hpp"
#include <iostream>
#include <vector>
int main() {
std::vector<int> heaps = {1, 4, 5};
std::cout << m1une::game::nim_sum(heaps) << '\n';
std::cout << m1une::game::nim_first_player_wins(heaps) << '\n';
auto move = m1une::game::nim_winning_move(heaps);
if (move) heaps[move->heap] = move->new_size;
std::vector<int> misere_heaps = {1, 1};
std::cout << m1une::game::misere_nim_first_player_wins(misere_heaps) << '\n';
}
Required by
Verified with
Code
#ifndef M1UNE_GAME_NIM_HPP
#define M1UNE_GAME_NIM_HPP 1
#include <iterator>
#include <optional>
#include <type_traits>
#include <utility>
namespace m1une {
namespace game {
template <typename T>
struct NimMove {
int heap;
T new_size;
};
template <typename Iterator>
auto nim_sum(Iterator first, Iterator last) {
using T = typename std::iterator_traits<Iterator>::value_type;
T result{};
while (first != last) {
result ^= *first;
++first;
}
return result;
}
template <typename Range>
auto nim_sum(const Range& heaps) {
using std::begin;
using std::end;
return nim_sum(begin(heaps), end(heaps));
}
template <typename Range>
bool nim_first_player_wins(const Range& heaps) {
return nim_sum(heaps) != 0;
}
template <typename Range>
auto nim_winning_move(const Range& heaps) {
using std::begin;
using std::end;
using T = std::decay_t<decltype(*begin(heaps))>;
const T sum = nim_sum(heaps);
if (sum == 0) return std::optional<NimMove<T>>{};
int index = 0;
for (
auto iterator = begin(heaps);
iterator != end(heaps);
++iterator, ++index
) {
const T new_size = *iterator ^ sum;
if (new_size < *iterator) return std::optional(NimMove<T>{index, new_size});
}
return std::optional<NimMove<T>>{};
}
template <typename Range>
bool misere_nim_first_player_wins(const Range& heaps) {
using std::begin;
using std::end;
auto first = begin(heaps);
const auto last = end(heaps);
bool odd_nonzero_heaps = false;
bool has_large_heap = false;
using T = typename std::iterator_traits<decltype(first)>::value_type;
T sum{};
for (; first != last; ++first) {
sum ^= *first;
if (*first != 0) {
odd_nonzero_heaps = !odd_nonzero_heaps;
}
if (*first > 1) has_large_heap = true;
}
return has_large_heap ? sum != 0 : !odd_nonzero_heaps;
}
template <typename Range>
auto misere_nim_winning_move(const Range& heaps) {
using std::begin;
using std::end;
using T = std::decay_t<decltype(*begin(heaps))>;
T sum{};
int ones = 0;
int large_heaps = 0;
int only_large_heap = -1;
int index = 0;
for (
auto iterator = begin(heaps);
iterator != end(heaps);
++iterator, ++index
) {
sum ^= *iterator;
if (*iterator == 1) ones++;
if (*iterator > 1) {
large_heaps++;
only_large_heap = index;
}
}
if (large_heaps == 0) {
if (ones == 0 || ones % 2 == 1) return std::optional<NimMove<T>>{};
index = 0;
for (
auto iterator = begin(heaps);
iterator != end(heaps);
++iterator, ++index
) {
if (*iterator == 1) return std::optional(NimMove<T>{index, T(0)});
}
}
if (large_heaps == 1) {
const T new_size = ones % 2 == 0 ? T(1) : T(0);
return std::optional(NimMove<T>{only_large_heap, new_size});
}
if (sum == 0) return std::optional<NimMove<T>>{};
index = 0;
for (auto iterator = begin(heaps); iterator != end(heaps); ++iterator, ++index) {
const T new_size = *iterator ^ sum;
if (new_size < *iterator) return std::optional(NimMove<T>{index, new_size});
}
return std::optional<NimMove<T>>{};
}
} // namespace game
} // namespace m1une
#endif // M1UNE_GAME_NIM_HPP#line 1 "game/nim.hpp"
#include <iterator>
#include <optional>
#include <type_traits>
#include <utility>
namespace m1une {
namespace game {
template <typename T>
struct NimMove {
int heap;
T new_size;
};
template <typename Iterator>
auto nim_sum(Iterator first, Iterator last) {
using T = typename std::iterator_traits<Iterator>::value_type;
T result{};
while (first != last) {
result ^= *first;
++first;
}
return result;
}
template <typename Range>
auto nim_sum(const Range& heaps) {
using std::begin;
using std::end;
return nim_sum(begin(heaps), end(heaps));
}
template <typename Range>
bool nim_first_player_wins(const Range& heaps) {
return nim_sum(heaps) != 0;
}
template <typename Range>
auto nim_winning_move(const Range& heaps) {
using std::begin;
using std::end;
using T = std::decay_t<decltype(*begin(heaps))>;
const T sum = nim_sum(heaps);
if (sum == 0) return std::optional<NimMove<T>>{};
int index = 0;
for (
auto iterator = begin(heaps);
iterator != end(heaps);
++iterator, ++index
) {
const T new_size = *iterator ^ sum;
if (new_size < *iterator) return std::optional(NimMove<T>{index, new_size});
}
return std::optional<NimMove<T>>{};
}
template <typename Range>
bool misere_nim_first_player_wins(const Range& heaps) {
using std::begin;
using std::end;
auto first = begin(heaps);
const auto last = end(heaps);
bool odd_nonzero_heaps = false;
bool has_large_heap = false;
using T = typename std::iterator_traits<decltype(first)>::value_type;
T sum{};
for (; first != last; ++first) {
sum ^= *first;
if (*first != 0) {
odd_nonzero_heaps = !odd_nonzero_heaps;
}
if (*first > 1) has_large_heap = true;
}
return has_large_heap ? sum != 0 : !odd_nonzero_heaps;
}
template <typename Range>
auto misere_nim_winning_move(const Range& heaps) {
using std::begin;
using std::end;
using T = std::decay_t<decltype(*begin(heaps))>;
T sum{};
int ones = 0;
int large_heaps = 0;
int only_large_heap = -1;
int index = 0;
for (
auto iterator = begin(heaps);
iterator != end(heaps);
++iterator, ++index
) {
sum ^= *iterator;
if (*iterator == 1) ones++;
if (*iterator > 1) {
large_heaps++;
only_large_heap = index;
}
}
if (large_heaps == 0) {
if (ones == 0 || ones % 2 == 1) return std::optional<NimMove<T>>{};
index = 0;
for (
auto iterator = begin(heaps);
iterator != end(heaps);
++iterator, ++index
) {
if (*iterator == 1) return std::optional(NimMove<T>{index, T(0)});
}
}
if (large_heaps == 1) {
const T new_size = ones % 2 == 0 ? T(1) : T(0);
return std::optional(NimMove<T>{only_large_heap, new_size});
}
if (sum == 0) return std::optional<NimMove<T>>{};
index = 0;
for (auto iterator = begin(heaps); iterator != end(heaps); ++iterator, ++index) {
const T new_size = *iterator ^ sum;
if (new_size < *iterator) return std::optional(NimMove<T>{index, new_size});
}
return std::optional<NimMove<T>>{};
}
} // namespace game
} // namespace m1une