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
|
|
|
|
|
|
|
|
type Coord = (Int, Int)
|
|
|
|
|
|
|
|
data Box = Box {
|
|
|
|
boxLocation :: Location,
|
|
|
|
boxWalls :: Array Coord Bool,
|
|
|
|
boxColor :: Attr
|
|
|
|
}
|
|
|
|
deriving (Show, Read, Eq)
|
|
|
|
|
|
|
|
data Location = Location Char Int Int
|
|
|
|
deriving (Read, Show, Ord, Eq)
|
|
|
|
|
|
|
|
data World = World {
|
|
|
|
worldBoxes :: Map Char Box,
|
|
|
|
worldMe :: Char
|
|
|
|
}
|
|
|
|
deriving (Show, Read, Eq)
|
|
|
|
|
|
|
|
makeWalls :: [String] -> Array Coord Bool
|
|
|
|
makeWalls rows = listArray ((ylo,xlo),(yhi,xhi)) (map (' '/=) (concat rows))
|
|
|
|
where
|
|
|
|
h = length rows
|
|
|
|
w = length (head rows)
|
|
|
|
(xlo,xhi) = mkRange w
|
|
|
|
(ylo,yhi) = mkRange h
|
|
|
|
|
2022-12-02 11:48:03 -08:00
|
|
|
mkRange :: Int -> (Int,Int)
|
2022-12-02 10:54:31 -08:00
|
|
|
mkRange n = (- (n-1)`div`2, n`div`2)
|
|
|
|
|
|
|
|
boxSize :: Box -> Int
|
|
|
|
boxSize box = yhi-ylo+1
|
|
|
|
where
|
|
|
|
((ylo,_),(yhi,_)) = bounds (boxWalls box)
|
|
|
|
|
|
|
|
|
2022-12-02 11:48:03 -08:00
|
|
|
solid :: Int -> Array Coord Bool
|
2022-12-02 10:54:31 -08:00
|
|
|
solid n = makeWalls (replicate n (replicate n 'x'))
|
|
|
|
|
|
|
|
-- Move an object
|
|
|
|
-- 1. remove it from the world
|
|
|
|
-- 2. compute where it would move to
|
|
|
|
-- 3. a. that spot is empty
|
|
|
|
-- b. try to move that object forward
|
|
|
|
-- c. try to move that object backward into me
|
|
|
|
|
|
|
|
move :: World -> (Int,Int) -> World
|
|
|
|
move world dir =
|
|
|
|
case moveBlock world Map.empty (myLocation world) dir of
|
|
|
|
Nothing -> world
|
|
|
|
Just changes ->
|
|
|
|
let f box = box { boxLocation = Map.findWithDefault (boxLocation box) (boxLocation box) changes } in
|
|
|
|
world { worldBoxes = fmap f (worldBoxes world)}
|
|
|
|
|
|
|
|
myLocation :: World -> Location
|
|
|
|
myLocation world =
|
|
|
|
boxLocation (worldBoxes world Map.! worldMe world)
|
|
|
|
|
|
|
|
isWall :: Location -> World -> Bool
|
|
|
|
isWall (Location n y x) world =
|
|
|
|
case Map.lookup n (worldBoxes world) of
|
|
|
|
Nothing -> True
|
|
|
|
Just box -> boxWalls box ! (y,x)
|
|
|
|
|
|
|
|
type Movement = (Int, Int)
|
|
|
|
|
|
|
|
moveBlock ::
|
|
|
|
World ->
|
|
|
|
Map Location (Int, Location) ->
|
|
|
|
Location ->
|
|
|
|
Movement ->
|
|
|
|
Maybe (Map Location Location)
|
|
|
|
|
|
|
|
-- moving into a wall, not possible
|
2022-12-02 11:48:03 -08:00
|
|
|
moveBlock world _ loc _
|
2022-12-02 10:54:31 -08:00
|
|
|
| isWall loc world = Nothing
|
|
|
|
|
|
|
|
-- move introduced a loop, trim off the tail and report success
|
2022-12-02 11:48:03 -08:00
|
|
|
moveBlock _ visited loc _
|
2022-12-02 10:54:31 -08:00
|
|
|
| Just (n,_) <- Map.lookup loc visited
|
|
|
|
= Just (fmap snd (Map.filter (\(a,_)->a >= n) visited))
|
|
|
|
|
|
|
|
moveBlock world visited loc dir =
|
|
|
|
case [(n,box) | (n,box) <- Map.assocs (worldBoxes world), boxLocation box == loc] of
|
|
|
|
-- moving an empty space, so we're done
|
|
|
|
[] -> Just (fmap snd visited)
|
|
|
|
|
|
|
|
-- moving a box
|
|
|
|
(name,box):_ ->
|
|
|
|
do loc' <- nextLoc world loc dir
|
|
|
|
guard (not (isWall loc' world))
|
|
|
|
moveBlock' world visited loc loc' dir name box
|
|
|
|
|
2022-12-02 11:48:03 -08:00
|
|
|
moveBlock' ::
|
|
|
|
World ->
|
|
|
|
Map Location (Int, Location) ->
|
|
|
|
Location ->
|
|
|
|
Location ->
|
|
|
|
Movement ->
|
|
|
|
Char ->
|
|
|
|
Box ->
|
|
|
|
Maybe (Map Location Location)
|
2022-12-02 10:54:31 -08:00
|
|
|
moveBlock' world visited loc loc' dir name box =
|
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-02 10:54:31 -08:00
|
|
|
do moveBlock world (addVisited loc loc' visited) loc' dir
|
|
|
|
|
2022-12-02 11:48:03 -08:00
|
|
|
moveInto =
|
2022-12-02 10:54:31 -08:00
|
|
|
do (n,b) <- boxAt world loc'
|
|
|
|
let locI = enterLoc n b dir
|
|
|
|
moveBlock' world visited loc locI dir name box -- beware epsilon!
|
|
|
|
|
2022-12-02 11:48:03 -08:00
|
|
|
moveToEat =
|
2022-12-02 10:54:31 -08:00
|
|
|
do let dir' = invert dir
|
|
|
|
let locE = enterLoc name box dir'
|
|
|
|
(name', box') <- boxAt world loc'
|
|
|
|
moveBlock' world (addVisited loc loc' visited) loc' locE dir' name' box'
|
|
|
|
|
|
|
|
enterLoc :: Char -> Box -> Movement -> Location
|
|
|
|
enterLoc name box dir =
|
|
|
|
case dir of
|
|
|
|
(-1, 0) -> Location name yhi (midpoint xlo xhi)
|
|
|
|
( 1, 0) -> Location name ylo (midpoint xlo xhi)
|
|
|
|
( 0,-1) -> Location name (midpoint ylo yhi) xhi
|
|
|
|
( 0, 1) -> Location name (midpoint ylo yhi) xlo
|
|
|
|
_ -> error "enterLoc: bad direction"
|
|
|
|
where
|
|
|
|
((ylo,xlo),(yhi,xhi)) = bounds (boxWalls box)
|
|
|
|
|
|
|
|
boxAt :: World -> Location -> Maybe (Char, Box)
|
|
|
|
boxAt world loc =
|
|
|
|
listToMaybe [(n,b) | (n,b) <- Map.assocs (worldBoxes world), boxLocation b == loc]
|
|
|
|
|
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 11:48:03 -08:00
|
|
|
midpoint :: Int -> Int -> Int
|
2022-12-02 10:54:31 -08:00
|
|
|
midpoint lo hi = (hi+lo)`div`2
|
|
|
|
|
2022-12-02 11:48:03 -08:00
|
|
|
addVisited ::
|
|
|
|
Location {- ^ start -} ->
|
|
|
|
Location {- ^ end -} ->
|
|
|
|
Map Location (Int, Location) ->
|
|
|
|
Map Location (Int, Location)
|
2022-12-02 10:54:31 -08:00
|
|
|
addVisited k v m = Map.insert k (Map.size m, v) m
|
|
|
|
|
2022-12-02 11:48:03 -08:00
|
|
|
nextLoc :: World -> Location -> (Int, Int) -> Maybe Location
|
2022-12-02 10:54:31 -08:00
|
|
|
nextLoc world loc (dy, dx) = go Set.empty loc
|
|
|
|
where
|
|
|
|
|
2022-12-02 11:48:03 -08:00
|
|
|
go _ (Location b y x)
|
2022-12-02 10:54:31 -08:00
|
|
|
| Just box <- Map.lookup b (worldBoxes world)
|
|
|
|
, inRange (bounds (boxWalls box)) (y+dy, x+dx)
|
|
|
|
= Just (Location b (y+dy) (x+dx))
|
|
|
|
|
2022-12-02 11:48:03 -08:00
|
|
|
go visited (Location b _ _)
|
2022-12-02 10:54:31 -08:00
|
|
|
| Just box <- Map.lookup b (worldBoxes world)
|
|
|
|
, Set.notMember b visited
|
|
|
|
= go (Set.insert b visited) (boxLocation box)
|
|
|
|
|
|
|
|
go _ _ = Nothing
|
|
|
|
|