patternMinor
Simulate Random Risk Battle
Viewed 0 times
randombattlesimulaterisk
Problem
For Prof. Yorgey's 2013 Haskell course, I'm working on a homework to simulate a Risk battle.
In addition, Prof. Yorgey implement the following code to simulate the random throwing of dice:
The second homework question asks to implement the
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:
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) $ m
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 = getRandomThe 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 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 = 0Testing
```
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) $ m
Solution
rollDieN doesn't look very random to me*Risk> map unDV $ rollDieN 100
[5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6]This looks better:
*Risk> map unDV $ evalRand (replicateM 100 die) (mkStdGen 123456)
[4,5,2,3,5,6,2,3,2,5,4,5,5,5,1,1,6,6,2,3,2,5,5,3,4,1,3,1,
3,2,4,4,3,2,2,6,2,2,3,1,6,6,4,5,3,6,6,6,5,2,2,1,6,3,6,3,
6,3,6,5,6,5,1,1,1,2,1,5,4,4,3,3,3,3,3,3,4,5,5,2,4,1,5,4,
4,2,1,1,5,1,4,2,6,5,3,5,2,3,6,1]Now we can write a function that will return the two army's dice rolls
rollDice n = replicateM n die
battleTest :: Battlefield -> Rand StdGen ([Int], [Int])
battleTest battlefield = do
attackerRolls <- rollDice $ getLegalAttackers (attackers battlefield)
defenderRolls <- rollDice $ getLegalDefenders (defenders battlefield)
return (map unDV attackerRolls, map unDV defenderRolls)And check that our output looks sane
*Risk> evalRand (battleTest (Battlefield 10 20)) (mkStdGen 12)
([6,3,2],[5,1])
*Risk> evalRand (battleTest (Battlefield 10 20)) (mkStdGen 123456)
([4,5,2],[3,5])Now assuming a function to update the battlefield based on dice rolls, we just change it to
battle :: Battlefield -> Rand StdGen Battlefield
battle battlefield = do
attackerRolls <- rollDice $ getLegalAttackers (attackers battlefield)
defenderRolls <- rollDice $ getLegalDefenders (defenders battlefield)
return $ nextBattlefield attackerRolls defenderRolls battlefieldThere's also a problem with
getLegalDefenders. The homework saysThe defending player may defend with up to two units (or only one if
that is all they have).
So instead of
getLegalDefenders :: Army -> Army
getLegalDefenders n
| n >= 4 = 2
| n == 3 = 1
| otherwise = 0It should look like
getLegalDefenders :: Army -> Army
getLegalDefenders n
| n >= 2 = 2
| n == 1 = 1
| otherwise = 0Or
getLegalDefenders n = max 0 (min 2 n)Code Snippets
*Risk> map unDV $ rollDieN 100
[5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6]*Risk> map unDV $ evalRand (replicateM 100 die) (mkStdGen 123456)
[4,5,2,3,5,6,2,3,2,5,4,5,5,5,1,1,6,6,2,3,2,5,5,3,4,1,3,1,
3,2,4,4,3,2,2,6,2,2,3,1,6,6,4,5,3,6,6,6,5,2,2,1,6,3,6,3,
6,3,6,5,6,5,1,1,1,2,1,5,4,4,3,3,3,3,3,3,4,5,5,2,4,1,5,4,
4,2,1,1,5,1,4,2,6,5,3,5,2,3,6,1]rollDice n = replicateM n die
battleTest :: Battlefield -> Rand StdGen ([Int], [Int])
battleTest battlefield = do
attackerRolls <- rollDice $ getLegalAttackers (attackers battlefield)
defenderRolls <- rollDice $ getLegalDefenders (defenders battlefield)
return (map unDV attackerRolls, map unDV defenderRolls)*Risk> evalRand (battleTest (Battlefield 10 20)) (mkStdGen 12)
([6,3,2],[5,1])
*Risk> evalRand (battleTest (Battlefield 10 20)) (mkStdGen 123456)
([4,5,2],[3,5])battle :: Battlefield -> Rand StdGen Battlefield
battle battlefield = do
attackerRolls <- rollDice $ getLegalAttackers (attackers battlefield)
defenderRolls <- rollDice $ getLegalDefenders (defenders battlefield)
return $ nextBattlefield attackerRolls defenderRolls battlefieldContext
StackExchange Code Review Q#78470, answer score: 6
Revisions (0)
No revisions yet.