Custom Actions ~ Design Document - uchicago-cs/chiventure GitHub Wiki

Functionality

User will be able to:

  1. Define an action
  2. Construct its effects and requirements from:
    • Pre-existing actions
    • Supplied keywords
      • We supply/implement these
      • Examples: set, say
    • Other actions defined in the WDL document

They will do so by:

  1. Constructing an action block from CCCs and parameters a. Represents a single step the program will take b. Format: {"block": “CCC”, "params": "param1 param2 … “} c. Example: {"block": "set", "params": "obj_CHAIR upright flipped"}
    • action block: {"block": "set", "params": "obj_CHAIR upright flipped"}
    • CCC: set
    • parameters: obj_CHAIR upright flipped
  2. Chaining those action blocks together into an action sequence
  3. Supplying a context a. i.e. item, location b. Will expand later
  4. Linking this sequence to our defined action

Example Custom Action

{
      "action": "act_PUSH",
      "context": "item",
      "item": "obj_CHAIR",
      "sequence": [
          {"block": "if", "params": "inventory has item_SPINACH"},
    	    {"block": "set", "params": "arg1 flipped"},
    	    {"block": "say", "params": "'You pushed the chair. It flipped!'"},
    	    {"block": "else", "params": ""},
    	    {"block": "say", "params": "'Couldn't push the chair. Spinach needed.''"},
    	    {"block": "endif", "params": ""},
    	    {"block": "say", "params": "'This is printed regardless of push success.'"},
          {"block": "lua", "params": "'path/to/example.lua' arg0 arg1 arg2 arg3"}
       ]
}

Action: act_PUSH

Context: item

Action sequence: all of the blocks

Action blocks: each { … }

CCCs: if, set, say, else, say, endif, say, lua

Types of CCC (Commands, Conditionals, Control)

These are the planned atomic blocks and a description of what each one is.

Note: The functions that execute the following atomic blocks are currently not intended to be exposed to the interface. They are simply listed here to indicate what the user is able to write in their WDL++ file.

Atomic Controls

  • IF/ELSE
  • WHILE/ENDWHILE
  • FOR/ENDFOR

Atomic Conditionals

EQ attribute attribute

  • Determine whether an attribute is equal to another
  • Results:
    • Returns an int status code (TRUE/FALSE)
    • Returns failure status code if attributes are invalid
  • Arguments:
    • Two attributes
    • Attributes must be compatible (int and double, or string, bool, char only)
    • Supports all attribute types
  • Notes:
    • Runs strcmp for strings and == for other types
    • double types are checked for similarity within small epsilon distance of each other (handle rounding errors)

LT, GT attribute attribute

  • Determine whether an attribute is less than or greater than another
  • Results:
    • Returns an int status code (TRUE/FALSE)
    • Returns failure status code if attributes are invalid
  • Arguments:
    • Two attributes
    • Supports int and double attribute types only

LTE, GTE attribute attribute

  • Determine whether the first attribute is less than or greater than or equal to the second attribute
  • Results:
    • Returns an int status code (TRUE/FALSE)
    • Returns failure status code if attributes are invalid
  • Arguments:
    • Two attributes
    • Supports int and double attribute types only

IN attribute container

  • Return a boolean determining whether the attribute (object) is in the container
  • Attribute must be string only (search by name)

Atomic Effects

SET attribute attribute

  • Set the first attribute’s value to be that of the second attribute.
  • Notes:
    • If an attribute is variable, use the value stored in that attribute. If it is a constant string, then it is considered a call to look up a temporary attribute with the same name as the value.
    • Attribute types must match

SAY phrase

  • Print the phrase for the player to read
  • Phrase must be an attribute with the string type. Can be constant or variable.

MOVE player room

  • Move the player to the specified room
  • Room must be valid

ADD/SUB/MULT/DIV attribute attribute attribute

  • Perform the specified arithmetic operation on the first and second attribute with the following logic (attribute_1 (OP) attribute_2) then store in the third attribute
  • Notes:
    • Attributes must be of matching types and valid for arithmetic (i.e. int/double). They can be variable or constant.
    • 3rd attribute can be a constant string--will be considered a call to store in a temporary variable with name as 3rd attribute’s value.

GEN type min max attribute

  • Generate a random number between min and max
  • Notes:
    • Type determines whether an integer or double is generated. Must be an attribute encoding either double or int in its attribute field.
    • Min/Max must be attributed with the same type as type attribute--they can, however, be constant or variable.
    • Attribute can be a constant string--will be considered a call to store in temporary variable with same name as attribute’s value

EXEC lua_script [args]

  • Execute a specified lua script
  • Notes:
    • The arguments passed after lua_script are simply listed in the order they are passed to the lua script

DO_EXISTING action_name [args]

  • Do the existing action with the following arguments passed to it
  • Notes:
    • Action must exist as a hardcoded action (functioning effectively as a synonym)

Design

The motivating structure behind the design for storing custom actions in memory is an Abstract Syntax Tree (AST), essentially a multiway tree (i.e. a tree whose nodes have arbitrary numbers of children) whose root represents the “first” action and the children are followed for subsequent actions. The sequence of action blocks is wrapped in a struct called “Action Sequence” which also contains temporary variable storage, action execution context, etc. and serves as the main object to be passed through and outside of the interface.

Structs

Action Sequence

Motivation:

  • Essentially a wrapper for the whole tree
  • Provides flexibility to accommodate future updates in functionality for different contexts
    • e.g. battle, player, and item contexts

Contents:

  • Pointer to the first block to be executed
  • What context the action should be executed in
    • E.g. On an item, between two items, in battle, not on a item, etc.
    • Necessary to determine validity of arguments passed in during runtime
  • Pointers to data for use in the context of executing the action
    • e.g. a pointer to an item to be acted upon, a pointer to the player object
    • Sequence-specific attributes
    • Limited to use within sequence itself, serving as temporary variables for modification and retrieval within sequence
    • Can be created and deleted as necessary

Current Contexts:

  • Player
  • Battle
  • Item
  • Item-item
typedef struct custom_action
{
    char *action_name;
    char *context;
    char *item;
    char *type;
    AST_block_t *head;
    UT_hash_handle hh;
} custom_action_t;

Blocks

Structs

  • block_t
    • a union which may represent any of the four types of blocks (see Control, Branch, Action, Conditional)
      • control block
      • branch block
      • action block
      • conditional block
  • AST_block_t
    • block_t block - (see above)
    • enum block_type - shows which type of block is contained within the block_t struct Usage
  • An abstraction of all the different block objects. Serves to make code cleaner and simplify execution logic.
typedef union block {
    control_block_t control_block;
    branch_block_t branch_block;
    action_block_t action_block;
    conditional_block_t conditional_block;
} block_t;

typedef struct AST_block {
    block_t block;
    enum block_type block_type;
    struct AST_block *next;
    struct AST_block *prev;
} AST_block_t;

Control

Control Block: block to introduce a single action

  • Introduce a new sequence of logic for execution
  • Pointed to by Branch blocks (see next section)
  • Contain a single pointer to the first action in the structure
  • Created to exist in the Branch block struct
typedef struct control_block {
    enum control_type control_type;
    AST_block* next
} control_block_t;

Branch

Branch Block: holds pointers to the 1+ Control block and 1+ Conditional block

  • Purpose: Essential element of control structure
    • Simplifies checking for correctness (all control structures will have conditionals)
    • Enables us to have just one pointer to the next AST block for Action and Control blocks
      • Reduces complexity and errors Initially, a branch block will support a simple AND/OR for all its Conditional blocks.
typedef struct branch_block {
    int num_conditionals;
    conditional_block_t** conditionals;
    int num_controls;
    control_block_t** controls;
} branch_block_t;

Action

Contents:

  • Action - data structure indicating an action from “Atomic Effects” section
  • Action arguments - list of attributes
  • Number of action arguments
  • Pointer to next block

Classes of actions - classifying atomic effects

  • Modifying existing attribute
  • Creating temporary attribute
    • Generating random numbers
    • Storing temporary values

A helpful way to think of actions would be as a function and its arguments.

typedef struct action_block {
    enum action_type action_type;
    int num_args;
    attribute_t** args;
    AST_block* next;
} action_block_t;

Conditional

Returns true or false Contains an operator and two attributes:

  • Binary comparison operator (represented as an enum) - see Atomic Conditionals.
  • Two attributes (represented as attribute_t structs - see Attributes). Types of attributes:
    • existing attributes (i.e. player’s gold)
    • temporary attributes (i.e. generated number, later deleted)
    • fixed values (i.e. 5) Process:
  • Performs a typecheck on the attributes (to determine if their types can be logically compared).
  • Compares the attributes using the operator. Always placed under a Branch block (see Branch).
typedef struct conditional_block {
    enum conditional_type conditional_type;
    attribute_t* left;
    attribute_t* right;
} conditional_block_t;

Attributes

Attribute Block: wrapper for a single primitive value

  • Has the feature of being able to store different types of primitive values
  • Helps to “enable” actions and conditionals
  • Types of Attributes:
    • Constants:
      • Value not tied to an object or field
      • Shouldn’t be tied to a persistent pointer outside the Action Sequence
      • Named “NULL”
    • Variables
      • Value tied to an object or field
      • Can exist in the Action Sequence (when tied to an object/field)
      • Have some definitive name
  • Types do need to be matched and checked for attributes since different types of primitive values are supported, which is something to not overlook during implementation
typedef union attribute_value {
    double double_val;
    char char_val;
    bool bool_val;
    char* str_val;
    int int_val;
} attribute_value_t;


typedef struct attribute {
    attribute_value_t value;
    attribute_type attribute_type;
    char* name;
} attribute_t;

Interface

search_for_custom_action

custom_action_t* search_for_custom_action(char* action_name, game* game)

Arguments:

  • action_name: The name of the custom action to be searched for
  • game: The current game chiventure is running, and the custom action is contained in Return:
  • custom_action_t* Pointer to the custom action with the same name as action_name if it exists
  • NULL if no such action_name custom action exists in game

do_custom_action

int do_custom_action(custom_action_t* action, char** args, int num_args)

Arguments:

  • action: A pointer to the custom action to be executed (most likely acquired from search_for_custom_action)
  • args: An array of strings of the arguments passed to the action (i.e. the words succeeding the action written in the command line)
  • num_args: The number of arguments being passed in Return:
  • SUCCESS on successful execution
  • FAILURE/TBD on specific types of failure

compile_custom_action

custom_action_t* compile_custom_action(json_dict_obj* json, game* game)

Build a custom action object and add it to the game’s dictionary of custom actions. Also associates custom actions with objects and attributes if necessary.

NOTE: Any objects or attributes the action is associated with need to be initialized before the action is compiled.

Arguments:

  • json: A dictionary(?) object containing the basic parsed json from a WDL++ file. See example below in Sample Parse and Data Structure. TBD, need to coordinate with WDL++ team.
  • game: The game the custom action should be associated with Return:
  • custom_action_t* A pointer to the compiled custom action if successful
  • NULL if there was an error parsing the json

free_custom_action

int free_custom_action(custom_action_t* action)

Free a custom action and all its associated structs

Arguments:

  • action: The custom action to be freed Return
  • SUCCESS
  • FAILURE

get_custom_action

char* get_custom_action_name(custom_action_t* action)

Arguments:

  • action: The custom action to be queried Return
  • char* A string containing the name of the custom action
  • NULL if the action passed in was invalid

Sample Parse and Data Structure

{
      "action": "act_PUSH",
      "context": "item",
      "item": "obj_CHAIR",
      "sequence": [
      	{"block": "if", "params": "inventory has item_SPINACH"},
    	{"block": "set", "params": "obj_CHAIR status flipped"},
    	{"block": "say", "params": "'You pushed the chair. It flipped!'"},
    	{"block": "else", "params": ""},
    	{"block": "say", "params": "'Couldn't push the chair. Spinach needed.''"},
    	{"block": "endif", "params": ""},
    	{"block": "say", "params": "'This is printed regardless of push success.'"}
      ]
}