Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

navigable robots table #2140

Merged
merged 18 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .hlint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
- {name: Data.List.head, within: []}
- {name: Prelude.head, within: [Swarm.Web.Tournament.Database.Query]}
- {name: Prelude.tail, within: []}
- {name: Prelude.maximum, within: [Swarm.Util]}
- {name: Prelude.minimum, within: []}
- {name: Prelude.!!, within: [Swarm.Util.indexWrapNonEmpty, TestEval]}
- {name: undefined, within: [Swarm.Language.Key, TestUtil]}
- {name: fromJust, within: []}
Expand Down
4 changes: 2 additions & 2 deletions src/swarm-doc/Swarm/Doc/Wiki/Cheatsheet.hs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import Swarm.Language.Syntax (Const (..))
import Swarm.Language.Syntax qualified as Syntax
import Swarm.Language.Text.Markdown as Markdown (docToMark)
import Swarm.Language.Typecheck (inferConst)
import Swarm.Util (showT)
import Swarm.Util (maximum0, showT)

-- * Types

Expand Down Expand Up @@ -99,7 +99,7 @@ listToRow mw xs = wrap '|' . T.intercalate "|" $ zipWith format mw xs
format w x = wrap ' ' x <> T.replicate (w - T.length x) " "

maxWidths :: [[Text]] -> [Int]
maxWidths = map (maximum . map T.length) . transpose
maxWidths = map (maximum0 . map T.length) . transpose

-- ** COMMANDS

Expand Down
14 changes: 7 additions & 7 deletions src/swarm-engine/Swarm/Game/State/Initialize.hs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ import Swarm.Game.World.Gen (Seed)
import Swarm.Language.Capability (constCaps)
import Swarm.Language.Syntax (allConst, erase)
import Swarm.Language.Types
import Swarm.Util (binTuples, (?))
import Swarm.Util (applyWhen, binTuples, (?))
import System.Clock qualified as Clock
import System.Random (mkStdGen)

Expand Down Expand Up @@ -143,14 +143,14 @@ pureScenarioToGameState scenario theSeed now toRun gsc =
-- If we are in creative mode, give base all the things
& ix baseID
. robotInventory
%~ case scenario ^. scenarioOperation . scenarioCreative of
False -> id
True -> union (fromElems (map (0,) things))
%~ applyWhen
kostmo marked this conversation as resolved.
Show resolved Hide resolved
(scenario ^. scenarioOperation . scenarioCreative)
(union (fromElems (map (0,) things)))
& ix baseID
. equippedDevices
%~ case scenario ^. scenarioOperation . scenarioCreative of
False -> id
True -> const (fromList devices)
%~ applyWhen
(scenario ^. scenarioOperation . scenarioCreative)
(const (fromList devices))

running = case robotList of
[] -> False
Expand Down
15 changes: 15 additions & 0 deletions src/swarm-topography/Swarm/Game/Universe.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Control.Lens (makeLenses, view)
import Data.Function (on)
import Data.Int (Int32)
import Data.Text (Text)
import Data.Text qualified as T
import Data.Yaml (FromJSON, ToJSON, Value (Object), parseJSON, withText, (.:))
import GHC.Generics (Generic)
import Linear (V2 (..))
Expand Down Expand Up @@ -82,3 +83,17 @@ defaultCosmicLocation = Cosmic DefaultRootSubworld origin

offsetBy :: Cosmic Location -> V2 Int32 -> Cosmic Location
offsetBy loc v = fmap (.+^ v) loc

-- ** Rendering

locationToString :: Location -> String
locationToString (Location x y) =
unwords $ map show [x, y]

renderCoordsString :: Cosmic Location -> String
renderCoordsString (Cosmic sw coords) =
unwords $ locationToString coords : suffix
where
suffix = case sw of
DefaultRootSubworld -> []
SubworldName swName -> ["in", T.unpack swName]
66 changes: 49 additions & 17 deletions src/swarm-tui/Swarm/TUI/Controller.hs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ module Swarm.TUI.Controller (
) where

-- See Note [liftA2 re-export from Prelude]
import Prelude hiding (Applicative (..))

