handle blocks having undefined location

This commit is contained in:
Eric Mertens 2022-12-07 13:37:02 -08:00
parent af5305cc24
commit 84f74366bb
9 changed files with 177 additions and 203 deletions

View File

@ -1,10 +1,10 @@
module Main (main) where module Main (main) where
import Control.Exception ( bracket ) import Control.Exception ( bracket )
import Data.List.NonEmpty (NonEmpty(..)) import Data.List.NonEmpty (NonEmpty(..), (<|))
import Data.List.NonEmpty qualified as NonEmpty import Data.List.NonEmpty qualified as NonEmpty
import Data.Char (toUpper) import Data.Char (toUpper)
import Data.List (intersperse) import Data.List (intersperse, sort)
import Data.Map (Map) import Data.Map (Map)
import Data.Map qualified as Map import Data.Map qualified as Map
import Graphics.Vty import Graphics.Vty
@ -29,7 +29,7 @@ data GameMode
getWorldList :: IO (Map String FilePath) getWorldList :: IO (Map String FilePath)
getWorldList = getWorldList =
do paths <- listDirectory "levels" do paths <- sort <$> listDirectory "levels"
pure (Map.fromList [(takeBaseName path, "levels" </> path) | path <- paths]) pure (Map.fromList [(takeBaseName path, "levels" </> path) | path <- paths])
main :: IO () main :: IO ()
@ -57,17 +57,18 @@ loop vty game =
case gameMode game of case gameMode game of
MenuMode -> MenuMode ->
do worldList <- getWorldList do worldList <- getWorldList
let (a,b) = Map.splitAt (gameSelect game) worldList
bnds <- displayBounds (outputIface vty) bnds <- displayBounds (outputIface vty)
update vty (picForImage (renderMenu bnds a b)) update vty (picForImage (renderMenu (gameSelect game) worldList bnds))
ev <- nextEvent vty ev <- nextEvent vty
case ev of case ev of
EvKey key _modifier -> EvKey key _modifier ->
case key of case key of
KEsc -> pure () KEsc -> pure ()
KUp | gameSelect game > 0 -> loop vty game{ gameSelect = gameSelect game - 1 } KUp | gameSelect game > 0 ->
KDown | gameSelect game + 1 < Map.size worldList -> loop vty game{ gameSelect = gameSelect game + 1 } loop vty game{ gameSelect = gameSelect game - 1 }
KEnter | Just (path,_) <- Map.minView b -> KDown | gameSelect game + 1 < Map.size worldList ->
loop vty game{ gameSelect = gameSelect game + 1 }
KEnter | (_, path) <- Map.elemAt (gameSelect game) worldList ->
do world <- parse <$> readFile path do world <- parse <$> readFile path
loop vty game{ gameMode = PlayMode (pure world) } loop vty game{ gameMode = PlayMode (pure world) }
_ -> loop vty game _ -> loop vty game
@ -79,11 +80,12 @@ loop vty game =
ev <- nextEvent vty ev <- nextEvent vty
case ev of case ev of
EvKey key _modifier -> EvKey key _modifier ->
let doMove m = game{ gameMode = PlayMode (move world m <| worlds) } in
case key of case key of
KUp -> loop vty game{ gameMode = PlayMode (NonEmpty.cons (move world (-1,0)) worlds) } KUp -> loop vty (doMove (-1, 0))
KDown -> loop vty game{ gameMode = PlayMode (NonEmpty.cons (move world (1,0) ) worlds) } KDown -> loop vty (doMove ( 1, 0))
KLeft -> loop vty game{ gameMode = PlayMode (NonEmpty.cons (move world (0,-1)) worlds) } KLeft -> loop vty (doMove ( 0,-1))
KRight -> loop vty game{ gameMode = PlayMode (NonEmpty.cons (move world (0,1) ) worlds) } KRight -> loop vty (doMove ( 0, 1))
KChar 'm' -> loop vty game { gameMode = MenuMode } KChar 'm' -> loop vty game { gameMode = MenuMode }
KChar 'r' -> loop vty game{ gameMode = PlayMode (pure (NonEmpty.last worlds)) } KChar 'r' -> loop vty game{ gameMode = PlayMode (pure (NonEmpty.last worlds)) }
KChar 'z' KChar 'z'
@ -95,24 +97,19 @@ loop vty game =
_ -> loop vty game _ -> loop vty game
renderMenu :: DisplayRegion -> Map String a -> Map String a -> Image renderMenu :: Int -> Map String a -> DisplayRegion -> Image
renderMenu (w,h) before after = renderMenu sel list (w,h)
pad ((w - imageWidth menu) `div` 2) 0 0 0 menu | hpad >= 0 = pad wpad hpad 0 0 menu
| otherwise = pad wpad 0 0 0 (cropTop (imageHeight menu + hpad) menu)
where where
hpad = h`div`2 - sel*6
wpad = max 0 (w - imageWidth menu) `div` 2
menu = menu =
case Map.minViewWithKey after of
Nothing -> bigString defAttr "empty menu"
Just ((k,_),after') ->
let len1 = (h-1)`div`2 `div` 6 in
pad 0 (max 0 (6 * (len1 - Map.size before))) 0 0 $
vertCat $ vertCat $
intersperse (char defAttr ' ') $ intersperse (char defAttr ' ') $
[ bigString defAttr x [ bigString (if sel == i then defAttr `withBackColor` cyan `withForeColor` white
| x <- drop (Map.size before - len1) (Map.keys before) else defAttr) k
] ++ | (i,k) <- zip [0..] (Map.keys list)
[bigString (defAttr `withBackColor` cyan `withForeColor` white) k] ++
[ bigString defAttr x
| x <- drop (Map.size after' - h`div`2) (Map.keys after')
] ]
bigString :: Attr -> String -> Image bigString :: Attr -> String -> Image

View File

@ -12,7 +12,7 @@ import Data.Set (Set)
type Coord = (Int, Int) type Coord = (Int, Int)
data Box = Box { data Box = Box {
boxLocation :: Location, boxLocation :: Maybe Location,
boxType :: BoxType, boxType :: BoxType,
boxColor :: Attr, boxColor :: Attr,
boxBoring :: Bool boxBoring :: Bool
@ -50,9 +50,9 @@ data World = World {
winCondition :: World -> Bool winCondition :: World -> Bool
winCondition world = winCondition world =
Set.isSubsetOf (worldButtons world) coverage && Set.isSubsetOf (worldButtons world) coverage &&
worldHome world == boxLocation (worldBoxes world Map.! worldMe world) Just (worldHome world) == boxLocation (worldBoxes world Map.! worldMe world)
where where
coverage = Set.fromList $ map boxLocation $ Map.elems (worldBoxes world) coverage = Set.fromList $ mapMaybe boxLocation $ Map.elems (worldBoxes world)
boxSize :: World -> Box -> Int boxSize :: World -> Box -> Int
@ -62,13 +62,16 @@ boxSize world box = yhi-ylo+1
move :: World -> (Int,Int) -> World move :: World -> (Int,Int) -> World
move world dir = move world dir =
case moveBlock world Map.empty (myLocation world) dir 0 of case myLocation world of
Nothing -> world
Just loc ->
case moveBlock world Map.empty loc dir 0 of
Nothing -> world Nothing -> world
Just changes -> Just changes ->
let f box = box { boxLocation = Map.findWithDefault (boxLocation box) (boxLocation box) changes } in let f box change = box { boxLocation = change } in
world { worldBoxes = fmap f (worldBoxes world)} world { worldBoxes = Map.mergeWithKey (\_ a b -> Just (f a b)) id (const Map.empty) (worldBoxes world) changes}
myLocation :: World -> Location myLocation :: World -> Maybe Location
myLocation world = myLocation world =
boxLocation (worldBoxes world Map.! worldMe world) boxLocation (worldBoxes world Map.! worldMe world)
@ -82,11 +85,11 @@ type Movement = (Int, Int)
moveBlock :: moveBlock ::
World -> World ->
Map Location (Int, Location) -> Map Location (Int, Char, Maybe Location) ->
Location -> Location ->
Movement -> Movement ->
Rational {- ^ offset -} -> Rational {- ^ offset -} ->
Maybe (Map Location Location) Maybe (Map Char (Maybe Location))
-- moving into a wall, not possible -- moving into a wall, not possible
moveBlock world _ loc _ _ moveBlock world _ loc _ _
@ -94,24 +97,25 @@ moveBlock world _ loc _ _
-- move introduced a loop, trim off the tail and report success -- move introduced a loop, trim off the tail and report success
moveBlock _ visited loc _ _ moveBlock _ visited loc _ _
| Just (n,_) <- Map.lookup loc visited | Just (n,_,_) <- Map.lookup loc visited
= Just (fmap snd (Map.filter (\(a,_)->a >= n) visited)) = Just (Map.fromList [(c,l) | (a,c,l) <- Map.elems visited, a >= n])
moveBlock world visited loc dir offset = moveBlock world visited loc dir offset =
case [(n,box) | (n,box) <- Map.assocs (worldBoxes world), boxLocation box == loc] of case [(n,box) | (n,box) <- Map.assocs (worldBoxes world), boxLocation box == Just loc] of
-- moving an empty space, so we're done -- moving an empty space, so we're done
[] -> Just (fmap snd visited) [] -> Just (Map.fromList [(c,l) | (_,c,l) <- Map.elems visited])
-- moving a box -- moving a box
(name,box):_ -> (name,box):_ ->
do (loc', offset') <- nextLoc world dir loc offset case nextLoc world dir loc offset of
--traceM (show "Next loc " ++ show loc' ++ " offset " ++ show offset') Nothing -> Just (Map.fromList ((name, Nothing) : [(c,l) | (_,c,l) <- Map.elems visited]))
guard (not (isWall world loc')) Just (loc', offset') ->
do guard (not (isWall world loc'))
moveBlock' world visited loc loc' dir name box Set.empty offset' moveBlock' world visited loc loc' dir name box Set.empty offset'
moveBlock' :: moveBlock' ::
World -> World ->
Map Location (Int, Location) -> Map Location (Int, Char, Maybe Location) ->
Location -> Location ->
Location -> Location ->
Movement -> Movement ->
@ -119,29 +123,32 @@ moveBlock' ::
Box -> Box ->
Set Location -> Set Location ->
Rational {- ^ offset -} -> Rational {- ^ offset -} ->
Maybe (Map Location Location) Maybe (Map Char (Maybe Location))
moveBlock' world visited loc loc' dir name box enters offset = moveBlock' world visited loc loc' dir name box enters offset =
msum [moveTo, moveInto, moveToEat] msum [moveTo, moveInto, moveToEat]
where where
moveTo = moveTo =
do moveBlock world (addVisited loc loc' visited) loc' dir 0 do moveBlock world (addVisited name loc (Just loc') visited) loc' dir 0
moveInto = moveInto =
do (n,b) <- boxAt world loc' do (n,b) <- boxAt world loc'
(locI, offset') <- enterLoc world n b dir offset (locI, offset') <- enterLoc world n b dir offset
if Set.member locI enters then do if Set.member locI enters then
epsilon <- findEpsilon world (locName loc') moveEpsilon
else
moveBlock' world visited loc locI dir name box (Set.insert locI enters) offset'
moveEpsilon =
do epsilon <- findEpsilon world (locName loc')
let eBox = worldBoxes world Map.! epsilon let eBox = worldBoxes world Map.! epsilon
(locI, offset') <- enterLoc world epsilon eBox dir offset (locI, offset') <- enterLoc world epsilon eBox dir offset
moveBlock' world visited loc locI dir name box (Set.insert locI enters) offset' moveBlock' world visited loc locI dir name box (Set.insert locI enters) offset'
else
moveBlock' world visited loc locI dir name box (Set.insert locI enters) offset'
moveToEat = moveToEat =
do let dir' = invert dir do let dir' = invert dir
(locE, _) <- enterLoc world name box dir' 0 (locE, _) <- enterLoc world name box dir' 0
(name', box') <- boxAt world loc' (name', box') <- boxAt world loc'
moveBlock' world (addVisited loc loc' visited) loc' locE dir' name' box' Set.empty 0 moveBlock' world (addVisited name loc (Just loc') visited) loc' locE dir' name' box' Set.empty 0
enterLoc :: World -> Char -> Box -> Movement -> Rational -> Maybe (Location, Rational) enterLoc :: World -> Char -> Box -> Movement -> Rational -> Maybe (Location, Rational)
enterLoc world name box dir@(dy,dx) offset = enterLoc world name box dir@(dy,dx) offset =
@ -168,7 +175,7 @@ enterLoc world name box dir@(dy,dx) offset =
boxAt :: World -> Location -> Maybe (Char, Box) boxAt :: World -> Location -> Maybe (Char, Box)
boxAt world loc = boxAt world loc =
listToMaybe [(n,b) | (n,b) <- Map.assocs (worldBoxes world), boxLocation b == loc] listToMaybe [(n,b) | (n,b) <- Map.assocs (worldBoxes world), boxLocation b == Just loc]
invert :: Movement -> Movement invert :: Movement -> Movement
invert (dy,dx) = (-dy, -dx) invert (dy,dx) = (-dy, -dx)
@ -177,11 +184,12 @@ midpoint :: Int -> Int -> Rational -> Int
midpoint lo hi offset = round (offset * fromIntegral (hi-lo+1)) midpoint lo hi offset = round (offset * fromIntegral (hi-lo+1))
addVisited :: addVisited ::
Char {- ^ name -} ->
Location {- ^ start -} -> Location {- ^ start -} ->
Location {- ^ end -} -> Maybe Location {- ^ end -} ->
Map Location (Int, Location) -> Map Location (Int, Char, Maybe Location) ->
Map Location (Int, Location) Map Location (Int, Char, Maybe Location)
addVisited k v m = Map.insert k (Map.size m, v) m addVisited name k v m = Map.insert k (Map.size m, name, v) m
nextLoc :: World -> Movement -> Location -> Rational -> Maybe (Location, Rational) nextLoc :: World -> Movement -> Location -> Rational -> Maybe (Location, Rational)
nextLoc world (dy, dx) = go Set.empty nextLoc world (dy, dx) = go Set.empty
@ -195,7 +203,8 @@ nextLoc world (dy, dx) = go Set.empty
go visited (Location b y x) offset go visited (Location b y x) offset
| Just box <- Map.lookup b (worldBoxes world) | Just box <- Map.lookup b (worldBoxes world)
, Set.notMember b visited , Set.notMember b visited
= go (Set.insert b visited) (boxLocation box) , Just boxLoc <- boxLocation box
= go (Set.insert b visited) boxLoc
$ (offset + fromIntegral (abs dy*x+abs dx*y)) $ (offset + fromIntegral (abs dy*x+abs dx*y))
/ fromIntegral (boxSize world box) / fromIntegral (boxSize world box)
@ -208,9 +217,10 @@ nextLoc world (dy, dx) = go Set.empty
findInfinity :: World -> Char -> Maybe Char findInfinity :: World -> Char -> Maybe Char
findInfinity world b = findInfinity world b =
listToMaybe [b' | (b', box) <- Map.assocs (worldBoxes world), Infinity i <- [boxType box], i == b] listToMaybe [b' | (b', box) <- Map.assocs (worldBoxes world)
, Infinity i <- [boxType box], i == b]
findEpsilon :: World -> Char -> Maybe Char findEpsilon :: World -> Char -> Maybe Char
findEpsilon world b = findEpsilon world b =
listToMaybe [b' | (b', box) <- Map.assocs (worldBoxes world), Epsilon i _ <- [boxType box], i == b] listToMaybe [b' | (b', box) <- Map.assocs (worldBoxes world)
, Epsilon i _ <- [boxType box], i == b]

View File

@ -18,7 +18,7 @@ parse str =
do let m = Map.fromListWith (++) [(k,[v]) | (_,_,kvs) <- bs, (k,v) <- kvs] do let m = Map.fromListWith (++) [(k,[v]) | (_,_,kvs) <- bs, (k,v) <- kvs]
World World
(Map.fromList [ (Map.fromList [
(n, b { boxLocation = head (m Map.! n)}) (n, b { boxLocation = fmap head (Map.lookup n m)})
| (n,b,_) <- bs | (n,b,_) <- bs
]) ])
p p
@ -61,7 +61,7 @@ parseBlock =
_ <- char '\n' _ <- char '\n'
xs1 <- parseWalls xs1 <- parseWalls
let locs = findLocs name xs1 let locs = findLocs name xs1
let b = Box undefined (Original (walls xs1)) color boring let b = Box Nothing (Original (walls xs1)) color boring
pure (name, b, locs) pure (name, b, locs)
"link" -> "link" ->
do [name] <- token do [name] <- token

View File

@ -1,6 +1,7 @@
module Rendering where module Rendering where
import Data.Array import Data.Array
import Data.Maybe
import Data.Map (Map) import Data.Map (Map)
import Data.Map qualified as Map import Data.Map qualified as Map
import Data.Set qualified as Set import Data.Set qualified as Set
@ -78,13 +79,13 @@ contentName world name box =
Epsilon{} -> name Epsilon{} -> name
renderBox :: World -> Map Location Char -> Box -> Char -> Int -> Int -> Image renderBox :: World -> Map Location Char -> Box -> Char -> Int -> Int -> Image
renderBox world locMap box name h w = renderBox world locMap box name boxh boxw =
vertCat [ vertCat [
horizCat [ horizCat [
renderCell world locMap name box y x h w renderCell world locMap name box y x cellh cellw
| (x,w) <- zip [xlo .. xhi] (divisions boxWidth w) | (x,cellw) <- zip [xlo .. xhi] (divisions boxWidth boxw)
] ]
| (y,h) <- zip [ylo .. yhi] (divisions boxHeight h) | (y,cellh) <- zip [ylo .. yhi] (divisions boxHeight boxh)
] ]
where where
((ylo,xlo),(yhi,xhi)) = bounds (boxWalls world box) ((ylo,xlo),(yhi,xhi)) = bounds (boxWalls world box)
@ -107,7 +108,7 @@ render flat world = picForLayers $
(if flat then renderFlat locMap world else []) ++ (if flat then renderFlat locMap world else []) ++
[drawNestedWorld locMap world] [drawNestedWorld locMap world]
where where
locMap = Map.fromList [(boxLocation loc, n) | (n, loc) <- Map.toList (worldBoxes world)] locMap = Map.fromList [(loc, n) | (n, box) <- Map.toList (worldBoxes world), loc <- maybeToList (boxLocation box)]
renderFlat :: Map Location Char -> World -> [Image] renderFlat :: Map Location Char -> World -> [Image]
renderFlat locMap world = renderFlat locMap world =
@ -137,22 +138,29 @@ drawNestedWorld locMap world =
horizCat $ horizCat $
intersperse (char defAttr ' ') intersperse (char defAttr ' ')
[ [
case stackedLoc world locMap (Location name1 y_ x_) of case myLocation world of
Nothing -> unit (withForeColor defAttr black) h w '?' Nothing
| dx==0 && dy==0 -> renderBox world locMap (worldBoxes world Map.! worldMe world) (worldMe world) h w
| otherwise -> infinityImage
Just (Location name0 _ _) ->
case boxLocation (worldBoxes world Map.! name0) of
Nothing
| dx==0 && dy==0 -> renderBox world locMap (worldBoxes world Map.! name0) name0 h w
| otherwise -> infinityImage
Just (Location name1 y1 x1) ->
case stackedLoc world locMap (Location name1 (y1+dy) (x1+dx)) of
Nothing -> infinityImage
Just (Location n y x) -> Just (Location n y x) ->
let box = worldBoxes world Map.! n in let box = worldBoxes world Map.! n in
renderCell world locMap n box y x h w renderCell world locMap n box y x h w
| x_ <- [x1-1 .. x1+1] | dx <- [-1 .. 1]
] ]
| y_ <- [y1-1 .. y1+1] | dy <- [-1 .. 1]
] ]
where where
infinityImage = unit (withForeColor defAttr black) h w '?'
h = worldHeight world h = worldHeight world
w = worldWidth world w = worldWidth world
-- name1 is the box the player is standing in
Location name0 _ _ = myLocation world
Location name1 y1 x1 = boxLocation (worldBoxes world Map.! name0)
stackedLoc :: World -> Map Location Char -> Location -> Maybe Location stackedLoc :: World -> Map Location Char -> Location -> Maybe Location
stackedLoc world locMap = go Set.empty stackedLoc world locMap = go Set.empty
@ -168,11 +176,10 @@ stackedLoc world locMap = go Set.empty
if inRange bnds (y, x) if inRange bnds (y, x)
then Just loc then Just loc
else else
let dx = overflow (xlo,xhi) x do let dx = overflow (xlo,xhi) x
dy = overflow (ylo,yhi) y dy = overflow (ylo,yhi) y
Location parent py px = boxLocation box Location parent py px <- boxLocation box
in fixup world locMap dy dx y x <$> go (Set.insert loc visited) (Location parent (py+dy) (px+dx)) fixup world locMap dy dx y x <$> go (Set.insert loc visited) (Location parent (py+dy) (px+dx))
overflow :: (Int, Int) -> Int -> Int overflow :: (Int, Int) -> Int -> Int
overflow (lo,hi) x overflow (lo,hi) x
@ -208,7 +215,7 @@ divisions divs size =
(fromIntegral i + 1 / 2) (fromIntegral i + 1 / 2)
/ fromIntegral size / fromIntegral size
* fromIntegral divs * fromIntegral divs
- 1/2 - 1/2 :: Rational
) ) :: Int
| i <- [0 ..size-1] | i <- [0 ..size-1]
] ]

