Enhanced CLI ~ How to Implement a New Action Operation - uchicago-cs/chiventure GitHub Wiki
Note: there are already groups of actions that are grouped into 4 kinds (5/23/2022). If you have a command that matches these types of input, there is a distinct workflow. These are:
ACTION <item>
- Such as
take chair
ACTION <path>
- Such as
go north
ACTION <item> <item>
- Such as
put orb on chair
ACTION <self>
- These are actions that involve some attribute about the player, like quests, stats, class, etc...
- Such as
view stats
- Such as
For cases where a team wants an action that fits into current kinds, or AM wants to make a new kind of action, see below
This would be for implementing a unique action that does not follow the formatting of any of the 4 types of actions.
Previous examples include view
, quit
, and credits
.
In the operations.h
file, you will find many commands with the following type ascription:
char *operation(char *tokens[TOKEN_LIST_SIZE], chiventure_ctx_t *ctx);
This is the formatting all operation functions must fall under, where tokens
is an array of strings outputted from parsing the command line input, and ctx
is the chiventure context struct which holds basically all other relevant information about the game.
Once you've decided to implement a new action type that is NOT any of the existing types, you need to actually write your function! Usually as CLI we are validating the input to make sure it has the correct parameters, such that we can feed those parameters into someone else's function. Generally, it will have the following control flow: (in pseudocode)
char *operation(char *tokens[TOKEN_LIST_SIZE], chiventure_ctx_t *ctx)
{
if (game is null)
return status message
if (missing game element, such curr_room == NULL)
return status message
if (too few or too many arguments)
return status message
else
//often there will be a string which we pass by reference into another groups function,
char *return_string;
int return_code = someone_elses_function(their arguments, &return_string);
//passing return string by reference to be modified in their function by something like sprintf
}
As of now (5/23/2022) the list of tokens will contain just the action word in the first slot, then all parsed arguments to that command
ie:
> view stats
is parsed to:
tokens[0] = "view"
tokens[1] = "stats"
tokens[2] = NULL
tokens[3] = NULL
At this point, chiventure probably will not recognize your input and run your new operation because it won't recognize your action token.
To fix this, you have to add the function and the associated action command verb (e.g. fight, talk, help, look) to a hash table of commands. This hash table is created upon the start of a game as a lookup_t struct (defined in cmd.h), and is initialized with all possible action commands and operations.
To do so, you must call the add_entry
function directly in the lookup_t_init
function. It should follow this format:
add_entry("LOOK",look_operation, NULL, t);
- The first argument is the action you want in all caps.
- The second argument is the name of the operation you want to call with this action. This operation should follow the general operation function type as defined above.
- The third argument is NULL, since this operation is not one of the four action kinds (re: above).
- The fourth argument is t, which is the name of the hash table that your operation and command will be stored in. It is the parameter of
lookup_t_init
.
Now that your action is recognized by chiventure, you should add it to be a possible suggestion to the user. In the operations.c
file, there is an array of strings called actions_for_sug
and a global variable called NUM_ACTIONS
. Add your new action in all caps to the end of the array, and increment the NUM_ACTIONS
variable by 1.
Congrats! At this point, after recompiling, your input should be recognized and run your operation.
All this pertains is updated the current kindX_action_operation
function to do whatever kind of checks it needs before calling someone else's function.
the add_action_entries
function in cmd.c
should handle adding the action to the command hash table.
You will likely have to update the global variables and constants in operations.c:
#define NUM_ACTIONS 31
...
#define min(x,y) (((x) <= (y)) ? (x) : (y))
char* actions_for_sug[NUM_ACTIONS] = {
"OPEN",
"CLOSE",
"PUSH",
...
NUM_ACTIONS
is the total number of unique actions
actions_for_sug
is a list of all actions for the suggestion operation to grab from, if your command isn't here it can't be recommended as an alternative when a command is misspelt.
If AM wants a new type, they will have already done the work of updating their internal structs. What this entails for CLI is
- add a new
kindX_action_operation
inoperations.c
- updating the hash table function
add_action_entries
- updating the same global variables in
operations.c
same as above
Same as adding a new action operation, adding a new operation for a kind of action follows the same format: (information about return string in section about implementation of new action)
char *kindX_operation(char *tokens[TOKEN_LIST_SIZE], chiventure_ctx_t *ctx)
{
if (game is null)
return status message
... some other checks ...
else
char *return_string;
int return_code = action_managements_do_X_action_function(their arguments, &return_string);
}
There isn't much else, it follows the same flow, and for these kinds of operations action management should have already made a function for it.
Currently, lookup_t_init
handles all of our unique actions, and add_action_entries
handles all of action managements actions.
add_action_entries
(as of 5/23/2022) follows this format:
void add_action_entries(lookup_t **table)
{
//this is AM's in-house function which makes a linked list of all actions and their kinds.
//this should have all actions with the new kind associated with them
list_action_type_t *all_actions = get_supported_actions();
while(all_actions != NULL)
{
action_type_t *curr_action = all_actions->act;
//actions are a type of enum, with particular values as defined in `include/action_management/action_structs.h`
//for every kind, we add an entry to the table with it's name and the kinds operation
if(curr_action->kind == 1)
{
add_entry(curr_action->c_name, kind1_action_operation, curr_action, table);
}
else if(curr_action->kind == 2)
{
add_entry(curr_action->c_name, kind2_action_operation, curr_action, table);
}
else if(curr_action->kind == 3)
{
add_entry(curr_action->c_name, kind3_action_operation, curr_action, table);
}
else if(curr_action->kind == 4)
{
add_entry(curr_action->c_name, kind4_action_operation, curr_action, table);
}
all_actions = all_actions->next;
}
}
all that you would need to add to add_action_entries
is a new else if like:
else if(curr_action->kind == X)
{
add_entry(curr_action->c_name, kindX_action_operation, curr_action, table);
}
And that's all! Action management should have done their own work of adding the actions and kinds to their structs.