Return Styles: Pseud0ch, Terminal, Valhalla, NES, Geocities, Blue Moon.

Pages: 1-

Tentacle sex automaton

Name: Anonymous 2013-09-10 17:18

I found this weird old Python file on my hard drive (which I think I got here) and tried reimplementing it in Haskell for shits and giggles. The original had a more verbose, plain text output, but I changed it to a curses-based thing to make debugging easy.

Guess I'll post it in several parts:

{-# LANGUAGE RecordWildCards, OverloadedStrings #-}
import Actors
import Races
import Simulate
import UI

import qualified Data.Text as T
import qualified Data.Text.IO as T

showText :: Show r => r -> T.Text
showText = T.pack . show

inYears n = n `div` 7

describe (Actor{..}) = T.concat
  [ T.concat names
  , ", a "
  , showText $ inYears age
  , "-year-old "
  , showText gender
  , " "
  , speciesName species
  , " at "
  , showText location
  ]

condition World{..} =
  length actors > 50 ||
  null actors ||
  tickNumber > 100

main = runGame

Name: Anonymous 2013-09-10 17:19

Actors.hs (part 1):

{-# LANGUAGE RecordWildCards #-}
module Actors where

import Decide

import Data.Text as T
import Control.Monad (replicateM, when, ap, liftM2)

data Gender = Male | Female
  deriving (Eq, Read, Show)

data Expectancy = Ages
  { gestation :: Int
  , puberty :: Int
  , adulthood :: Int
  , sterility :: Int
  , death :: Int
  } deriving (Eq, Read, Show)

data Species = Species
  { speciesName :: Text
  , typicalAgeMap :: Expectancy
  , defaultSigils :: [(Gender, (Char, Char))]
  , broodSize :: (Int, Int)
  , fatigue :: Int
  } deriving (Eq, Read, Show)

data Actor = Actor
  { names :: [Text]
  , sigils :: (Char, Char)
  , species :: Species
  , ageMap :: Expectancy
  , age :: Int
  , gender :: Gender
  , location :: (Int, Int)
  , mindState :: ActorMode
  , bodyState :: Maybe Pregnancy
  , alive :: Bool
  } deriving (Eq, Read, Show)

Name: Anonymous 2013-09-10 17:20

Actors.hs (part 2):

data ActorMode = Wandering Int | HavingSex Int | Horny
  deriving (Eq, Read, Show)

data Pregnancy = Pregnant
  { numOffspring :: Int
  , whenBegan :: Int
  , whenEnding :: Int
  , withSpecies :: Species
  } deriving (Eq, Read, Show)

actorName = T.concat . names

ageInYears = flip div 365 . age

isChild = liftM2 (<) age (puberty . ageMap)

isFatigued Actor{ mindState = Wandering n } = n > 0

canHaveSex a@Actor { mindState = Horny, bodyState = Nothing } =
  not (isChild a)
canHaveSex _ = False

showSigil a@(Actor { sigils = (child, adult) })
  | isChild a = child
  | otherwise = adult

fertility (Actor { gender = Female, age = age, ageMap = ageMap })
  | age == adulthood = 1.0
  | age < puberty = 0.0
  | age < adulthood =
    interpolate (float age) (float puberty, 0.7) (float adulthood, 1.0)
  | age > adulthood =
    interpolate (float age) (float adulthood, 1.0) (float sterility, 0.0)
  where Ages{..} = ageMap
        float = fromIntegral
fertility _ = 0.0

virility (Actor { gender = Male, age = age, ageMap = ageMap })
  | age == adulthood = 1.0
  | age < puberty = 0.0
  | age < adulthood =
    interpolate (float age) (float puberty, 0.4) (float adulthood, 1.0)
  | age > adulthood =
    interpolate (float age) (float adulthood, 1.0) (float sterility, 0.0)
  where Ages{..} = ageMap
        float = fromIntegral
virility _ = 0.0

Name: Anonymous 2013-09-10 17:22

Races.hs:

{-# LANGUAGE OverloadedStrings #-}
module Races where

import Actors

demon = Species
  { speciesName = "tentacle demon"
  , broodSize = (4, 8)
  , defaultSigils = [(Male, ('x', 'X')), (Female, ('s', 'S'))]
  , fatigue = 20
  , typicalAgeMap = Ages
    { gestation = 200
    , puberty = 400
    , adulthood = 750
    , sterility = 1800
    , death = 2000
    }
  }

human = Species
  { speciesName = "human"
  , broodSize = (1, 3)
  , defaultSigils = [(Male, ('m', 'M')), (Female, ('f', 'F'))]
  , fatigue = 15
  , typicalAgeMap = Ages
    { gestation = 50
    , puberty = 300
    , adulthood = 700
    , sterility = 1200
    , death = 1250
    }
  }

Name: Anonymous 2013-09-10 17:23

Simulate.hs (part 1):

{-# LANGUAGE RecordWildCards, OverloadedStrings #-}
module Simulate where

import Decide
import Actors
import Races

import System.Random
import Control.Monad (replicateM)
import Data.List (partition)

data World = World
  { tickNumber :: Int
  , actors :: [Actor]
  , bounds :: ((Int, Int), (Int, Int))
  } deriving (Eq, Show)

makeExpectancy Ages{..} = do
  gestation <- roughly gestation
  puberty <- roughly puberty
  adulthood <- roughly adulthood
  sterility <- roughly sterility
  death <- roughly death
  return Ages{..}

makeActor species@(Species{..}) location = do
  ageMap <- makeExpectancy typicalAgeMap
  isFemale <- randomIO
  let names = ["somebody"]
      age = 0
      gender = if isFemale then Female else Male
      Just sigils = lookup gender defaultSigils
      bodyState = Nothing
      mindState = Wandering (puberty ageMap)
      alive = True
  return Actor{..}

wanderFrom (x, y) = do
  x <- x +/- 1
  y <- y +/- 1
  return (x, y)

ageUp actor = actor { age = age actor + 1 }

Name: Anonymous 2013-09-10 17:24

Simulate.hs (part 2):

doEncounters actors tickNumber = do
  let (breeders, rest) = partition canHaveSex actors
      groups = batch location breeders
  breeders' <- mapM (runEncounter tickNumber) groups
  return (concat (rest : breeders'))

runEncounter :: Int -> [Actor] -> IO [Actor]
runEncounter tickNumber (x : y : ys) = do
  let (a, b) = if virility x > virility y
                then (x, y)
                else (y, x)
  b <- impregnate tickNumber a b
  ab' <- mapM beginSex [a, b]
  return (ab' ++ ys)
runEncounter _ xs = return xs

beginSex :: Actor -> IO Actor
beginSex Actor{..} = do
  mindState <- HavingSex `fmap` roughly 10
  return Actor{..}

impregnate :: Int -> Actor -> Actor -> IO Actor
impregnate whenBegan father mother
  | virility father + fertility mother > 1.0 = do
    pregnant <- do
      let withSpecies = species father
          gperiod = gestation (ageMap father)
      numOffspring <- randomRIO (broodSize withSpecies)
      whenEnding <- (whenBegan +) `fmap` roughly gperiod
      return Pregnant{..}
    return mother { bodyState = Just pregnant }
impregnate _ _ nope = return nope

giveBirth time mother@(Actor{ bodyState = Just Pregnant{..} })
  | whenEnding >= time = do
    locations <- replicateM numOffspring $ wanderFrom (location mother)
    children <- mapM (makeActor withSpecies) locations
    return (mother { bodyState = Nothing } : children)
giveBirth _ whatever = return [whatever]

cullDead Actor{..} | not alive || age > death ageMap = return []
cullDead actor = return [actor]

Name: Anonymous 2013-09-10 17:25

Simulate.hs (part 3):

anyLoc (low, high) = do
  x <- randomRIO (fst low, fst high)
  y <- randomRIO (snd low, snd high)
  return (x, y)

randomAges Actor{..} = do
  age <- puberty ageMap +/- 100
  return Actor{..}

makeWorld width height = do
  let tickNumber = 1
      species = take 20 (repeat human) ++ take 5 (repeat demon)
      w = width `div` 2
      h = height `div` 2
      bounds = ((0 - w, 0 - h), (0 + w, 0 + h))
  locations <- mapM anyLoc (take 25 $ repeat bounds)
  actors <- sequence $ zipWith makeActor species locations
  actors <- mapM randomAges actors
  return World{..}

clamp v low high
  | v < low = low
  | v > high = high
  | otherwise = v

stepMindState (Actor{..}) =
  case mindState of
    Horny -> Horny
    Wandering 0 -> Horny
    Wandering n -> Wandering (n - 1)
    HavingSex 0 -> Wandering (fatigue species)
    HavingSex n -> HavingSex (n - 1)

stepActor ((lx, ly), (hx, hy)) a@(Actor{..}) = do
  mindState <- return $ stepMindState a
  location <- case stepMindState a of
                HavingSex _ -> do
                  return location
                _ -> do
                  (x, y) <- wanderFrom location
                  return (clamp x lx hx, clamp y ly hy)
  return Actor{..}

stepWorld (World{..}) = do
  tickNumber <- return (tickNumber + 1)
  actors <- concat `fmap` mapM (giveBirth tickNumber) (map ageUp actors)
  actors <- concat `fmap` mapM cullDead actors
  actors <- mapM (stepActor bounds) actors
  actors <- doEncounters actors tickNumber
  return World{..}

simulateUntil :: World -> (World -> Bool) -> IO World
simulateUntil world p = do
  world <- stepWorld world
  if p world
     then return world
     else simulateUntil world p

Name: Anonymous 2013-09-10 17:26

UI.hs:

{-# LANGUAGE RecordWildCards #-}
module UI where

import Control.Concurrent (threadDelay)
import Control.Exception (bracket_)
import Control.Monad (forM_)
import System.IO (hReady, stdin)
import UI.HSCurses.Curses

import Actors
import Simulate

withCurses = initCurses `bracket_` endWin

renderSigil = toEnum . fromEnum . showSigil

screenCoords World{ bounds = (low, high) } (x, y) = do
  -- Assume the coords are properly bounded for now
  let x' = x - fst low
      y' = snd high - y
  return (x', y')

drawWorld w@(World{..}) = do
  erase
  forM_ actors $ \a@Actor{..} -> do
    (x, y) <- screenCoords w location
    mvAddCh y x (renderSigil a)
  refresh

runGame = withCurses $ do
  (ySize, xSize) <- scrSize
  world <- makeWorld xSize ySize
  runLoop world

runLoop w = do
  drawWorld w
  test <- checkInput
  if test
     then stepWorld w >>= runLoop
     else return ()

checkInput = do
  test <- hReady stdin
  if test
     then do
       input <- getCh
       return $ case input of
                  KeyChar '.' -> True
                  _ -> False
     else do
       threadDelay 100000
       return True

Name: Anonymous 2013-09-10 17:27

Decide.hs (last part):

module Decide where

import System.Random
import Data.Array.IO
import Data.List (groupBy, sort, sortBy)
import Data.Function (on)
import Control.Monad (forM)

n +/- k = do
  r <- randomRIO (0, k * 2)
  return $ (n - k) + r

roughly n = n +/- (n `div` 10)

shuffle :: [a] -> IO [a]
shuffle xs = do
  let n = length xs
      -- Have to declare a type
      new :: [a] -> IO (IOArray Int a)
      new = newListArray (1, n)
  ar <- new xs
  forM [1 .. n] $ \i -> do
    j <- randomRIO (i, n)
    vi <- readArray ar i
    vj <- readArray ar j
    writeArray ar j vi
    return vj

priorityOn f xs = do
  batches <- mapM shuffle $ groupBy ((==) `on` f) $ sort xs
  return (concat batches)

batch f xs = groupBy ((==) `on` f) $ sortBy (compare `on` f) xs

interpolate j (i, yi) (k, yk)
  | j < i = yi
  | j > k = yk
  | otherwise = yi + (yk - yi) * (j - i) / (k - i)

Name: Anonymous 2013-09-11 10:55

but I changed it to a curses-based thing to make debugging easy.
That's why TDD exists, fucking shitfucker

Name: Anonymous 2013-09-11 13:10

>>10
I could be a smartass about "it compiles so my code is right" but realistically there's just one unit test that would have helped me out:

testAgeRestriction = do
  world <- makeWorld 10 10
  let condition World{..} = null actors || any childHavingSex actors
      childHavingSex a = isChild a && isHavingSex a
  World{..} <- simulateUntil world condition
  return $ if any childHavingSex actors
              then Left "THERE ARE CHILDREN HAVING SEX"
              else Right "OK, all entities dead"


Turning on the warnings in my compiler would also have helped, since the source of this bug was a binding I didn't use.

Name: Anonymous 2013-10-16 0:16

Made a drop-in replacement for the curses UI. Just change the import line in Main (from UI to UI.Gloss) and it should work. It highlights sex, pregnancy, and young/old age. Gloss is all right.

module UI.Gloss where

import Control.Applicative ((<$>))
import Control.Monad (forM)

import Graphics.Gloss
import Graphics.Gloss.Interface.IO.Simulate

import Actors
import Simulate

screenCoords World{ bounds = (low, high) } (x, y) =
  return (9 * x, 9 * y)

drawWorld :: World -> IO Picture
drawWorld world = do
  pictures <- forM (actors world) $ drawActor world
  return (Pictures pictures)

drawActor :: World -> Actor -> IO Picture
drawActor world actor = do
  (x, y) <- screenCoords world (location actor)
  let sigil = showSigil actor
      colorMod
        | age actor < puberty (ageMap actor) = light
        | age actor > sterility (ageMap actor) = light . light . light
        | otherwise = id
      color
        | sigil `elem` "fF" = magenta
        | sigil `elem` "mM" = blue
        | sigil `elem` "sS" = red
        | sigil `elem` "xX" = green
        | otherwise = white
      scale = 0.075
      colorSigil = Color (colorMod color) . Translate (-3) (-4) .
        Scale scale scale $ Text [sigil]
      signal
        | HavingSex n <- mindState actor =
          [Color white $ Circle (realToFrac n)]
        | Just (Pregnant {}) <- bodyState actor =
          [Color cyan $ Circle 7]
        | otherwise = []
  return . Translate (realToFrac x) (realToFrac y) . Pictures $
    (colorSigil:signal)

runGame :: IO ()
runGame = do
  world <- makeWorld 64 48
  let disp = InWindow "Genie" (640, 480) (320, 240)
      grayRatio = 9 / 10
      bg = mixColors grayRatio (1 - grayRatio) black white
      fps = 10
  simulateIO disp bg fps world drawWorld $ \_ _ -> stepWorld

Don't change these.
Name: Email:
Entire Thread Thread List