parabox/app/Model.hs

187 lines
5.0 KiB
Haskell
Raw Normal View History

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,
2022-12-02 15:28:05 -08:00
boxType :: BoxType,
2022-12-02 10:54:31 -08:00
boxColor :: Attr
}
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
Link c -> boxWalls world (worldBoxes world Map.! c)
data BoxType
= Original (Array Coord Bool)
| Link Char
deriving (Show, Read, Eq)
2022-12-02 10:54:31 -08:00
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)
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
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)
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 ->
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 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 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
2022-12-02 15:28:05 -08:00
guard (not (isWall world loc'))
2022-12-02 10:54:31 -08:00
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'
2022-12-02 15:28:05 -08:00
let locI = enterLoc world n b dir
2022-12-02 10:54:31 -08:00
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
2022-12-02 15:28:05 -08:00
let locE = enterLoc world name box dir'
2022-12-02 10:54:31 -08:00
(name', box') <- boxAt world loc'
moveBlock' world (addVisited loc loc' visited) loc' locE dir' name' box'
2022-12-02 15:28:05 -08:00
enterLoc :: World -> Char -> Box -> Movement -> Location
enterLoc world name box dir =
2022-12-02 10:54:31 -08:00
case dir of
2022-12-02 15:28:05 -08:00
(-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
2022-12-02 10:54:31 -08:00
_ -> error "enterLoc: bad direction"
where
2022-12-02 15:28:05 -08:00
name' = case boxType box of
Link c -> c
Original{} -> name
((ylo,xlo),(yhi,xhi)) = bounds (boxWalls world box)
2022-12-02 10:54:31 -08:00
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)
2022-12-02 15:28:05 -08:00
, inRange (bounds (boxWalls world box)) (y+dy, x+dx)
2022-12-02 10:54:31 -08:00
= 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