Open jdanish opened 1 year ago
Two more thoughts: 1) It might be nice to make this a name and value, so 'red' might have a value of 'red' or a value of '255, 0, 0' or whatever. 2) If we can use some of this for setting color for at least some cases ... that'd be amazing.
@jdanish We could use some more examples of enumerated variables. Other than color, can you provide three different examples of enumerated variables and how they might be used?
Organism type: producer,consumer, decomposer
Energy level low (1), medium (5), high (10)
Fish state: hungry, content, full, dead
Sorry ... how to use:
For type, it might be that these are just labels that get applied and the enum is just to help guarantee consistent spelling. Alternatively, if a character is type "producer" then it gains energy from touching the sun. If it is type "consumer" it won't, but gains energy from touching a plant. Etc.
For energy level, this might be a way to start the characters out at one of 3 levels using a drop-down with a label rather than having to guess good numbers.
Fish, uses the state to determine how it looks (if hungry show the hungry thought balloon, if content, do not, if dead, do not interact). Etc.
Thinking this through some more, we might have to implement an optionsString
type and an optionsNumber
type, otherwise any math you do will be broken, e.g. string "5" is not the same as number 5.
I suppose we could add a way to specify the value type with out a new keyword. But kind of depends on how expressions will be handled.
Would it be possible to do a setOptions similar to setMin? Otherwise that makes sense. Thanks!
@jdanish working through the design of enums...I want to run this by you and @dsriseah to make sure that this makes sense.
I think you're wanting to do multiple things:
But the GEMSCRIPT limitations around being able to assign GVar values to properties and feature properties are still active. e.g. we still can't do something like prop entityType setTo PRODUCER
where PRODUCER
is a enum/dictionary value. We still have to rely on stack operations to do that kind of assignment.
We did look into trying to implement setToOption
, but after much time exploring numerous approaches, including building "options" into GVars, it became clear that while we could support that approach via pure GEMSCRIPT coding, the wizard interface would require a very deep architectural changes to make it work.
After conferring with Sri, we think the appropriate solution is to introduce the concept of Constants. (This design doc will turn into the wiki writeup)
This mostly works now, but there is a ton of cleanup that we still have to do to get it to a demo-able state. And if we want color support for the Costume feature, we would also need to add some new methods.
Does this sound like it would address your needs?
Constants can be defined for any agent, including the global agent. Use the addConstant
keyword.
To assign a prop
or featureProp
to a constant value, use the constantPush
keyword.
addConstant
keywordYou define agents using the addConstant
keyword
Syntax
addConstant <constantName> <GVar Type> <constantValue>
Where:
<constantName>
-- the name of the constant, e.g. 'PRODUCER`. (Note we use UPPERCASE to designate constant names to help clarify use, though technically there isn't a restriction.)<GVar Type>
-- the variable type of the constant, can be string
, number
, or boolean
.<constantValue>
-- optional value for the constant. If a target value is not set, the value is automatically set to the constant name, e.g. addConstant PRODUCER string
would assign the constant PRODUCER
to the string "PRODUCER"
.Example:
addConstant PRODUCER string 'prod'
constantPush
keywordA prop
or featProp
can be set to a constant by using stack operations.
Syntax
constantPush <constantName>
Where:
<constantName>
-- the name of the constant, e.g. 'PRODUCER`. (Note we use UPPERCASE to designate constant names to help clarify use, though technically there isn't a restriction.)Example:
addConstant PRODUCER string 'prod'
constantPush PRODUCER
propPop Fish.entityType
Constants can be referenced as an agent object property and used as part of expressions. e.g.
... {{ ...character.constant.PRODUCER... }} ...
... {{ ...Fish.constant.PRODUCER... }} ...
Typically you would use a constant reference as part of a testing expression, e.g.
ifExpr {{ Fish.prop.entityType.value === Fish.constant.PRODUCER }} [[
dbgOut "I am a producer"
]]
NOTE that ifProp
doesn't work because we GVars can't reference another object
addConstant BEAVERCOSTUME string 'beaver.json'
// THIS WON'T WORK -- `BEAVERCOSTUME` is not defined in this context
ifProp character.costumeType equal BEAVERCOSTUME [[ ... ]]
Since we are using agent object references, references to the global agent can also work. This is how you would define global constants.
# BLUEPRINT Global
// Add Global Constant
addConstant EVIL string 'evil'
addConstant GOOD string 'good'
# BLUEPRINT Shark
// use pop/push
constantPush Global.EVIL
propPop Shark.alignment
// Test Global Constant
ifExpr {{ Shark.prop.alignment.value === Global.constant.EVIL }} [[
dbgOut "Bwahaha"
]]
color
)Colors are a special use case. While we can introduce the concept of tuples, it's probably easier at this point to use css strings to define colors. e.g.
// define the colors
addConstant RED string '#f00'
addConstant GREEN string '#0f0'
addConstant BLUE string '#00f'
We can then add a new feature property that can be used to set Costume colors. (We can't use a feature method call because we would run into the same problem of not being able to reference a constant). For example, we can introduce a colorCSS
feature property that simply accepts css strings, so you could potentially use #f00
, #f003
, #ff0000
, #ff000033
, red
, rgba(255,0,0,0.3)
, and any other valid css string. The downside of this is we would lose the ability to do any math on colors without adding conversion methods.
// use push to set the color
constantPush GREEN
// define a new Costume property that supports css hex
featPropPop character.colorCSS
Some example scripts:
// Strings
addConstant PRODUCER string 'prod' // string can be blank, just used for testing/confirming values
addConstant CONSUMER string 'cons'
addConstant DECOMPOSER string 'decomp'
// Numbers
addConstant LOW number 0
addConstant MED number 1
addConstant HIGH number 2
// use pop/push
constantPush PRODUCER
propPop Fish.entityType
# BLUEPRINT Global
addConstant EVIL string 'evil'
addConstant GOOD string 'good'
# BLUEPRINT Shark
// use pop/push
constantPush Global.EVIL
propPop Shark.alignment
ifExpr {{ Fish.prop.entityType.value === Fish.constant.PRODUCER }} [[
dbgOut "I am a producer"
]]
NOTE that ifProp
doesn't work because we GVars can't reference another object
addConstant BEAVERCOSTUME string 'beaver.json'
// THIS WON'T WORK -- `BEAVERCOSTUME` is not defined in this context
ifProp character.costumeType equal BEAVERCOSTUME [[ ... ]]
ifExpr {{ Shark.prop.alignment.value === Global.constant.EVIL }} [[
dbgOut "Bwahaha"
]]
This will be dependent on adding a new method/property to the Costume Feature to support something like this.
// define the colors
addConstant RED string '#f00'
addConstant GREEN string '#0f0'
addConstant BLUE string '#00f'
// use push to set the color
constantPush GREEN
// define a new Costume property that supports hex
featPropPop character.colorHex
I feel like I'm missing something here, and apologize. The main goal is to simplify the selection of values by a student. This feels like it is a) no different from using an all caps property, and b) requires advanced scripting anyhow (e.g., Exp). So while the color setting is cool, this doesn't really help simplify kid's lives and I am actually not sure I am seeing the advantage of using a constant over a prop that is well named? I apologize if I am missing something key.
Yeah, the fundamental problem is that we cannot assign a GVar value to another property without resorting to stack operations. So any implementation is going to look like this.
We can hack together a code-only version that only supports options for local agents (not global), but as soon as we try to implement a wizard for this, it all falls apart. The wizard can't access the options list.
e.g. we can write this in GEMSCRIPT. and it works great...
// define the options
addProp colour string 'black'
prop colour addOption RED string '#f00'
prop colour addOption GREEN string '#0f0'
prop colour addOption BLUE string '#00f'
// assign prop to an option
prop colour setToOption RED
..but if we use the wizard to select the line prop colour setToOption RED
, the option RED
is treated as a string, not a selectable list of options. To do what you want, we'd have to somehow construct a list of options (RED, GREEN BLUE) to pull out of the colour
prop, and more importantly, be able to access the values of the options (e.g. #f00
, #0f0
, etc). But during the wizard construction time, we can't access those values, so we can't construct the list of choices.
It occurs to me one possible terribly hacky workaround is to add something similar to the comment-bookmark widget (779a95f812e27c29e92471f367e2d76a102ef3f9) where we bypass the standard wizard UI and inject a selection popup UI to access the list of options. The question is can we read the agent property options to construct the list. This would be hacky and probably setting a bad precedent.
Pardon my ignorant question but thinking about how the Boolean wizard works, what if we had another gVar type that functioned like a Boolean but you could add key / value pairs and they all appear in the wizard as a drop-down? We could assume they are all strings or have an enumString and enumNumber (maybe called optionsString and optionsNumber)? I think that’d work like a mega Boolean from a user’s perspective and would make it so you don’t need to set to arbitrary values but a set list.Or if we have a setToOption instead of setTo which pulls the list? I like the first idea better but either would be better than the stack operations which I think only I understand…Or would that have the same problem? I love getting colors to be easier but I fear I’d never use constants because I can more easily have a comment above:// change this to ‘producer’ or ‘consumer’prop Animal.type setTo ‘producer’Kids could make mistakes but it’s far more intuitive than the stack functions for them I think.
Yeah, stack operations are not at all intuitive.
And the questions aren't ignorant. It's a complex system that I definitely have trouble getting my head around.
Creating another GVar type was essentially what I did with the addConstant
keyword. The basic problem is that while GEMSCRIPT allows us to make constructs like <keyword> <set-to-method> <value>
, the <value>
parameter only allows us to reference a simple string, number, or boolean, NOT a GVar or more complicated object. So even though we can arbitrarily define new GVars (e.g. a constant 'PRODUCER' via addConstant addOption 'PRODUCER'
or a boolean-like variable), our referent <value>
can't be the the GVar object -- prop entityType setTo PRODUCER
doesn't work because PRODUCER
is not a simple javascript string, number, or boolean, but a GVar or other object. prop entityType setTo "producer"
works because "producer"
is a simple string, but prop entityType setTo PRODUCER
doesn't work because PRODUCER
isn't a simple string, number, or boolean. This is why we need to use stack operations for all this stuff.
That said, I'm playing with a workaround hack ala comment styles, where we essentially add another compile pass to first pull out constant definitions, and then hack an interface that allows you to select one of the defined constants. This works because we don't fundamentally change the script engine, but instead just shim in a UI to support selection. We'll see if it works.
OK, cool. I’ll wait on the hack before offering more rambling then :)
The idea is that if you declare a variable, you could then set options (rather than min and max) that would then be made available in the wizard. So, something like:
addProp color options prop color addOption red prop color addOption blue prop color addOption green
And then if you need to test or set it, the wizard would give a list of the options so that you knew that you were picking a valid choice.
I could imagine these ultimately being part of a number or string (or both)?
Something like:
addProp color string 'gray' prop color addOption 'red' prop color addOption 'green'
Then instead of setTo you could use setToOption and it'd give you a list of options?