// file: nim.rex // author: Robert Keller // purpose: playing the nim game: // The game state is a list of positive integers, representing tokens in a // number of piles. Players alternate turns, removing some number of tokens // from a single pile (including all tokens). The player who takes the // last token wins. // Call play_nim on a list of piles to get rex's move. // For example, // // rex > play_nim([7, 3, 1, 6, 5]); // [1, 3, 1, 6, 5] // // which indicates that rex has taken 6 tokens from the first pile. play_nim(L) = make_new_list(nim_sum(L), L); nim_sum(L) = reduce(xor, 0, L); // make nim-sum of list L make_new_list(s, L) = drop_zeroes( s > 0 ? winning_move(s, L) : fake_it(L)); // winning_move function is called when a win is possible (s > 0) // It makes a new list obtained by changing the first item n for which // xor(s, n) < n to xor(s, n), which simulates removal of n-xor(s, n) // tokens winning_move(s, L) = change_first((n) => (xor(s, n) < n), (n) => xor(s, n), L); // When the nim-sum is not 0, a win can't be assure. Just make a move, // such as reducing the first element by 1. fake_it(L) = change_first((n) => n > 0, (n) => n-1, L); // drop_zeroes drops any 0's in the list drop_zeroes(L) = drop((n) => n == 0, L); // The functions change_first and xor seem to be best done by // "low-level" functional programming. // change_first(P, F, L) finds the first item in L satisfying P and // returns a new list in which that item, say A, is replaced by F(A). change_first(P, F, []) => []; change_first(P, F, [A | X]) => P(A) ? [F(A) | X]; change_first(P, F, [A | X]) => [A | change_first(P, F, X)]; // xor computes the bit-wise exclusive-or of // two numbers' binary representations xor(0, N) => N; xor(M, 0) => M; xor(1, 1) => 0; xor(M, N) => 2*xor(M/2, N/2) + xor(M % 2, N % 2); test() = map(play_nim, [[9, 2, 1, 5, 3, 7], [8, 8, 8, 8]]);