Limiters with boolean operators

Something I wanna do soon is to add some Limiters operating on other limiters (NOT, AND, OR), so you could e.g. grant a bonus to all non-undead creatures except devils. Now there are multiple ways to extend the current syntax for this. Perhaps the most straight-forward would be the following:

Option 1:

"limiters" : [
	{
		"type" : "NOT",
		"parameters" : [
			{
				"type" : "OR",
				"parameters" : [
					"IS_UNDEAD",
					{
						"type" : "CREATURE_TYPE_LIMITER",
						"parameters" : [ "devil", true ]
					}
				]
			}
		]
	}
]

or equivalently (in the logical sense)

"limiters" : [
	{
		"type" : "AND",
		"parameters" : [
			{
				"type" : "NOT",
				"parameters" : [ "IS_UNDEAD" ]
			},
			{
				"type" : "NOT",
				"parameters" : [
					{
						"type" : "CREATURE_TYPE_LIMITER",
						"parameters" : [ "devil", true ]
					}
				]
			}
		]
	}
]

It’s perhaps a bit bulky to write, but intuitive to use. One thing to consider for this is how to handle the array of limiters given. I can see two sub-options here:

1a) Deprecate the "limiters" : [ ... ] syntax and instead use "limiter" : ..., i.e. provide only a single limiter.
1b) Keep the array of limiters syntax, and combine them using an "AND" limiter (a "OR" limiter makes little sense as an empty limiter list should be equivalent to no limiter list) This also matches current behaviour.

Another option would be to just use binary AND and OR limiters (i.e. 2 parameters), and describe the whole expression in pre-fix notation (I don’t see infix working without adding brackets to resolve ambiguities, and that’ll become messy).

Option 2:

"limiters" : [
	"NOT",
	"OR",
	"IS_UNDEAD",
	{
		"type" : "CREATURE_TYPE_LIMITER",
		"parameters" : [ "devil", true ]
	}
]

or equivalently

"limiters" : [
	"AND",
	"NOT",
	"IS_UNDEAD",
	"NOT",
	{
		"type" : "CREATURE_TYPE_LIMITER",
		"parameters" : [ "devil", true ]
	}
]

This approach is more compact to write, but rather less readable.

I’m strongly leaning towards Option 1, but undecided between 1a) and 1b). Maybe there’s a better way? What do others think?

To visualize some more, Option 1a) would turn into

"limiter" : {
	"type" : "NOT",
	"parameters" : [
		{
			"type" : "OR",
			"parameters" : [
				"IS_UNDEAD",
				{
					"type" : "CREATURE_TYPE_LIMITER",
					"parameters" : [ "devil", true ]
				}
			]
		}
	]
}

and respectively

"limiter" : {
	"type" : "AND",
	"parameters" : [
		{
			"type" : "NOT",
			"parameters" : [ "IS_UNDEAD" ]
		},
		{
			"type" : "NOT",
			"parameters" : [
				{
					"type" : "CREATURE_TYPE_LIMITER",
					"parameters" : [ "devil", true ]
				}
			]
		}
	]
},

while Option 1b) would turn into

"limiters" : [
	{
		"type" : "NOT",
		"parameters" : [
			{
				"type" : "OR",
				"parameters" : [
					"IS_UNDEAD",
					{
						"type" : "CREATURE_TYPE_LIMITER",
						"parameters" : [ "devil", true ]
					}
				]
			}
		]
	}
]

and respectively

"limiters" : [
	{
		"type" : "NOT",
		"parameters" : [ "IS_UNDEAD" ]
	},
	{
		"type" : "NOT",
		"parameters" : [
			{
				"type" : "CREATURE_TYPE_LIMITER",
				"parameters" : [ "devil", true ]
			}
		]
	}
]

For the (by far) most common case of only having a single limiter, Option 1a) would be more compact, but the tradeof is that we’ll have to deprecate some syntax.

And I guess there’s Option 1c) of having both the "limiter" and "limiters" option, i.e., instead of deprecating "limiters" we use the 1b) approach for them. But perhaps not ideal to have multiple (non-deprecated) ways of doing the same thing.

We already have logical operators in few json formats (building requirements, victory conditions). See LogicalExpression class. We should use similar syntax here. This syntax is derived from json schema, that we also have (Validation namespace).

Sounds good. If I understand it correctly, the examples would then look like this:

"limiters" : [
	"noneOf",
	[
		"IS_UNDEAD",
		{
			"type" : "CREATURE_TYPE_LIMITER",
			"parameters" : [ "devil", true ]
		}
	]
]

which is now simpler due to noneOf = not anyOf, and

"limiters" : [
	"allOf",
	[
		"noneOf",
		[ "IS_UNDEAD" ]
	],
	[
		"noneOf",
		[
			{
				"type" : "CREATURE_TYPE_LIMITER",
				"parameters" : [ "devil", true ]
			}
		]
	]
]

Essentially prefix notation with forced bracketing. :slight_smile:

1 Like

“not” = “noneOf”?

Ah yes, “noneOf” would be consistent with other VCMI usage. Thanks, I’ll update.

noneOf(…) = not(anyOf(…))

May be we should and NAND operation too, but how to name it consistent with NOR = noneOf

Alternatively (though less consistent with other VCMI usage), we could omit bottom-level brackets which are unnecessary. This would change the expressions to

"limiter" : [
	"noneOf",
	"IS_UNDEAD",
	{
		"type" : "CREATURE_TYPE_LIMITER",
		"parameters" : [ "devil", true ]
	}
]

and

"limiter" : [
	"allOf",
	[
		"noneOf",
		"IS_UNDEAD"
	],
	[
		"noneOf",
		{
			"type" : "CREATURE_TYPE_LIMITER",
			"parameters" : [ "devil", true ]
		}
	]
]

which is a bit more concise. For single limiters we could then just write

"limiters" : "IS_UNDEAD"

or

"limiters" :  {
    "type" : "CREATURE_TYPE_LIMITER",
    "parameters" : [ "devil", true ]
}

We could permit extra [ ] for downward compatibility. Renaming to “limiter” may still be an option.

Edit: Instead of renaming, we could also treat arrays without leading operator as if prefixed with allOf.

We could call it notAllOf ?
Though I doubt it’ll enjoy frequent usage, noneOf seems like something that would come up much more frequently, and it doesn’t add to expressiveness.

This is the exactly the name I want to avoid :smile:

If that’s your first thought too, then it’s intuitive and a good choice. :wink:

Maybe it’s offtopic here, but how about adding limiters for faction identifier and for alignment (evil, neutral, good)?

1 Like