make if in scripts more generic, and allow an else branch too

This commit is contained in:
Crypto City 2022-06-03 13:13:48 +00:00
parent 867e9b359f
commit 5474600c6a
6 changed files with 247 additions and 64 deletions

View File

@ -187,14 +187,6 @@ static void print_action_string(const Action &a, std::string &source, const std:
{
std::string I(base_indent);
std::string s;
if (!a.conditions.empty())
{
source += I + "if (";
for (const Expression &e: a.conditions)
source += get_expression_string(e);
source += ")\n";
I += base_indent;
}
switch (a.type)
{
case action_none:
@ -251,6 +243,42 @@ static void print_action_string(const Action &a, std::string &source, const std:
CHECK_AND_ASSERT_THROW_MES(!a.str.empty(), "Expected a string for action_storyline_event");
source += I + "storyline event \"" + escape(a.str) + "\"\n";
break;
case action_if:
{
source += I + "if (";
for (const Expression &e: a.conditions)
source += get_expression_string(e);
source += ") {\n";
bool has_else = false;
for (const auto &e: a.actions)
{
CHECK_AND_ASSERT_THROW_MES(e.weight.type == op_unsigned, "Unexpected type in if action weight");
if (e.weight.data != 1)
{
has_else = true;
continue;
}
std::string s;
print_action_string(e.action, s, base_indent, indent);
source += I + s;
}
source += I + "}\n";
if (has_else)
{
source += I + "else {\n";
for (const auto &e: a.actions)
{
CHECK_AND_ASSERT_THROW_MES(e.weight.type == op_unsigned, "Unexpected type in if action weight");
if (e.weight.data != 0)
continue;
std::string s;
print_action_string(e.action, s, base_indent, indent);
source += I + s;
}
source += I + "}\n";
}
}
break;
}
}
@ -1223,12 +1251,6 @@ static bool execute(cryptonote::cc_command_t cmd, BlockchainStateProxy &proxy, g
if (action.type == action_none)
return true;
for (const Expression &e: action.conditions)
{
if (!is_condition_true(proxy, e, account, owner, city, seed, salt))
return true;
}
cryptonote::cc_account_data_t ad;
const auto value_signed = action.ops.empty() || action.ops[0].type == op_none ? std::make_pair<uint64_t, bool>(0, false) : get_attribute(proxy, action.ops[0], account, owner, city, seed, salt);
const uint64_t value = value_signed.first;
@ -1367,6 +1389,19 @@ static bool execute(cryptonote::cc_command_t cmd, BlockchainStateProxy &proxy, g
case action_storyline_event:
events.add(cmd, account) << "Storyline event: " << action.str;
break;
case action_if:
{
CHECK_AND_ASSERT_MES(action.conditions.size() == 1, false, "If action should have one condition");
CHECK_AND_ASSERT_MES(!action.actions.empty(), false, "If action should not be empty");
const uint64_t branch = is_condition_true(proxy, action.conditions[0], account, owner, city, seed, salt) ? 1 : 0;
for (const auto &a: action.actions)
{
CHECK_AND_ASSERT_MES(a.weight.type == op_unsigned, false, "Unexpected type in if action");
if (a.weight.data == branch && !execute(cmd, proxy, events, script, account, owner, city, a.action, seed, salt))
return false;
}
}
break;
}
return true;
}

View File

@ -159,6 +159,7 @@ typedef enum ActionType
action_set_player_variable,
action_set_local_variable,
action_storyline_event,
action_if,
} ActionType;
static const struct
{
@ -178,6 +179,7 @@ static const struct
{ "set player variable", action_set_player_variable },
{ "set local variable", action_set_local_variable },
{ "storyline_event", action_storyline_event },
{ "if", action_if },
};
#ifdef __cplusplus
@ -381,16 +383,16 @@ typedef cc::script::Script Script;
struct script_partial_state_t
{
std::vector<Operand> operands;
std::vector<Expression> expressions;
std::vector<std::vector<Expression>> expressions;
std::vector<WeightedAction> actions;
uint32_t pick;
std::vector<uint32_t> action_group_count;
State state;
Choice choice;
bool use_choice_reserves;
Script *script;
script_partial_state_t(): pick(0), use_choice_reserves(false), script(new Script()) { }
script_partial_state_t(): use_choice_reserves(false), script(new Script()) { expressions.push_back({}); }
~script_partial_state_t() { delete script; }
};
@ -423,9 +425,12 @@ void set_partial_state_public(script_partial_state_t *state);
void set_partial_state_override_string(script_partial_state_t *state, uint32_t n, const char *text);
void set_partial_state_override_local(script_partial_state_t *state, const char *text, uint32_t n);
void set_partial_state_action(script_partial_state_t *state, ActionType type, const char *str, unsigned num_ops);
void set_partial_state_action_pick_count_up(script_partial_state_t *state);
void set_partial_state_action_add_action_group_count(script_partial_state_t *state);
void set_partial_state_action_inc_action_group_count(script_partial_state_t *state);
void set_partial_state_action_set_weight(script_partial_state_t *state);
void set_partial_state_action_pick(script_partial_state_t *state);
void set_partial_state_action_if(script_partial_state_t *state);
void set_partial_state_action_else(script_partial_state_t *state);
void set_partial_state_action_percent(script_partial_state_t *state);
void set_partial_state_state_init_actions(script_partial_state_t *state);
void set_partial_state_state_text(script_partial_state_t *state, const char *str);

View File

@ -171,13 +171,13 @@ void set_partial_state_binary_expression(script_partial_state_t *state, Function
e.op1 = state->operands[n_ops - 1];
state->operands.pop_back();
state->operands.pop_back();
state->expressions.push_back(std::move(e));
state->expressions.back().push_back(std::move(e));
}
void set_partial_state_binary_compound_expression(script_partial_state_t *state, FunctionType type)
{
MDEBUG("compound expression: " << get_function_name(type));
if (state->expressions.size() < 2)
if (state->expressions.back().size() < 2)
{
tfscript_error("Binary compound expression needs at least two expressions");
return;
@ -185,18 +185,18 @@ void set_partial_state_binary_compound_expression(script_partial_state_t *state,
Expression e;
e.function = type;
const size_t n_ops = state->expressions.size();
e.expressions.push_back(state->expressions[n_ops - 2]);
e.expressions.push_back(state->expressions[n_ops - 1]);
state->expressions.pop_back();
state->expressions.pop_back();
state->expressions.push_back(std::move(e));
const size_t n_ops = state->expressions.back().size();
e.expressions.push_back(state->expressions.back()[n_ops - 2]);
e.expressions.push_back(state->expressions.back()[n_ops - 1]);
state->expressions.back().pop_back();
state->expressions.back().pop_back();
state->expressions.back().push_back(std::move(e));
}
void set_partial_state_unary_expression(script_partial_state_t *state, FunctionType type)
{
MDEBUG("expression: " << get_function_name(type));
if (state->expressions.size() < 1)
if (state->expressions.back().size() < 1)
{
tfscript_error("Unary expression needs at least one expression");
return;
@ -204,9 +204,9 @@ void set_partial_state_unary_expression(script_partial_state_t *state, FunctionT
Expression e;
e.function = type;
e.expressions.push_back(std::move(state->expressions.back()));
state->expressions.pop_back();
state->expressions.push_back(std::move(e));
e.expressions.push_back(std::move(state->expressions.back().back()));
state->expressions.back().pop_back();
state->expressions.back().push_back(std::move(e));
}
void set_partial_state_unary_predicate(script_partial_state_t *state, FunctionType type)
@ -222,13 +222,13 @@ void set_partial_state_unary_predicate(script_partial_state_t *state, FunctionTy
e.op0 = state->operands.back();
state->operands.pop_back();
state->expressions.push_back(std::move(e));
state->expressions.back().push_back(std::move(e));
}
void set_partial_state_ternary(script_partial_state_t *state)
{
MDEBUG("ternary");
if (state->expressions.size() < 1)
if (state->expressions.back().size() < 1)
{
tfscript_error("Ternary expression needs at least one expression");
return;
@ -240,8 +240,8 @@ void set_partial_state_ternary(script_partial_state_t *state)
}
Operand op;
op.type = op_cond;
op.expr.push_back(state->expressions[state->expressions.size() - 1]);
state->expressions.pop_back();
op.expr.push_back(state->expressions.back()[state->expressions.back().size() - 1]);
state->expressions.back().pop_back();
op.ops.push_back(state->operands[state->operands.size() - 2]);
op.ops.push_back(state->operands[state->operands.size() - 1]);
state->operands.pop_back();
@ -252,15 +252,15 @@ void set_partial_state_ternary(script_partial_state_t *state)
void set_partial_state_precondition(script_partial_state_t *state, const char *text)
{
MDEBUG("precondition");
if (state->expressions.size() < 1)
if (state->expressions.back().size() < 1)
{
tfscript_error("Precondition needs at least one condition");
return;
}
state->script->preconditions.push_back(std::move(state->expressions.back()));
state->script->preconditions.push_back(std::move(state->expressions.back().back()));
if (text)
state->script->preconditions.back().text = text;
state->expressions.pop_back();
state->expressions.back().pop_back();
}
void set_partial_state_name(script_partial_state_t *state, const char *s)
@ -367,14 +367,19 @@ void set_partial_state_action(script_partial_state_t *state, ActionType type, co
a.ops.push_back(std::move(state->operands[state->operands.size() - num_ops + i]));
for (unsigned int i = 0; i < num_ops; ++i)
state->operands.pop_back();
a.conditions = std::move(state->expressions);
state->expressions.clear();
}
void set_partial_state_action_pick_count_up(script_partial_state_t *state)
void set_partial_state_action_add_action_group_count(script_partial_state_t *state)
{
MDEBUG("pick one");
++state->pick;
MDEBUG("add action group count");
state->action_group_count.push_back(0);
state->expressions.push_back({});
}
void set_partial_state_action_inc_action_group_count(script_partial_state_t *state)
{
MDEBUG("inc action group count");
++state->action_group_count.back();
}
void set_partial_state_action_set_weight(script_partial_state_t *state)
@ -397,27 +402,99 @@ void set_partial_state_action_set_weight(script_partial_state_t *state)
void set_partial_state_action_pick(script_partial_state_t *state)
{
MDEBUG("pick");
if (state->pick == 0)
if (state->actions.empty())
{
tfscript_error("Pick action requires at least one action");
tfscript_error("Pick actions is empty");
return;
}
if (state->actions.size() < state->pick)
const uint32_t group_count = state->action_group_count.back();
state->action_group_count.pop_back();
if (state->actions.size() < group_count)
{
tfscript_error("Pick action found fewer actions than needed");
return;
}
state->actions.insert(state->actions.begin() + state->actions.size() - state->pick, WeightedAction{});
Action &pick = state->actions[state->actions.size() - 1 - state->pick].action;
for (uint32_t i = 0; i < state->pick; ++i)
{
pick.actions.push_back(std::move(state->actions.back()));
state->actions.insert(state->actions.begin() + state->actions.size() - group_count, WeightedAction{});
Action &pick = state->actions[state->actions.size() - 1 - group_count].action;
for (uint32_t i = 0; i < group_count; ++i)
pick.actions.push_back(std::move(state->actions[state->actions.size() - group_count + i]));
for (uint32_t i = 0; i < group_count; ++i)
state->actions.pop_back();
}
pick.type = action_pick;
state->pick = 0;
pick.conditions = std::move(state->expressions);
state->expressions.clear();
if (!state->expressions.back().empty())
pick.conditions.push_back(std::move(state->expressions.back().back()));
state->expressions.pop_back();
}
void set_partial_state_action_if(script_partial_state_t *state)
{
MDEBUG("if");
if (state->actions.empty())
{
tfscript_error("If action is empty");
return;
}
const uint32_t group_count = state->action_group_count.back();
state->action_group_count.pop_back();
if (state->actions.size() < group_count)
{
tfscript_error("If action found fewer actions than needed");
return;
}
state->actions.insert(state->actions.begin() + state->actions.size() - group_count, WeightedAction{});
Action &a = state->actions[state->actions.size() - 1 - group_count].action;
for (uint32_t i = 0; i < group_count; ++i)
{
a.actions.push_back(std::move(state->actions[state->actions.size() - group_count + i]));
a.actions.back().weight.type = op_unsigned;
a.actions.back().weight.data = 1;
}
for (uint32_t i = 0; i < group_count; ++i)
state->actions.pop_back();
a.type = action_if;
if (state->expressions.back().empty())
{
tfscript_error("If action has no expression");
return;
}
a.conditions.push_back(std::move(state->expressions.back().back()));
state->expressions.pop_back();
}
void set_partial_state_action_else(script_partial_state_t *state)
{
MDEBUG("else");
if (state->actions.empty())
{
tfscript_error("Else action is empty");
return;
}
const uint32_t group_count = state->action_group_count.back();
state->action_group_count.pop_back();
if (state->actions.size() < group_count)
{
tfscript_error("Else action found fewer actions than needed");
return;
}
Action &a = state->actions[state->actions.size() - 1 - group_count].action;
if (a.type != action_if)
{
tfscript_error("Else does not match the expected if");
return;
}
for (uint32_t i = 0; i < group_count; ++i)
{
a.actions.push_back(std::move(state->actions[state->actions.size() - group_count + i]));
a.actions.back().weight.type = op_unsigned;
a.actions.back().weight.data = 0;
}
for (uint32_t i = 0; i < group_count; ++i)
state->actions.pop_back();
state->expressions.pop_back();
}
void set_partial_state_action_percent(script_partial_state_t *state)
@ -440,8 +517,6 @@ void set_partial_state_action_percent(script_partial_state_t *state)
percent.actions[0].weight = std::move(state->operands.back());
state->actions.pop_back();
state->operands.pop_back();
percent.conditions = std::move(state->expressions);
state->expressions.clear();
}
void set_partial_state_state_init_actions(script_partial_state_t *state)
@ -502,14 +577,14 @@ void set_partial_state_choice_selected_text(script_partial_state_t *state, const
void set_partial_state_choice_enabled(script_partial_state_t *state)
{
MDEBUG("choice condition");
if (state->expressions.size() < 1)
if (state->expressions.back().size() < 1)
{
tfscript_error("Choice condition requires at least one condition");
return;
}
auto &c = state->expressions.back();
auto &c = state->expressions.back().back();
state->choice.enabled.push_back(std::move(c));
state->expressions.pop_back();
state->expressions.back().pop_back();
}
void set_partial_state_choice_actions(script_partial_state_t *state)
@ -557,7 +632,9 @@ void finalize_partial_state(script_partial_state_t **state)
{
MDEBUG("finalizing state");
if (!(*state)->operands.empty()) tfscript_error("Not all operands were used up: " + std::to_string((*state)->operands.size()) + " leftover");
if (!(*state)->expressions.empty()) tfscript_error("Not all expressions were used up: " + std::to_string((*state)->expressions.size()) + " leftover");
if (!(*state)->expressions.back().empty()) tfscript_error("Not all expressions were used up: " + std::to_string((*state)->expressions.back().size()) + " leftover");
(*state)->expressions.pop_back();
if (!(*state)->expressions.empty()) tfscript_error("Not all expressions levels were popped: " + std::to_string((*state)->expressions.size()) + " leftover");
if (!(*state)->actions.empty()) tfscript_error("Not all actions were used up: " + std::to_string((*state)->actions.size()) + " leftover");
MDEBUG("clearing state");

View File

@ -107,6 +107,7 @@ coin { return COIN; }
food { return FOOD; }
custom { return CUSTOM; }
if { return IF; }
else { return ELSE; }
storyline { return STORYLINE; }
restricted { return RESTRICTED; }

View File

@ -36,7 +36,7 @@ static script_partial_state_t *state = NULL;
%token <number> ADD SUB MUL MIN MAX NOT DIV MOD SQRT
%token <number> PERCENT PAY CONSUME TEMPERATURE_NUMBER TEMPERATURE RANDOM
%token <number> IMAGE UI CHOICES CHOICE NEXT SELECTED ENABLED ACTIONS PUBLIC IS BLOCKCHAIN BY OVERRIDES ID
%token <number> GEMSTONE BLOCK LABOUR COIN FOOD CUSTOM IF STORYLINE RESTRICTED
%token <number> GEMSTONE BLOCK LABOUR COIN FOOD CUSTOM IF ELSE STORYLINE RESTRICTED
%type <number> script script_contents script_content choice_contents choice_content
%type <number> name owner description icon precondition reserves state public
@ -48,6 +48,7 @@ static script_partial_state_t *state = NULL;
%type <number> reserves_contents reserves_content state_contents state_content
%type <number> comparison overrides overrides_contents overrides_content
%type <number> selected_text
%type <number> action_if if_actions
%left ADD SUB MUL AND OR DIV MOD
%left BADGE ATTRIBUTE DISCOVERER HEIGHT ITEM RANDOM
@ -122,7 +123,7 @@ action: action_percent
| action_set_global_variable
| action_storyline_event
| action_none
| IF '(' expression ')' action
| action_if
;
action_percent: PERCENT operand action { set_partial_state_action_percent(state); }
@ -152,10 +153,10 @@ action_set_global_variable: SET GLOBAL STRING operand { set_partial_state_action
action_set_local_variable: SET LOCAL STRING operand { set_partial_state_action(state, action_set_local_variable, $3, 1); }
;
action_pick: PICK '{' pick_actions '}' { set_partial_state_action_pick(state); }
action_pick: PICK '{' { set_partial_state_action_add_action_group_count(state); } pick_actions '}' { set_partial_state_action_pick(state); }
;
pick_actions: pick_actions pick_action { set_partial_state_action_pick_count_up(state); }
pick_actions: pick_actions pick_action { set_partial_state_action_inc_action_group_count(state); }
| {}
;
@ -165,6 +166,17 @@ pick_action: WEIGHT operand action { set_partial_state_action_set_weight(state);
action_storyline_event: STORYLINE EVENT STRING { set_partial_state_action(state, action_storyline_event, $3, 0); }
;
action_if: IF { set_partial_state_action_add_action_group_count(state); } '(' expression ')' '{' if_actions '}' { set_partial_state_action_if(state); } optional_else
;
if_actions: if_actions action { set_partial_state_action_inc_action_group_count(state); }
| {}
;
optional_else: ELSE { set_partial_state_action_add_action_group_count(state); } '{' if_actions '}' { set_partial_state_action_else(state); }
| {}
;
action_none: NONE { set_partial_state_action(state, action_none, NULL, 0); }
;

View File

@ -6291,6 +6291,59 @@ script {
assert 'reserve' not in res
assert game_256 == [x.amount for x in res.item_balances if x.type == 256][0]
print('Testing if/else in scripts')
script = """
script {
name "if/else test"
state "0" {
choice { text "end" }
choice { text "1" actions { if (local "x" == 1) { set local "z" 1 } else { set local "z" 2 } } next state "0" }
choice { text "2" actions { set local "x" 1 } next state "0" }
choice { text "3" actions { set local "x" 2 } next state "0" }
}
}
"""
self.wallet[3].cc_create_script(source = script)
res = self.generate_blocks('TF1MMBg4zx18SnZC6oswCRB7DzdVeUKce5NdCMSWUHNY4wNvNhzmFj4WqZY2bFj8R1REAWR3qAH5zD7sjXyHz3tVayzHSswqymx', 1)
res = daemon.cc_get_scripts()
assert len(res.scripts) > 1
script_id = res.scripts[-1].index
# start the script
res = self.wallet[2].cc_start_script(script_id, city = 0)
res = self.generate_blocks('TF1MMBg4zx18SnZC6oswCRB7DzdVeUKce5NdCMSWUHNY4wNvNhzmFj4WqZY2bFj8R1REAWR3qAH5zD7sjXyHz3tVayzHSswqymx', 1)
res = daemon.cc_get_account(account2_id)
assert res.script == script_id
assert res.script_city == 0
assert res.script_owner == GAME_ACCOUNT
assert res.script_state == 0
assert 'script_local_variables' not in res or len(res.script_local_variables) == 0
res = self.wallet[2].cc_script_choice(script_id, state = 0, city = 0, owner = GAME_ACCOUNT, choice = 1)
res = self.generate_blocks('TF1MMBg4zx18SnZC6oswCRB7DzdVeUKce5NdCMSWUHNY4wNvNhzmFj4WqZY2bFj8R1REAWR3qAH5zD7sjXyHz3tVayzHSswqymx', 1)
res = daemon.cc_get_account(account2_id)
assert res.script_local_variables[0] == { 'name': 'z', 'value': 2}
res = self.wallet[2].cc_script_choice(script_id, state = 0, city = 0, owner = GAME_ACCOUNT, choice = 2)
res = self.generate_blocks('TF1MMBg4zx18SnZC6oswCRB7DzdVeUKce5NdCMSWUHNY4wNvNhzmFj4WqZY2bFj8R1REAWR3qAH5zD7sjXyHz3tVayzHSswqymx', 1)
res = self.wallet[2].cc_script_choice(script_id, state = 0, city = 0, owner = GAME_ACCOUNT, choice = 1)
res = self.generate_blocks('TF1MMBg4zx18SnZC6oswCRB7DzdVeUKce5NdCMSWUHNY4wNvNhzmFj4WqZY2bFj8R1REAWR3qAH5zD7sjXyHz3tVayzHSswqymx', 1)
res = daemon.cc_get_account(account2_id)
assert res.script_local_variables[0] == { 'name': 'z', 'value': 1} or res.script_local_variables[1] == { 'name': 'z', 'value': 1}
res = self.wallet[2].cc_script_choice(script_id, state = 0, city = 0, owner = GAME_ACCOUNT, choice = 3)
res = self.generate_blocks('TF1MMBg4zx18SnZC6oswCRB7DzdVeUKce5NdCMSWUHNY4wNvNhzmFj4WqZY2bFj8R1REAWR3qAH5zD7sjXyHz3tVayzHSswqymx', 1)
res = self.wallet[2].cc_script_choice(script_id, state = 0, city = 0, owner = GAME_ACCOUNT, choice = 1)
res = self.generate_blocks('TF1MMBg4zx18SnZC6oswCRB7DzdVeUKce5NdCMSWUHNY4wNvNhzmFj4WqZY2bFj8R1REAWR3qAH5zD7sjXyHz3tVayzHSswqymx', 1)
res = daemon.cc_get_account(account2_id)
assert res.script_local_variables[0] == { 'name': 'z', 'value': 2} or res.script_local_variables[1] == { 'name': 'z', 'value': 2}
# end the script
res = self.wallet[2].cc_script_choice(script_id, state = 0, city = 0, owner = GAME_ACCOUNT, choice = 0)
res = self.generate_blocks('TF1MMBg4zx18SnZC6oswCRB7DzdVeUKce5NdCMSWUHNY4wNvNhzmFj4WqZY2bFj8R1REAWR3qAH5zD7sjXyHz3tVayzHSswqymx', 1)
def check_gold_consistency(self):
daemon = self.daemon