To manage dialogue choices.


§1. Scanning the dialogue choices in pass 0. Choices have already been parsed a little. For example,

    -- (if the shortbread is carried) "Offer the shortbread"

will have become:

    DIALOGUE_CHOICE_NT
        DIALOGUE_SELECTION_NT ""Offer the shortbread""
        DIALOGUE_CLAUSE_NT "if the shortbread is carried"

§2.

dialogue_choice *DialogueChoices::new(parse_node *PN) {
    int L = Annotations::read_int(PN, dialogue_level_ANNOT);
    if (L < 0) L = 0;
    int flow_control = FALSE;
    dialogue_choice *dc = CREATE(dialogue_choice);
    Initialise the choice2.2;
    Parse the clauses just enough to classify them2.3;
    Add the choice to the world model2.9;
    dc->as_node = DialogueNodes::add_to_current_beat(L, NULL, dc);
    return dc;
}

§2.1.

typedef struct dialogue_choice {
    struct parse_node *choice_at;
    struct wording choice_name;
    struct instance *as_instance;
    struct dialogue_node *as_node;
    struct parse_node *selection;
    struct wording selection_parameter;
    struct dialogue_beat *to_perform;
    struct parse_node *to_perform_expression;
    struct dialogue_choice_compilation_data compilation_data;
    int selection_type;
    CLASS_DEFINITION
} dialogue_choice;

§2.2. Initialise the choice2.2 =

    dc->choice_at = PN;
    dc->choice_name = EMPTY_WORDING;
    dc->as_node = NULL;
    dc->selection = NULL;
    dc->selection_parameter = EMPTY_WORDING;
    dc->to_perform = NULL;
    dc->to_perform_expression = NULL;
    dc->selection_type = AGAIN_DSEL;
    dc->compilation_data = RTDialogueChoices::new(PN, dc);

§2.3. Parse the clauses just enough to classify them2.3 =

    int failed_already = FALSE;
    for (parse_node *clause = PN->down; clause; clause = clause->next) {
        wording CW = Node::get_text(clause);
        if (Node::is(clause, DIALOGUE_CLAUSE_NT)) {
            if (<dialogue-choice-clause>(CW)) {
                Annotations::write_int(clause, dialogue_choice_clause_ANNOT, <<r>>);
                if (<<r>> == CHOICE_NAME_DCC) {
                    wording NW = GET_RW(<dialogue-choice-clause>, 1);
                    if (<instance>(NW)) {
                        instance *I = <<rp>>;
                        DialogueBeats::non_unique_instance_problem(I, K_dialogue_choice);
                    } else {
                        dc->choice_name = NW;
                    }
                }
            }
        } else if (Node::is(clause, DIALOGUE_SELECTION_NT)) {
            dc->selection = clause;
            if (<dialogue-selection>(CW)) {
                dc->selection_type = <<r>>;
                switch (dc->selection_type) {
                    case INSTEAD_OF_DSEL:
                    case AFTER_DSEL:
                    case BEFORE_DSEL:
                    case PERFORM_DSEL:
                    case ENDING_DSEL:
                    case ENDING_SAYING_DSEL:
                    case ENDING_FINALLY_DSEL:
                    case ENDING_FINALLY_SAYING_DSEL:
                        dc->selection_parameter = GET_RW(<dialogue-selection>, 1);
                        break;
                }
            } else {
                Problems::quote_source(1, current_sentence);
                Problems::quote_wording(2, CW);
                StandardProblems::handmade_problem(Task::syntax_tree(),
                    _p_(PM_ChoiceSelectionUnknown));
                Problems::issue_problem_segment(
                    "The dialogue choice offered by %1 is apparently '%2', but that "
                    "isn't one of the possible ways to write a choice.");
                Problems::issue_problem_end();
                failed_already = TRUE;
            }
        } else internal_error("damaged DIALOGUE_CHOICE_NT subtree");
    }
    if (failed_already == FALSE) Check the flow notation2.3.1;

