Sunday, August 10, 2014

nibbles

Remember this game? It came bundled with QBasic, back in the good old MS-DOS days...

The game's many subsequent clones often called it Snake instead.

Sunday, January 26, 2014

prime curve

This curve is created, Logo-style, by the trail of a turtle that makes a clockwise quarter-turn at every prime iteration; the screenshot shows the curve after about 730000 steps.
import Control.Arrow ((***))
import Control.Monad (void, liftM3, when)
import Data.Bits (shift)
import Data.Numbers.Primes (primes)
import Graphics.UI.SDL as SDL
   
(xres, yres) = (800, 800)
  
main = withInit [InitVideo] $ do
  win <- setVideoMode xres yres 32 []
  fillRect win (Just $ Rect 0 0 xres yres) (Pixel 0)
  enableEvent SDLMouseMotion False
  setCaption "Prime Curve" "Prime Curve"
  run win (xres `div` 2, yres `div` 2) [1..] nesw rgbs
 where
  nesw    = cycle $ map f [(0,-1), (1,0), (0,1), (-1,0)]
  f (x,y) = (x+) *** (+y)
   
run w p (n:ns) (d:ds) (c:cs) = do
  drawCell w p c
  let ds' = if prime n then ds else d:ds
  when (n `mod` 13 == 0) $ SDL.flip w
  e <- pollEvent
  case e of
   KeyUp (Keysym SDLK_ESCAPE _ _) -> void save
   _                              -> run w (d p) ns ds' cs
 where
  prime n = n `elem` takeWhile (< n + 1) primes
  save    = saveBMP w "out.bmp" >> print n
   
drawCell w p c =
  draw p =<< createRGBSurface [SWSurface] 1 1 32 0 0 0 0
 where
  rect x y     = Just $ Rect x y 1 1
  draw (x,y) s = do fillRect s (rect 0 0) (Pixel c)
                    blitSurface s (rect 0 0) w (rect x y)
 
rgbs = cycle . map f $ liftM3 (,,) ns ns ns
 where
  ns        = [151, 153.. 255]
  f (r,g,b) = shift r 16 + shift g 8 + b

Tuesday, July 31, 2012

dart outs

For those who like to play X01 Games.
Update: with an unexpected application to Project Euler #109!
import Control.Applicative (liftA2)
import Data.List (sort, nub, sortBy, maximum)
import Data.Ord (comparing)
import System.Environment (getArgs)
  
data Dart n = Single n | Double n | Triple n | Bull | None deriving Show
  
main = mapM_ print . out . read . head =<< getArgs
  
out n = check . sort' . map (map dart) $ sortBy (comparing maximum) sums
 where
  check ns = if null ns then [None] else head ns
  sort'    = sortBy (comparing $ sum . map ease)
  sums     = nub $ filter ((== n) . sum) combos
  
---------------------------------------------------------------------------
  
combos = concat $ zipWith combosOf [1..3] $ repeat segments
  
segments = concat $ replicate 3 $ sort $ 50 : liftA2 (*) [1..3] [1..20]
   
dart n
  | n == 50    = Bull
  | n `elem` s = Single n
  | n `elem` d = Double (n `div` 2)
  | n `elem` t = Triple (n `div` 3)
 where
  [s,d,t] = liftA2 (map . (*)) [1..3] [[1..20]]
                  
ease (Single _) = 1
ease (Bull)     = 2
ease (Double _) = 3
ease (Triple _) = 4
                   
combosOf 0 _      = [[]]
combosOf _ []     = []
combosOf k (x:xs) = map (x:) (combosOf (k-1) xs) ++ combosOf k xs

Tuesday, April 24, 2012

efficiency

These 61 characters compute a 20899-digit number in 1 second @ 1ghz.

main = print $ fibs !! 100000

fibs = 0 : scanl (+) 1 fibs

Thursday, October 20, 2011

graph search

An implementation of general graph search. The search strategy is determined by the definition on line 59, cost. The expression shown below produces an A* search. If instead one substitutes cost = sum . map dist . segments, the procedure becomes a uniform-cost search. Using length finds the path with fewest nodes, and simiarly dist [last ps, goal] produces a greedy best-first algorithm.

Sunday, June 5, 2011

simple alarm clock

