@@ -8,6 +8,11 @@ module Main where
88----------------------------------------------------------------------------
99import Control.Concurrent (threadDelay )
1010import Control.Monad (forever , when )
11+ import Data.Foldable (toList )
12+ import Data.Sequence (Seq , ViewL (.. ), ViewR (.. ))
13+ import qualified Data.Sequence as Seq
14+ import Data.Set (Set )
15+ import qualified Data.Set as Set
1116----------------------------------------------------------------------------
1217import Miso hiding (Phase )
1318import Miso.CSS hiding (ms , background , Phase )
@@ -34,17 +39,21 @@ data Dir = DUp | DDown | DLeft | DRight deriving (Show, Eq)
3439data Phase = NotStarted | Playing | GameOver deriving (Show , Eq )
3540
3641data Model = Model
37- { _snake :: ! [(Int , Int )]
38- , _dir :: ! Dir
39- , _queued :: ! Dir
40- , _food :: ! (Int , Int )
41- , _score :: ! Int
42- , _phase :: ! Phase
42+ { _snake :: ! (Seq (Int , Int ))
43+ , _occupied :: ! (Set (Int , Int ))
44+ , _dir :: ! Dir
45+ , _queued :: ! Dir
46+ , _food :: ! (Int , Int )
47+ , _score :: ! Int
48+ , _phase :: ! Phase
4349 } deriving (Show , Eq )
4450
45- snake :: Lens Model [( Int , Int )]
51+ snake :: Lens Model ( Seq ( Int , Int ))
4652snake = lens _snake $ \ r x -> r { _snake = x }
4753
54+ occupied :: Lens Model (Set (Int , Int ))
55+ occupied = lens _occupied $ \ r x -> r { _occupied = x }
56+
4857dir :: Lens Model Dir
4958dir = lens _dir $ \ r x -> r { _dir = x }
5059
@@ -81,20 +90,24 @@ foreign export javascript "hs_start" main :: IO ()
8190#endif
8291#endif
8392
84- initSnake :: [(Int , Int )]
85- initSnake = [(10 ,10 ),(9 ,10 ),(8 ,10 )]
93+ initSnake :: Seq (Int , Int )
94+ initSnake = Seq. fromList [(10 ,10 ),(9 ,10 ),(8 ,10 )]
95+
96+ initOccupied :: Set (Int , Int )
97+ initOccupied = Set. fromList [(10 ,10 ),(9 ,10 ),(8 ,10 )]
8698
8799initFood :: (Int , Int )
88100initFood = (15 ,10 )
89101
90102emptyModel :: Model
91103emptyModel = Model
92- { _snake = initSnake
93- , _dir = DRight
94- , _queued = DRight
95- , _food = initFood
96- , _score = 0
97- , _phase = NotStarted
104+ { _snake = initSnake
105+ , _occupied = initOccupied
106+ , _dir = DRight
107+ , _queued = DRight
108+ , _food = initFood
109+ , _score = 0
110+ , _phase = NotStarted
98111 }
99112
100113app :: App Model Action
@@ -130,20 +143,20 @@ step DDown (x,y) = (x, y+1)
130143step DLeft (x,y) = (x- 1 , y)
131144step DRight (x,y) = (x+ 1 , y)
132145
133- pickFood :: [ (Int , Int )] -> IO (Int , Int )
134- pickFood body = do
146+ pickFood :: Set (Int , Int ) -> IO (Int , Int )
147+ pickFood occ = do
135148 [rx, ry] <- replicateRM 2
136149 let x = floor (rx * fromIntegral gridSize) `mod` gridSize
137150 y = floor (ry * fromIntegral gridSize) `mod` gridSize
138- if (x, y) `elem` body then pickFood body else pure (x, y)
151+ if Set. member (x, y) occ then pickFood occ else pure (x, y)
139152
140153updateModel :: Action -> Effect parent props Model Action
141154updateModel = \ case
142155 NoOp -> pure ()
143156
144157 NewGame -> do
145158 put emptyModel { _phase = Playing }
146- io $ pickFood initSnake >>= pure . PlaceFood
159+ io $ pickFood initOccupied >>= pure . PlaceFood
147160
148161 PlaceFood pos -> food .= pos
149162
@@ -160,20 +173,27 @@ updateModel = \case
160173 Playing -> do
161174 let d = _queued m
162175 body = _snake m
163- newHead = step d (head body)
176+ occ = _occupied m
177+ newHead = case Seq. viewl body of h :< _ -> step d h; _ -> (0 ,0 )
164178 (nx, ny) = newHead
165179 wall = nx < 0 || ny < 0 || nx >= gridSize || ny >= gridSize
166- self = newHead `elem` tail body
180+ self = Set. member newHead occ
167181 dir .= d
168182 if wall || self
169183 then phase .= GameOver
170- else do
171- let ate = newHead == _food m
172- newBody = if ate then newHead : body else newHead : init body
173- snake .= newBody
174- when ate $ do
175- score += 1
176- io $ pickFood newBody >>= pure . PlaceFood
184+ else case Seq. viewr body of
185+ EmptyR -> pure ()
186+ init' :> tailCell -> do
187+ let ate = newHead == _food m
188+ newBody | ate = newHead Seq. <| body
189+ | otherwise = newHead Seq. <| init'
190+ newOcc | ate = Set. insert newHead occ
191+ | otherwise = Set. insert newHead (Set. delete tailCell occ)
192+ snake .= newBody
193+ occupied .= newOcc
194+ when ate $ do
195+ score += 1
196+ io $ pickFood newOcc >>= pure . PlaceFood
177197
178198----------------------------------------------------------------------------
179199-- View
@@ -353,9 +373,10 @@ renderFood (fx, fy) =
353373 ]
354374 ]
355375
356- renderSnake :: [(Int , Int )] -> [View Model Action ]
357- renderSnake [] = []
358- renderSnake (h: tl) = map renderBody (reverse tl) ++ [renderHead h]
376+ renderSnake :: Seq (Int , Int ) -> [View Model Action ]
377+ renderSnake body = case Seq. viewl body of
378+ EmptyL -> []
379+ h :< tl -> map renderBody (toList (Seq. reverse tl)) ++ [renderHead h]
359380
360381renderHead :: (Int , Int ) -> View Model Action
361382renderHead (hx, hy) =
0 commit comments