principleMinor
Nim game in Haskell implementing optimal strategy
Viewed 0 times
optimalnimgamehaskellstrategyimplementing
Problem
I was inspired by Stas Kurlin's Nim game to write my own. I'm new to Haskell, and quite unfamiliar with monads, do notation, and -- in general -- functional design patterns.
In the game of nim, two players begin with a number of sized piles, e.g. piles of stones. Each player moves by taking a (non-zero) number of stones from one pile. The winning player is the one who takes the last stone (i.e. the one who makes every pile size identically zero).
In my game, a
The user interacts with the game by entering space-separated pile sizes, which are then parsed into a list of Word64s, and these Word64s are converted to a
The goal of this
The function
(In this case nextMove reduces the size of the largest pile by one; I don't have any good reason why, except that it probably makes it inconvenient for the human opponent, who must calculate the nim-sum to play optimally, but probably can't calculate the bitwise-xor or a list of large integers as fast as she could a list of smaller integers.)
(See this)
Like I said, I'm unfam
In the game of nim, two players begin with a number of sized piles, e.g. piles of stones. Each player moves by taking a (non-zero) number of stones from one pile. The winning player is the one who takes the last stone (i.e. the one who makes every pile size identically zero).
In my game, a
NimPosition is a Map from Word64s to Word64s, where the keys are distinct pile sizes, and the values are the number of piles with that size.The user interacts with the game by entering space-separated pile sizes, which are then parsed into a list of Word64s, and these Word64s are converted to a
NimPosition using the fromList function.The goal of this
Map implementation is to ensure that each NimPosition has a unique representation without making the user have to think too hard about how to enter a position during play. However, I'm not too Data.Map is necessary; it makes more sense to me now to have a NimPosition be a list of Word64s, and ensure that each NimPosition is unique by having fromList be a sort function.The function
nextMove (which I realize now is not a terribly descriptive name) calculates the optimal move to make from a given NimPosition. In the case that the bitwise-xor (aka nim-sum) of all the pile sizes isn't zero, then the optimal play is the (not necessarily unique) move that makes the nim-sum zero. If the nim-sum is already zero, there is no way to make it zero, so there is no optimal move.(In this case nextMove reduces the size of the largest pile by one; I don't have any good reason why, except that it probably makes it inconvenient for the human opponent, who must calculate the nim-sum to play optimally, but probably can't calculate the bitwise-xor or a list of large integers as fast as she could a list of smaller integers.)
(See this)
Like I said, I'm unfam
Solution
Some ideas:
-
For
-
Rather than
Otherwise nice program! I also like that you meaningfully named variables, this really helps reading the code.
- For
BitandBinaryusenewtyperather thandatato get rid ofdata's run-time overhead.
- Instead of custom
Bitsyou could use theData.Bitsinstance ofInteger. This would simplify or remove a lot related code.
- As you noted, for
NimPositionyou could either use just a list, or even a multi-set.
-
For
findNumWithLeadingBit function maximumBy seems to be useful. Or perhaps even more simplified, something like (untested)where
withLengths = map (id &&& (length . show . toBinary)) xs
maxBinaryLength = maximum . map snd $ withLengths
numsWithMaxBinaryLength = filter ((== maxBinaryLength) . snd) withLengths
maxBinaryLengthIsUnique = length numsWithMaxBinaryLength == 1-
Rather than
if it's often more readable to use guards, for example:nextMove prevPosition | winning prevPosition = ...
| otherwise = ...- Code that tries various options, to eventually find one that matches some criteria, can be often nicely expressed using the
[]orMaybemonad usingMonadPlusfunctions. Package monadplus has more useful functions, for examplemfromList.
- It's strongly recommended to include types for all top-level functions.
Otherwise nice program! I also like that you meaningfully named variables, this really helps reading the code.
Code Snippets
where
withLengths = map (id &&& (length . show . toBinary)) xs
maxBinaryLength = maximum . map snd $ withLengths
numsWithMaxBinaryLength = filter ((== maxBinaryLength) . snd) withLengths
maxBinaryLengthIsUnique = length numsWithMaxBinaryLength == 1nextMove prevPosition | winning prevPosition = ...
| otherwise = ...Context
StackExchange Code Review Q#78817, answer score: 2
Revisions (0)
No revisions yet.