disco-0.1.0.0: example/nim.disco
import list
I : Bool
I = true
O : Bool
O = false
||| Convert a natural number into a list of bits, with the *least*
||| significant bit first.
!!! toBinary 0 == ([] : List(Bool))
!!! toBinary 1 == ([I] : List(Bool))
!!! toBinary 2 == ([O,I] : List(Bool))
!!! toBinary 534 == ([O,I,I,O,I,O,O,O,O,I] : List(Bool))
toBinary : N -> List(Bool)
toBinary 0 = []
toBinary n =
{? O :: toBinary (n // 2) if 2 divides n
, I :: toBinary (n // 2) otherwise
?}
||| Convert a list of bits (with the least significant bit first) back
||| into a natural number. Left inverse of toBinary.
!!! fromBinary [O,O,I,I] == 12
!!! fromBinary [O,O,O,O] == 0
!!! ∀ n : N. fromBinary (toBinary n) == n
fromBinary : List(Bool) -> N
fromBinary [] = 0
fromBinary (false :: b) = 2 * fromBinary b
fromBinary (true :: b) = 1 + 2 * fromBinary b
xorB : Bool * Bool -> Bool
xorB = ~/=~
xor : List(Bool) * List(Bool) -> List(Bool)
xor([], bs) = bs
xor(bs, []) = bs
xor(a::as, b::bs) = (xorB(a,b)) :: xor(as, bs)
xorN : N*N -> N
xorN(a, b) = fromBinary (xor(toBinary a, toBinary b))
nimSum : List(N) -> N
nimSum ns = fromBinary(reduce(xor, [], each(toBinary, ns)))
xorPile : N -> List(N) -> List(N)
xorPile _ [] = []
xorPile x (n :: ns)
= {? xorN(x, n) :: ns if xorN(x, n) < n
, n :: xorPile x ns otherwise
?}
||| Perform the optimal nim move, or report that the position is a
||| losing position.
!!! nimMove [1,5,8] == (right [1,5,4] : Unit + List(N))
nimMove : List(N) -> Unit + List(N)
nimMove ls =
let s = nimSum ls
, ls' = xorPile s ls
in {? left unit if ls == ls'
, right ls' otherwise
?}