packages feed

roguestar-gl-0.2.1: src/Actions.lhs

\section{Actions}

\begin{code}
module Actions
    (takeUserInputAction,
     getValidActions,
     ActionInput(..),
     select_race_action_names,
     select_base_class_action_names,
     executeContinueAction)
    where

import System.Exit
import Control.Monad
import Control.Monad.Error
import Driver
import Data.IORef
import Data.List
import PrintText
import Tables
import Data.Maybe
import System.IO

data ActionInput = ActionInput {
    action_driver_object :: DriverObject,
    action_print_text_object :: PrintTextObject }

type Action = ActionInput -> ErrorT String IO (IO ())

actionValid :: ActionInput -> Action -> IO Bool
actionValid action_input action = 
    do result <- runErrorT $ action action_input
       return $ either (const False) (const True) result

executeAction :: ActionInput -> Action -> IO ()
executeAction action_input action =
    do result <- runErrorT $ action action_input
       either (\s -> printText (action_print_text_object action_input) UnexpectedEvent ("unable to execute action: " ++ s))
              (id)
              result
       return ()

executeContinueAction :: ActionInput -> IO ()
executeContinueAction action_input =
    do either (const $ return()) id =<< (runErrorT $ (snd continue_action) action_input)
       return ()

quit_action :: (String,Action)
quit_action = ("quit",
               \_ -> return $ exitWith ExitSuccess)

-- |
-- Constructs an action such that the action is valid only if it is listed in
-- a table from the engine.  The first parameter identifies the table column to look up,
-- (table name, table id, header to look under).  The second parameter is the name of the
-- action as it will be send to the engine.  The third parameter is the action_param.
-- The action_param must be listed under the specified header of the specified table,
-- or the action will not be allowed to execute.
--
-- For example: selectTableAction ("people","0","name") "select-person-to-call-state" "call-with-telephone" "carl"
-- 
-- \> game query state
-- answer: state select-person-to-call-state
-- \> game query people
-- begin-table people 0 name
-- john
-- wendy
-- carl
-- bob
-- end-table
-- \> game action call-with-telephone Carl
-- 
-- In this case the action executes because the state is actually select-person-to-call-state and Carl
-- is actually listed under the "name" header of the "people" table.
--
-- In practice this function is used for things like the race-selection-state and the class-selection-state
-- where we select from a predifined list of possible choices, but the engine further restricts the choices.
-- Each possible choice is it's own action that will only return action_valid if it is listed in the appropriate
-- table from the engine. 
--
selectTableAction :: (String,String,String) -> String -> String -> String -> Action
selectTableAction (the_table_name,the_table_id,the_table_header) allowed_state action_name action_param = 
    \action_input ->
        do state <- maybe (fail "") return =<< (lift $ driverGetAnswer (action_driver_object action_input) "state")
           guard $ state == allowed_state
           table <- maybe (fail "") return =<< (lift $ driverGetTable (action_driver_object action_input) the_table_name the_table_id)
           guard $ [action_param] `elem` tableSelect table [the_table_header]
           return $ driverAction (action_driver_object action_input) [action_name, action_param]

-- |
-- An action that depends on the state flag of the game engine and an arbitrary constant parameter.
-- For example, actions that operate directionally are parameterized
-- on the eight directions (n,s,w,e,nw,ne,sw,se).
--
-- i.e., parameterizedAction "player-turn" "move" "nw" becomes 
-- ("move-nw",driverAction _ ["move","nw"])
--
parameterizedAction :: String -> String -> String -> (String,Action)
parameterizedAction allowed_state action_name parameter =
    (action_name ++ "-" ++ parameter,
     stateGuard allowed_state $ \action_input ->
         return $ driverAction (action_driver_object action_input) [action_name,parameter])

-- |
-- Guard an action to only run during a specific state.
--
stateGuard :: String -> Action -> Action
stateGuard allowed_state actionM action_input =
    do guard =<< (liftM (== Just allowed_state) $ lift $ driverGetAnswer (action_driver_object action_input) "state")
       actionM action_input

-- |
-- An action that depends just on the state flag of the game engine.
--
stateLinkedAction :: String -> String -> (String,Action)
stateLinkedAction allowed_state action_name = 
    (action_name,
     stateGuard allowed_state $ \action_input ->
         return $ driverAction (action_driver_object action_input) [action_name])

