mirror of
https://github.com/sbrow/risk-multilang.git
synced 2025-12-29 16:37:39 -05:00
feat: Added Haskell and Go.
This commit is contained in:
5
haskell/CHANGELOG.md
Normal file
5
haskell/CHANGELOG.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Revision history for risk
|
||||
|
||||
## 0.1.0.0 -- YYYY-mm-dd
|
||||
|
||||
* First version. Released on an unsuspecting world.
|
||||
19
haskell/app/Main.hs
Normal file
19
haskell/app/Main.hs
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env stack
|
||||
-- stack runghc
|
||||
|
||||
import Risk
|
||||
|
||||
maximum' :: Ord a => [a] -> a
|
||||
maximum' = foldr1 (\x y ->if x >= y then x else y)
|
||||
|
||||
maximum'' :: Ord a => [a] -> a
|
||||
maximum'' [x] = x
|
||||
maximum'' (x:x':xs) = maximum' ((if x >= x' then x else x'):xs)
|
||||
|
||||
a :: [Int]
|
||||
a = [3,5,8,6]
|
||||
|
||||
main :: IO ()
|
||||
main = print $ maximum'' a
|
||||
-- main = print $ possibleOutcomes 3 3
|
||||
-- main = print $ Result(3, 5) + Result(1, 2)
|
||||
2
haskell/cabal.project.local
Normal file
2
haskell/cabal.project.local
Normal file
@@ -0,0 +1,2 @@
|
||||
ignore-project: False
|
||||
tests: True
|
||||
46
haskell/risk.cabal
Normal file
46
haskell/risk.cabal
Normal file
@@ -0,0 +1,46 @@
|
||||
cabal-version: 2.4
|
||||
name: risk
|
||||
version: 0.1.0.0
|
||||
|
||||
-- A short (one-line) description of the package.
|
||||
-- synopsis:
|
||||
|
||||
-- A longer description of the package.
|
||||
-- description:
|
||||
|
||||
-- A URL where users can report bugs.
|
||||
-- bug-reports:
|
||||
|
||||
-- The license under which the package is released.
|
||||
-- license:
|
||||
author: spencer
|
||||
maintainer: brower.spencer@gmail.com
|
||||
|
||||
-- A copyright notice.
|
||||
-- copyright:
|
||||
-- category:
|
||||
extra-source-files: CHANGELOG.md
|
||||
|
||||
executable risk
|
||||
main-is: Main.hs
|
||||
|
||||
-- Modules included in this executable, other than Main.
|
||||
other-modules: Dice, Risk
|
||||
|
||||
-- LANGUAGE extensions used by modules in this package.
|
||||
-- other-extensions:
|
||||
build-depends: base ^>=4.14.1.0, containers, combinat
|
||||
hs-source-dirs: app, src
|
||||
default-language: Haskell2010
|
||||
|
||||
Test-Suite test
|
||||
type: exitcode-stdio-1.0
|
||||
main-is: Spec.hs
|
||||
hs-source-dirs: test
|
||||
default-language: Haskell2010
|
||||
build-depends:
|
||||
base >= 4 && < 5,
|
||||
tasty >= 1.4.1,
|
||||
tasty-hunit,
|
||||
tasty-quickcheck,
|
||||
tasty-smallcheck
|
||||
21
haskell/src/Dice.hs
Normal file
21
haskell/src/Dice.hs
Normal file
@@ -0,0 +1,21 @@
|
||||
module Dice (d, dice, total) where
|
||||
|
||||
infixl 5 `d`
|
||||
d :: Integer -> Integer -> [[Integer]]
|
||||
n `d` s = dice n s
|
||||
|
||||
-- Returns the entire possibility space of rolling d dice with sides sides.
|
||||
dice :: Integer -> Integer -> [[Integer]]
|
||||
dice d sides
|
||||
| d == 1 = [ [d] | d<-[1..sides]]
|
||||
| otherwise = combine (dice (d-1) sides) (dice 1 sides)
|
||||
|
||||
-- Returns the total possible dice combinations
|
||||
total :: Integer -> Integer -> Integer
|
||||
total d sides
|
||||
| d == 1 = sides
|
||||
| otherwise = (total (d-1) sides) * sides
|
||||
|
||||
-- Get the union of all combinations of set a with set b
|
||||
combine :: [[Integer]] -> [[Integer]] -> [[Integer]]
|
||||
combine a b = [ x ++ y | x<-a, y<-b ]
|
||||
128
haskell/src/Risk.hs
Executable file
128
haskell/src/Risk.hs
Executable file
@@ -0,0 +1,128 @@
|
||||
-- | Functions for exploring the possibility space of the board game Risk.
|
||||
module Risk (Result (Result), Possibility (Possibility), battle, fight, odds, possibleOutcomes) where
|
||||
|
||||
import Data.List
|
||||
import Data.Ord
|
||||
import Data.Tree
|
||||
import Dice
|
||||
|
||||
-- | Represents A number of attackers and defenders respectively.
|
||||
data Result = Result (Integer, Integer) deriving (Eq, Ord, Show)
|
||||
|
||||
instance Num Result where
|
||||
Result (x, y) + Result (x', y') = Result (x + x', y + y')
|
||||
Result (x, y) - Result (x', y') = Result (x - x', y - y')
|
||||
Result (x, y) * Result (x', y') = Result (x * x', y * y')
|
||||
abs (Result (x, y)) = Result (abs x, abs y)
|
||||
signum (Result (x, y)) = Result (signum x, signum y)
|
||||
|
||||
-- | A Particular result, and the Probability of arriving at it.
|
||||
-- The Probability should be a number between 0 and 1, inclusive.
|
||||
data Possibility = Possibility (Result, Float) deriving (Ord, Show)
|
||||
|
||||
instance Num Possibility where
|
||||
Possibility (a, b) + Possibility (a', b') = Possibility (a, b + b')
|
||||
Possibility (a, b) - Possibility (a', b') = Possibility (a, b - b')
|
||||
abs pos = pos
|
||||
signum pos = 1
|
||||
|
||||
instance Eq Possibility where
|
||||
Possibility (a, b) == Possibility (a', b') = a == a'
|
||||
|
||||
-- | Compares two dice and returns the casualties as a 'Result'.
|
||||
-- The 'Result' returned is relative e.g.
|
||||
-- @
|
||||
-- Result(0, 1)
|
||||
-- @
|
||||
-- . You must add the 'Result' to Your starting numbers.
|
||||
battle :: Integer -> Integer -> Result
|
||||
battle att def
|
||||
| att == 0 = Result (0, 0)
|
||||
| def == 0 = Result (0, 0)
|
||||
| att <= def = Result (-1, 0)
|
||||
| att > def = Result (0, -1)
|
||||
|
||||
-- | Returns the losses of a battle given the attacking and defending dice rolls.
|
||||
-- Requires pre-sorted arguments
|
||||
fight :: ((Integer, Integer, Integer), (Integer, Integer)) -> Result
|
||||
fight ((attHigh, attMid, attLow), (defHigh, defLow)) = battle attHigh defHigh + battle attMid defLow
|
||||
|
||||
-- | Returns a Tree of all the possible results of fighting,
|
||||
-- including intermediate (non-terminal) results.
|
||||
odds :: Integer -> Integer -> Tree Possibility
|
||||
odds att def = unfoldTree buildNode $ Possibility (Result (att, def), 1.0)
|
||||
where
|
||||
buildNode :: Possibility -> (Possibility, [Possibility])
|
||||
buildNode node
|
||||
| def == 0 || att == 0 = (node, [])
|
||||
| def == 1 && att == 1 = (node, oneVoneLosses node)
|
||||
| def == 1 && att == 2 = (node, twoVoneLosses node)
|
||||
| def == 1 && att >= 3 = (node, threeVoneLosses node)
|
||||
| def >= 2 && att == 1 = (node, oneVtwoLosses node)
|
||||
| def >= 2 && att == 2 = (node, twoVtwoLosses node)
|
||||
| def >= 2 && att >= 3 = (node, threeVtwoLosses node)
|
||||
where
|
||||
Possibility (Result (att, def), p) = node
|
||||
|
||||
-- | Returrns a linked list of all the possible terminal outcomes of fighting.
|
||||
-- Assumes attackers and defenders will always roll as many dice as possible,
|
||||
-- And will contiune fighting until one side is dead.
|
||||
possibleOutcomes :: Integer -> Integer -> [Possibility]
|
||||
possibleOutcomes att def = outcomes
|
||||
where
|
||||
outcomes = [sumProbabilities p | p <- groupedResults]
|
||||
sumProbabilities = foldr (+) (Possibility (Result (0, 0), 0))
|
||||
groupedResults = (group . reverse . sort . removeStems . flatten) results
|
||||
removeStems = filter onlyLeaves
|
||||
onlyLeaves (Possibility (Result (att, def), p)) = att == 0 || def == 0
|
||||
results = odds att def
|
||||
|
||||
-- Converts a list of dice rolls into tuples that can be fed to fight.
|
||||
toRolls :: [[Integer]] -> [((Integer, Integer, Integer), (Integer, Integer))]
|
||||
toRolls dice = [toTuple . sortArgs . splitArgs $ x | x <- dice]
|
||||
where
|
||||
-- Split args int attackers and defenders
|
||||
splitArgs = splitAt 3
|
||||
sortArgs (att, def) = (sortOn Down att, sortOn Down def)
|
||||
toTuple (x, y) = ((x !! 0, x !! 1, x !! 2), (y !! 0, y !! 1))
|
||||
|
||||
groupRolls :: [((Integer, Integer, Integer), (Integer, Integer))] -> [[Result]]
|
||||
groupRolls rolls = (group . reverse . sort) [fight roll | roll <- rolls]
|
||||
|
||||
rollsByProbability :: [((Integer, Integer, Integer), (Integer, Integer))] -> Integer -> Possibility -> [Possibility]
|
||||
rollsByProbability rolls tot (Possibility (units, p)) = [Possibility (units + head loss, p * (fromIntegral (length loss) / fromIntegral tot)) | loss <- groupRolls rolls]
|
||||
|
||||
oneVoneLosses :: Possibility -> [Possibility]
|
||||
oneVoneLosses = rollsByProbability rolls (total 2 6)
|
||||
where
|
||||
rolls = toRolls . fill $ 2 `d` 6
|
||||
fill rolls = [[roll !! 0, 0, 0, roll !! 1, 0] | roll <- rolls]
|
||||
|
||||
twoVoneLosses :: Possibility -> [Possibility]
|
||||
twoVoneLosses = rollsByProbability rolls (total 3 6)
|
||||
where
|
||||
rolls = toRolls . fill $ 3 `d` 6
|
||||
fill rolls = [[roll !! 0, roll !! 1, 0, roll !! 2, 0] | roll <- rolls]
|
||||
|
||||
oneVtwoLosses :: Possibility -> [Possibility]
|
||||
oneVtwoLosses = rollsByProbability rolls (total 3 6)
|
||||
where
|
||||
rolls = toRolls . fill $ 3 `d` 6
|
||||
fill rolls = [[roll !! 0, 0, 0, roll !! 1, roll !! 2] | roll <- rolls]
|
||||
|
||||
twoVtwoLosses :: Possibility -> [Possibility]
|
||||
twoVtwoLosses = rollsByProbability rolls (total 4 6)
|
||||
where
|
||||
rolls = toRolls . fill $ 4 `d` 6
|
||||
fill rolls = [[roll !! 0, roll !! 1, 0, roll !! 2, roll !! 3] | roll <- rolls]
|
||||
|
||||
threeVtwoLosses :: Possibility -> [Possibility]
|
||||
threeVtwoLosses = rollsByProbability rolls (total 5 6)
|
||||
where
|
||||
rolls = toRolls $ 5 `d` 6
|
||||
|
||||
threeVoneLosses :: Possibility -> [Possibility]
|
||||
threeVoneLosses = rollsByProbability rolls (total 4 6)
|
||||
where
|
||||
rolls = toRolls . fill $ 4 `d` 6
|
||||
fill rolls = [[roll !! 0, roll !! 1, roll !! 2, roll !! 3, 0] | roll <- rolls]
|
||||
29
haskell/test/Spec.hs
Normal file
29
haskell/test/Spec.hs
Normal file
@@ -0,0 +1,29 @@
|
||||
import Test.Tasty
|
||||
import Test.Tasty.HUnit
|
||||
import Test.Tasty.QuickCheck
|
||||
import qualified Test.Tasty.SmallCheck as SC
|
||||
|
||||
arith :: Integer -> Integer -> Property
|
||||
arith x y = (x > 0) && (y > 0) ==> (x+y)^2 > x^2 + y^2
|
||||
|
||||
negation :: Integer -> Bool
|
||||
negation x = abs (x^2) >= x
|
||||
|
||||
suite :: TestTree
|
||||
suite = testGroup "Test Suite" [
|
||||
testGroup "Units"
|
||||
[ testCase "Equality" $ True @=? True
|
||||
, testCase "Assertion" $ assert $ (length [1,2,3]) == 3
|
||||
],
|
||||
|
||||
testGroup "QuickCheck tests"
|
||||
[ testProperty "Quickcheck test" arith
|
||||
],
|
||||
|
||||
testGroup "SmallCheck tests"
|
||||
[ SC.testProperty "Negation" negation
|
||||
]
|
||||
]
|
||||
|
||||
main :: IO ()
|
||||
main = defaultMain suite
|
||||
Reference in New Issue
Block a user