Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add imap to world DSL, with examples #1990

Merged
merged 4 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions data/scenarios/00-ORDER.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ Speedruns
Testing
Vignettes
Mechanics
World Examples
1 change: 1 addition & 0 deletions data/scenarios/Testing/1320-world-DSL/00-ORDER.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ constant.yaml
erase.yaml
override.yaml
coords.yaml
reflect.yaml
46 changes: 46 additions & 0 deletions data/scenarios/Testing/1320-world-DSL/reflect.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
version: 1
name: Reflection (imap) test
description: |
A world with both horizontal and vertical reflection symmetry,
created with 'imap'.
creative: false
objectives:
- goal:
- Pick up four trees
condition: |
as base {n <- count "tree"; return (n >= 4)}
robots:
- name: base
loc: [0, 0]
dir: north
devices:
- logger
- grabber
- treads
- branch predictor
- scanner
- ADT calculator
- comparator
- GPS receiver
- bitcoin
solution: |
def x = \n. \c. if (n==0) {} {c; x (n-1) c} end
def ifC = \p. \t. \e. b <- p; if b t e end
def findTree = ifC (ishere "tree") {whereami} {move; findTree} end
def ell = \d. turn right; x (2*d) move; grab; return () end
def grabTrees = \loc. let x = fst loc in let y = snd loc in grab; ell y; ell x; ell y end
n <- random 10;
x (n+1) move; turn right; move;
loc <- findTree;
grabTrees loc
known: [tree]
world:
dsl: |
let trees = if (hash % 4 == 0) then {tree, dirt} else {stone}
in
overlay
[ mask (x >= 0 && y >= 0) trees
, mask (x >= 0 && y < 0) (imap x (-y) trees)
, mask (x < 0 && y >= 0) (imap (-x) y trees)
, mask (x < 0 && y < 0) (imap (-x) (-y) trees)
]
4 changes: 4 additions & 0 deletions data/scenarios/World Examples/00-ORDER.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
clearing.yaml
rorschach.yaml
stretch.yaml
translate.yaml
22 changes: 22 additions & 0 deletions data/scenarios/World Examples/clearing.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
version: 1
name: Clearing
description: |
The base is in a clearing in the forest: the area within a certain
radius of the base is completely clear of trees; then there are
random trees at increasing density up to another radius; outside of
the outer radius there are only trees.
creative: true
robots:
- name: base
display:
char: Ω
loc: [0, 0]
dir: north
world:
dsl: |
overlay
[ {dirt}
, mask ((x*x + 4*y*y) >= (6*6) && (x*x + 4*y*y) <= (30*30))
(let h = hash % 24 in if (36 + h*h) <= (x*x + 4*y*y) then {tree,dirt} else {dirt} )
, mask ((x*x + 4*y*y) > (30*30)) {tree, dirt}
]
21 changes: 21 additions & 0 deletions data/scenarios/World Examples/rorschach.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
version: 1
name: Rorschach
description: |
A world with both horizontal and vertical reflection symmetry,
created with `imap`{=snippet}.
creative: true
robots:
- name: base
dir: north
loc: [0, 0]
known: [tree]
world:
dsl: |
let trees = if (hash % 4 == 0) then {tree, dirt} else {stone}
in
overlay
[ mask (x >= 0 && y >= 0) trees
, mask (x >= 0 && y < 0) (imap x (-y) trees)
, mask (x < 0 && y >= 0) (imap (-x) y trees)
, mask (x < 0 && y < 0) (imap (-x) (-y) trees)
]
16 changes: 16 additions & 0 deletions data/scenarios/World Examples/stretch.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version: 1
name: Stretch
description: |
A world created by stretching a random pattern of trees, with the
amount of stretching determined by the distance from the origin.
creative: true
robots:
- name: base
dir: north
loc: [0, 0]
known: [tree]
world:
dsl: |
let trees = if (hash % 4 == 0) then {tree, dirt} else {stone}
in
imap (if (y == 0) then 0 else (x/abs(y))) (if (abs x <= 1) then 0 else (y/abs(x/2))) trees
24 changes: 24 additions & 0 deletions data/scenarios/World Examples/translate.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
version: 1
name: Translate
description: |
An illustration of how to use `imap`{=snippet} to translate. A basic patch is
created and then overlaid at various translations. Note that since
`imap`{=snippet} works by mapping a function over the coordinates, translation
is "backwards": for example, `imap (x+4)`{=snippet} translates 4 units to the
*left*.
creative: true
robots:
- name: base
dir: north
loc: [0, 0]
known: [rock]
world:
dsl: |
let patch = mask (abs(x) <= 4 && abs(y) <= 4) (if ((x + y) % 2 == 0) then {rock, dirt} else {dirt})
in
overlay
[ patch
, imap (x+6) (y+3) patch
, imap (x-10) (y-7) patch
, imap (x-14) (y+5) patch
]
6 changes: 6 additions & 0 deletions data/worlds/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ repetitions of `S` separated by `,`.
| 'let' (<ident> '=' <exp>)*, 'in' <exp>
| 'overlay' '[' <exp>+, ']'
| 'mask' <atom> <atom>
| 'imap' <atom> <atom> <atom>
| '"' <nonquote>+ '"'
| '(' <exp> ')'

Expand Down Expand Up @@ -186,4 +187,9 @@ entities but also some empty cells.
https://libnoise.sourceforge.net/glossary/index.html#perlinnoise
- `mask b e` takes the value of `e` where `b` is true, and is empty
elsewhere.
- `imap` has type `World int -> World int -> World a -> World a`, and
creates a new world from a reference world using the given index
lookup functions. That is, `imap wx wy wa` yields the world
`\c -> wa (wx c, wy c)`. For example, `imap (-x) y w` reflects the
world `w` across the line `y = 0`.
- `"foo"` imports the DSL term in `worlds/foo.world`.
22 changes: 12 additions & 10 deletions src/swarm-scenario/Swarm/Game/World/Compile.hs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,9 @@ import Data.Tagged (Tagged (unTagged))
import Numeric.Noise.Perlin (noiseValue, perlin)
import Swarm.Game.Location (pattern Location)
import Swarm.Game.World.Abstract (BTerm (..))
import Swarm.Game.World.Coords (Coords (..), coordsToLoc)
import Swarm.Game.World.Coords (Coords (..), coordsToLoc, locToCoords)
import Swarm.Game.World.Gen (Seed)
import Swarm.Game.World.Interpret (interpReflect, interpRot)
import Swarm.Game.World.Syntax (Axis (..), Rot, World)
import Swarm.Game.World.Syntax (Axis (..), World)
import Swarm.Game.World.Typecheck (Applicable (..), Const (..), Empty (..), NotFun, Over (..))
import Witch (from)
import Witch.Encoding qualified as Encoding
Expand Down Expand Up @@ -71,9 +70,8 @@ compileConst seed = \case
CCoord ax -> CFun $ \(CConst (coordsToLoc -> Location x y)) -> CConst (fromIntegral (case ax of X -> x; Y -> y))
CHash -> compileHash seed
CPerlin -> compilePerlin
CReflect ax -> compileReflect ax
CRot rot -> compileRot rot
COver -> binary (<!>)
CIMap -> compileIMap
K -> CFun $ \x -> CFun $ const x
S -> CFun $ \f -> CFun $ \g -> CFun $ \x -> f $$ x $$ (g $$ x)
I -> CFun id
Expand Down Expand Up @@ -110,11 +108,15 @@ compilePerlin =
where
sample (i, j) noise = noiseValue noise (fromIntegral i / 2, fromIntegral j / 2, 0)

compileReflect :: Axis -> CTerm (World a -> World a)
compileReflect ax = CFun $ \w -> CFun $ \(CConst c) -> w $$ CConst (interpReflect ax c)

compileRot :: Rot -> CTerm (World a -> World a)
compileRot rot = CFun $ \w -> CFun $ \(CConst c) -> w $$ CConst (interpRot rot c)
compileIMap :: NotFun a => CTerm (World Integer -> World Integer -> World a -> World a)
compileIMap =
CFun $ \wx ->
CFun $ \wy ->
CFun $ \wa ->
CFun $ \c ->
let mkCoords :: CTerm Integer -> CTerm Integer -> CTerm Coords
mkCoords (CConst x) (CConst y) = CConst (locToCoords (Location (fromIntegral x) (fromIntegral y)))
in wa $$ mkCoords (wx $$ c) (wy $$ c)

type family NoFunParams a :: Constraint where
NoFunParams (a -> b) = (NotFun a, NoFunParams b)
Expand Down
23 changes: 3 additions & 20 deletions src/swarm-scenario/Swarm/Game/World/Interpret.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
module Swarm.Game.World.Interpret (
interpBTerm,
interpConst,
interpReflect,
interpRot,
) where

import Control.Applicative (Applicative (..))
Expand All @@ -20,9 +18,9 @@ import Data.Tagged (unTagged)
import Numeric.Noise.Perlin (noiseValue, perlin)
import Swarm.Game.Location (pattern Location)
import Swarm.Game.World.Abstract (BTerm (..))
import Swarm.Game.World.Coords (Coords (..), coordsToLoc)
import Swarm.Game.World.Coords (Coords (..), coordsToLoc, locToCoords)
import Swarm.Game.World.Gen (Seed)
import Swarm.Game.World.Syntax (Axis (..), Rot (..))
import Swarm.Game.World.Syntax (Axis (..))
import Swarm.Game.World.Typecheck (Const (..), Empty (..), Over (..))
import Witch (from)
import Witch.Encoding qualified as Encoding
Expand Down Expand Up @@ -64,27 +62,12 @@ interpConst seed = \case
let noise = perlin (fromIntegral s) (fromIntegral o) k p
sample (i, j) = noiseValue noise (fromIntegral i / 2, fromIntegral j / 2, 0)
in \(Coords ix) -> sample ix
CReflect ax -> \w -> w . interpReflect ax
CRot r -> \w -> w . interpRot r
CFI -> fromInteger
COver -> (<!>)
CIMap -> \wx wy a c -> a (locToCoords (Location (fromIntegral (wx c)) (fromIntegral (wy c))))
K -> const
S -> (<*>)
I -> id
B -> (.)
C -> flip
Φ -> liftA2

-- | Interprect a reflection.
interpReflect :: Axis -> Coords -> Coords
interpReflect ax (Coords (r, c)) = Coords (case ax of X -> (r, -c); Y -> (-r, c))

-- | Interpret a rotation.
interpRot :: Rot -> Coords -> Coords
interpRot rot (Coords crd) = Coords (rotTuple rot crd)
where
rotTuple = \case
Rot0 -> id
Rot90 -> \(r, c) -> (-c, r)
Rot180 -> \(r, c) -> (-r, -c)
Rot270 -> \(r, c) -> (c, -r)
10 changes: 10 additions & 0 deletions src/swarm-scenario/Swarm/Game/World/Parse.hs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ reservedWords =
, "mask"
, "empty"
, "abs"
, "imap"
]

-- | Skip spaces and comments.
Expand Down Expand Up @@ -139,6 +140,7 @@ parseWExpAtom =
<|> parseOverlay
<|> parseMask
<|> parseImport
<|> parseIMap
-- <|> parseCat
-- <|> parseStruct
<|> parens parseWExp
Expand Down Expand Up @@ -238,6 +240,14 @@ parseMask = do
parseImport :: Parser WExp
parseImport = WImport . into @Text <$> between (symbol "\"") (symbol "\"") (some (satisfy (/= '"')))

parseIMap :: Parser WExp
parseIMap = do
reserved "imap"
wx <- parseWExpAtom
wy <- parseWExpAtom
wa <- parseWExpAtom
return $ WOp IMap [wx, wy, wa]

-- parseCat :: Parser WExp
-- parseCat =
-- WCat
Expand Down
13 changes: 1 addition & 12 deletions src/swarm-scenario/Swarm/Game/World/Syntax.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ module Swarm.Game.World.Syntax (
RawCellVal,
CellTag (..),
CellVal (..),
Rot (..),
Var,
Axis (..),
Op (..),
Expand Down Expand Up @@ -65,16 +64,6 @@ instance PrettyPrec CellVal where
++ [(Just CellEntity, e ^. entityName) | EJust (Last e) <- [ent]]
++ map ((Just CellRobot,) . view trobotName) rs

data Rot = Rot0 | Rot90 | Rot180 | Rot270
deriving (Eq, Ord, Show, Bounded, Enum)

instance PrettyPrec Rot where
prettyPrec _ = \case
Rot0 -> "rot0"
Rot90 -> "rot90"
Rot180 -> "rot180"
Rot270 -> "rot270"

type Var = Text

data Axis = X | Y
Expand All @@ -83,7 +72,7 @@ data Axis = X | Y
instance PrettyPrec Axis where
prettyPrec _ = \case X -> "x"; Y -> "y"

data Op = Not | Neg | And | Or | Add | Sub | Mul | Div | Mod | Eq | Neq | Lt | Leq | Gt | Geq | If | Perlin | Reflect Axis | Rot Rot | Mask | Overlay | Abs
data Op = Not | Neg | And | Or | Add | Sub | Mul | Div | Mod | Eq | Neq | Lt | Leq | Gt | Geq | If | Perlin | Mask | Overlay | Abs | IMap
Comment on lines 74 to +75
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were there no worlds using reflection and rotation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. Reflection wasn't even fully implemented, as it turns out. And rotation is not very interesting until you have things like structures you can refer to from within the language.

deriving (Eq, Ord, Show)

------------------------------------------------------------
Expand Down
19 changes: 11 additions & 8 deletions src/swarm-scenario/Swarm/Game/World/Typecheck.hs
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,8 @@ data Const :: Type -> Type where
CCoord :: Axis -> Const (World Integer)
CHash :: Const (World Integer)
CPerlin :: Const (Integer -> Integer -> Double -> Double -> World Double)
CReflect :: Axis -> Const (World a -> World a)
CRot :: Rot -> Const (World a -> World a)
COver :: (Over a, NotFun a) => Const (a -> a -> a)
CIMap :: NotFun a => Const (World Integer -> World Integer -> World a -> World a)
-- Combinators generated during elaboration + variable abstraction
K :: Const (a -> b -> a)
S :: Const ((a -> b -> c) -> (a -> b) -> a -> c)
Expand Down Expand Up @@ -186,9 +185,8 @@ instance PrettyPrec (Const α) where
CCoord ax -> ppr ax
CHash -> "hash"
CPerlin -> "perlin"
CReflect ax -> case ax of X -> "vreflect"; Y -> "hreflect"
CRot rot -> ppr rot
COver -> "over"
CIMap -> "imap"
K -> "K"
S -> "S"
I -> "I"
Expand Down Expand Up @@ -403,6 +401,13 @@ checkOver (TTyBase BFloat) a = a
checkOver (TTyBase BCell) a = a
checkOver ty _ = throwError $ NoInstance "Over" ty

checkNotFun :: (Has (Throw CheckErr) sig m) => TTy ty -> (NotFun ty => m a) -> m a
checkNotFun (TTyBase BBool) a = a
checkNotFun (TTyBase BInt) a = a
checkNotFun (TTyBase BFloat) a = a
checkNotFun (TTyBase BCell) a = a
checkNotFun ty _ = throwError $ NoInstance "NotFun" ty

------------------------------------------------------------
-- Existential wrappers

Expand Down Expand Up @@ -532,10 +537,9 @@ inferOp [SomeTy tyA] Gt = Some (tyA :->: tyA :->: TTyBool) <$> checkOrd tyA (ret
inferOp [SomeTy tyA] Geq = Some (tyA :->: tyA :->: TTyBool) <$> checkOrd tyA (return $ embed CGeq)
inferOp [SomeTy tyA] If = return $ Some (TTyBool :->: tyA :->: tyA :->: tyA) (embed CIf)
inferOp _ Perlin = return $ Some (TTyInt :->: TTyInt :->: TTyFloat :->: TTyFloat :->: TTyWorld TTyFloat) (embed CPerlin)
inferOp [SomeTy tyA] (Reflect r) = return $ Some (TTyWorld tyA :->: TTyWorld tyA) (embed (CReflect r))
inferOp [SomeTy tyA] (Rot r) = return $ Some (TTyWorld tyA :->: TTyWorld tyA) (embed (CRot r))
inferOp [SomeTy tyA] Mask = Some (TTyWorld TTyBool :->: TTyWorld tyA :->: TTyWorld tyA) <$> checkEmpty tyA (return $ embed CMask)
inferOp [SomeTy tyA] Overlay = Some (tyA :->: tyA :->: tyA) <$> checkOver tyA (return $ embed COver)
inferOp [SomeTy tyA] IMap = Some (TTyWorld TTyInt :->: TTyWorld TTyInt :->: TTyWorld tyA :->: TTyWorld tyA) <$> checkNotFun tyA (return $ embed CIMap)
inferOp tys op = error $ "bad call to inferOp: " ++ show tys ++ " " ++ show op

-- | Given a raw operator and the terms the operator is applied to,
Expand All @@ -553,10 +557,9 @@ inferOp tys op = error $ "bad call to inferOp: " ++ show tys ++ " " ++ show op
typeArgsFor :: Op -> [Some (TTerm g)] -> [SomeTy]
typeArgsFor op (t : _)
| op `elem` [Neg, Abs, Add, Sub, Mul, Div, Mod, Eq, Neq, Lt, Leq, Gt, Geq] = [getBaseType t]
typeArgsFor (Reflect _) (t : _) = [getBaseType t]
typeArgsFor (Rot _) (t : _) = [getBaseType t]
typeArgsFor op (_ : t : _)
| op `elem` [If, Mask, Overlay] = [getBaseType t]
typeArgsFor IMap (_ : _ : t : _) = [getBaseType t]
typeArgsFor _ _ = []

-- | Typecheck the application of an operator to some terms, returning
Expand Down
Loading