L-seed 0.1 → 0.2
raw patch · 20 files changed
+535/−116 lines, 20 filesdep +random-shufflenew-component:exe:renderAsPNGPVP ok
version bump matches the API change (PVP)
Dependencies added: random-shuffle
API changes (from Hackage documentation)
+ Lseed.Data: GardenSource :: IO (Garden ()) -> (Planted () -> IO GrammarFile) -> IO (Maybe String) -> GardenSource
+ Lseed.Data: MatchDistance :: Matchable
+ Lseed.Data: constGardenSource :: Garden () -> GardenSource
+ Lseed.Data: data GardenSource
+ Lseed.Data: getGarden :: GardenSource -> IO (Garden ())
+ Lseed.Data: getScreenMessage :: GardenSource -> IO (Maybe String)
+ Lseed.Data: getUpdatedCode :: GardenSource -> Planted () -> IO GrammarFile
+ Lseed.Data: instance Monoid Observer
+ Lseed.Data: obShutdown :: Observer -> IO ()
+ Lseed.Data: plantOwnerName :: Planted a -> String
+ Lseed.Data: scMessage :: ScreenContent -> Maybe String
+ Lseed.Data: siDistance :: StipeInfo -> Double
+ Lseed.Data.Functions: plantWeightedPieceLengths :: Double -> Plant a -> Plant Double
+ Lseed.Data.Functions: weightedPlantLength :: Plant a -> Double
+ Lseed.Geometry: gardenOffset :: AnnotatedGarden -> (Double, Double, Double)
- Lseed.Data: Observer :: IO () -> (Integer -> GrowingGarden -> IO ()) -> ((ClockTime -> ScreenContent) -> IO ()) -> (GrowingGarden -> IO ()) -> Observer
+ Lseed.Data: Observer :: IO () -> (Integer -> Angle -> GrowingGarden -> IO ()) -> ((ClockTime -> ScreenContent) -> IO ()) -> (GrowingGarden -> IO ()) -> IO () -> Observer
- Lseed.Data: Planted :: Double -> Integer -> GrammarFile -> Plant a -> Planted a
+ Lseed.Data: Planted :: Double -> Integer -> String -> GrammarFile -> Plant a -> Planted a
- Lseed.Data: ScreenContent :: AnnotatedGarden -> Double -> String -> ScreenContent
+ Lseed.Data: ScreenContent :: AnnotatedGarden -> Double -> String -> Maybe String -> ScreenContent
- Lseed.Data: StipeInfo :: Double -> Double -> Double -> Double -> Angle -> Angle -> GrowthState -> Double -> Double -> StipeInfo
+ Lseed.Data: StipeInfo :: Double -> Double -> Double -> Double -> Angle -> Angle -> Double -> Double -> Double -> GrowthState -> StipeInfo
- Lseed.Data: obState :: Observer -> Integer -> GrowingGarden -> IO ()
+ Lseed.Data: obState :: Observer -> Integer -> Angle -> GrowingGarden -> IO ()
- Lseed.Logic: applyGenome :: (RandomGen g) => Angle -> g -> GrowingGarden -> GrowingGarden
+ Lseed.Logic: applyGenome :: (RandomGen g) => Angle -> g -> GrowingGarden -> [(GrowingPlanted, [Double])]
- Lseed.Mainloop: lseedMainLoop :: Bool -> Observer -> Integer -> Garden () -> IO ()
+ Lseed.Mainloop: lseedMainLoop :: Bool -> Observer -> GardenSource -> Integer -> IO ()
Files
- L-seed.cabal +9/−2
- examples/hopper.txt +1/−1
- examples/segments.txt +4/−0
- src/Lseed/Constants.hs +6/−6
- src/Lseed/DB.hs +23/−8
- src/Lseed/Data.hs +35/−6
- src/Lseed/Data/Functions.hs +8/−0
- src/Lseed/Geometry.hs +30/−3
- src/Lseed/Grammar/Parse.hs +3/−2
- src/Lseed/LSystem.hs +7/−6
- src/Lseed/Logic.hs +23/−22
- src/Lseed/Mainloop.hs +19/−5
- src/Lseed/Renderer/Cairo.hs +210/−29
- src/Lseed/StipeInfo.hs +4/−3
- src/dbclient.hs +55/−6
- src/dbscorer.hs +27/−8
- src/fastScorer.hs +13/−6
- src/main.hs +12/−2
- src/renderAsPNG.hs +36/−0
- src/validate.hs +10/−1
L-seed.cabal view
@@ -1,5 +1,5 @@ Name: L-seed-Version: 0.1+Version: 0.2 Cabal-Version: >= 1.6 License: BSD3 License-file: LICENSE@@ -75,7 +75,8 @@ if flag(Database) Build-Depends: HDBC-odbc,- HDBC+ HDBC,+ random-shuffle == 0.0.2 Exposed-Modules: Lseed.DB @@ -85,6 +86,12 @@ Executable runGarden Main-Is: main.hs+ Hs-Source-Dirs: src/+ if ! flag(RendererCairo)+ Buildable: False++Executable renderAsPNG+ Main-Is: renderAsPNG.hs Hs-Source-Dirs: src/ if ! flag(RendererCairo) Buildable: False
examples/hopper.txt view
@@ -1,7 +1,7 @@ Rule "Start" WHEN Length <= 0 BRANCH AT 100% ANGLE = 30°, LENGTH = 0.5-BRANCH AT 100% ANGLE = -30°, LENGTH = 0.5+ ANGLE = -30°, LENGTH = 0.5 RULE "Links" WHEN Direction > 0 AND Direction < 140°
+ examples/segments.txt view
@@ -0,0 +1,4 @@+RULE Regel+WHEN TAG = ""+BRANCH ANGLE = 0°, LENGTH = 0.1+SET TAG = "done"
src/Lseed/Constants.hs view
@@ -13,7 +13,7 @@ blossomSize :: Double blossomSize = 0.03 stipeWidth :: Double-stipeWidth = 0.01+stipeWidth = 0.005 -- | Light and growths interpolation frequency ticksPerDay :: Integer@@ -24,7 +24,7 @@ -- 1 means: Can grow one stipeLength during one day, when catching the sunlight -- with one branch of (projected) length screenwidth growthPerDayAndLight :: Double-growthPerDayAndLight = 15.0+growthPerDayAndLight = 40.0 -- | Plants up to this size get an boost in growths smallPlantBoostSize :: Double@@ -34,9 +34,9 @@ smallPlantBoostLength :: Double smallPlantBoostLength = 0.2 --- | Cost (in light units) per (length for maintaining the plant)^2, to limit the growth of the plants+-- | Cost (in light units) per (sum for all branches (length * distance), to limit the growth of the plants costPerLength :: Double-costPerLength = 0.0005+costPerLength = 0.0015 -- | Cost (in length growths equivalent) per seed to be grown seedGrowthCost :: Double@@ -44,11 +44,11 @@ -- | Branch translucency. Proportion of light that is let through by a plant lightFalloff :: Double-lightFalloff = 0.4+lightFalloff = 0.7 -- | Length of one day, in seconds dayLength :: Double-dayLength = 8.0+dayLength = 15 -- | ε eps = 1e-9
src/Lseed/DB.hs view
@@ -1,6 +1,7 @@ module Lseed.DB ( DBCode(..) , getCodeToRun+ , getUpdatedCodeFromDB , addFinishedSeasonResults ) where @@ -11,6 +12,7 @@ import Lseed.Data import Lseed.Data.Functions+import Data.Maybe data DBCode = DBCode { dbcUserName :: String@@ -21,17 +23,17 @@ } deriving (Show) -withLseedDB :: (Connection -> IO t) -> IO t-withLseedDB what = do- dn <- readFile "../db.conf"+withLseedDB :: FilePath -> (Connection -> IO t) -> IO t+withLseedDB conf what = do+ dn <- readFile conf conn <- connectODBC dn res <- what conn disconnect conn return res -getCodeToRun :: IO [DBCode]-getCodeToRun = withLseedDB $ \conn -> do- let getCodeQuery = "SELECT plant.ID AS plantid, user.ID AS userid, code, plant.Name AS plantname, user.Name AS username from plant, user WHERE user.NextSeed = plant.ID;"+getCodeToRun :: FilePath -> IO [DBCode]+getCodeToRun conf = withLseedDB conf $ \conn -> do+ let getCodeQuery = "SELECT plant.ID AS plantid, user.ID AS userid, code, plant.Name AS plantname, user.Name AS username from plant, user WHERE plant.Valid AND user.NextSeed = plant.ID;" stmt <- prepare conn getCodeQuery execute stmt [] result <- fetchAllRowsMap' stmt@@ -42,10 +44,23 @@ (fromSql (m ! "plantid")) (fromSql (m ! "code")) -addFinishedSeasonResults garden = withLseedDB $ \conn -> do +getUpdatedCodeFromDB :: FilePath -> Integer -> IO (Maybe DBCode)+getUpdatedCodeFromDB conf userid = withLseedDB conf $ \conn -> do+ let query = "SELECT plant.ID AS plantid, user.ID AS userid, code, plant.Name AS plantname, user.Name AS username from plant, user WHERE plant.Valid AND user.NextSeed = plant.ID AND user.ID = ?;"+ stmt <- prepare conn query+ execute stmt [toSql userid]+ result <- fetchAllRowsMap' stmt+ return $ listToMaybe $ flip map result $ \m -> + DBCode (fromSql (m ! "username"))+ (fromSql (m ! "userid"))+ (fromSql (m ! "plantname"))+ (fromSql (m ! "plantid"))+ (fromSql (m ! "code"))++addFinishedSeasonResults conf garden = withLseedDB conf $ \conn -> do let owernerscore = M.toList $ foldr go M.empty garden where go p = M.insertWith (+) (plantOwner p) (plantLength (phenotype p))- run conn "INSERT INTO SEASON VALUES (NULL, False)" []+ run conn "INSERT INTO season VALUES (NULL, False)" [] stmt <- prepare conn "SELECT LAST_INSERT_ID()" execute stmt [] id <- (head . head) `fmap` fetchAllRows' stmt
src/Lseed/Data.hs view
@@ -7,6 +7,7 @@ import Control.Arrow (second) import Data.Monoid import System.Time (ClockTime)+import Data.Monoid -- | User Tag type UserTag = String@@ -25,6 +26,7 @@ data Planted a = Planted { plantPosition :: Double -- ^ Position in the garden, interval [0,1] , plantOwner :: Integer -- ^ Id of the user that owns this plant+ , plantOwnerName:: String -- ^ Name of the owner of the plant , genome :: GrammarFile -- ^ Lsystem in use , phenotype :: Plant a -- ^ Actual current form of the plant }@@ -56,9 +58,10 @@ , siSubLight :: Double , siAngle :: Angle , siDirection :: Angle- , siGrowth :: GrowthState , siOffset :: Double -- ^ Sideways position, relative to Plant origin , siHeight :: Double -- ^ Vertical distance from bottom+ , siDistance :: Double -- ^ Distance from root+ , siGrowth :: GrowthState } deriving (Show) @@ -77,23 +80,37 @@ { scGarden :: AnnotatedGarden , scLightAngle :: Double , scTime :: String+ , scMessage :: Maybe String } -- | Main loop observers data Observer = Observer {- -- | Called once, before the main loop starts+ -- | Called once per season, before the main loop starts obInit :: IO ()- -- | Called once per tick, with the current tick number and the current- -- state of the garden- , obState :: Integer -> GrowingGarden -> IO ()+ -- | Called once per tick, with the current tick number corresponding+ -- light angle and the current state of the garden+ , obState :: Integer -> Angle -> GrowingGarden -> IO () -- | Also called once per tick, with a function that calculates the -- information that should be displayed given a point in time , obGrowingState :: (ClockTime -> ScreenContent) -> IO () -- | Called before the main loop quits, with the last state of the garden , obFinished :: GrowingGarden -> IO ()+ -- | Called once before program termination+ , obShutdown :: IO () }-nullObserver = Observer (return ()) (\_ _ -> return ()) (\_ -> return ()) (\_ -> return ())+nullObserver = Observer (return ()) (\_ _ _ -> return ()) (\_ -> return ()) (\_ -> return ()) (return ()) +-- | Methods to get the initial garden and the updated code when a plant multiplies+data GardenSource = GardenSource {+ -- | Called at the beginning of a season, to aquire the garden+ getGarden :: IO (Garden ())+ -- | Given a plant, returns the genome to be used for a seedling.+ , getUpdatedCode :: Planted () -> IO GrammarFile+ -- | Text to display on the screen+ , getScreenMessage :: IO (Maybe String)+ }+constGardenSource :: Garden () -> GardenSource+constGardenSource garden = GardenSource (return garden) (return . genome) (return Nothing) -- | A complete grammar file type GrammarFile = [ GrammarRule ]@@ -125,6 +142,7 @@ | MatchSubLength | MatchDirection | MatchAngle+ | MatchDistance deriving (Read,Show) data Cmp@@ -177,3 +195,14 @@ instance Traversable Planted where sequenceA planted = (\x -> planted { phenotype = x }) <$> sequenceA (phenotype planted)++instance Monoid Observer where+ mempty = nullObserver+ obs1 `mappend` obs2 = nullObserver {+ obInit = obInit obs1 >> obInit obs2,+ obState = \d g -> obState obs1 d g >> obState obs2 d g,+ obGrowingState = \f -> obGrowingState obs1 f >> obGrowingState obs2 f,+ obFinished = \g -> obFinished obs1 g >> obFinished obs2 g,+ obShutdown = obShutdown obs1 >> obShutdown obs2+ }+
src/Lseed/Data/Functions.hs view
@@ -8,6 +8,14 @@ plantPieceLengths (Plant _ len ang ut ps) = Plant len len ang ut (map plantPieceLengths ps) +plantWeightedPieceLengths :: Double -> Plant a -> Plant Double+plantWeightedPieceLengths dist (Plant _ len ang ut ps) =+ Plant ((dist + 0.5*len) * len) len ang ut $+ map (plantWeightedPieceLengths (dist + len)) ps++weightedPlantLength :: Plant a -> Double+weightedPlantLength = plantTotalSum . plantWeightedPieceLengths 0+ plantLength :: Plant a -> Double plantLength = plantTotalSum . plantPieceLengths
src/Lseed/Geometry.hs view
@@ -9,6 +9,7 @@ import Data.Maybe import Data.Ord import qualified Data.Map as M+import qualified Data.Foldable as F import Control.Monad hiding (mapM,forM) import Data.Traversable (mapM,forM) import Prelude hiding (mapM)@@ -128,7 +129,7 @@ where -- Calculation based on the ray at the mid point mid = (x1 + x2) / 2 -- Light intensity- width = abs ((x2 - x1) * sin angle)+ width = abs ((x2 - x1) * sin angle) * lightIntensity (curlines, otherlines) = partition (\(l,_,_) -> lineAtRay mid l) llines sorted = sortBy (\(l1,_,_) (l2,_,_) -> aboveFirst mid l1 l2)@@ -137,6 +138,8 @@ shine intensity (l,i,amount) = (intensity * lightFalloff, (l,i,amount + (1-lightFalloff) * intensity)) + lightIntensity = sin angle+ polygons = concatMap go intervals where go (x1,x2) = if null sorted then [nothingPoly] else lightedPolys where mid = (x1 + x2) / 2@@ -148,7 +151,7 @@ p2 = unprojectPoint x1 floor p3 = unprojectPoint x2 floor p4 = unprojectPoint x2 ceiling- in (p1,p2,p3,p4,1)+ in (p1,p2,p3,p4, lightIntensity) firstPoly = let p1 = unprojectPoint x1 ceiling p2 = unprojectPoint x1 (head sorted) p3 = unprojectPoint x2 (head sorted)@@ -166,7 +169,7 @@ p4 = unprojectPoint x2 l1 in (p1,p2,p3,p4)) sorted (tail sorted) polys' = [firstPoly] ++ polys ++ [lastPoly]- lightedPolys = snd $ mapAccumL shine 1 polys'+ lightedPolys = snd $ mapAccumL shine lightIntensity polys' shine intensity (p1,p2,p3,p4) = ( intensity * lightFalloff , (p1,p2,p3,p4,intensity)) @@ -187,3 +190,27 @@ -- Undo the STRefs mapM (mapM (\(d,stRef) -> (,) d <$> readSTRef stRef)) gardenWithPointers +-- | Slightly shifts angles +windy s = mapGarden (mapPlanted (go 0))+ where go d p = let a' = pAngle p + + windFactor * offset * pLength p * cos (d + pAngle p)+ d' = (d+a')+ in p { pAngle = a'+ , pData = (pData p) { siDirection = d' }+ , pBranches = map (go d') (pBranches p)+ }+ offset = sin (windChangeFrequency * s)+ windFactor = 0.015+ windChangeFrequency = 1++-- | For a Garden, calculates the maximum size to the left, to the right, and+-- maximum height+gardenOffset :: AnnotatedGarden -> (Double, Double, Double)+gardenOffset = pad . F.foldr max3 (0.5,0.5,0) . map (F.foldr max3 (0.5,0.5,0) . go )+ where go planted = fmap (\si -> ( siOffset si + plantPosition planted+ , siOffset si + plantPosition planted + , siHeight si+ )+ ) planted+ max3 (a,b,c) (a',b',c') = (min a a', max b b', max c c')+ pad (a,b,c) = (a-0.02,b+0.02,c+0.02)
src/Lseed/Grammar/Parse.hs view
@@ -11,7 +11,7 @@ -- The lexer lexer = P.makeTokenParser $ javaStyle { P.reservedNames = ["RULE", "WHEN", "SET", "Tag", "Light", "Branch", "At",- "Length", "Light", "Sublength", "Sublight", "Direction", "Angle",+ "Length", "Light", "Sublength", "Sublight", "Direction", "Angle", "Distance", "BY", "TO", "PRIORITY", "WEIGHT", "Blossom"] } @@ -147,6 +147,7 @@ , ("SUBLIGHT", MatchSubLight) , ("ANGLE", MatchAngle) , ("DIRECTION", MatchDirection)+ , ("DISTANCE", MatchDistance) ] pCmp = @@ -167,4 +168,4 @@ ) <|> float (deg >> return (value / 180 * pi)) <|> return value -deg = reservedOp "\194\176"+deg = reservedOp "\194\176" <|> reservedOp "\176"
src/Lseed/LSystem.hs view
@@ -9,10 +9,10 @@ import Data.List applyLSystem :: RandomGen g => g -> GrammarFile -> AnnotatedPlant -> GrowingPlant-applyLSystem rgen rules plant = let (maxPrio, result) = go maxPrio plant -- great use of lazyness here+applyLSystem rgen rules plant = let (maxPrio, result) = go rgen maxPrio plant -- great use of lazyness here in result- where go :: Int -> AnnotatedPlant -> (Int, GrowingPlant)- go maxPrio p@(Plant { pUserTag = oldUt+ where go :: RandomGen g => g -> Int -> AnnotatedPlant -> (Int, GrowingPlant)+ go rgen maxPrio p@(Plant { pUserTag = oldUt , pLength = oldSize , pAngle = ang , pBranches = ps@@ -26,7 +26,7 @@ filter ((>= maxPrio) . fst) $ choices of [] -> noAction- choices' -> chooseWeighted rgen choices'+ choices' -> chooseWeighted rgen' choices' ) where applyRule :: GrammarRule -> (Int, (Int, GrowingPlant)) applyRule r = (grPriority r, (grWeight r, applyAction (grAction r)))@@ -55,8 +55,8 @@ } noAction = p { pData = NoGrowth, pBranches = ps' }- - (subPrios, ps') = unzip $ map (go maxPrio) ps+ (rgen':rgens) = unfoldr (Just . split) rgen+ (subPrios, ps') = unzip $ zipWith (\r -> go r maxPrio) rgens ps -- Some general checks to rule out unwanted rules isValid :: GrowingPlant -> Bool@@ -86,6 +86,7 @@ getMatchable MatchSubLight = siSubLight si getMatchable MatchDirection = siDirection si getMatchable MatchAngle = siAngle si+ getMatchable MatchDistance = siDistance si doCompare LE = (<=) doCompare Less = (<)
src/Lseed/Logic.hs view
@@ -38,40 +38,38 @@ EnlargingTo l2 -> l2 - l1 GrowingSeed done -> (1-done) * seedGrowthCost +-- | For a GrowingGarden, calculates the current amount of light and then+-- advance the growth. This ought to be called after applyGenome growGarden :: (RandomGen g) => Angle -> g -> GrowingGarden -> (Double -> GrowingGarden)-growGarden angle rgen garden = sequence $ zipWith growPlanted garden' lightings- where lightings = map (plantTotalSum . fmap snd . phenotype) $ lightenGarden angle garden'- garden' = applyGenome angle rgen garden+growGarden angle rgen garden = sequence $ zipWith growPlanted garden totalLight+ where totalLight = map (plantTotalSum . fmap snd . phenotype) $ lightenGarden angle garden -- | For all Growing plants that are done, find out the next step--- This involves creating new plants if some are done-applyGenome :: (RandomGen g) => Angle -> g -> GrowingGarden -> GrowingGarden -applyGenome angle rgen garden = concat $ zipWith applyGenome' rgens aGarden+-- If new plants are to be created, these are returned via their position, next+-- to their parent plant.+applyGenome :: (RandomGen g) => Angle -> g -> GrowingGarden -> [(GrowingPlanted,[Double])]+applyGenome angle rgen garden = zipWith applyGenome' rgens aGarden where rgens = unfoldr (Just . split) rgen aGarden = annotateGarden angle garden applyGenome' rgen planted = if remainingGrowth siGrowth planted < eps- then planted { phenotype = applyLSystem rgen+ then ( planted { phenotype = applyLSystem rgen (genome planted) (phenotype planted) -- here, we throw away the last eps of growth. Is that a problem?- } :- collectSeeds rgen planted- else [fmap siGrowth planted]- collectSeeds :: (RandomGen g) => g -> AnnotatedPlanted -> GrowingGarden+ }+ , collectSeeds rgen planted)+ else (fmap siGrowth planted,[])+ collectSeeds :: (RandomGen g) => g -> AnnotatedPlanted -> [Double] collectSeeds rgen planted = snd $ F.foldr go (rgen,[]) planted- where go si (rgen,newPlants) = case siGrowth si of+ where go si (rgen,seedPoss) = case siGrowth si of GrowingSeed _ -> let spread = ( - siHeight si + siOffset si , siHeight si + siOffset si ) (posDelta,rgen') = randomR spread rgen- p = Planted (plantPosition planted + posDelta)- (plantOwner planted)- (genome planted)- (fmap (const NoGrowth) inititalPlant)- in (rgen, p:newPlants)- _ -> (rgen,newPlants)+ in (rgen', posDelta:seedPoss)+ _ -> (rgen,seedPoss) -- | Applies an L-System to a Plant, putting the new length in the additional -- information field@@ -79,10 +77,10 @@ growPlanted planted light = let remainingLength = remainingGrowth id planted in if remainingLength > eps- then let sizeOfPlant = plantLength (phenotype planted)- lightAvailable = light - costPerLength * sizeOfPlant^2- lowerBound = if sizeOfPlant < smallPlantBoostSize- then smallPlantBoostLength+ then let sizeOfPlant = weightedPlantLength (phenotype planted)+ lightAvailable = light - costPerLength * sizeOfPlant+ lowerBound = if sizeOfPlant < smallPlantBoostSize && not (doesBlossom (phenotype planted))+ then (1 - sizeOfPlant / smallPlantBoostSize) * smallPlantBoostLength else 0 allowedGrowths = max lowerBound $ (growthPerDayAndLight * lightAvailable) /@@ -91,6 +89,9 @@ growthFraction = growthThisTick / remainingLength in \tickDiff -> applyGrowth (tickDiff * growthFraction) planted else const planted++doesBlossom (Plant { pData = (GrowingSeed _) }) = True+doesBlossom (Plant { pBranches = ps }) = any doesBlossom ps -- | Applies Growth at given fraction, leaving the target length in place applyGrowth :: Double -> GrowingPlanted -> GrowingPlanted
src/Lseed/Mainloop.hs view
@@ -17,10 +17,11 @@ -- observer informed about any changes. lseedMainLoop :: Bool -- ^ Run in real time, e.g. call 'threadDelay' -> Observer -- ^ Who to notify about the state of the game+ -> GardenSource -- ^ Where do get the plant code from -> Integer -- ^ Maximum days to run- -> Garden () -- ^ Initial garden state -> IO ()-lseedMainLoop rt obs maxDays garden = do+lseedMainLoop rt obs gardenSource maxDays = do+ garden <- getGarden gardenSource obInit obs let nextDay (tick, garden) = let (day, tickOfDay) = tick `divMod` ticksPerDay in@@ -32,10 +33,23 @@ rgen <- newStdGen let sampleAngle = lightAngle $ (fromIntegral tickOfDay + 0.5) / fromIntegral ticksPerDay- let growingGarden = growGarden sampleAngle rgen garden+ let newGardenWithSeeds = applyGenome sampleAngle rgen garden+ rgen <- newStdGen+ newGarden <- fmap concat $ forM newGardenWithSeeds $+ \(parent,seedPoss) -> fmap (parent:) $ forM seedPoss $ \seedPos -> do+ genome <- getUpdatedCode gardenSource (fmap (const ()) parent)+ return $ Planted (plantPosition parent + seedPos)+ (plantOwner parent)+ (plantOwnerName parent)+ genome+ (fmap (const NoGrowth) inititalPlant) - obState obs tick garden+ let growingGarden = growGarden sampleAngle rgen newGarden+++ obState obs tick sampleAngle garden when rt $ do+ text <- getScreenMessage gardenSource obGrowingState obs $ \later -> let tickDiff = timeSpanFraction tickLength tickStart later dayDiff = (fromIntegral tickOfDay + tickDiff) /@@ -44,7 +58,7 @@ visualizeAngle = lightAngle dayDiff gardenNow = annotateGarden visualizeAngle $ growingGarden tickDiff- in ScreenContent gardenNow visualizeAngle timeInfo+ in ScreenContent gardenNow visualizeAngle timeInfo text threadDelay (round (tickLength * 1000 * 1000)) nextDay (succ tick, growingGarden 1)
src/Lseed/Renderer/Cairo.hs view
@@ -10,15 +10,76 @@ import Lseed.Data.Functions import Lseed.Constants import Lseed.Geometry+import Lseed.StipeInfo import Text.Printf import System.Time+import qualified Data.Map as M+import Data.List+import Data.Ord+import System.Time +colors :: [ (Double, Double, Double) ]+colors = cycle $ [ (r,g,b) | r <- [0.0,0.4], b <- [0.0, 0.4], g <- [1.0,0.6,0.8]]++pngDailyObserver :: FilePath -> Observer+pngDailyObserver filename = nullObserver {+ obGrowingState = \scGen -> do+ ScreenContent garden angle timeInfo mbMessage <-+ scGen `fmap` getClockTime + let (w,h) = (800,600)+ let h' = fromIntegral h / fromIntegral w++ let (xLeft,xRight,xHeight) = gardenOffset garden+ scaleY = h'*(1-groundLevel)/xHeight+ scaleX = 1/(max 1 xRight - min 0 xLeft)+ scaleXY = minimum [1, scaleX, scaleY]+ shiftX = min 0 xLeft ++ withImageSurface FormatRGB24 w h $ \sur -> do+ renderWith sur $ do+ -- Set up coordinates+ translate 0 (fromIntegral h)+ scale 1 (-1)+ scale (fromIntegral w) (fromIntegral w)+ translate 0 groundLevel+ setLineWidth stipeWidth++ preserve $ do+ scale scaleXY scaleXY+ translate (-shiftX) 0+ render angle garden++ maybe (return ()) (renderMessage angle h') mbMessage+ renderTimeInfo timeInfo+ renderStats h' garden+ surfaceWriteToPNG sur filename+ }++pngObserver :: IO Observer+pngObserver = return $ nullObserver {+ obFinished = \garden -> do+ let (w,h) = (400,400)+ withImageSurface FormatRGB24 w h $ \sur -> do+ renderWith sur $ do+ -- Set up coordinates+ translate 0 (fromIntegral h)+ scale 1 (-1)+ scale (fromIntegral w) (fromIntegral w)+ translate (-0.5) 0+ scale 2 2+ translate 0 groundLevel+ setLineWidth stipeWidth++ render (pi/3) (annotateGarden (pi/3) garden)+ surfaceWriteToPNG sur "/dev/fd/1"+ }+ cairoObserver :: IO Observer cairoObserver = do initGUI -- global renderer state- currentGardenRef <- newIORef (const (ScreenContent [] (pi/2) "No time yet"))+ currentGardenRef <- newIORef (const (ScreenContent [] (pi/2) "No time yet" Nothing)) -- widgets canvas <- drawingAreaNew@@ -36,20 +97,33 @@ -- The actual drawing function onExpose canvas$ \e -> do scGen <- readIORef currentGardenRef- ScreenContent garden angle timeInfo <-+ ScreenContent garden angle timeInfo mbMessage <- scGen `fmap` getClockTime + s <- clockTimeToDouble `fmap` getClockTime dwin <- widgetGetDrawWindow canvas (w,h) <- drawableGetSize dwin+ let h' = fromIntegral h / fromIntegral w++ let (xLeft,xRight,xHeight) = gardenOffset garden+ scaleY = h'*(1-groundLevel)/xHeight+ scaleX = 1/(max 1 xRight - min 0 xLeft)+ scaleXY = minimum [1, scaleX, scaleY]+ shiftX = min 0 xLeft + renderWithDrawable dwin $ do -- Set up coordinates translate 0 (fromIntegral h) scale 1 (-1)- scale (fromIntegral w) (fromIntegral (w))+ scale (fromIntegral w) (fromIntegral w) translate 0 groundLevel- setLineWidth stipeWidth - render angle garden+ preserve $ do+ scale scaleXY scaleXY+ translate (-shiftX) 0+ render angle (windy s garden)+ maybe (return ()) (renderMessage angle h') mbMessage renderTimeInfo timeInfo+ renderStats h' garden return True timeoutAdd (widgetQueueDraw canvas >> return True) 20@@ -58,16 +132,15 @@ { obGrowingState = \scGen -> do writeIORef currentGardenRef scGen widgetQueueDraw canvas- , obFinished = \_ ->- mainQuit+ , obShutdown = mainQuit } render :: Double -> AnnotatedGarden -> Render () render angle garden = do -- TODO the following can be optimized to run allKindsOfStuffWithAngle only once. -- by running it here. This needs modification to lightenGarden and mapLine- renderGround- mapM_ renderLightedPoly (lightPolygons angle (gardenToLines garden))+ renderSky angle+ --mapM_ renderLightedPoly (lightPolygons angle (gardenToLines garden)) --mapM_ renderLightedLine (lightenLines angle (gardenToLines garden)) --mapM_ renderLine (gardenToLines garden)@@ -75,25 +148,68 @@ mapM_ renderPlanted garden - renderInfo angle garden+ renderGround + --renderInfo garden+ renderPlanted :: AnnotatedPlanted -> Render () renderPlanted planted = preserve $ do translate (plantPosition planted) 0- setSourceRGB 0 0.8 0 setLineCap LineCapRound- renderPlant (phenotype planted)+ let c = colors !! fromIntegral (plantOwner planted)+ renderPlant (Just (renderFlag (take 10 (plantOwnerName planted))))+ c (phenotype planted) -renderPlant :: AnnotatedPlant -> Render () -renderPlant (Plant si len ang ut ps) = preserve $ do- rotate ang- setLineWidth (stipeWidth*(0.5 + 0.5 * sqrt (siSubLength si)))+renderFlag :: String -> Render ()+renderFlag text = preserve $ do+ scale 1 (-1)+ setFontSize (groundLevel/2)+ ext <- textExtents text++ preserve $ do+ translate (stipeWidth) (groundLevel/2)+ rectangle 0+ (textExtentsYbearing ext + groundLevel/2)+ (textExtentsXadvance ext)+ (-textExtentsYbearing ext - groundLevel/2 - groundLevel/2)+ setSourceRGB 1 1 1+ fill++ setSourceRGB 0 0 0+ showText text++ setLineWidth (groundLevel/10)+ setSourceRGB 0 0 0 moveTo 0 0- lineTo 0 (len * stipeLength)- setSourceRGB 0 0.8 0+ lineTo (stipeWidth + textExtentsXadvance ext) 0 stroke+++-- | Renders a plant, or part of a plant, with a given colour. If the Render+-- argument is given, it is drawn at the end of the plant, if there are no+-- branches, or passed to exactly one branch.+renderPlant :: (Maybe (Render ())) -> (Double,Double,Double) -> AnnotatedPlant -> Render () +renderPlant leaveR color@(r,g,b) (Plant si len ang ut ps) = preserve $ do+ rotate ang+ withLinearPattern 0 0 0 (len * stipeLength) $ \pat -> do+ let darkenByBegin = 1/(1 + (siSubLength si)/15)+ let darkenByEnd = 1/(1 + (siSubLength si - siLength si)/15)+ patternAddColorStopRGB pat 0+ (darkenByBegin*r) (darkenByBegin*g) (darkenByBegin*b) + patternAddColorStopRGB pat 1+ (darkenByEnd*r) (darkenByEnd*g) (darkenByEnd*b) + setSource pat+ --setLineWidth (stipeWidth*(0.5 + 0.5 * sqrt (siSubLength si)))+ setLineWidth stipeWidth+ moveTo 0 0+ lineTo 0 (len * stipeLength)+ stroke translate 0 (len * stipeLength)- mapM_ renderPlant ps+ if null ps+ then fromMaybe (return ()) leaveR+ else sequence_ $ zipWith (\r p -> renderPlant r color p)+ (leaveR : repeat Nothing)+ ps case siGrowth si of GrowingSeed done -> do setSourceRGB 1 1 0@@ -152,11 +268,14 @@ setSourceRGB 0 0 intensity fill -renderInfo angle garden = do+renderInfo garden = do forM_ garden $ \planted -> do let x = plantPosition planted+ {- let text1 = printf "Light: %.2f" $ siSubLight . pData . phenotype $ planted+ -}+ let text1 = plantOwnerName planted let text2 = printf "Size: %.2f" $ siSubLength . pData . phenotype $ planted preserve $ do@@ -164,28 +283,90 @@ setSourceRGB 0 0 0 setFontSize (groundLevel/2) moveTo x (0.9*groundLevel)- showText text1- moveTo x (0.5*groundLevel) showText text2+ moveTo x (0.5*groundLevel)+ showText text1 -renderTimeInfo timeStr = do- preserve $ do+renderTimeInfo timeStr = preserve $ do scale 1 (-1) setSourceRGB 0 0 0 setFontSize (groundLevel/2) moveTo 0 (0.5*groundLevel) showText timeStr +renderMessage angle h text = preserve $ do+ scale 1 (-1)+ setSourceRGB 0 0 0+ translate (0.5) (2.5*groundLevel - h) + setFontSize (groundLevel)++ let bullet = " * "+ ext <- textExtents (text ++ bullet)++ rectangle (-0.25)+ (textExtentsYbearing ext + groundLevel)+ (0.5)+ (-textExtentsYbearing ext - groundLevel - groundLevel)+ setSourceRGB 1 1 1+ fillPreserve+ clip++ let textWidth = textExtentsXbearing ext + textExtentsXadvance ext+ textCount = ceiling $ 0.5/textWidth+ scroll = 3 * (angle + pi/2)/(2*pi)+ scroll' = scroll - fromIntegral (floor scroll)+ scrollDist = fromIntegral textCount * textWidth+ translate (-0.25 - scroll' * scrollDist) 0++ setSourceRGB 0 0 0+ showText $ intercalate bullet $ replicate (2*textCount) text++renderStats h garden = do+ let owernerscore = foldr (\p -> M.insertWith (+) (plantOwnerName p) (plantLength (phenotype p))) M.empty garden++ setFontSize (groundLevel/2)+ let texts = map (\(n,s) -> printf "%s: %.1f" (take 20 n) s) $+ reverse $+ sortBy (comparing snd) $+ (M.toList owernerscore)+ unless (null texts) $ preserve $ do+ scale 1 (-1)+ translate 0 (1.5*groundLevel - h) + + textE <- mapM (\t -> (,) t `fmap` textExtents t) texts++ let totalHeight = groundLevel/4 + fromIntegral (length texts) * (1.0*groundLevel/2)+ let totalWidth = maximum $ map (\x -> textExtentsXadvance (snd x)) textE++ rectangle 0+ (-1.0*groundLevel/2)+ totalWidth+ (totalHeight)+ setSourceRGB 1 1 1+ fill++ forM_ texts $ \text -> do+ setSourceRGB 0 0 0+ moveTo 0 0+ showText text+ translate 0 (1.0*groundLevel/2)++++renderSky :: Angle -> Render ()+renderSky angle = do+ -- Clear Background+ setSourceRGB 0 0 (sin angle)+ paint+ renderGround :: Render () renderGround = do- -- Clear Background- rectangle 0 0 1 100- setSourceRGB 0 0 1- fill setSourceRGB (140/255) (80/255) (21/255)- rectangle 0 0 1 (-groundLevel)+ rectangle (-1) 0 3 (-(1+groundLevel)) fill -- | Wrapper that calls 'save' and 'restore' before and after the argument preserve :: Render () -> Render () preserve r = save >> r >> restore++clockTimeToDouble (TOD s p) = fromIntegral s + fromIntegral p/(1000*1000*1000*1000)
src/Lseed/StipeInfo.hs view
@@ -9,8 +9,8 @@ annotateGarden angle = map (mapPlanted annotatePlant) . lightenGarden angle annotatePlant :: Plant (GrowthState, Double) -> AnnotatedPlant-annotatePlant = go 0 0 0- where go d o h (Plant (gs, light) len ang ut ps) = Plant (StipeInfo+annotatePlant = go 0 0 0 0+ where go d o h dist (Plant (gs, light) len ang ut ps) = Plant (StipeInfo { siLength = len , siSubLength = len + sum (map (siSubLength . pData) ps') , siLight = light@@ -20,8 +20,9 @@ , siGrowth = gs , siOffset = o' , siHeight = h'+ , siDistance = dist }) len ang ut ps'- where ps' = map (go d' o' h') ps+ where ps' = map (go d' o' h' (dist+len)) ps d' = (d+ang) o' = o - len * stipeLength * sin d' h' = h + len * stipeLength * cos d'
src/dbclient.hs view
@@ -7,13 +7,62 @@ import Control.Applicative import Control.Monad import Text.Printf+import System.Environment+import Data.Monoid+import Data.Maybe+import System.Random+import System.Random.Shuffle (shuffle') -getGarden = spread <$> map (either (error.show) id . parseGrammar "" . dbcCode)- <$> getCodeToRun- where spread gs = zipWith (\g p -> Planted ((fromIntegral p + 0.5) / l) p g inititalPlant) gs [0..]+randomize l = shuffle' l (length l) <$> newStdGen++getDBGarden conf = do+ dbc <- getCodeToRun conf+ gs <- randomize $ mapMaybe compileDBCode dbc+ return $ spread gs+ where spread gs = zipWith (\(u,n,g) p ->+ Planted ((fromIntegral p + 0.5) / l)+ u+ n+ g+ inititalPlant+ ) gs [0..] where l = fromIntegral (length gs) +compileDBCode dbc =+ case parseGrammar "" (dbcCode dbc) of+ Left err -> Nothing+ Right grammarFile -> Just (dbcUserID dbc, dbcUserName dbc, grammarFile)++dbc2genome = either (const Nothing) Just . parseGrammar "" . dbcCode++getDBUpdate conf planted = fromMaybe (genome planted) <$>+ maybe Nothing dbc2genome <$>+ getUpdatedCodeFromDB conf (plantOwner planted)++scoringObs conf = nullObserver {+ obFinished = \garden -> do+ forM_ garden $ \planted -> do+ printf "Plant from %d at %.4f: Total size %.4f\n"+ (plantOwner planted)+ (plantPosition planted)+ (plantLength (phenotype planted))+ addFinishedSeasonResults conf garden+ }++nothingNull "" = Nothing+nothingNull s = Just s+ main = do- garden <- getGarden- obs <- cairoObserver- lseedMainLoop True obs 1 garden+ args <- getArgs+ case args of+ [conf, pngfile, textfile] -> do+ obs <- cairoObserver+ let obs' = obs `mappend` scoringObs conf `mappend` pngDailyObserver pngfile+ let gs = GardenSource (getDBGarden conf)+ (getDBUpdate conf)+ (nothingNull <$> readFile textfile) + lseedMainLoop True obs' gs 40+ obShutdown obs'+ _ -> do+ putStrLn "L-Seed DB client application."+ putStrLn "Please pass DB configuration file, a PNG file to write, and a text file with messages on the command line."
src/dbscorer.hs view
@@ -6,27 +6,46 @@ import Control.Applicative import Control.Monad import Text.Printf+import System.Environment -getGarden = spread <$> map compileDBCode- <$> getCodeToRun- where spread gs = zipWith (\(u,g) p -> Planted ((fromIntegral p + 0.5) / l) u g inititalPlant) gs [0..]+getDBGarden conf = spread <$> map compileDBCode <$> getCodeToRun conf+ where spread gs = zipWith (\(u,n,g) p ->+ Planted ((fromIntegral p + 0.5) / l)+ u+ n+ g+ inititalPlant+ ) gs [0..] where l = fromIntegral (length gs) compileDBCode dbc = case parseGrammar "" (dbcCode dbc) of Left err -> error (show err)- Right grammarFile -> (dbcUserID dbc, grammarFile)+ Right grammarFile -> (dbcUserID dbc, dbcUserName dbc, grammarFile) -scoringObs = nullObserver {+dbc2genome = either (error.show) id . parseGrammar "" . dbcCode++getDBUpdate conf planted = maybe (genome planted) dbc2genome <$>+ getUpdatedCodeFromDB conf (plantOwner planted)++scoringObs conf = nullObserver { obFinished = \garden -> do forM_ garden $ \planted -> do printf "Plant from %d at %.4f: Total size %.4f\n" (plantOwner planted) (plantPosition planted) (plantLength (phenotype planted))- addFinishedSeasonResults garden+ addFinishedSeasonResults conf garden } main = do- garden <- getGarden- lseedMainLoop False scoringObs 10 garden+ args <- getArgs+ case args of+ [conf] -> do+ lseedMainLoop False+ (scoringObs conf)+ (GardenSource (getDBGarden conf) (getDBUpdate conf) (return Nothing))+ 30+ _ -> do+ putStrLn "L-Seed DB client application."+ putStrLn "Please pass DB configuration file on the command line."
src/fastScorer.hs view
@@ -25,21 +25,28 @@ else do genomes <- mapM parseFile args doit (spread genomes)- where spread gs = zipWith (\g p -> Planted ((fromIntegral p + 0.5) / l) p g inititalPlant) gs [0..]+ where spread gs = zipWith (\g p ->+ Planted ((fromIntegral p + 0.5) / l)+ p+ (show p)+ g+ inititalPlant+ ) gs [0..] where l = fromIntegral (length gs) scoringObs = nullObserver { obFinished = \garden -> do forM_ garden $ \planted -> do- printf "Plant from %d at %.4f: Total size %.4f\n"+ printf "Plant from %s (%d) at %.4f: Total size %.4f\n"+ (plantOwnerName planted) (plantOwner planted) (plantPosition planted) (plantLength (phenotype planted))- let owernerscore = foldr (\p -> M.insertWith (+) (plantOwner p) (plantLength (phenotype p))) M.empty garden- forM_ (M.toList owernerscore) $ \(o,s) -> - printf "Sum for %d: %.4f\n" o s+ let owernerscore = foldr (\p -> M.insertWith (+) (plantOwner p, plantOwnerName p)(plantLength (phenotype p))) M.empty garden+ forM_ (M.toList owernerscore) $ \((o,n),s) -> + printf "Sum for %s (%d): %.4f\n" n o s } main = readArgs $ \garden -> do- lseedMainLoop False scoringObs 30 garden+ lseedMainLoop False scoringObs (constGardenSource garden) 30
src/main.hs view
@@ -24,9 +24,19 @@ else do genomes <- mapM parseFile args doit (spread genomes)- where spread gs = zipWith (\g p -> Planted ((fromIntegral p + 0.5) / l) p g inititalPlant) gs [0..]+ where spread gs = zipWith (\g p ->+ Planted ((fromIntegral p + 0.5) / l)+ p+ ("Player " ++ (show p))+ g+ inititalPlant+ ) gs [0..] where l = fromIntegral (length gs) main = readArgs $ \garden -> do obs <- cairoObserver- lseedMainLoop True obs 200 garden+ lseedMainLoop True+ obs+ ((constGardenSource garden) { getScreenMessage = (return (Just "bla blubb"))})+ 30+ obShutdown obs
+ src/renderAsPNG.hs view
@@ -0,0 +1,36 @@+import Lseed.Data+import Lseed.Data.Functions+import Lseed.Grammar.Parse+import Lseed.Constants+import Lseed.Mainloop+import Control.Monad+import Debug.Trace+import System.Environment+import System.Time+import System.Random+import Lseed.Renderer.Cairo+import Data.Maybe+import Graphics.Rendering.Cairo++main = do+ args <- getArgs+ let name = fromMaybe "Some Plant" $ listToMaybe args++ file <- getContents+ case parseGrammar name file of+ Left _ -> do + let (w,h) = (300,300)+ withImageSurface FormatRGB24 w h $ \sur -> do+ renderWith sur $ do+ setSourceRGB 1 1 1+ paint++ translate 0 (0.5* fromIntegral h)+ setFontSize (0.1* fromIntegral h)+ setSourceRGB 0 0 0+ showText "Syntax Error"+ surfaceWriteToPNG sur "/dev/fd/1"+ Right genome -> do+ let garden = [Planted 0.5 0 name genome inititalPlant]+ obs <- pngObserver+ lseedMainLoop False obs (constGardenSource garden) 10
src/validate.hs view
@@ -11,6 +11,7 @@ import Text.Parsec.Pos import Lseed.Grammar.Parse import Text.JSON+import System.Exit valid = encode $ makeObj [ ("valid", showJSON True) ] @@ -24,4 +25,12 @@ errorMessages $ error) ] -main = interact $ either invalid (const valid) . parseGrammar "stdin"+main = do+ file <- getContents+ case (parseGrammar "stdin" file) of+ Left err -> do+ putStr (invalid err)+ exitWith (ExitFailure 1)+ Right _ -> do+ putStr valid+ exitWith ExitSuccess