Finally a useful program! ;)
import System.Time
import System.Environment (getArgs)
import Control.Monad (forever)
import Graphics.UI.SDL as SDL
import Graphics.UI.SDL.Mixer
 
main = do
  SDL.init [InitAudio]
  openAudio 22050 AudioS16Sys 2 4096
  h:m:_ <- fmap (map read) getArgs
  wait h m
 
wait h m = do
  now <- toCalendarTime =<< getClockTime
  if h == ctHour now && m == ctMin now
    then alarm
    else delay 5000 >> wait h m
 
alarm = do
  mus <- loadMUS "sawtooth.ogg"
  forever $ playMusic mus 1 >> delay 7000

Sunday, May 22, 2011

the game of life

I find using a point set (rather than an array) to represent this CA surprisingly convenient. It suffers no edge effects, and I suspect its average-case complexity is less, since it's a function of the live cells' population, which grows little as a typical (random) pattern evolves.

An array representation on the other hand, grows proportionally with the area spanned by all live cells. Since random patterns quite often fire gliders in opposite directions, this area can grow very quickly.

Anyway, this program takes a .cells pattern file for an optional argument; you can also press 'r' while paused for a random pattern, or click to create your own.

{-# LANGUAGE PackageImports #-}
{-# LANGUAGE TupleSections #-}
 
import Graphics.UI.SDL as SDL
import System.Environment (getArgs)
import Control.Arrow ((***))
import Control.Monad (liftM2, join)
import Data.List (delete, unfoldr)
import System.Random.Mersenne.Pure64 (newPureMT, randomInt)
import qualified "hashmap" Data.HashSet as S
import qualified "unordered-containers" Data.HashMap.Strict as M
 
(xres, yres, cellSz) = (1600, 900, 3)
 
main = withInit [InitVideo] $ do
  win  <- setVideoMode xres yres 32 [Fullscreen]
  args <- getArgs
  pat  <- case args of
           [s] -> loadPattern s
           _   -> return []
  enableEvent SDLMouseMotion False
  setCaption "Life" "Life"
  pause win pat
 
pause w cs = do
  delay 128
  drawCells w cs
  e <- pollEvent
  case e of
   KeyUp (Keysym SDLK_ESCAPE _ _) -> return ()
   KeyUp (Keysym SDLK_SPACE _ _)  -> run w cs
   KeyUp (Keysym SDLK_r  _ _)     -> pause w =<< randPattern
   MouseButtonUp x y _            -> click (scale x, scale y)
   _                              -> pause w cs
 where
  scale                 = (`div` cellSz) . fromIntegral
  click c | c `elem` cs = pause w $ delete c cs
          | otherwise   = pause w $ c:cs
 
run w cs = do
  drawCells w cs
  e <- pollEvent
  case e of
   KeyUp (Keysym SDLK_ESCAPE _ _) -> return ()
   KeyUp (Keysym SDLK_SPACE  _ _) -> pause w cs
   _                              -> run w $ next cs
 
drawCells w cs = do
  fillRect w (Just $ Rect 0 0 xres yres) (Pixel 0)
  c <- createRGBSurface [SWSurface] cellSz cellSz 32 0 0 0 0
  mapM_ (draw c . scale) cs
  SDL.flip w
 where
  rect (x,y) = Just $ Rect x y cellSz cellSz
  scale      = join (***) (* cellSz)
  draw c p   = do fillRect c Nothing $ Pixel 0xFFFFFF
                  blitSurface c Nothing w $ rect p

----------------------------------------------------------------

loadPattern = fmap parse . readFile   -- reads .cells format
 where
  parse = center . clean . coord . strip . lines
  strip = dropWhile $ (== '!') . head
  coord = zipWith zip $ map (zip [0..] . repeat) [0..]
  clean = concatMap $ map fst . filter ((== 'O') . snd)
 
randPattern = fmap f newPureMT
 where
  f = center . uncurry zip . splitAt 48 . g
  g = map (`rem` 9) . unfoldr (Just . randomInt)

center = map $ (x+) *** (+y)
 where
  [x,y] = map (`div` (2 * cellSz)) [xres, yres]
 
next cs = [i | (i,n) <- M.toList neighbors,
           n == 3 || (n == 2 && S.member i cs')]
 where
  cs'         = S.fromList cs
  moore (x,y) = tail $ liftM2 (,) [x, x+1, x-1] [y, y+1, y-1]
  neighbors   = M.fromListWith (+) $ map (,1) $ moore =<< cs

Monday, February 21, 2011

magic squares

If your magic square needs are more industrial there are always better ways...
import Data.List (transpose, intersect)
 
answers  =  filter valid squares
              where valid m = diag1 m == 15 && diag2 m == 15
 
squares  =  horz combos `intersect` vert combos
              where vert = map transpose . horz
 
combos   =  [ [a,b,c] | a <- ns, b <- ns, c <- ns, a + b + c == 15 ]
              where ns = [1..9]
 
horz m   =  [ [a,b,c] | a <- m, b <- m, c <- m, uniq (a ++ b ++ c) ]
 
diag1 m  =  head (head m) + ((m !! 1) !! 1) + last (last m)
  
diag2 m  =  last (head m) + ((m !! 1) !! 1) + head (last m)
 
uniq ns  =  let f (n:ns) xs = n `notElem` xs && f ns (n:xs)
                f _ _       = True
            in f ns []

Tuesday, October 6, 2009

plenary ant

Here's a more polished edition of the basic Langton's ant previously posted. As in this version of Conway's Life, you can pause with the space key, and press r while paused to restart with a random pattern. Just for fun I also threw in colors. On exiting, the code prints the iteration count - given populous initial states, it's interesting how much this value can vary before the ant builds its inevitable highway.
import Data.Bits (shift)
import Data.List (unfoldr)
import Control.Arrow ((***)) 
import Control.Monad (when, join)
import Data.Set (insert, delete, member, empty, fromList, toList)
import Graphics.UI.SDL as SDL
import System.Random.Mersenne.Pure64 (newPureMT, randomInt)
 
(xres, yres, sq, cast) = (1600, 900, 3, fromIntegral)
 
origin = (xres `div` 2 `div` sq, yres `div` 2 `div` sq)
 
main = withInit [InitVideo] $ do
  w <- setVideoMode xres yres 32 [NoFrame]
  enableEvent SDLMouseMotion False
  setCaption "Langton's Ant" "Langton's Ant"
  pause w origin (0,1) empty
 
pause w p v ps = do
  delay 128
  e <- pollEvent
  case e of
   KeyUp (Keysym SDLK_ESCAPE _ _) -> return ()
   KeyUp (Keysym SDLK_SPACE _ _)  -> run w [1..] p v ps
   KeyUp (Keysym SDLK_r  _ _)     -> randomize
   _                              -> pause w p v ps
 where
  randomize = do
    ps <- randPattern
    render w ps
    pause w origin (0,1) ps
  
run w (n:ns) p v ps = do
  when (n `mod` 7 == 0) $ render w ps
  e <- pollEvent
  case e of
   KeyUp (Keysym SDLK_ESCAPE _ _) -> print n
   KeyUp (Keysym SDLK_SPACE  _ _) -> pause w p v ps
   _                              -> continue
 where
  continue   = run w ns (move p $ g v) (g v) $ f p ps
  b          = member p ps
  f          = if b then delete else insert
  g          = if b then fl else fr
  move (x,y) = (x+) *** (+y)
  fr (x,y)   = if x == 0 then (-y,x) else (y,x)
  fl (x,y)   = if x == 0 then (y,x)  else (y,-x)
 
render w ps = do
  fillRect w (Just $ Rect 0 0 xres yres) $ Pixel 0
  mapM_ (draw w . join (***) (* sq)) $ toList ps
  SDL.flip w
  
draw w p = f p =<< g [SWSurface] sq sq 32 0 0 0 0
 where
  rect x y  = Just $ Rect x y sq sq
  g         = createRGBSurface
  f (x,y) s = do fillRect s (rect 0 0) $ Pixel $ rgb x y
                 blitSurface s (rect 0 0) w $ rect x y
 
randPattern = fmap (fromList . f) newPureMT
 where
  f = center . uncurry zip . splitAt 11000 . g
  g = map (`rem` 512) . unfoldr (Just . randomInt)
 
center = map $ (x+) *** (+y)
 where
  [x,y] = map (`div` (2 * sq)) [xres, yres]
 
rgb x y = shift r 16 + shift g 8 + 128
 where
  r = round $ (cast x / cast xres) * 255
  g = round $ (cast y / cast yres) * 255