Open jkomoros opened 7 years ago
We'd need the SanitizedProperties map client-side, so we can know when our legal method is running over sanitized values.
We could either just canonically transpile the moves' legal methods to JS via web assembly or GopherJS.
Or we could enforce that Legal methods return a LegalExpression, which is a tree and can be evaluated. The pros of this are that a) various moves can reuse sub-trees (like Only allow if targetPlayerIndex is current player), and b) the expression can be serialized as JSON and executed clientside with an engine that just has to written by the main server package, not in packages for particular games.
The question is if the second option is generic enough. I think it is, once we have ComputedProperties (#146).
type LegalExpression interface {
Evaluate(state *State) error
}
type BooleanExpression interface {
Evaluate(state *State) bool
}
type BooleanLogic int
const (
BooleanAnd BooleanLogic = iota
BooleanOr
)
//implements LegalExpression
type ExpressionErrorGroup struct {
SubExpressions []LegalExpression
//If BooleanAnd, will bail at the first SubExpression that returns an error, and return that. Returns nil if no sub-expression errors.
//If BooleanOr, will return nil as soon as it finds the first sub-expression that evaluates to Nil. If there are no no nils, will return the error from the first sub-expression
GroupType BooleanLogic
}
//Its Evaluate() will call evaluate on the boolean expression. If the result is true, and we wantTrue, will return nil. If it's not what we want, will return an error with the Error Description.
//implements LegalExpression
type ExpressionBoolean struct {
Expression BooleanExpression
WantTrue bool
ErrorDescription string
}
type ComparsionType int
const (
ComparisonEquality ComparsionType = iota
ComparisonInequality
ComparisonGreaterThan
ComparisonLessThan
ComparisonGreaterThanOrEqualTo
ComparsionLessThanOrEqualTo
)
//implements BooleanExpression
type SimpleBooleanExpressionComparison struct {
Op ComparisonType
Left ExpressionLeaf
Right ExpressionLeaf
}
type StackComparisonType int
const (
StackComparisonAll StackComparisonType = iota
StackComparsionAny
StackComparisonNone
)
//Implements BooleanExpression
type StackBooleanExpression struct {
Op StackComparisonType
Stack StatePropertyRef
Other ExpressionLeaf
}
//Hmm, handling all of the possible types
type ExpressionLeaf interface {
Value(state *State) interface{}
}
//StatePropertyRef implements ExpressionLeaf
//Implements ExpressionLeaf
type Constant struct {
i interface{}
}
//Value just returns the int
//Alternatively, instead of having a container type for constants, could just have ExpressionComparison see if it's a PropLeaf and call Value() on it. Then Constants don't need wrappers
type IntLeaf struct {
Value int
}
Hmm, this is going to get really hard with all of the different types. GroupTypes like Stacks will want operations to make sure that given components are in a given stack, or NOT in a stack, and many others.
Kind of sort of related to #82
There will be so many extra types, that implies it should be in a separate library.
The core library just expects you to have a Legal(state) error method. The server expects your moves to have a LegalExpression() *LegalExpression to evaluate.
Hmm, thats' hard to do. We can do it with storage manager in NewServer because storage manager is atop level separate thing...
Things that clientside will need:
These are two different things. The first is way easier, and this issue only captures it.