Skip to content

Commit 3a17ff2

Browse files
committed
More efficient data structures
1 parent 20cbf2e commit 3a17ff2

1 file changed

Lines changed: 52 additions & 31 deletions

File tree

Main.hs

Lines changed: 52 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ module Main where
88
----------------------------------------------------------------------------
99
import Control.Concurrent (threadDelay)
1010
import 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
----------------------------------------------------------------------------
1217
import Miso hiding (Phase)
1318
import Miso.CSS hiding (ms, background, Phase)
@@ -34,17 +39,21 @@ data Dir = DUp | DDown | DLeft | DRight deriving (Show, Eq)
3439
data Phase = NotStarted | Playing | GameOver deriving (Show, Eq)
3540

3641
data 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))
4652
snake = 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+
4857
dir :: Lens Model Dir
4958
dir = 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

8799
initFood :: (Int, Int)
88100
initFood = (15,10)
89101

90102
emptyModel :: Model
91103
emptyModel = 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

100113
app :: App Model Action
@@ -130,20 +143,20 @@ step DDown (x,y) = (x, y+1)
130143
step DLeft (x,y) = (x-1, y)
131144
step 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

140153
updateModel :: Action -> Effect parent props Model Action
141154
updateModel = \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

360381
renderHead :: (Int, Int) -> View Model Action
361382
renderHead (hx, hy) =

0 commit comments

Comments
 (0)