2022-12-02 10:54:31 -08:00
|
|
|
module Model where
|
|
|
|
|
|
|
|
import Data.Array
|
|
|
|
import Data.Map (Map)
|
|
|
|
import Data.Map qualified as Map
|
|
|
|
import Graphics.Vty
|
|
|
|
import Control.Monad
|
|
|
|
import Data.Maybe
|
|
|
|
import Data.Set qualified as Set
|
2022-12-02 20:53:45 -08:00
|
|
|
import Data.Set (Set)
|
2022-12-02 10:54:31 -08:00
|
|
|
|
|
|
|
type Coord = (Int, Int)
|
|
|
|
|
|
|
|
data Box = Box {
|
2022-12-07 13:37:02 -08:00
|
|
|
boxLocation :: Maybe Location,
|
2022-12-02 15:28:05 -08:00
|
|
|
boxType :: BoxType,
|
2022-12-03 13:33:02 -08:00
|
|
|
boxColor :: Attr,
|
|
|
|
boxBoring :: Bool
|
2022-12-02 10:54:31 -08:00
|
|
|
}
|
|
|
|
deriving (Show, Read, Eq)
|
|
|
|
|
2022-12-02 15:28:05 -08:00
|
|
|
boxWalls :: World -> Box -> Array Coord Bool
|
|
|
|
boxWalls world box =
|
|
|
|
case boxType box of
|
|
|
|
Original walls -> walls
|
2022-12-07 17:31:07 -08:00
|
|
|
Link c -> boxWalls world (boxIx world c)
|
|
|
|
Infinity c -> boxWalls world (boxIx world c)
|
2022-12-04 15:59:22 -08:00
|
|
|
Epsilon _ walls -> walls
|
2022-12-02 15:28:05 -08:00
|
|
|
|
|
|
|
data BoxType
|
|
|
|
= Original (Array Coord Bool)
|
2022-12-04 15:59:22 -08:00
|
|
|
| Link Char
|
|
|
|
| Infinity Char
|
|
|
|
| Epsilon Char (Array Coord Bool)
|
2022-12-02 15:28:05 -08:00
|
|
|
deriving (Show, Read, Eq)
|
|
|
|
|
2022-12-04 15:59:22 -08:00
|
|
|
data Location = Location { locName :: Char, locY :: Int, locX :: Int }
|
2022-12-02 10:54:31 -08:00
|
|
|
deriving (Read, Show, Ord, Eq)
|
|
|
|
|
|
|
|
data World = World {
|
|
|
|
worldBoxes :: Map Char Box,
|
2022-12-02 20:53:45 -08:00
|
|
|
worldMe :: Char,
|
|
|
|
worldButtons :: Set Location,
|
2022-12-07 10:11:06 -08:00
|
|
|
worldHome :: Location,
|
|
|
|
worldHeight :: Int,
|
|
|
|
worldWidth :: Int
|
2022-12-02 10:54:31 -08:00
|
|
|
}
|
|
|
|
deriving (Show, Read, Eq)
|
|
|
|
|
2022-12-02 21:33:44 -08:00
|
|
|
winCondition :: World -> Bool
|
|
|
|
winCondition world =
|
|
|
|
Set.isSubsetOf (worldButtons world) coverage &&
|
2022-12-07 17:31:07 -08:00
|
|
|
Just (worldHome world) == boxLocation (boxIx world (worldMe world))
|
2022-12-02 21:33:44 -08:00
|
|
|
where
|
2022-12-07 17:31:07 -08:00
|
|
|
coverage = Set.fromList (mapMaybe boxLocation (Map.elems (worldBoxes world)))
|
2022-12-02 21:33:44 -08:00
|
|
|
|
2022-12-02 10:54:31 -08:00
|
|
|
|
2022-12-02 15:28:05 -08:00
|
|
|
boxSize :: World -> Box -> Int
|
|
|
|
boxSize world box = yhi-ylo+1
|
2022-12-02 10:54:31 -08:00
|
|
|
where
|
2022-12-02 15:28:05 -08:00
|
|
|
((ylo,_),(yhi,_)) = bounds (boxWalls world box)
|
2022-12-02 10:54:31 -08:00
|
|
|
|
|
|
|
move :: World -> (Int,Int) -> World
|
|
|
|
move world dir =
|
2022-12-07 13:37:02 -08:00
|
|
|
case myLocation world of
|
2022-12-02 10:54:31 -08:00
|
|
|
Nothing -> world
|
2022-12-07 13:37:02 -08:00
|
|
|
Just loc ->
|
|
|
|
case moveBlock world Map.empty loc dir 0 of
|
|
|
|
Nothing -> world
|
|
|
|
Just changes ->
|
|
|
|
let f box change = box { boxLocation = change } in
|
|
|
|
world { worldBoxes = Map.mergeWithKey (\_ a b -> Just (f a b)) id (const Map.empty) (worldBoxes world) changes}
|
|
|
|
|
|
|
|
myLocation :: World -> Maybe Location
|
2022-12-07 17:31:07 -08:00
|
|
|
myLocation world = boxLocation (boxIx world (worldMe world))
|
2022-12-02 10:54:31 -08:00
|
|
|
|
2022-12-02 15:28:05 -08:00
|
|
|
isWall :: World -> Location -> Bool
|
|
|
|
isWall world (Location n y x) =
|
2022-12-02 10:54:31 -08:00
|
|
|
case Map.lookup n (worldBoxes world) of
|
|
|
|
Nothing -> True
|
2022-12-02 15:28:05 -08:00
|
|
|
Just box -> boxWalls world box ! (y,x)
|
2022-12-02 10:54:31 -08:00
|
|
|
|
|
|
|
type Movement = (Int, Int)
|
|
|
|
|
|
|
|
moveBlock ::
|
|
|
|
World ->
|
2022-12-07 13:37:02 -08:00
|
|
|
Map Location (Int, Char, Maybe Location) ->
|
2022-12-02 10:54:31 -08:00
|
|
|
Location ->
|
|
|
|
Movement ->
|
2022-12-02 18:49:26 -08:00
|
|
|
Rational {- ^ offset -} ->
|
2022-12-07 13:37:02 -08:00
|
|
|
Maybe (Map Char (Maybe Location))
|
2022-12-02 10:54:31 -08:00
|
|
|
|
|
|
|
-- moving into a wall, not possible
|
2022-12-02 18:49:26 -08:00
|
|
|
moveBlock world _ loc _ _
|
2022-12-02 15:28:05 -08:00
|
|
|
| isWall world loc = Nothing
|
2022-12-02 10:54:31 -08:00
|
|
|
|
|
|
|
-- move introduced a loop, trim off the tail and report success
|
2022-12-02 18:49:26 -08:00
|
|
|
moveBlock _ visited loc _ _
|
2022-12-07 13:37:02 -08:00
|
|
|
| Just (n,_,_) <- Map.lookup loc visited
|
|
|
|
= Just (Map.fromList [(c,l) | (a,c,l) <- Map.elems visited, a >= n])
|
2022-12-02 10:54:31 -08:00
|
|
|
|
2022-12-02 18:49:26 -08:00
|
|
|
moveBlock world visited loc dir offset =
|
2022-12-07 13:37:02 -08:00
|
|
|
case [(n,box) | (n,box) <- Map.assocs (worldBoxes world), boxLocation box == Just loc] of
|
2022-12-02 10:54:31 -08:00
|
|
|
-- moving an empty space, so we're done
|
2022-12-07 13:37:02 -08:00
|
|
|
[] -> Just (Map.fromList [(c,l) | (_,c,l) <- Map.elems visited])
|
2022-12-02 10:54:31 -08:00
|
|
|
|
|
|
|
-- moving a box
|
|
|
|
(name,box):_ ->
|
2022-12-07 13:37:02 -08:00
|
|
|
case nextLoc world dir loc offset of
|
|
|
|
Nothing -> Just (Map.fromList ((name, Nothing) : [(c,l) | (_,c,l) <- Map.elems visited]))
|
|
|
|
Just (loc', offset') ->
|
|
|
|
do guard (not (isWall world loc'))
|
|
|
|
moveBlock' world visited loc loc' dir name box Set.empty offset'
|
2022-12-02 10:54:31 -08:00
|
|
|
|
2022-12-02 11:48:03 -08:00
|
|
|
moveBlock' ::
|
2022-12-07 13:37:02 -08:00
|
|
|
World ->
|
|
|
|
Map Location (Int, Char, Maybe Location) ->
|
|
|
|
Location ->
|
|
|
|
Location ->
|
|
|
|
Movement ->
|
|
|
|
Char ->
|
2022-12-02 18:49:26 -08:00
|
|
|
Box ->
|
2022-12-04 15:59:22 -08:00
|
|
|
Set Location ->
|
2022-12-02 18:49:26 -08:00
|
|
|
Rational {- ^ offset -} ->
|
2022-12-07 13:37:02 -08:00
|
|
|
Maybe (Map Char (Maybe Location))
|
2022-12-04 15:59:22 -08:00
|
|
|
moveBlock' world visited loc loc' dir name box enters offset =
|
2022-12-02 11:48:03 -08:00
|
|
|
msum [moveTo, moveInto, moveToEat]
|
2022-12-02 10:54:31 -08:00
|
|
|
where
|
2022-12-02 11:48:03 -08:00
|
|
|
moveTo =
|
2022-12-07 13:37:02 -08:00
|
|
|
do moveBlock world (addVisited name loc (Just loc') visited) loc' dir 0
|
2022-12-02 10:54:31 -08:00
|
|
|
|
2022-12-02 11:48:03 -08:00
|
|
|
moveInto =
|
2022-12-02 10:54:31 -08:00
|
|
|
do (n,b) <- boxAt world loc'
|
2022-12-04 15:59:22 -08:00
|
|
|
(locI, offset') <- enterLoc world n b dir offset
|
2022-12-07 13:37:02 -08:00
|
|
|
if Set.member locI enters then
|
|
|
|
moveEpsilon
|
2022-12-04 15:59:22 -08:00
|
|
|
else
|
|
|
|
moveBlock' world visited loc locI dir name box (Set.insert locI enters) offset'
|
|
|
|
|
2022-12-07 13:37:02 -08:00
|
|
|
moveEpsilon =
|
|
|
|
do epsilon <- findEpsilon world (locName loc')
|
2022-12-07 17:31:07 -08:00
|
|
|
let eBox = boxIx world epsilon
|
2022-12-07 13:37:02 -08:00
|
|
|
(locI, offset') <- enterLoc world epsilon eBox dir offset
|
|
|
|
moveBlock' world visited loc locI dir name box (Set.insert locI enters) offset'
|
|
|
|
|
2022-12-02 11:48:03 -08:00
|
|
|
moveToEat =
|
2022-12-02 10:54:31 -08:00
|
|
|
do let dir' = invert dir
|
2022-12-04 15:59:22 -08:00
|
|
|
(locE, _) <- enterLoc world name box dir' 0
|
2022-12-07 13:37:02 -08:00
|
|
|
(name', box') <- boxAt world loc'
|
|
|
|
moveBlock' world (addVisited name loc (Just loc') visited) loc' locE dir' name' box' Set.empty 0
|
2022-12-02 10:54:31 -08:00
|
|
|
|
2022-12-04 15:59:22 -08:00
|
|
|
enterLoc :: World -> Char -> Box -> Movement -> Rational -> Maybe (Location, Rational)
|
2022-12-02 18:49:26 -08:00
|
|
|
enterLoc world name box dir@(dy,dx) offset =
|
2022-12-04 15:59:22 -08:00
|
|
|
do name' <-
|
|
|
|
case boxType box of
|
|
|
|
Link c -> Just c
|
|
|
|
Original{} -> Just name
|
|
|
|
Infinity{} -> Nothing
|
|
|
|
Epsilon {} -> Just name
|
|
|
|
|
|
|
|
let go y x = Just
|
|
|
|
(Location name' y x,
|
|
|
|
fromIntegral(boxSize world box) * offset
|
|
|
|
- fromIntegral ((abs dy *x + abs dx*y)))
|
2022-12-07 13:37:02 -08:00
|
|
|
|
2022-12-04 15:59:22 -08:00
|
|
|
((ylo,xlo),(yhi,xhi)) = bounds (boxWalls world box)
|
2022-12-02 18:49:26 -08:00
|
|
|
|
2022-12-04 15:59:22 -08:00
|
|
|
case dir of
|
|
|
|
(-1, 0) -> go yhi (midpoint xlo xhi offset)
|
|
|
|
( 1, 0) -> go ylo (midpoint xlo xhi offset)
|
|
|
|
( 0,-1) -> go (midpoint ylo yhi offset) xhi
|
|
|
|
( 0, 1) -> go (midpoint ylo yhi offset) xlo
|
|
|
|
_ -> error "enterLoc: bad direction"
|
2022-12-02 10:54:31 -08:00
|
|
|
|
|
|
|
boxAt :: World -> Location -> Maybe (Char, Box)
|
|
|
|
boxAt world loc =
|
2022-12-07 13:37:02 -08:00
|
|
|
listToMaybe [(n,b) | (n,b) <- Map.assocs (worldBoxes world), boxLocation b == Just loc]
|
2022-12-02 10:54:31 -08:00
|
|
|
|
2022-12-07 17:31:07 -08:00
|
|
|
boxIx :: World -> Char -> Box
|
|
|
|
boxIx world name = worldBoxes world Map.! name
|
|
|
|
|
2022-12-02 11:48:03 -08:00
|
|
|
invert :: Movement -> Movement
|
2022-12-02 10:54:31 -08:00
|
|
|
invert (dy,dx) = (-dy, -dx)
|
|
|
|
|
2022-12-02 18:49:26 -08:00
|
|
|
midpoint :: Int -> Int -> Rational -> Int
|
|
|
|
midpoint lo hi offset = round (offset * fromIntegral (hi-lo+1))
|
2022-12-02 10:54:31 -08:00
|
|
|
|
2022-12-02 11:48:03 -08:00
|
|
|
addVisited ::
|
2022-12-07 13:37:02 -08:00
|
|
|
Char {- ^ name -} ->
|
2022-12-02 11:48:03 -08:00
|
|
|
Location {- ^ start -} ->
|
2022-12-07 13:37:02 -08:00
|
|
|
Maybe Location {- ^ end -} ->
|
|
|
|
Map Location (Int, Char, Maybe Location) ->
|
|
|
|
Map Location (Int, Char, Maybe Location)
|
|
|
|
addVisited name k v m = Map.insert k (Map.size m, name, v) m
|
2022-12-02 10:54:31 -08:00
|
|
|
|
2022-12-02 18:49:26 -08:00
|
|
|
nextLoc :: World -> Movement -> Location -> Rational -> Maybe (Location, Rational)
|
|
|
|
nextLoc world (dy, dx) = go Set.empty
|
2022-12-02 10:54:31 -08:00
|
|
|
where
|
2022-12-02 18:49:26 -08:00
|
|
|
|
|
|
|
go _ (Location b y x) offset
|
2022-12-02 10:54:31 -08:00
|
|
|
| Just box <- Map.lookup b (worldBoxes world)
|
2022-12-02 15:28:05 -08:00
|
|
|
, inRange (bounds (boxWalls world box)) (y+dy, x+dx)
|
2022-12-02 18:49:26 -08:00
|
|
|
= Just (Location b (y+dy) (x+dx), offset)
|
2022-12-07 13:37:02 -08:00
|
|
|
|
2022-12-02 18:49:26 -08:00
|
|
|
go visited (Location b y x) offset
|
2022-12-02 10:54:31 -08:00
|
|
|
| Just box <- Map.lookup b (worldBoxes world)
|
|
|
|
, Set.notMember b visited
|
2022-12-07 13:37:02 -08:00
|
|
|
, Just boxLoc <- boxLocation box
|
|
|
|
= go (Set.insert b visited) boxLoc
|
2022-12-02 18:49:26 -08:00
|
|
|
$ (offset + fromIntegral (abs dy*x+abs dx*y))
|
|
|
|
/ fromIntegral (boxSize world box)
|
2022-12-02 10:54:31 -08:00
|
|
|
|
2022-12-04 15:59:22 -08:00
|
|
|
go visited (Location b y x) offset
|
|
|
|
| Set.member b visited
|
|
|
|
, Just b' <- findInfinity world b
|
|
|
|
= go visited (Location b' y x) offset
|
|
|
|
|
2022-12-02 18:49:26 -08:00
|
|
|
go _ _ _ = Nothing
|
2022-12-02 10:54:31 -08:00
|
|
|
|
2022-12-04 15:59:22 -08:00
|
|
|
findInfinity :: World -> Char -> Maybe Char
|
|
|
|
findInfinity world b =
|
2022-12-07 13:37:02 -08:00
|
|
|
listToMaybe [b' | (b', box) <- Map.assocs (worldBoxes world)
|
|
|
|
, Infinity i <- [boxType box], i == b]
|
2022-12-04 15:59:22 -08:00
|
|
|
|
|
|
|
findEpsilon :: World -> Char -> Maybe Char
|
|
|
|
findEpsilon world b =
|
2022-12-07 13:37:02 -08:00
|
|
|
listToMaybe [b' | (b', box) <- Map.assocs (worldBoxes world)
|
|
|
|
, Epsilon i _ <- [boxType box], i == b]
|