continue_action :: (String,Action)
continue_action = ("continue",\action_input ->
    do guard =<< (liftM (== Just "yes") $ lift $ driverGetAnswer (action_driver_object action_input) "snapshot")
       return $ driverAction (action_driver_object action_input) ["continue"])

moveAction :: String -> (String,Action)
moveAction = parameterizedAction "player-turn" "move"

turnAction :: String -> (String,Action)
turnAction = parameterizedAction "player-turn" "turn"

fireAction :: String -> (String,Action)
fireAction = parameterizedAction "player-turn" "fire"

reroll_action :: (String,Action)
reroll_action = stateLinkedAction "class-selection" "reroll"

pickup_action :: (String,Action)
pickup_action = stateLinkedAction "player-turn" "pickup"

drop_action :: (String,Action)
drop_action = stateLinkedAction "player-turn" "drop"

wield_action :: (String,Action)
wield_action = stateLinkedAction "player-turn" "wield"

unwield_action :: (String,Action)
unwield_action = stateLinkedAction "player-turn" "unwield"

selectRaceAction :: String -> (String,Action)
selectRaceAction s = 
    (s,selectTableAction ("player-races","0","name") "race-selection" "select-race" s)

selectBaseClassAction :: String -> (String,Action)
selectBaseClassAction s = 
    (s,selectTableAction ("base-classes","0","class") "class-selection" "select-class" s)

select_race_action_names :: [String]
select_race_action_names = [--"anachronid",
			    "androsynth",
			    "ascendant",
			    "caduceator",
			    "encephalon",
			    --"goliath",
			    --"hellion",
			    --"kraken",
			    --"myrmidon",
			    --"perennial",
			    "recreant",
			    "reptilian"]

select_race_actions :: [(String,Action)]
select_race_actions = map selectRaceAction select_race_action_names

select_base_class_action_names :: [String]
select_base_class_action_names = ["barbarian",
				  "consular",
				  "engineer",
				  "forceadept",
				  "marine",
				  "ninja",
				  "pirate",
				  "scout",
				  "shepherd",
				  "thief",
				  "warrior"]

select_base_class_actions :: [(String,Action)]
select_base_class_actions = map selectBaseClassAction select_base_class_action_names

eight_directions :: [String]
eight_directions = ["n","ne","nw","e","w","se","sw","s"]

move_actions :: [(String,Action)]
move_actions = map moveAction eight_directions

turn_actions :: [(String,Action)]
turn_actions = map turnAction eight_directions

fire_actions :: [(String,Action)]
fire_actions = map fireAction eight_directions

all_actions :: [(String,Action)]
all_actions = [continue_action,quit_action,reroll_action,pickup_action,drop_action,wield_action,unwield_action] ++
              select_race_actions ++ 
	      select_base_class_actions ++
	      move_actions ++
              turn_actions ++
	      fire_actions

lookupAction :: String -> (String,Action)
lookupAction x = (x,fromMaybe (error $ "tried to operate on an unknown action named " ++ x) $ lookup x all_actions)

-- |
-- Answer a complete list of all actions that can be run at this time.
-- Accepts an optional list of action names to choose from; if Nothing,
-- uses all concievable actions as that list.
--
getValidActions :: ActionInput -> Maybe [String] -> IO [String]
getValidActions action_input actions_list = 
    do valid_action_pairs <- filterM (actionValid action_input . snd) $ 
                                 maybe all_actions (map lookupAction) actions_list
       return $ map fst valid_action_pairs

-- |
-- Takes a list of action names and executes the first action in the list that is valid
-- in the current context, based on each action's action_valid entry.
-- Returns True if an action was taken, False otherwise.
--
takeUserInputAction :: ActionInput -> [String] -> IO Bool
takeUserInputAction _ [] = return False
takeUserInputAction action_input action_names =
    do valid_actions <- getValidActions action_input (Just action_names)
       let action = map lookupAction valid_actions
       case length action of
           0 -> return False
	   1 -> do executeAction action_input $ snd $ head action
                   return True
	   _ -> do hPutStrLn stderr ("Action bindings warning: multiple valid action for binding: " ++ (concat $ intersperse ", " $ map fst action))
		   executeAction action_input $ snd $ head action
                   return True
\end{code}