parabox/app/Model.hs

228 lines
6.8 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
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 {
boxLocation :: 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
Link c -> boxWalls world (worldBoxes world Map.! c)
2022-12-04 15:59:22 -08:00
Infinity c -> boxWalls world (worldBoxes world Map.! c)
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,
worldHome :: Location
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 &&
worldHome world == boxLocation (worldBoxes world Map.! worldMe world)
where
coverage = Set.fromList $ map boxLocation $ Map.elems (worldBoxes world)
2022-12-02 10:54:31 -08:00
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 :: World -> (Int,Int) -> World
move world dir =
2022-12-02 18:49:26 -08:00
case moveBlock world Map.empty (myLocation world) dir 0 of
2022-12-02 10:54:31 -08:00
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 ->
2022-12-02 18:49:26 -08:00
Rational {- ^ offset -} ->
2022-12-02 10:54:31 -08:00
Maybe (Map Location Location)
-- 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-02 10:54:31 -08:00
| Just (n,_) <- Map.lookup loc visited
= Just (fmap snd (Map.filter (\(a,_)->a >= n) visited))
2022-12-02 18:49:26 -08:00
moveBlock world visited loc dir offset =
2022-12-02 10:54:31 -08:00
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):_ ->
2022-12-02 18:49:26 -08:00
do (loc', offset') <- nextLoc world dir loc offset
--traceM (show "Next loc " ++ show loc' ++ " offset " ++ show offset')
2022-12-02 15:28:05 -08:00
guard (not (isWall world loc'))
2022-12-04 15:59:22 -08:00
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' ::
World ->
Map Location (Int, 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-02 11:48:03 -08:00
Maybe (Map Location 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-02 18:49:26 -08:00
do moveBlock world (addVisited loc 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
if Set.member locI enters then do
epsilon <- findEpsilon world (locName loc')
let eBox = worldBoxes world Map.! epsilon
(locI, offset') <- enterLoc world epsilon eBox dir 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'
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-02 10:54:31 -08:00
(name', box') <- boxAt world loc'
2022-12-04 15:59:22 -08:00
moveBlock' world (addVisited loc 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)))
((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 =
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 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 ::
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 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-02 10:54:31 -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
= go (Set.insert b visited) (boxLocation box)
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 =
listToMaybe [b' | (b', box) <- Map.assocs (worldBoxes world), Infinity i <- [boxType box], i == b]
findEpsilon :: World -> Char -> Maybe Char
findEpsilon world b =
listToMaybe [b' | (b', box) <- Map.assocs (worldBoxes world), Epsilon i _ <- [boxType box], i == b]