feat: Added Haskell and Go.

This commit is contained in:
Spencer Brower
2022-07-04 18:49:33 -04:00
parent dffa0a4c1b
commit 6ac20afead
19 changed files with 1346 additions and 0 deletions

5
haskell/CHANGELOG.md Normal file
View 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
View 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)

View File

@@ -0,0 +1,2 @@
ignore-project: False
tests: True

46
haskell/risk.cabal Normal file
View 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
View 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
View 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
View 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