§2.3.1. Check the flow notation2.3.1 =

    int left_arrow = FALSE, right_arrow = FALSE, dash = FALSE;
    switch (DialogueChoices::flow_direction(dc)) {
        case DIALOGUE_NOT_FLOWING: dash = TRUE; break;
        case DIALOGUE_FLOWING_LEFT: left_arrow = TRUE; break;
        case DIALOGUE_FLOWING_RIGHT: right_arrow = TRUE; break;
    }
    vocabulary_entry *symbol = Lexer::word(Wordings::first_wn(Node::get_text(PN)));
    if ((dash) && (symbol != DOUBLEDASH_V)) {
        Problems::quote_source(1, current_sentence);
        StandardProblems::handmade_problem(Task::syntax_tree(),
            _p_(PM_ChoiceDashDashExpected));
        Problems::issue_problem_segment(
            "The dialogue choice offered by %1 should open with '--', "
            "since it offers a choice, rather than '->' or '<-' which "
            "relate to the flow of the script.");
        Problems::issue_problem_end();
    }
    if ((left_arrow) && (symbol != LEFTARROW_V)) {
        if ((symbol == DOUBLEDASH_V) && (PN->down) && (dc->selection == NULL)) {
            current_sentence = dc->choice_at;
            Problems::quote_source(1, current_sentence);
            StandardProblems::handmade_problem(Task::syntax_tree(), _p_(PM_ChoiceSelectionMissing));
            Problems::issue_problem_segment(
                "The dialogue choice offered by %1 doesn't say what the option actually is, "
                "which is allowed only if it is a simple '--' used as a divider. Here, it "
                "seems to have some bracketed annotations as well. That must be wrong.");
            Problems::issue_problem_end();
        } else {
            Problems::quote_source(1, current_sentence);
            StandardProblems::handmade_problem(Task::syntax_tree(),
                _p_(PM_ChoiceLeftArrowExpected));
            Problems::issue_problem_segment(
                "The dialogue choice offered by %1 should open with '<-', "
                "since it relates to backwards flow within the current beat, "
                "rather than '->' (forwards flow) or '--' (an option).");
            Problems::issue_problem_end();
        }
    }
    if ((right_arrow) && (symbol != RIGHTARROW_V)) {
        Problems::quote_source(1, current_sentence);
        StandardProblems::handmade_problem(Task::syntax_tree(),
            _p_(PM_ChoiceRightArrowExpected));
        Problems::issue_problem_segment(
            "The dialogue choice offered by %1 should open with '->', "
            "since it relates to flow out of the current beat, "
            "rather than '<-' (backwards flow) or '--' (an option).");
        Problems::issue_problem_end();
    }
    if ((left_arrow) || (right_arrow)) flow_control = TRUE;

§2.4. As with the analogous clauses for Dialogue Beats, each clause can be one of the following possibilities:

enum CHOICE_NAME_DCC from 1
enum IF_DCC
enum UNLESS_DCC
enum PROPERTY_DCC

§2.5. Using:

<dialogue-choice-clause> ::=
    this is the { ... choice } |                     ==> { CHOICE_NAME_DCC, - }
    if ... |                                         ==> { IF_DCC, - }
    unless ... |                                     ==> { UNLESS_DCC, - }
    ...                                              ==> { PROPERTY_DCC, - }

§2.6.

void DialogueChoices::write_dcc(OUTPUT_STREAM, int c) {
    switch(c) {
        case CHOICE_NAME_DCC:           WRITE("CHOICE_NAME"); break;
        case IF_DCC:                    WRITE("IF"); break;
        case UNLESS_DCC:                WRITE("UNLESS"); break;
        case PROPERTY_DCC:              WRITE("PROPERTY"); break;
        default:                        WRITE("?"); break;
    }
}

§2.7.

enum AGAIN_DSEL from 1                 <-
enum ANOTHER_CHOICE_DSEL               -> another choice
enum PERFORM_DSEL                      -> perform the falling beat
enum STOP_DSEL                         -> stop
enum ENDING_DSEL                       -> end the story
enum ENDING_SAYING_DSEL                -> end the story saying "You have succeeded"
enum ENDING_FINALLY_DSEL               -> end the story finally
enum ENDING_FINALLY_SAYING_DSEL        -> end the story finally saying "You have failed"
enum TEXTUAL_DSEL                      — "Run out of the room screaming"
enum BEFORE_DSEL                       — before taking the pocket watch
enum INSTEAD_OF_DSEL                   — instead of taking something
enum AFTER_DSEL                        — after examining the rabbit hole
enum OTHERWISE_DSEL                    — otherwise
enum CHOOSE_RANDOMLY_DSEL              — choose randomly
enum SHUFFLE_THROUGH_DSEL              — shuffle through
enum CYCLE_THROUGH_DSEL                — cycle through
enum STEP_THROUGH_DSEL                 — step through
enum STEP_THROUGH_AND_STOP_DSEL        — step through and stop
enum OR_DSEL                           — or
define DIALOGUE_NOT_FLOWING 0
define DIALOGUE_FLOWING_LEFT 1
define DIALOGUE_FLOWING_RIGHT 2
int DialogueChoices::flow_direction(dialogue_choice *dc) {
    switch (dc->selection_type) {
        case AGAIN_DSEL:
            return DIALOGUE_FLOWING_LEFT;
        case PERFORM_DSEL:
        case STOP_DSEL:
        case ENDING_DSEL:
        case ENDING_SAYING_DSEL:
        case ENDING_FINALLY_DSEL:
        case ENDING_FINALLY_SAYING_DSEL:
        case ANOTHER_CHOICE_DSEL:
            return DIALOGUE_FLOWING_RIGHT;
    }
    return DIALOGUE_NOT_FLOWING;
}

