commit 2388c2b29a4255582483889b391af42cee4f041a
parent db78854047b4f82f1b5404ce65c4d81acdc7162d
Author: Jared Tobin <jared@jtobin.ca>
Date:   Thu, 16 Aug 2018 10:55:56 -0230
Cabal and stack.
Diffstat:
8 files changed, 215 insertions(+), 3 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -2,3 +2,4 @@ ti3sample
 *swp
 *.o
 *.hi
+.stack-work
diff --git a/ChangeLog.md b/ChangeLog.md
@@ -0,0 +1,5 @@
+# Revision history for ti3-sampler
+
+## 0.1.0  -- 2018-08-16
+
+* Initial release.
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2018, Jared Tobin
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Jared Tobin nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
@@ -1,18 +1,35 @@
-
 # ti-sampler
 
 Sample random board locations in a TI3 game.
 
 Useful for e.g. setting domain counters only on some limited percentage of the
-board.
+board.  I find chocking the board full of domain counters slows the game down a
+little too much, but you still want a few of them there for fun.  A proportion
+of about 0.275 tiles seems to strike a very good balance.
+
+Supports three, four, five, and six player games.  The tile coordinates are
+just reported in terms of position on the nth ring of the board.  North is one,
+and then you just count clockwise from there.
+
+This takes into account the different board configurations for different
+players, and so doesn't sample home systems.  Additionally, Mecatol Rex is
+always included in the result.
+
+For the three player game, just interpret the provided coordinates as if all
+tiles were present on the board.
 
 ## Usage
 
+Just clone the repo, install [Stack](https://www.haskellstack.org/), and run
+something like:
+
 ```
-$ stack runghc -- Sampler 4 0.1
+$ stack exec ti3-sampler 4 0.1
 Outer 18
 Outer 11
 Mid 6
 Rex
 ```
 
+The first argument is the number of players -- the second is the desired tile
+proportion.
diff --git a/Setup.hs b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/src/Main.hs b/src/Main.hs
@@ -0,0 +1,129 @@
+{-# OPTIONS_GHC -Wall #-}
+
+import Control.Monad.Primitive
+import qualified Data.Map.Strict as MS
+import System.Random.MWC
+import System.Random.MWC.Distributions (bernoulli)
+import System.Environment
+
+data Players =
+    Three
+  | Four
+  | Five
+  | Six
+
+data Coord =
+    Rex
+  | Inner Int
+  | Mid Int
+  | Outer Int
+  deriving (Show, Eq, Ord)
+
+data Tile =
+    Special
+  | Plain Bool
+  deriving Show
+
+type Board = MS.Map Coord Tile
+
+empty :: Board
+empty = MS.fromList (zip coords (repeat (Plain False))) where
+  coords = mconcat [rex, inner, mid, outer]
+  rex    = [Rex]
+  inner  = fmap Inner [1..6]
+  mid    = fmap Mid [1..12]
+  outer  = fmap Outer [1..18]
+
+board :: Players -> Board
+board players = case players of
+    Three -> MS.alter special Rex
+           $ MS.alter special (Outer 4)
+           $ MS.alter special (Outer 5)
+           $ MS.alter special (Outer 6)
+           $ MS.alter special (Outer 9)
+           $ MS.alter special (Outer 10)
+           $ MS.alter special (Outer 11)
+           $ MS.alter special (Outer 15)
+           $ MS.alter special (Outer 16)
+           $ MS.alter special (Outer 17)
+           $ MS.alter special (Outer 1)
+           $ MS.alter special (Outer 7)
+           $ MS.alter special (Outer 13)
+           empty
+
+    Four -> MS.alter special Rex
+          $ MS.alter special (Outer 3)
+          $ MS.alter special (Outer 8)
+          $ MS.alter special (Outer 12)
+          $ MS.alter special (Outer 17)
+          empty
+
+    Five -> MS.alter special Rex
+          $ MS.alter special (Outer 3)
+          $ MS.alter special (Outer 7)
+          $ MS.alter special (Outer 10)
+          $ MS.alter special (Outer 13)
+          $ MS.alter special (Outer 17)
+          empty
+
+    Six  -> MS.alter special Rex
+          $ MS.alter special (Outer 1)
+          $ MS.alter special (Outer 4)
+          $ MS.alter special (Outer 7)
+          $ MS.alter special (Outer 10)
+          $ MS.alter special (Outer 13)
+          $ MS.alter special (Outer 16)
+          empty
+  where
+    special :: Maybe Tile -> Maybe Tile
+    special tile = case tile of
+      Just Plain {} -> Just Special
+      _             -> tile
+
+primsample :: Double -> Board -> Gen RealWorld -> IO Board
+primsample prob brd gen = loop gen mempty (MS.toList brd)
+  where
+    loop prng acc tiles = case tiles of
+      []     -> return (MS.fromList acc)
+      (t:ts) -> case t of
+        (c, Plain False) -> do
+          coin <- bernoulli prob prng
+          loop prng ((c, Plain coin):acc) ts
+
+        _ -> loop prng (t:acc) ts
+
+sample :: Players -> Double -> IO Board
+sample players prob = withSystemRandom . asGenIO $
+  primsample prob (board players)
+
+render :: Board -> [Coord]
+render brd = loop mempty (MS.toList brd) where
+  loop acc tiles = case tiles of
+    []     -> acc
+    (t:ts) -> case t of
+      (Rex, _)        -> loop (Rex:acc) ts
+      (c, Plain True) -> loop (c:acc) ts
+      _               -> loop acc ts
+
+main :: IO ()
+main = do
+  args <- getArgs
+  case args of
+    (n:p:_) -> do
+      let players = case (read n :: Int) of
+            3 -> Just Three
+            4 -> Just Four
+            5 -> Just Five
+            6 -> Just Six
+            _ -> Nothing
+
+          prob    = read p :: Double
+
+      case players of
+        Nothing  -> putStrLn "invalid number of players"
+        Just nps -> do
+          brd <- sample nps prob
+          mapM_ print (render brd)
+
+    _ -> putStrLn "USAGE: ./sample <NPLAYERS> <PROBABILITY>"
+
diff --git a/stack.yaml b/stack.yaml
@@ -0,0 +1,6 @@
+flags: {}
+packages:
+- '.'
+extra-deps: []
+resolver: lts-12.5
+
diff --git a/ti3-sampler.cabal b/ti3-sampler.cabal
@@ -0,0 +1,22 @@
+name:                ti3-sampler
+version:             0.1.0
+synopsis:            Sample random locations on a TI3 board.
+description:         Sample random locations on a TI3 board.
+homepage:            https://github.com/jtobin/ti3-sampler
+license:             BSD3
+license-file:        LICENSE
+author:              Jared Tobin
+maintainer:          jared@jtobin.io
+build-type:          Simple
+extra-source-files:  ChangeLog.md, README.md
+cabal-version:       >=1.10
+
+executable ti3-sampler
+  main-is:             Main.hs
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+  build-depends:
+      base       >= 4.10 && < 5
+    , containers >= 0.5  && < 0.6
+    , mwc-random >= 0.13 && < 0.15
+    , primitive  >= 0.6  && < 0.7