For Prof. Yorgey's 2013 Haskell course, I'm working on a homework to simulate a Risk battle.
type Army = Int
data Battlefield = Battlefield { attackers :: Army, defenders :: Army }
deriving (Show, Eq)
In addition, Prof. Yorgey implement the following code to simulate the random throwing of dice:
newtype DieValue = DV { unDV :: Int }
deriving (Eq, Ord, Show, Num)
first :: (a -> b) -> (a, c) -> (b, c)
first f (a, c) = (f a, c)
instance Random DieValue where
random = first DV . randomR (1,6)
randomR (low,hi) = first DV . randomR (max 1 (unDV low), min 6 (unDV hi))
die :: Rand StdGen DieValue
die = getRandom
The second homework question asks to implement the battle
function:
which simulates a single battle (as explained above) between two opposing armies. That is, it should simulate randomly rolling the appropriate number of dice, interpreting the results, and updating the two armies to reflect casualties. You may assume that each player will attack or defend with the maximum number of units they are allowed.
And then for my implementation:
battle :: Battlefield -> Rand StdGen Battlefield
battle bf = return $ battleOneRound bf
battleOneRound :: Battlefield -> Battlefield
battleOneRound bf = updateArmy bf (compete a_dice d_dice)
where
a_dice = rollDieN . getLegalAttackers $ (attackers bf)
d_dice = rollDieN . getLegalDefenders $ (defenders bf)
rollDieN :: Army -> [DieValue]
rollDieN n
| n <= 0 = []
| otherwise = evalRand die (mkStdGen (1000*n)) : rollDieN (n-1)
type AttackersDice = [DieValue]
type DefendersDice = [DieValue]
-- defender must have at least 2 left at base
getLegalDefenders :: Army -> Army
getLegalDefenders n
| n >= 4 = 2
| n == 3 = 1
| otherwise = 0
-- attackers must have at least 1 left at base
getLegalAttackers :: Army -> Army
getLegalAttackers n
| n >= 4 = 3
| n == 3 = 2
| n == 2 = 1
| otherwise = 0
Testing
ghci> let bf = battle $ Battlefield 10 20
ghci> (evalRand bf) $ mkStdGen 10
Battlefield {attackers = 8, defenders = 20}
ghci> (evalRand bf) $ mkStdGen 5
Battlefield {attackers = 8, defenders = 20}
ghci> (evalRand bf) $ mkStdGen 444444
Battlefield {attackers = 8, defenders = 20}
As you can see, the actual StdGen
argument does not appear to have any impact on the battle results. That's because rollDieN
uses mkStdGen (1000*n)
to simulate random die throws.
Please critique this implementation. I'm not sure if I've achieved randomness in the battle
function.
StdGen
not affecting the outcome, that is intended behavior, correct? \$\endgroup\$battle
function in a way that aligns with the homework question. \$\endgroup\$