§2.8.

<dialogue-selection> ::=
    <quoted-text> |                                   ==> { TEXTUAL_DSEL, - }
    another choice |                                  ==> { ANOTHER_CHOICE_DSEL, - }
    stop |                                            ==> { STOP_DSEL, - }
    end the story |                                   ==> { ENDING_DSEL, - }
    end the story finally |                           ==> { ENDING_FINALLY_DSEL, - }
    end the story saying { <quoted-text> } |          ==> { ENDING_SAYING_DSEL, - }
    end the story finally |                           ==> { ENDING_FINALLY_DSEL, - }
    end the story finally saying { <quoted-text> } |  ==> { ENDING_FINALLY_SAYING_DSEL, - }
    otherwise |                                       ==> { OTHERWISE_DSEL, - }
    instead of ... |                                  ==> { INSTEAD_OF_DSEL, - }
    after ... |                                       ==> { AFTER_DSEL, - }
    before ... |                                      ==> { BEFORE_DSEL, - }
    perform <definite-article> ... |                  ==> { PERFORM_DSEL, - }
    perform ... |                                     ==> { PERFORM_DSEL, - }
    choose randomly |                                 ==> { CHOOSE_RANDOMLY_DSEL, - }
    shuffle through |                                 ==> { SHUFFLE_THROUGH_DSEL, - }
    cycle through |                                   ==> { CYCLE_THROUGH_DSEL, - }
    step through |                                    ==> { STEP_THROUGH_DSEL, - }
    step through and stop |                           ==> { STEP_THROUGH_AND_STOP_DSEL, - }
    or                                                ==> { OR_DSEL, - }

§2.9. Each choice produces an instance of the kind dialogue choice, using the name given in its clauses if one was.

Add the choice to the world model2.9 =

    if (K_dialogue_choice == NULL) internal_error("DialogueKit has not created K_dialogue_choice");
    wording W = dc->choice_name;
    if (Wordings::empty(W)) {
        TEMPORARY_TEXT(faux_name)
        if (flow_control)
            WRITE_TO(faux_name, "flow-%d", dc->allocation_id + 1);
        else
            WRITE_TO(faux_name, "choice-%d", dc->allocation_id + 1);
        W = Feeds::feed_text(faux_name);
        DISCARD_TEXT(faux_name)
    }
    pcalc_prop *prop = Propositions::Abstract::to_create_something(K_dialogue_choice, W);
    Assert::true(prop, CERTAIN_CE);
    dc->as_instance = Instances::latest();

§3. Processing choices after pass 1. It's now a little later, and the following is called to look at each choice. There's not much to do: just to identify the beat to be performed, if there is one.

void DialogueChoices::decide_choice_performs(void) {
    dialogue_choice *dc;
    LOOP_OVER(dc, dialogue_choice) {
        current_sentence = dc->choice_at;
        if ((dc->selection_type == CHOOSE_RANDOMLY_DSEL) ||
            (dc->selection_type == SHUFFLE_THROUGH_DSEL) ||
            (dc->selection_type == CYCLE_THROUGH_DSEL) ||
            (dc->selection_type == STEP_THROUGH_DSEL) ||
            (dc->selection_type == STEP_THROUGH_AND_STOP_DSEL) ||
            (dc->selection_type == OR_DSEL)) {
            pcalc_prop *prop = AdjectivalPredicates::new_atom_on_x(
                EitherOrProperties::as_adjective(P_recurring), FALSE);
            prop = Propositions::concatenate(
                Propositions::Abstract::prop_to_set_kind(K_dialogue_choice), prop);
            inference_subject *subj = Instances::as_subject(dc->as_instance);
            Assert::true_about(prop, subj, CERTAIN_CE);
        }
        for (parse_node *clause = dc->choice_at->down; clause; clause = clause->next) {
            if (Node::is(clause, DIALOGUE_CLAUSE_NT)) {
                wording CW = Node::get_text(clause);
                int c = Annotations::read_int(clause, dialogue_choice_clause_ANNOT);
                switch (c) {
                    case PROPERTY_DCC: {
                        <dialogue-choice-clause>(CW);
                        wording A = GET_RW(<dialogue-choice-clause>, 1);
                        <np-articled-list>(A);
                        parse_node *AL = <<rp>>;
                        DialogueChoices::parse_property(dc, AL);
                        break;
                    }
                }
            }
        }
        if (dc->selection_type == PERFORM_DSEL) {
            dialogue_beat *db;
            LOOP_OVER(db, dialogue_beat)
                if (Wordings::match(dc->selection_parameter, db->beat_name))
                    dc->to_perform = db;
            if (dc->to_perform == NULL) {
                if (<s-value>(dc->selection_parameter)) {
                    parse_node *val = <<rp>>;
                    if (Dash::check_value(val, K_dialogue_beat) == ALWAYS_MATCH)
                        dc->to_perform_expression = val;
                } else {
                    Problems::quote_source(1, current_sentence);
                    Problems::quote_wording(2, dc->selection_parameter);
                    StandardProblems::handmade_problem(Task::syntax_tree(),
                        _p_(PM_ChoicePerformsUnknown));
                    Problems::issue_problem_segment(
                        "The dialogue choice offered by %1 asks to perform the beat '%2', "
                        "but I don't recognise that as the name of any beat in the story.");
                    Problems::issue_problem_end();
                }
            }
        }
        if ((dc->selection_type == OTHERWISE_DSEL) ||
            (DialogueChoices::flow_direction(dc) != DIALOGUE_NOT_FLOWING))
            DialogueChoices::apply_property(dc, P_recurring);
    }
}

void DialogueChoices::parse_property(dialogue_choice *dc, parse_node *AL) {
    if (Node::is(AL, AND_NT)) {
        DialogueChoices::parse_property(dc, AL->down);
        DialogueChoices::parse_property(dc, AL->down->next);
    } else if (Node::is(AL, UNPARSED_NOUN_NT)) {
        wording A = Node::get_text(AL);
        if (<s-value-uncached>(A)) {
            parse_node *val = <<rp>>;
            if (Rvalues::is_CONSTANT_construction(val, CON_property)) {
                property *prn = Rvalues::to_property(val);
                if (Properties::is_either_or(prn)) {
                    DialogueChoices::apply_property(dc, prn);
                    return;
                }
            }
            if ((Specifications::is_description(val)) || (Node::is(val, TEST_VALUE_NT))) {
                DialogueChoices::apply_property_value(dc, val);
                return;
            }
            LOG("Unexpected prop: $T\n", val);
        } else {
            LOG("Unrecognised prop: '%W'\n", A);
        }
        Problems::quote_source(1, current_sentence);
        Problems::quote_wording(2, A);
        StandardProblems::handmade_problem(Task::syntax_tree(),
            _p_(PM_ChoiceMarkupUnknown));
        Problems::issue_problem_segment(
            "The dialogue choice %1 should apparently be '%2', but that "
            "isn't something I recognise as a property which a choice can have.");
        Problems::issue_problem_end();
    }
}

§4.

void DialogueChoices::apply_property(dialogue_choice *dc, property *prn) {
    inference_subject *subj = Instances::as_subject(dc->as_instance);
    pcalc_prop *prop = AdjectivalPredicates::new_atom_on_x(
        EitherOrProperties::as_adjective(prn), FALSE);
    prop = Propositions::concatenate(
        Propositions::Abstract::prop_to_set_kind(K_dialogue_choice), prop);
    Assert::true_about(prop, subj, CERTAIN_CE);
}

void DialogueChoices::apply_property_value(dialogue_choice *dc, parse_node *val) {
    inference_subject *subj = Instances::as_subject(dc->as_instance);
    pcalc_prop *prop = Descriptions::to_proposition(val);
    if (prop) {
        prop = Propositions::concatenate(
            Propositions::Abstract::prop_to_set_kind(K_dialogue_choice), prop);
        Assert::true_about(prop, subj, CERTAIN_CE);
    }
}

§5. So what remains to be done? Everything is done except for code to be compiled at runtime. See Dialogue Choice Instances (in runtime).