View File

@ -1,4 +1,4 @@
player p player p height 98 width 196
block t white boring block t white boring
▓▓▓▓▓ ▓▓▓▓▓
▓▓▓▓▓ ▓▓▓▓▓
@ -6,15 +6,13 @@ block t white boring
▓▓▓▓▓ ▓▓▓▓▓
▓▓▓▓▓ ▓▓▓▓▓
block w white interesting block w white interesting
▓▓▓▓▓▓▓▓▓ ▓ ▓
▓▓ ▓▓ ▓ g G ▓
▓▓ g G ▓▓ ▓ ▓
▓▓ ▓▓ ▓ = p ▓
▓▓ = p ▓▓ ▓ ▓
▓▓ ▓▓ ▓▓▓▓▓ ▓
▓▓▓▓▓▓ ▓▓ ▓▓ε▓▓▓▓
▓▓▓ε▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
link G g green link G g green
link H g green link H g green
block g green interesting block g green interesting

View File

@ -1,24 +1,12 @@
player p player p
block t black boring
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓t▓▓▓w▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓ε▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
block w white interesting block w white interesting
▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓ ▓ p ▓
▓▓ p ▓▓ ▓ G H ▓
▓▓ G H ▓▓ ▓ ▓
▓▓ = ▓▓ ▓ -▓
▓▓ -▓▓ ▓▓▓g▓=▓
▓▓▓▓g▓=▓▓ ▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
link G g green link G g green
link H g green link H g green
block g green interesting block g green interesting
@ -27,10 +15,6 @@ block g green interesting
epsilon ε g green epsilon ε g green
b b
@ -39,25 +23,13 @@ epsilon ε g green
block p magenta boring block p magenta boring
▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓
▓▓▓▓▓▓▓▓▓ ▓ ▓ ▓
▓▓ ▓▓▓ ▓▓ ▓▓▓▓▓
▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓
▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
block b blue interesting block b blue interesting
▓▓▓▓▓▓▓▓▓ ▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓ ▓ ▓
▓▓▓
▓▓▓
▓▓▓
▓▓▓ ▓▓▓
▓▓▓ ▓▓▓
▓▓▓ ▓▓▓

