Skip to content

Instantly share code, notes, and snippets.

@quickdudley
Created June 26, 2019 21:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save quickdudley/10de1b3962607faafbe7d35727716e97 to your computer and use it in GitHub Desktop.
Save quickdudley/10de1b3962607faafbe7d35727716e97 to your computer and use it in GitHub Desktop.
{-# LANGUAGE FlexibleInstances, OverloadedStrings #-}
module Main where
import Data.List
import qualified Data.Map as M
import Data.IORef
import System.Random
import System.Directory
import Control.Monad
import Control.Concurrent
import Codec.Picture
import qualified Network.Wai as Wai
import qualified Network.HTTP.Types as Wai
import qualified Network.Wai.Handler.Warp as Warp
main :: IO ()
main = do
cgr <- newIORef M.empty
createDirectoryIfMissing True "plots/lg"
forkIO $ Warp.run 4024 $ \req respond -> do
cg <- readIORef cgr
respond $ Wai.responseLBS Wai.status200 [("Content-Type","image/png")] $
let Right f = encodeDynamicPng (ImageRGB8 $ plot cg) in f
let
six :: M.Map L256 Integer
six = M.fromList $ (\n -> (n,(totalNeighbours n - 1) * 2)) <$> allNodes
sm = stabilizeM (const $ writeIORef cgr)
identity <- loadOrCompute "plots/lg/0.png" $ do
d3 <- sm six
sm $ M.unionWith (+) six $ fmap negate d3
let
single = M.fromList $ flip (,) 1 <$> allNodes
go n m = do
let
n' = n + 1
mu = M.unionWith (+) m single
[pfn,nfn] = ["plots/lg/" ++ show x ++ ".png" | x <- [n', -n']]
pm <- loadOrCompute pfn $ sm mu
loadOrCompute nfn $ do
d <- sm $ M.unionWith (+) six pm
sm $ M.unionWith (+) six $ negate <$> d
go n' pm
go 0 identity
class (Ord g) => Graph g where
totalNeighbours :: g -> Integer
concreteNeighbours :: g -> [g]
class Graph g => Grid g where
gridBounds :: Foldable f => f g -> ((Int,Int),(Int,Int))
gridPoint :: Int -> Int -> g
class Graph g => FiniteGrid g where
allNodes :: [g]
instance (Integral a) => Graph (a,a) where
totalNeighbours _ = 4
concreteNeighbours (x,y) = do
f <- [\n -> (x + n,y), \n -> (x,y + n)]
d <- [-1,1]
return (f d)
data BT128 = BT128 !Int !Int deriving (Eq,Ord,Show)
instance Graph BT128 where
totalNeighbours = const 4
concreteNeighbours (BT128 x y) = let
go a b = if b == 127 || b == 0
then filter ((&&) <$> (>= 0) <*> (<= 127)) [a + 1, a - 1]
else map (`mod` 128) [a + 1, a - 1]
in BT128 <$> go x y <*> go y x
instance Grid BT128 where
gridBounds = const ((0,0),(127,127))
gridPoint = BT128
instance FiniteGrid BT128 where
allNodes = BT128 <$> [0 .. 127] <*> [0 .. 127]
data L256 = L256 !Int !Int deriving (Eq,Ord,Show)
instance Graph L256 where
totalNeighbours = const 4
concreteNeighbours (L256 x y) = do
(p,r) <- [
(x + 256 * y, \p' -> let (y',x') = p' `divMod` 256 in L256 x' y'),
(y + 256 * (255 - x), \p' -> let (x',y') = p' `divMod` 256 in L256 (255 - x') y')
]
p' <- [p + 1, p - 1]
if p' >= 65536 || p' < 0 then [] else [()]
return (r p')
instance Grid L256 where
gridBounds = const ((0,0),(255,255))
gridPoint = L256
instance FiniteGrid L256 where
allNodes = L256 <$> [0 .. 255] <*> [0 .. 255]
loadOrCompute :: Grid g => FilePath -> IO (M.Map g Integer) -> IO (M.Map g Integer)
loadOrCompute fn fb = let
fallback = do
r <- fb
savePlot r fn
return r
in doesFileExist fn >>= \ae -> if ae
then readPng fn >>= \rr -> case rr of
Right (ImageRGB8 img) -> return $ M.fromList $ do
x <- [0 .. imageWidth img - 1]
y <- [0 .. imageHeight img - 1]
let
node = gridPoint x y
colour = pixelAt img x y
Just n = find (\n' -> chooseColour n' == colour) [0 ..]
return (node,n)
_ -> fallback
else fallback
-- As defined on Wikiepedia
step :: Graph g => M.Map g Integer -> M.Map g Integer
step m = M.fromListWith (+) $ do
o@(p,h) <- M.toList m
let t = totalNeighbours p
if h < t
then return o
else let
a = case h - t of
0 -> id
a' -> ((p,a') :)
r = flip (,) 1 <$> concreteNeighbours p
in a r
-- Likely converges faster than `step`; definitely more interesting to watch.
-- Converges to the same state due to Abelian properties of the sandpile model.
stepv2 :: Graph g => M.Map g Integer -> M.Map g Integer
stepv2 m = M.filter (/= 0) $ M.fromListWith (+) $ do
o@(p,h) <- M.toList m
if h > 0
then do
let (s,h') = h `divMod` (totalNeighbours p)
(p,h') : (flip (,) s <$> concreteNeighbours p)
else return o
stabilize :: Graph g => M.Map g Integer -> M.Map g Integer
stabilize m
| M.foldrWithKey (\k h r -> if h >= totalNeighbours k then False else r) True m = stabilize (stepv2 m)
| otherwise = m
stabilizeM :: (Graph g, Monad m) => (Integer -> M.Map g Integer -> m ()) -> M.Map g Integer -> m (M.Map g Integer)
stabilizeM df = go 1 where
go n m
| M.foldrWithKey (\k h r -> if h >= totalNeighbours k then False else r) True m = df n m >> return m
| otherwise = df n m >> go (n + 1) (stepv2 m)
run :: Graph g => M.Map g Integer -> [M.Map g Integer]
run = go where
go m = let
n = step m
in m : if n == m then [] else go n
runv2 :: Graph g => M.Map g Integer -> [M.Map g Integer]
runv2 = go where
go m
| M.foldr (\h r -> if h >= 4 then True else r) False m = m : go (stepv2 m)
| otherwise = [m]
instance (Integral a) => Grid (a,a) where
gridBounds = foldl' (\((x0,y0),(xn,yn)) (x,y) -> let
x0' = min (fromIntegral x) x0
y0' = min (fromIntegral y) y0
xn' = max (fromIntegral x) xn
yn' = max (fromIntegral y) yn
in x0' `seq` y0' `seq` xn' `seq` yn' `seq` ((x0',y0'),(xn',yn'))
) ((0,0),(0,0))
gridPoint x y = (fromIntegral x, fromIntegral y)
mBounds :: Grid g => M.Map g Integer -> ((Int,Int),(Int,Int))
mBounds = gridBounds . M.keys
plot :: Grid g => M.Map g Integer -> Image PixelRGB8
plot m = let
((x0,y0),(xn,yn)) = mBounds m
cf x y = gridPoint (x + x0) (y + y0)
in generateImage (\x y -> chooseColour $ fromIntegral $ case M.lookup (cf x y) m of
Nothing -> 0
Just v -> v
) (xn - x0 + 1) (yn - y0 + 1)
savePlot :: Grid g => M.Map g Integer -> FilePath -> IO ()
savePlot m fn = savePngImage fn (ImageRGB8 (plot m))
chooseColour n = PixelRGB8
(fromIntegral $ n * 123 + 113)
(fromIntegral $ n * 25 + 143)
(fromIntegral $ n * 61 + 53)
testPalette = savePngImage "palette.png" $
ImageRGB8 $
generateImage (\x _ -> chooseColour $ x `div` 25)
(25 * 7)
25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment