Skip to content

Commit 479b10e

Browse files
committed
initial exploration
0 parents  commit 479b10e

File tree

4 files changed

+260
-0
lines changed

4 files changed

+260
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
*~
2+
dist-newstyle/
3+
TAGS

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Revision history for robot-exploration
2+
3+
## 0.1.0.0 -- YYYY-mm-dd
4+
5+
* First version. Released on an unsuspecting world.

app/Main.hs

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
{-# LANGUAGE OverloadedStrings #-}
2+
{-# LANGUAGE TemplateHaskell #-}
3+
4+
module Main where
5+
6+
import Control.Concurrent (forkIO, threadDelay)
7+
import Control.Lens
8+
import Control.Lens.Unsound (lensProduct)
9+
import Control.Monad.State
10+
import Data.Map (Map)
11+
import qualified Data.Map as M
12+
import Data.Maybe
13+
import Data.Set (Set)
14+
import qualified Data.Set as S
15+
import Data.Void
16+
import Linear
17+
18+
import Brick
19+
import Brick.BChan
20+
import qualified Brick.Widgets.Border as B
21+
import qualified Brick.Widgets.Border.Style as BS
22+
import qualified Brick.Widgets.Center as C
23+
import qualified Graphics.Vty as V
24+
25+
import Data.Text (Text)
26+
import Text.Megaparsec hiding (State)
27+
import Text.Megaparsec.Char
28+
import qualified Text.Megaparsec.Char.Lexer as L
29+
30+
------------------------------------------------------------
31+
-- AST
32+
33+
data Command
34+
= Wait
35+
| Move
36+
| TL
37+
| TR
38+
| Harvest
39+
deriving (Eq, Ord, Show)
40+
41+
type Program = [Command]
42+
43+
------------------------------------------------------------
44+
-- Parsing
45+
------------------------------------------------------------
46+
47+
type Parser = Parsec Void Text
48+
49+
--------------------------------------------------
50+
-- Lexer
51+
52+
sc :: Parser ()
53+
sc = L.space
54+
space1
55+
(L.skipLineComment "//")
56+
(L.skipBlockComment "/*" "*/")
57+
58+
lexeme :: Parser a -> Parser a
59+
lexeme = L.lexeme sc
60+
61+
symbol :: Text -> Parser Text
62+
symbol = L.symbol sc
63+
64+
reserved :: Text -> Parser ()
65+
reserved w = (lexeme . try) $ string' w *> notFollowedBy alphaNumChar
66+
67+
--------------------------------------------------
68+
-- Parser
69+
70+
parseCommand :: Parser Command
71+
parseCommand =
72+
Move <$ reserved "move"
73+
<|> TL <$ reserved "left"
74+
<|> TR <$ reserved "right"
75+
<|> Harvest <$ reserved "harvest"
76+
77+
parseProgram :: Parser Program
78+
parseProgram = many parseCommand
79+
80+
------------------------------------------------------------
81+
-- State machine
82+
------------------------------------------------------------
83+
84+
data Robot = Robot
85+
{ _location :: V2 Int
86+
, _direction :: V2 Int
87+
, _robotProgram :: Program
88+
}
89+
deriving (Eq, Ord, Show)
90+
91+
data Item = Resource Char
92+
deriving (Eq, Ord, Show)
93+
94+
data GameState = GameState
95+
{ _baseProgram :: Program
96+
, _robots :: [Robot]
97+
, _world :: [[Char]]
98+
, _inventory :: Map Item Int
99+
}
100+
deriving (Eq, Ord, Show)
101+
102+
makeLenses ''Robot
103+
makeLenses ''GameState
104+
105+
step :: State GameState ()
106+
step = do
107+
rs <- use robots
108+
rs' <- catMaybes <$> forM rs stepRobot
109+
robots .= rs'
110+
111+
doStep :: GameState -> GameState
112+
doStep = execState step
113+
114+
stepRobot :: Robot -> State GameState (Maybe Robot)
115+
stepRobot r = case r ^. robotProgram of
116+
[] -> return Nothing
117+
(cmd : p) -> Just <$> exec cmd (r & robotProgram .~ p)
118+
119+
exec :: Command -> Robot -> State GameState Robot
120+
exec Wait r = return r
121+
exec Move r = return $ (r & location %~ (^+^ (r ^. direction)))
122+
exec TL r = return $ (r & direction %~ vLeft)
123+
exec TR r = return $ (r & direction %~ vRight)
124+
exec Harvest r = do
125+
let V2 row col = r ^. location
126+
mh <- preuse $ world . ix row . ix col
127+
case mh of
128+
Nothing -> return ()
129+
Just h -> do
130+
world . ix row . ix col .= ' '
131+
inventory . at (Resource h) . non 0 += 1
132+
return r
133+
134+
vLeft (V2 x y) = V2 (-y) (x)
135+
vRight (V2 x y) = V2 y (-x)
136+
137+
------------------------------------------------------------
138+
-- UI
139+
140+
data Tick = Tick
141+
142+
type Name = ()
143+
144+
app :: App GameState Tick Name
145+
app = App
146+
{ appDraw = drawUI
147+
, appChooseCursor = neverShowCursor
148+
, appHandleEvent = handleEvent
149+
, appStartEvent = return
150+
, appAttrMap = const theMap
151+
}
152+
153+
robotAttr :: AttrName
154+
robotAttr = "robotAttr"
155+
156+
theMap :: AttrMap
157+
theMap = attrMap V.defAttr
158+
[ (robotAttr, fg V.cyan `V.withStyle` V.bold)
159+
]
160+
161+
handleEvent :: GameState -> BrickEvent Name Tick -> EventM Name (Next GameState)
162+
handleEvent g (AppEvent Tick) = continue $ doStep g
163+
handleEvent g (VtyEvent (V.EvKey (V.KChar 'q') [])) = halt g
164+
handleEvent g (VtyEvent (V.EvKey V.KEsc [])) = halt g
165+
handleEvent g _ = continue g
166+
167+
drawUI :: GameState -> [Widget Name]
168+
drawUI g =
169+
[ C.center $ drawWorld g <+> padLeft (Pad 2) (drawInventory (g ^. inventory))]
170+
171+
drawWorld :: GameState -> Widget Name
172+
drawWorld g = withBorderStyle BS.unicode
173+
$ B.border
174+
$ padAll 1
175+
$ vBox (imap (\r -> hBox . imap (\c x -> drawLoc r c x)) (g ^. world))
176+
where
177+
robotLocs = M.fromList $ g ^.. robots . traverse . lensProduct location direction
178+
drawLoc r c x = case M.lookup (V2 r c) robotLocs of
179+
Just dir -> withAttr robotAttr $ str (robotDir dir)
180+
Nothing -> str [x]
181+
182+
robotDir (V2 0 1) = ""
183+
robotDir (V2 0 (-1)) = ""
184+
robotDir (V2 1 0) = ""
185+
robotDir (V2 (-1) 0) = ""
186+
187+
drawInventory :: Map Item Int -> Widget Name
188+
drawInventory inv = withBorderStyle BS.unicode
189+
$ B.borderWithLabel (str "Inventory")
190+
$ padAll 1
191+
$ vLimit 10
192+
$ padBottom Max
193+
$ vBox
194+
$ map drawItem (M.assocs inv)
195+
196+
drawItem :: (Item, Int) -> Widget Name
197+
drawItem (Resource c, n) = padRight (Pad 1) (str [c]) <+> showCount n
198+
where
199+
showCount = hLimit 7 . padLeft Max . str . show
200+
201+
------------------------------------------------------------
202+
203+
testGameState :: GameState
204+
testGameState = GameState [] [Robot (V2 0 0) (V2 0 1) testProgram] ["*.*$", "%**a"] M.empty
205+
206+
testProgram :: Program
207+
testProgram = [Wait, Harvest, Move, Harvest, TR, Move, Harvest, TL, Move, Harvest, Harvest]
208+
209+
main :: IO ()
210+
main = do
211+
chan <- newBChan 10
212+
forkIO $ forever $ do
213+
writeBChan chan Tick
214+
threadDelay 500000 -- decides how fast your game moves
215+
let buildVty = V.mkVty V.defaultConfig
216+
initialVty <- buildVty
217+
void $ customMain initialVty buildVty (Just chan) app testGameState

robot-exploration.cabal

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
cabal-version: 2.4
2+
name: robot-exploration
3+
version: 0.1.0.0
4+
5+
-- A short (one-line) description of the package.
6+
-- synopsis:
7+
8+
-- A longer description of the package.
9+
-- description:
10+
11+
-- A URL where users can report bugs.
12+
-- bug-reports:
13+
14+
-- The license under which the package is released.
15+
-- license:
16+
author: Brent Yorgey
17+
maintainer: byorgey@gmail.com
18+
19+
-- A copyright notice.
20+
-- copyright:
21+
-- category:
22+
extra-source-files: CHANGELOG.md
23+
24+
executable robot-exploration
25+
main-is: Main.hs
26+
27+
-- Modules included in this executable, other than Main.
28+
-- other-modules:
29+
30+
-- LANGUAGE extensions used by modules in this package.
31+
-- other-extensions:
32+
build-depends: base ^>=4.14.1.0, brick, vty, megaparsec, text, containers, linear, lens, mtl
33+
hs-source-dirs: app
34+
default-language: Haskell2010
35+
ghc-options: -threaded

0 commit comments

Comments
 (0)