View File

@ -10,15 +10,9 @@ block t white boring
▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓
block a white interesting block a white interesting
▓▓▓▓▓▓▓▓▓ p
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓ P▓
▓▓▓p ▓▓▓
▓▓▓ ∞▓▓▓
▓▓▓ P▓▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
link P p magenta link P p magenta
infinity ∞ p magenta infinity ∞ p magenta
block p magenta interesting block p magenta interesting
@ -32,32 +26,10 @@ block p magenta interesting
▓ c ▓ c
block c green interesting block c green interesting
▓▓▓▓▓▓▓▓▓ ▓▓▓
▓▓▓▓▓▓▓▓▓ =▓
▓▓▓▓▓▓▓▓▓ ▓▓▓
▓▓▓
= ▓▓▓
▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
block 1 blue boring block 1 blue boring
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
block 2 blue boring block 2 blue boring
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓

17
levels/player19.txt Normal file
View File

@ -0,0 +1,17 @@
player p
block p magenta interesting
p ▓
1 2
c
block c green interesting
▓▓▓
=▓
▓▓▓
block 1 blue boring
block 2 blue boring

View File

@ -17,14 +17,15 @@ block g green interesting
block y yellow interesting block y yellow interesting
▓ ▓ ▓ ▓
▓ ▓
▓ ▓
▓ ▓ ▓ ▓
block p magenta boring block p magenta boring
▓▓▓▓▓ ▓▓▓▓▓
▓ ▓ ▓ ▓ ▓ ▓
▓▓▓▓▓ ▓▓▓▓▓
▓▓▓▓▓ ▓▓▓▓▓
▓▓▓▓▓