import Brick hiding (Direction, Location)
import Brick.Focus
Expand All @@ -36,10 +35,11 @@ import Brick.Widgets.Dialog
import Brick.Widgets.Edit (Editor, applyEdit, handleEditorEvent)
import Brick.Widgets.List (handleListEvent)
import Brick.Widgets.List qualified as BL
import Brick.Widgets.TabularList.Mixed
import Control.Applicative (pure)
import Control.Category ((>>>))
import Control.Lens as Lens
import Control.Monad (unless, void, when)
import Control.Monad (forM_, unless, void, when)
import Control.Monad.Extra (whenJust)
import Control.Monad.IO.Class (MonadIO (liftIO))
import Control.Monad.State (MonadState, execState)
Expand Down Expand Up @@ -87,7 +87,7 @@ import Swarm.Language.Value (Value (VKey), envTypes)
import Swarm.Log
import Swarm.TUI.Controller.EventHandlers
import Swarm.TUI.Controller.SaveScenario (saveScenarioInfoOnQuit)
import Swarm.TUI.Controller.UpdateUI (updateAndRedrawUI)
import Swarm.TUI.Controller.UpdateUI
import Swarm.TUI.Controller.Util
import Swarm.TUI.Editor.Controller qualified as EC
import Swarm.TUI.Editor.Model
Expand All @@ -101,7 +101,11 @@ import Swarm.TUI.Model.Name
import Swarm.TUI.Model.Repl
import Swarm.TUI.Model.StateUpdate
import Swarm.TUI.Model.UI
import Swarm.TUI.Model.UI.Gameplay
import Swarm.TUI.View.Robot (getList)
import Swarm.TUI.View.Robot.Type
import Swarm.Util hiding (both, (<<.=))
import Prelude hiding (Applicative (..))

-- ~~~~ Note [liftA2 re-export from Prelude]
--
Expand Down Expand Up @@ -292,7 +296,11 @@ handleMainEvent forceRedraw ev = do
Web (RunWebCode e r) -> runBaseWebCode e r
UpstreamVersion _ -> error "version event should be handled by top-level handler"
VtyEvent (V.EvResize _ _) -> invalidateCache
EscapeKey | Just m <- s ^. uiState . uiGameplay . uiDialogs . uiModal -> closeModal m
EscapeKey
| Just m <- s ^. uiState . uiGameplay . uiDialogs . uiModal ->
if s ^. uiState . uiGameplay . uiDialogs . uiRobot . isDetailsOpened
then uiState . uiGameplay . uiDialogs . uiRobot . isDetailsOpened .= False
else closeModal m
-- Pass to key handler (allows users to configure bindings)
-- See Note [how Swarm event handlers work]
VtyEvent (V.EvKey k m)
Expand Down Expand Up @@ -375,19 +383,30 @@ closeModal m = do
handleModalEvent :: V.Event -> EventM Name AppState ()
handleModalEvent = \case
V.EvKey V.KEnter [] -> do
mdialog <- preuse $ uiState . uiGameplay . uiDialogs . uiModal . _Just . modalDialog
toggleModal QuitModal
case dialogSelection =<< mdialog of
Just (Button QuitButton, _) -> quitGame
Just (Button KeepPlayingButton, _) -> toggleModal KeepPlayingModal
Just (Button StartOverButton, StartOver currentSeed siPair) -> do
invalidateCache
restartGame currentSeed siPair
Just (Button NextButton, Next siPair) -> do
quitGame
invalidateCache
startGame siPair Nothing
_ -> return ()
modal <- preuse $ uiState . uiGameplay . uiDialogs . uiModal . _Just . modalType
case modal of
Just RobotsModal -> do
robotDialog <- use $ uiState . uiGameplay . uiDialogs . uiRobot
unless (robotDialog ^. isDetailsOpened) $ do
let widget = robotDialog ^. robotListContent . robotsListWidget
forM_ (BL.listSelectedElement $ getList widget) $ \x -> do
Brick.zoom (uiState . uiGameplay . uiDialogs . uiRobot) $ do
isDetailsOpened .= True
updateRobotDetailsPane $ snd x
_ -> do
mdialog <- preuse $ uiState . uiGameplay . uiDialogs . uiModal . _Just . modalDialog
toggleModal QuitModal
case dialogSelection =<< mdialog of
Just (Button QuitButton, _) -> quitGame
Just (Button KeepPlayingButton, _) -> toggleModal KeepPlayingModal
Just (Button StartOverButton, StartOver currentSeed siPair) -> do
invalidateCache
restartGame currentSeed siPair
Just (Button NextButton, Next siPair) -> do
quitGame
invalidateCache
startGame siPair Nothing
_ -> return ()
ev -> do
Brick.zoom (uiState . uiGameplay . uiDialogs . uiModal . _Just . modalDialog) (handleDialogEvent ev)
modal <- preuse $ uiState . uiGameplay . uiDialogs . uiModal . _Just . modalType
Expand Down Expand Up @@ -418,6 +437,19 @@ handleModalEvent = \case
refreshList $ uiState . uiGameplay . uiDialogs . uiStructure . structurePanelListWidget
StructureSummary -> handleInfoPanelEvent modalScroll (VtyEvent ev)
_ -> handleInfoPanelEvent modalScroll (VtyEvent ev)
Just RobotsModal -> Brick.zoom (uiState . uiGameplay . uiDialogs . uiRobot) $ case ev of
V.EvKey (V.KChar '\t') [] -> robotDetailsFocus %= focusNext
_ -> do
isInDetailsMode <- use isDetailsOpened
if isInDetailsMode
then Brick.zoom (robotListContent . robotDetailsPaneState . logsList) $ handleListEvent ev
else do
Brick.zoom (robotListContent . robotsListWidget) $
handleMixedListEvent ev

-- Ensure list widget content is updated immediately
widget <- use $ robotListContent . robotsListWidget
forM_ (BL.listSelectedElement $ getList widget) $ updateRobotDetailsPane . snd
_ -> handleInfoPanelEvent modalScroll (VtyEvent ev)
where
refreshGoalList lw = nestEventM' lw $ handleListEventWithSeparators ev shouldSkipSelection
Expand Down
1 change: 1 addition & 0 deletions src/swarm-tui/Swarm/TUI/Controller/EventHandlers/Frame.hs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import Swarm.TUI.Controller.Util
import Swarm.TUI.Model
import Swarm.TUI.Model.Achievements (popupAchievement)
import Swarm.TUI.Model.UI
import Swarm.TUI.Model.UI.Gameplay
import System.Clock

ticksPerFrameCap :: Int
Expand Down
1 change: 1 addition & 0 deletions src/swarm-tui/Swarm/TUI/Controller/EventHandlers/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import Swarm.TUI.Model.DebugOption (DebugOption (ToggleCreative, ToggleWorldEdit
import Swarm.TUI.Model.Dialog.Goal
import Swarm.TUI.Model.Event (MainEvent (..), SwarmEvent (..))
import Swarm.TUI.Model.UI
import Swarm.TUI.Model.UI.Gameplay
import System.Clock (Clock (..), TimeSpec (..), getTime)

-- | Main keybindings event handler while running the game itself.
Expand Down
1 change: 1 addition & 0 deletions src/swarm-tui/Swarm/TUI/Controller/EventHandlers/REPL.hs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import Swarm.TUI.Model
import Swarm.TUI.Model.Event
import Swarm.TUI.Model.Repl
import Swarm.TUI.Model.UI
import Swarm.TUI.Model.UI.Gameplay

-- | Handle a user input key event for the REPL.
--
Expand Down
1 change: 1 addition & 0 deletions src/swarm-tui/Swarm/TUI/Controller/EventHandlers/Robot.hs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import Swarm.TUI.List
import Swarm.TUI.Model
import Swarm.TUI.Model.Event
import Swarm.TUI.Model.UI
import Swarm.TUI.Model.UI.Gameplay
import Swarm.TUI.View.Util (generateModal)

-- | Handle user input events in the robot panel.
Expand Down
1 change: 1 addition & 0 deletions src/swarm-tui/Swarm/TUI/Controller/EventHandlers/World.hs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import Swarm.TUI.Controller.Util
import Swarm.TUI.Model
import Swarm.TUI.Model.Event
import Swarm.TUI.Model.UI
import Swarm.TUI.Model.UI.Gameplay

-- | Handle a user input event in the world view panel.
worldEventHandlers :: [KeyEventHandler SwarmEvent (EventM Name AppState)]
Expand Down
1 change: 1 addition & 0 deletions src/swarm-tui/Swarm/TUI/Controller/SaveScenario.hs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import Swarm.TUI.Model
import Swarm.TUI.Model.Achievements (attainAchievement')
import Swarm.TUI.Model.Repl
import Swarm.TUI.Model.UI
import Swarm.TUI.Model.UI.Gameplay
import System.FilePath (splitDirectories)
import Prelude hiding (Applicative (..))

Expand Down
50 changes: 46 additions & 4 deletions src/swarm-tui/Swarm/TUI/Controller/UpdateUI.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,28 @@
module Swarm.TUI.Controller.UpdateUI (
updateUI,
updateAndRedrawUI,
updateRobotDetailsPane,
) where

import Brick hiding (Direction, Location)
import Brick.Focus

-- See Note [liftA2 re-export from Prelude]
import Brick hiding (Direction, Location, on)
import Brick.Focus
import Brick.Widgets.List qualified as BL
import Control.Applicative (liftA2, pure)
import Control.Lens as Lens
import Control.Monad (unless, when)
import Control.Monad (forM_, unless, when)
import Control.Monad.IO.Class (liftIO)
import Data.Foldable (toList)
import Data.Function (on)
import Data.List.Extra (enumerate)
import Data.Map qualified as M
import Data.Maybe (isNothing)
import Data.String (fromString)
import Data.Text qualified as T
import Data.Vector qualified as V
import Swarm.Game.Entity hiding (empty)
import Swarm.Game.Robot
import Swarm.Game.Robot.Activity
import Swarm.Game.Robot.Concrete
import Swarm.Game.State
import Swarm.Game.State.Landscape
Expand All @@ -42,7 +46,11 @@ import Swarm.TUI.Model.Dialog.Popup (Popup (..), addPopup)
import Swarm.TUI.Model.Name
import Swarm.TUI.Model.Repl
import Swarm.TUI.Model.UI
import Swarm.TUI.Model.UI.Gameplay
import Swarm.TUI.View.Objective qualified as GR
import Swarm.TUI.View.Robot
import Swarm.TUI.View.Robot.Type
import Swarm.Util (applyJust)
import Witch (into)
import Prelude hiding (Applicative (..))

Expand Down Expand Up @@ -165,6 +173,8 @@ updateUI = do

newPopups <- generateNotificationPopups

doRobotListUpdate g

let redraw =
g ^. needsRedraw
|| inventoryUpdated
Expand All @@ -174,6 +184,38 @@ updateUI = do
|| newPopups
pure redraw

doRobotListUpdate :: GameState -> EventM Name AppState ()
doRobotListUpdate g = do
gp <- use $ uiState . uiGameplay
dOps <- use $ uiState . uiDebugOptions

let rd =
mkRobotDisplay $
RobotRenderingContext
{ _mygs = g
, _gameplay = gp
, _timing = gp ^. uiTiming
, _uiDbg = dOps
}
oldList = getList $ gp ^. uiDialogs . uiRobot . robotListContent . robotsListWidget
maybeOldSelected = snd <$> BL.listSelectedElement oldList

-- Since we're replacing the entire contents of the list, we need to preserve the
-- selected row here.
maybeModificationFunc =
updateList . BL.listFindBy . ((==) `on` view (robot . robotID)) <$> maybeOldSelected

uiState . uiGameplay . uiDialogs . uiRobot . robotListContent . robotsListWidget .= applyJust maybeModificationFunc rd

Brick.zoom (uiState . uiGameplay . uiDialogs . uiRobot) $
forM_ maybeOldSelected updateRobotDetailsPane

updateRobotDetailsPane :: RobotWidgetRow -> EventM Name RobotDisplay ()
updateRobotDetailsPane robotPayload =
Brick.zoom robotListContent $ do
robotDetailsPaneState . cmdHistogramList . BL.listElementsL .= V.fromList (M.toList (robotPayload ^. robot . activityCounts . commandsHistogram))
robotDetailsPaneState . logsList . BL.listElementsL .= robotPayload ^. robot . robotLog

-- | Either pops up the updated Goals modal
-- or pops up the Congratulations (Win) modal, or pops
-- up the Condolences (Lose) modal.
Expand Down
6 changes: 4 additions & 2 deletions src/swarm-tui/Swarm/TUI/Controller/Util.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

-- |
-- SPDX-License-Identifier: BSD-3-Clause
--
-- Keyboard key event patterns and drawing utilities
module Swarm.TUI.Controller.Util where

import Brick hiding (Direction)
Expand Down Expand Up @@ -35,15 +37,15 @@ import Swarm.Language.Capability (Capability (CDebug))
import Swarm.Language.Syntax hiding (Key)
import Swarm.TUI.Model (
AppState,
FocusablePanel,
ModalType (..),
Name (..),
gameState,
modalScroll,
uiState,
)
import Swarm.TUI.Model.Name
import Swarm.TUI.Model.Repl (REPLHistItem, REPLPrompt, REPLState, addREPLItem, replHistory, replPromptText, replPromptType)
import Swarm.TUI.Model.UI
import Swarm.TUI.Model.UI.Gameplay
import Swarm.TUI.View.Util (generateModal)
import System.Clock (Clock (..), getTime)

Expand Down
1 change: 1 addition & 0 deletions src/swarm-tui/Swarm/TUI/Editor/Controller.hs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import Swarm.TUI.Editor.Util qualified as EU
import Swarm.TUI.Model
import Swarm.TUI.Model.Name
import Swarm.TUI.Model.UI
import Swarm.TUI.Model.UI.Gameplay
import Swarm.Util (hoistMaybe)
import Swarm.Util.Erasable (maybeToErasable)
import System.Clock
Expand Down
2 changes: 1 addition & 1 deletion src/swarm-tui/Swarm/TUI/Editor/Masking.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Swarm.Game.Universe
import Swarm.Game.World.Coords
import Swarm.TUI.Editor.Model
import Swarm.TUI.Editor.Util qualified as EU
import Swarm.TUI.Model.UI
import Swarm.TUI.Model.UI.Gameplay

shouldHideWorldCell :: UIGameplay -> Coords -> Bool
shouldHideWorldCell ui coords =
Expand Down
Loading