From 5bb873fddc40abbb642d904c59b03c31de7f0206 Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 9 Mar 2018 15:06:33 -0800 Subject: [PATCH 1/7] Initial version of cost estimation working. Values are pretty bogus, but they're values. --- src/main/scala/wordbots/CodeGenerator.scala | 2 +- src/main/scala/wordbots/CostEstimator.scala | 168 +++++++++++++++++++ src/main/scala/wordbots/WordbotsServer.scala | 12 +- 3 files changed, 175 insertions(+), 7 deletions(-) create mode 100644 src/main/scala/wordbots/CostEstimator.scala diff --git a/src/main/scala/wordbots/CodeGenerator.scala b/src/main/scala/wordbots/CodeGenerator.scala index 12b51d9..edc07cd 100644 --- a/src/main/scala/wordbots/CodeGenerator.scala +++ b/src/main/scala/wordbots/CodeGenerator.scala @@ -24,7 +24,7 @@ object CodeGenerator { case Destroy(target) => s"(function () { actions['destroy'](${g(target)}); })" case Discard(target) => s"(function () { actions['discard'](${g(target)}); })" case Draw(target, num) => s"(function () { actions['draw'](${g(target)}, ${g(num)}); })" - case EndTurn => "(function () { actions['endTurn'](); })" + case EndTurn => s"(function () { actions['endTurn'](); })" case GiveAbility(target, ability) => s"""(function () { actions['giveAbility'](${g(target)}, \\"${escape(g(ability))}\\"); })""" case ModifyAttribute(target, attr, op) => s"(function () { actions['modifyAttribute'](${g(target)}, ${g(attr)}, ${g(op)}); })" case ModifyEnergy(target, op) => s"(function () { actions['modifyEnergy'](${g(target)}, ${g(op)}); })" diff --git a/src/main/scala/wordbots/CostEstimator.scala b/src/main/scala/wordbots/CostEstimator.scala new file mode 100644 index 0000000..e94f65c --- /dev/null +++ b/src/main/scala/wordbots/CostEstimator.scala @@ -0,0 +1,168 @@ +package wordbots +/* +* scoring: good is positive, bad is negative. applying to enemy multiplies by -1. +* */ + +object CostEstimator { + def estimateCost(node: AstNode, mode:Option[String]): String = + (baseCost(mode)(astEst(node))).toString + + private def baseCost(mode:Option[String]) : Float=>Float ={ + mode match{ + case Some("Object") => (x => 1 + 1.5f * x)//objects are more expensive than events and have a higher base cost + case Some("Event") => (x => 0 + 1 * x) + case _ => (x => 1 * x) + } + } + + // scalastyle:off method.length + // scalastyle:off cyclomatic.complexity + //scalastyle:off magic.number + //estimate the cost of an AST node + private def astEst(node: AstNode): Float = { + node match{ + // Meta + case If(condition, action) => 1 * childCosts(node).product + case MultipleActions(actions) => 1 * childCosts(node).sum + case MultipleAbilities(abilities) => 1 * childCosts(node).sum + case Until(TurnsPassed(num), action) => 1 * childCosts(node).sum + + // Actions: Normal + case BecomeACopy(source, target) => 1 * childCosts(node).sum + case CanAttackAgain(target) => 1 * childCosts(node).sum + case CanMoveAgain(target) => 1 * childCosts(node).sum + case CanMoveAndAttackAgain(target) => 1 * childCosts(node).sum + case DealDamage(target, num) => -1 * childCosts(node).sum + case Destroy(target) => -2 * childCosts(node).sum + case Discard(target) => -1 * childCosts(node).sum + case Draw(_,_) => 1 * childCosts(node).sum + case EndTurn => 1 + case GiveAbility(target, ability) => 1 * childCosts(node).sum + case ModifyAttribute(target, attr, op)=>1 * childCosts(node).sum + case ModifyEnergy(target, op) => 1 * childCosts(node).sum + case MoveObject(target, dest) => 1 * childCosts(node).sum + case PayEnergy(target, amount) => -0.5f * childCosts(node).sum + case RemoveAllAbilities(target) => 1 * childCosts(node).sum + case RestoreAttribute(target, Health, Some(num)) => 1 * childCosts(node).sum + case RestoreAttribute(target, Health, None) => 1 * childCosts(node).sum + case ReturnToHand(target) => 1 * childCosts(node).sum + case SetAttribute(target, attr, num)=> 1 * childCosts(node).sum + case SwapAttributes(target, attr1, attr2) => 1 * childCosts(node).sum + case TakeControl(player, target) => 2 * childCosts(node).sum + + // Actions: Utility + case SaveTarget(target) => 1 * childCosts(node).sum + + // Activated and triggered abilities + case ActivatedAbility(action) => 1 * childCosts(node).sum + case TriggeredAbility(trigger, Instead(action)) => 1 * childCosts(node).sum + case TriggeredAbility(trigger, action) => 1 * childCosts(node).sum + + // Passive abilities + case ApplyEffect(target, effect) => 1 * childCosts(node).sum + case AttributeAdjustment(target, attr, op) => 1 * childCosts(node).sum + case FreezeAttribute(target, attr) => 1 * childCosts(node).sum + case HasAbility(target, ability) => 1 * childCosts(node).sum + + // Effects + case CanOnlyAttack(target) => 1 * childCosts(node).sum + + // Triggers + case AfterAttack(targetObj, objectType) => 1 * childCosts(node).sum + case AfterCardPlay(targetPlayer, cardType) => 1 * childCosts(node).sum + case AfterDamageReceived(targetObj) => 1 * childCosts(node).sum + case AfterDestroyed(targetObj, cause) => 1 * childCosts(node).sum + case AfterMove(targetObj) => 1 * childCosts(node).sum + case AfterPlayed(targetObj) => 1 * childCosts(node).sum + case BeginningOfTurn(targetPlayer) => 1 * childCosts(node).sum + case EndOfTurn(targetPlayer) => 1 * childCosts(node).sum + + // Target objects + case ChooseO(collection) => 2 * childCosts(node).sum + case AllO(collection) => 3 * childCosts(node).sum + case RandomO(num, collection) => 1 * childCosts(node).sum + case ThisObject => 1 + case ItO => 1 + case ItP => 1 + case That => 1 + case They => 1 + case SavedTargetObject => 1 + + // Target cards + case ChooseC(collection) => 1 * childCosts(node).sum + case AllC(collection) => 1 * childCosts(node).sum + case RandomC(num, collection) => 0.5f * childCosts(node).sum + + // Target players + case Self => 1 + case Opponent => -1 + case AllPlayers => 1 + case ControllerOf(targetObject) => 1 * childCosts(node).sum + + // Conditions + case AdjacentTo(obj) => 1 * childCosts(node).sum + case AttributeComparison(attr, comp)=> 1 * childCosts(node).sum + case ControlledBy(player) => 1 * childCosts(node).sum + case HasProperty(property) => 1 * childCosts(node).sum + case Unoccupied => 1 + case WithinDistanceOf(distance, obj)=> 1 * childCosts(node).sum + + // Global conditions + case CollectionExists(coll) => 1 * childCosts(node).sum + case TargetHasProperty(target, property) => 1 * childCosts(node).sum + + // Arithmetic operations + case Constant(num) => 1 * childCosts(node).sum + case Plus(num) => 1 * childCosts(node).sum + case Minus(num) => 1 * childCosts(node).sum + case Multiply(num) => 1 * childCosts(node).sum + case Divide(num, RoundedDown) => 1 * childCosts(node).sum + case Divide(num, RoundedUp) => 1 * childCosts(node).sum + + // Comparisons + case EqualTo(num) => 1 * childCosts(node).sum + case GreaterThan(num) => 1 * childCosts(node).sum + case GreaterThanOrEqualTo(num) => 1 * childCosts(node).sum + case LessThan(num) => 1 * childCosts(node).sum + case LessThanOrEqualTo(num) => 1 * childCosts(node).sum + + + // Numbers + case Scalar(int) => childCosts(node).sum * childCosts(node).sum + case AttributeSum(collection, attr) => 1 * childCosts(node).sum + case AttributeValue(obj, attr) => 1 * childCosts(node).sum + case Count(collection) => 1 * childCosts(node).sum + case EnergyAmount(player) => 1 * childCosts(node).sum + + // Collections + case AllTiles => 1 + case CardsInHand(player, cardType) => 1 * childCosts(node).sum + case ObjectsMatchingConditions(objType, conditions) => 1 * childCosts(node).product + case Other(collection) => 1 * childCosts(node).sum + case TilesMatchingConditions(conditions) => 1 * childCosts(node).sum +/* + // Labels + case m: MultiLabel => m.labels.map(g).mkString("[", ", ", "]") + case l: Label => s"'${getLabelName(l)}'"*/ + + case _ => 1 * childCosts(node).sum + } + } + + + //try to calculate a cost for anything and everything + private def genericEstimate(a:Any): Float = a match{ + case n:AstNode => astEst(n)//AST node, complex. + case n:Int => n + case c:Seq[Condition] => c.map(child=>genericEstimate(child)).product //conditional series are multiplied - maybe? + case c:Seq[Action] => c.map(child=>genericEstimate(child)).sum //hmm. might want to mult instead of add sometimes - configurable? + // scalastyle:off regex + case _=> println("error unknown type in generic estimate."); 0 + // scalastyle:on regex + } + + //for each child of the node, run genericEstimate() + private def childCosts(node: AstNode) :Iterator[Float]= + node.productIterator.map[Float](child=>genericEstimate(child)) +} + diff --git a/src/main/scala/wordbots/WordbotsServer.scala b/src/main/scala/wordbots/WordbotsServer.scala index a544cb3..648400a 100644 --- a/src/main/scala/wordbots/WordbotsServer.scala +++ b/src/main/scala/wordbots/WordbotsServer.scala @@ -65,7 +65,7 @@ object WordbotsServer extends ServerApp { parse(input, mode) match { case SuccessfulParse(parse, ast, parsedTokens) => format match { - case Some("js") => successResponse(CodeGenerator.generateJS(ast), parsedTokens) + case Some("js") => successResponse(CodeGenerator.generateJS(ast), parsedTokens, CostEstimator.estimateCost(ast,mode)) case Some("svg") => Ok(parse.toSvg, headers(Some("image/svg+xml"))) case _ => BadRequest("{\"error\": \"Invalid format\"}", headers()) } @@ -78,7 +78,7 @@ object WordbotsServer extends ServerApp { request.as(jsonOf[Seq[ParseRequest]]).flatMap { parseRequests: Seq[ParseRequest] => val responseBody: String = parseRequests.map { req: ParseRequest => val responseJson = parse(req.input, Option(req.mode)) match { - case SuccessfulParse(_, ast, parsedTokens) => successResponseJson(CodeGenerator.generateJS(ast), parsedTokens) + case SuccessfulParse(_, ast, parsedTokens) => successResponseJson(CodeGenerator.generateJS(ast), parsedTokens, CostEstimator.estimateCost(ast,Option(req.mode))) case FailedParse(error, unrecognizedTokens) => errorResponseJson(error, unrecognizedTokens) } s""""${req.input.replaceAllLiterally("\"", "\\\"")}": $responseJson""" @@ -122,8 +122,8 @@ object WordbotsServer extends ServerApp { } } - def successResponseJson(js: String, parsedTokens: Seq[String] = Seq()): String = { - "{\"js\": \"" + js + "\", \"tokens\": [" + parsedTokens.mkString("\"", "\",\"", "\"") + "]}" + def successResponseJson(js: String, parsedTokens: Seq[String] = Seq(), estCost: String): String = { + "{\"js\": \"" + js + "\", \"tokens\": [" + parsedTokens.mkString("\"", "\",\"", "\"") + "], \"estCost\": \"" + estCost + "\"}" } def errorResponseJson(error: ParserError = ParserError("Parse failed"), unrecognizedTokens: Seq[String] = Seq()): String = { @@ -132,8 +132,8 @@ object WordbotsServer extends ServerApp { "\"unrecognizedTokens\": [" + unrecognizedTokens.mkString("\"", "\",\"", "\"") + "]}" } - def successResponse(js: String, parsedTokens: Seq[String] = Seq()): Task[Response] = { - Ok(successResponseJson(js, parsedTokens), headers()) + def successResponse(js: String, parsedTokens: Seq[String] = Seq(), estCost:String): Task[Response] = { + Ok(successResponseJson(js, parsedTokens,estCost), headers()) } def errorResponse(error: ParserError = ParserError("Parse failed"), unrecognizedTokens: Seq[String] = Seq()): Task[Response] = { From c4ff9c0775871812a137e03c6cb2350524117453 Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 9 Mar 2018 15:45:00 -0800 Subject: [PATCH 2/7] changed many sums to products, should fix some cost sign issues. also added in verbose logging, will remove in future. --- src/main/scala/wordbots/CostEstimator.scala | 54 +++++++++++--------- src/main/scala/wordbots/WordbotsServer.scala | 6 +-- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/main/scala/wordbots/CostEstimator.scala b/src/main/scala/wordbots/CostEstimator.scala index e94f65c..cfa34dd 100644 --- a/src/main/scala/wordbots/CostEstimator.scala +++ b/src/main/scala/wordbots/CostEstimator.scala @@ -5,7 +5,7 @@ package wordbots object CostEstimator { def estimateCost(node: AstNode, mode:Option[String]): String = - (baseCost(mode)(astEst(node))).toString + (baseCost(mode)(genericEstimate(node))).toString private def baseCost(mode:Option[String]) : Float=>Float ={ mode match{ @@ -32,23 +32,23 @@ object CostEstimator { case CanAttackAgain(target) => 1 * childCosts(node).sum case CanMoveAgain(target) => 1 * childCosts(node).sum case CanMoveAndAttackAgain(target) => 1 * childCosts(node).sum - case DealDamage(target, num) => -1 * childCosts(node).sum + case DealDamage(target, num) => -1 * childCosts(node).product case Destroy(target) => -2 * childCosts(node).sum case Discard(target) => -1 * childCosts(node).sum - case Draw(_,_) => 1 * childCosts(node).sum + case Draw(target,num) => 1 * childCosts(node).product case EndTurn => 1 - case GiveAbility(target, ability) => 1 * childCosts(node).sum - case ModifyAttribute(target, attr, op)=>1 * childCosts(node).sum - case ModifyEnergy(target, op) => 1 * childCosts(node).sum + case GiveAbility(target, ability) => 1 * childCosts(node).product + case ModifyAttribute(target, attr, op)=>1 * childCosts(node).product + case ModifyEnergy(target, op) => 1 * childCosts(node).product case MoveObject(target, dest) => 1 * childCosts(node).sum - case PayEnergy(target, amount) => -0.5f * childCosts(node).sum + case PayEnergy(target, amount) => -0.5f * childCosts(node).product case RemoveAllAbilities(target) => 1 * childCosts(node).sum case RestoreAttribute(target, Health, Some(num)) => 1 * childCosts(node).sum case RestoreAttribute(target, Health, None) => 1 * childCosts(node).sum case ReturnToHand(target) => 1 * childCosts(node).sum - case SetAttribute(target, attr, num)=> 1 * childCosts(node).sum - case SwapAttributes(target, attr1, attr2) => 1 * childCosts(node).sum - case TakeControl(player, target) => 2 * childCosts(node).sum + case SetAttribute(target, attr, num)=> 1 * childCosts(node).product + case SwapAttributes(target, attr1, attr2) => 1 * childCosts(node).sum//TODO FIX + case TakeControl(player, target) => 2 * childCosts(node).product // Actions: Utility case SaveTarget(target) => 1 * childCosts(node).sum @@ -101,7 +101,7 @@ object CostEstimator { // Conditions case AdjacentTo(obj) => 1 * childCosts(node).sum - case AttributeComparison(attr, comp)=> 1 * childCosts(node).sum + case AttributeComparison(attr, comp)=> 1 * childCosts(node).product case ControlledBy(player) => 1 * childCosts(node).sum case HasProperty(property) => 1 * childCosts(node).sum case Unoccupied => 1 @@ -114,13 +114,13 @@ object CostEstimator { // Arithmetic operations case Constant(num) => 1 * childCosts(node).sum case Plus(num) => 1 * childCosts(node).sum - case Minus(num) => 1 * childCosts(node).sum - case Multiply(num) => 1 * childCosts(node).sum + case Minus(num) => -1 * childCosts(node).sum + case Multiply(num) => 2 * childCosts(node).sum case Divide(num, RoundedDown) => 1 * childCosts(node).sum case Divide(num, RoundedUp) => 1 * childCosts(node).sum // Comparisons - case EqualTo(num) => 1 * childCosts(node).sum + case EqualTo(num) => 0.5f * childCosts(node).sum case GreaterThan(num) => 1 * childCosts(node).sum case GreaterThanOrEqualTo(num) => 1 * childCosts(node).sum case LessThan(num) => 1 * childCosts(node).sum @@ -128,7 +128,7 @@ object CostEstimator { // Numbers - case Scalar(int) => childCosts(node).sum * childCosts(node).sum + case Scalar(int) => scala.math.pow(childCosts(node).sum,2).toFloat case AttributeSum(collection, attr) => 1 * childCosts(node).sum case AttributeValue(obj, attr) => 1 * childCosts(node).sum case Count(collection) => 1 * childCosts(node).sum @@ -140,10 +140,10 @@ object CostEstimator { case ObjectsMatchingConditions(objType, conditions) => 1 * childCosts(node).product case Other(collection) => 1 * childCosts(node).sum case TilesMatchingConditions(conditions) => 1 * childCosts(node).sum -/* + // Labels - case m: MultiLabel => m.labels.map(g).mkString("[", ", ", "]") - case l: Label => s"'${getLabelName(l)}'"*/ + case m: MultiLabel => {println("multilabel. what is it?");1} + case l: Label => 1 case _ => 1 * childCosts(node).sum } @@ -151,18 +151,24 @@ object CostEstimator { //try to calculate a cost for anything and everything - private def genericEstimate(a:Any): Float = a match{ + private def genericEstimate(a:Any): Float ={ + val v = genericEstimateZ(a);println(a.toString + " has estimate " + v);v + } + + private def genericEstimateZ(a:Any): Float = a match{ case n:AstNode => astEst(n)//AST node, complex. case n:Int => n - case c:Seq[Condition] => c.map(child=>genericEstimate(child)).product //conditional series are multiplied - maybe? - case c:Seq[Action] => c.map(child=>genericEstimate(child)).sum //hmm. might want to mult instead of add sometimes - configurable? - // scalastyle:off regex + case c:Seq[Any] => c.map(child=>genericEstimate(child)).product //conditional series are multiplied - maybe? + //case c:Seq[Action] => c.map(child=>genericEstimate(child)).sum //hmm. might want to mult instead of add sometimes - configurable? + // scalastyle:off regex case _=> println("error unknown type in generic estimate."); 0 // scalastyle:on regex } //for each child of the node, run genericEstimate() - private def childCosts(node: AstNode) :Iterator[Float]= - node.productIterator.map[Float](child=>genericEstimate(child)) + private def childCosts(node: AstNode) :Iterator[Float]= { + println("object " + node.toString + "has " + node.productArity + " children."); + node.productIterator.map[Float](child => genericEstimate(child)) + } } diff --git a/src/main/scala/wordbots/WordbotsServer.scala b/src/main/scala/wordbots/WordbotsServer.scala index 648400a..f46d069 100644 --- a/src/main/scala/wordbots/WordbotsServer.scala +++ b/src/main/scala/wordbots/WordbotsServer.scala @@ -23,9 +23,9 @@ object WordbotsServer extends ServerApp { object FormatParamMatcher extends OptionalQueryParamDecoderMatcher[String]("format") object ModeParamMatcher extends OptionalQueryParamDecoderMatcher[String]("mode") - val host = "0.0.0.0" - val defaultPort = 8080 - val port = (Option(System.getenv("PORT")) orElse Option(System.getenv("HTTP_PORT"))).map(_.toInt).getOrElse(defaultPort) + val host : String = "0.0.0.0" + val defaultPort : Int = 8080 + val port : Int = (Option(System.getenv("PORT")) orElse Option(System.getenv("HTTP_PORT"))).map(_.toInt).getOrElse(defaultPort) lazy val lexicon: Map[String, Seq[(String, String)]] = { Lexicon.lexicon.map From 3e3a070d7329a42d6c2108c4e9839dada6fd894c Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 9 Mar 2018 16:04:09 -0800 Subject: [PATCH 3/7] Changed most sums to products, as they're much more common and much more likely to be the right thing. --- src/main/scala/wordbots/CostEstimator.scala | 108 ++++++++++---------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/src/main/scala/wordbots/CostEstimator.scala b/src/main/scala/wordbots/CostEstimator.scala index cfa34dd..9326c08 100644 --- a/src/main/scala/wordbots/CostEstimator.scala +++ b/src/main/scala/wordbots/CostEstimator.scala @@ -5,13 +5,13 @@ package wordbots object CostEstimator { def estimateCost(node: AstNode, mode:Option[String]): String = - (baseCost(mode)(genericEstimate(node))).toString + {println("\n\n---new cost---");(baseCost(mode)(genericEstimate(node))).toString} private def baseCost(mode:Option[String]) : Float=>Float ={ mode match{ - case Some("Object") => (x => 1 + 1.5f * x)//objects are more expensive than events and have a higher base cost - case Some("Event") => (x => 0 + 1 * x) - case _ => (x => 1 * x) + case Some("Object") => (x => x) + case Some("Event") => (x => x) + case _ => (x => x) } } @@ -28,54 +28,54 @@ object CostEstimator { case Until(TurnsPassed(num), action) => 1 * childCosts(node).sum // Actions: Normal - case BecomeACopy(source, target) => 1 * childCosts(node).sum - case CanAttackAgain(target) => 1 * childCosts(node).sum - case CanMoveAgain(target) => 1 * childCosts(node).sum - case CanMoveAndAttackAgain(target) => 1 * childCosts(node).sum + case BecomeACopy(source, target) => 1 * childCosts(node).product + case CanAttackAgain(target) => 1 * childCosts(node).product + case CanMoveAgain(target) => 1 * childCosts(node).product + case CanMoveAndAttackAgain(target) => 1 * childCosts(node).product case DealDamage(target, num) => -1 * childCosts(node).product - case Destroy(target) => -2 * childCosts(node).sum - case Discard(target) => -1 * childCosts(node).sum + case Destroy(target) => -2 * childCosts(node).product + case Discard(target) => -1 * childCosts(node).product case Draw(target,num) => 1 * childCosts(node).product case EndTurn => 1 case GiveAbility(target, ability) => 1 * childCosts(node).product case ModifyAttribute(target, attr, op)=>1 * childCosts(node).product case ModifyEnergy(target, op) => 1 * childCosts(node).product - case MoveObject(target, dest) => 1 * childCosts(node).sum + case MoveObject(target, dest) => 1 * childCosts(node).product case PayEnergy(target, amount) => -0.5f * childCosts(node).product - case RemoveAllAbilities(target) => 1 * childCosts(node).sum - case RestoreAttribute(target, Health, Some(num)) => 1 * childCosts(node).sum - case RestoreAttribute(target, Health, None) => 1 * childCosts(node).sum - case ReturnToHand(target) => 1 * childCosts(node).sum + case RemoveAllAbilities(target) => 1 * childCosts(node).product + case RestoreAttribute(target, Health, Some(num)) => 1 * childCosts(node).product + case RestoreAttribute(target, Health, None) => 1 * childCosts(node).product + case ReturnToHand(target) => 1 * childCosts(node).product case SetAttribute(target, attr, num)=> 1 * childCosts(node).product - case SwapAttributes(target, attr1, attr2) => 1 * childCosts(node).sum//TODO FIX + case SwapAttributes(target, attr1, attr2) => 1 * childCosts(node).product case TakeControl(player, target) => 2 * childCosts(node).product // Actions: Utility - case SaveTarget(target) => 1 * childCosts(node).sum + case SaveTarget(target) => 1 * childCosts(node).product // Activated and triggered abilities - case ActivatedAbility(action) => 1 * childCosts(node).sum - case TriggeredAbility(trigger, Instead(action)) => 1 * childCosts(node).sum - case TriggeredAbility(trigger, action) => 1 * childCosts(node).sum + case ActivatedAbility(action) => 1 * childCosts(node).product + case TriggeredAbility(trigger, Instead(action)) => 1 * childCosts(node).product + case TriggeredAbility(trigger, action) => 1 * childCosts(node).product // Passive abilities - case ApplyEffect(target, effect) => 1 * childCosts(node).sum - case AttributeAdjustment(target, attr, op) => 1 * childCosts(node).sum - case FreezeAttribute(target, attr) => 1 * childCosts(node).sum - case HasAbility(target, ability) => 1 * childCosts(node).sum + case ApplyEffect(target, effect) => 1 * childCosts(node).product + case AttributeAdjustment(target, attr, op) => 1 * childCosts(node).product + case FreezeAttribute(target, attr) => 1 * childCosts(node).product + case HasAbility(target, ability) => 1 * childCosts(node).product // Effects - case CanOnlyAttack(target) => 1 * childCosts(node).sum + case CanOnlyAttack(target) => 1 * childCosts(node).product // Triggers - case AfterAttack(targetObj, objectType) => 1 * childCosts(node).sum - case AfterCardPlay(targetPlayer, cardType) => 1 * childCosts(node).sum - case AfterDamageReceived(targetObj) => 1 * childCosts(node).sum - case AfterDestroyed(targetObj, cause) => 1 * childCosts(node).sum - case AfterMove(targetObj) => 1 * childCosts(node).sum - case AfterPlayed(targetObj) => 1 * childCosts(node).sum - case BeginningOfTurn(targetPlayer) => 1 * childCosts(node).sum - case EndOfTurn(targetPlayer) => 1 * childCosts(node).sum + case AfterAttack(targetObj, objectType) => 1 * childCosts(node).product + case AfterCardPlay(targetPlayer, cardType) => 1 * childCosts(node).product + case AfterDamageReceived(targetObj) => 1 * childCosts(node).product + case AfterDestroyed(targetObj, cause) => 0.8f * childCosts(node).product + case AfterMove(targetObj) => 2 * childCosts(node).product + case AfterPlayed(targetObj) => 1 * childCosts(node).product + case BeginningOfTurn(targetPlayer) => 2 * childCosts(node).product + case EndOfTurn(targetPlayer) => 2 * childCosts(node).product // Target objects case ChooseO(collection) => 2 * childCosts(node).sum @@ -97,38 +97,38 @@ object CostEstimator { case Self => 1 case Opponent => -1 case AllPlayers => 1 - case ControllerOf(targetObject) => 1 * childCosts(node).sum + case ControllerOf(targetObject) => 1 * childCosts(node).product // Conditions - case AdjacentTo(obj) => 1 * childCosts(node).sum + case AdjacentTo(obj) => 1 * childCosts(node).product case AttributeComparison(attr, comp)=> 1 * childCosts(node).product - case ControlledBy(player) => 1 * childCosts(node).sum - case HasProperty(property) => 1 * childCosts(node).sum + case ControlledBy(player) => 1 * childCosts(node).product + case HasProperty(property) => 1 * childCosts(node).product case Unoccupied => 1 case WithinDistanceOf(distance, obj)=> 1 * childCosts(node).sum // Global conditions - case CollectionExists(coll) => 1 * childCosts(node).sum - case TargetHasProperty(target, property) => 1 * childCosts(node).sum + case CollectionExists(coll) => 1 * childCosts(node).product + case TargetHasProperty(target, property) => 1 * childCosts(node).product // Arithmetic operations - case Constant(num) => 1 * childCosts(node).sum - case Plus(num) => 1 * childCosts(node).sum - case Minus(num) => -1 * childCosts(node).sum - case Multiply(num) => 2 * childCosts(node).sum - case Divide(num, RoundedDown) => 1 * childCosts(node).sum - case Divide(num, RoundedUp) => 1 * childCosts(node).sum + case Constant(num) => 1 * childCosts(node).product + case Plus(num) => 1 * childCosts(node).product + case Minus(num) => -1 * childCosts(node).product + case Multiply(num) => 2 * childCosts(node).product + case Divide(num, RoundedDown) => 1 * childCosts(node).product + case Divide(num, RoundedUp) => 1 * childCosts(node).product // Comparisons - case EqualTo(num) => 0.5f * childCosts(node).sum - case GreaterThan(num) => 1 * childCosts(node).sum - case GreaterThanOrEqualTo(num) => 1 * childCosts(node).sum - case LessThan(num) => 1 * childCosts(node).sum - case LessThanOrEqualTo(num) => 1 * childCosts(node).sum + case EqualTo(num) => 0.5f * childCosts(node).product + case GreaterThan(num) => 1 * childCosts(node).product + case GreaterThanOrEqualTo(num) => 1 * childCosts(node).product + case LessThan(num) => 1 * childCosts(node).product + case LessThanOrEqualTo(num) => 1 * childCosts(node).product // Numbers - case Scalar(int) => scala.math.pow(childCosts(node).sum,2).toFloat + case Scalar(int) => scala.math.pow(childCosts(node).product,2).toFloat case AttributeSum(collection, attr) => 1 * childCosts(node).sum case AttributeValue(obj, attr) => 1 * childCosts(node).sum case Count(collection) => 1 * childCosts(node).sum @@ -136,16 +136,16 @@ object CostEstimator { // Collections case AllTiles => 1 - case CardsInHand(player, cardType) => 1 * childCosts(node).sum + case CardsInHand(player, cardType) => 1 * childCosts(node).product case ObjectsMatchingConditions(objType, conditions) => 1 * childCosts(node).product case Other(collection) => 1 * childCosts(node).sum - case TilesMatchingConditions(conditions) => 1 * childCosts(node).sum + case TilesMatchingConditions(conditions) => 1 * childCosts(node).product // Labels case m: MultiLabel => {println("multilabel. what is it?");1} case l: Label => 1 - case _ => 1 * childCosts(node).sum + case _ => 1 * childCosts(node).product } } From c054c9454e24572ba89f480a28fdd95c4c548a14 Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 9 Mar 2018 16:07:50 -0800 Subject: [PATCH 4/7] changed most of the rest of sums to products. --- src/main/scala/wordbots/CostEstimator.scala | 25 ++++++++++----------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/main/scala/wordbots/CostEstimator.scala b/src/main/scala/wordbots/CostEstimator.scala index 9326c08..faf894f 100644 --- a/src/main/scala/wordbots/CostEstimator.scala +++ b/src/main/scala/wordbots/CostEstimator.scala @@ -25,7 +25,7 @@ object CostEstimator { case If(condition, action) => 1 * childCosts(node).product case MultipleActions(actions) => 1 * childCosts(node).sum case MultipleAbilities(abilities) => 1 * childCosts(node).sum - case Until(TurnsPassed(num), action) => 1 * childCosts(node).sum + case Until(TurnsPassed(num), action) => 1 * childCosts(node).product // Actions: Normal case BecomeACopy(source, target) => 1 * childCosts(node).product @@ -78,9 +78,9 @@ object CostEstimator { case EndOfTurn(targetPlayer) => 2 * childCosts(node).product // Target objects - case ChooseO(collection) => 2 * childCosts(node).sum - case AllO(collection) => 3 * childCosts(node).sum - case RandomO(num, collection) => 1 * childCosts(node).sum + case ChooseO(collection) => 2 * childCosts(node).product + case AllO(collection) => 3 * childCosts(node).product + case RandomO(num, collection) => 1 * childCosts(node).product case ThisObject => 1 case ItO => 1 case ItP => 1 @@ -89,9 +89,9 @@ object CostEstimator { case SavedTargetObject => 1 // Target cards - case ChooseC(collection) => 1 * childCosts(node).sum - case AllC(collection) => 1 * childCosts(node).sum - case RandomC(num, collection) => 0.5f * childCosts(node).sum + case ChooseC(collection) => 1 * childCosts(node).product + case AllC(collection) => 1 * childCosts(node).product + case RandomC(num, collection) => 0.5f * childCosts(node).product // Target players case Self => 1 @@ -105,7 +105,7 @@ object CostEstimator { case ControlledBy(player) => 1 * childCosts(node).product case HasProperty(property) => 1 * childCosts(node).product case Unoccupied => 1 - case WithinDistanceOf(distance, obj)=> 1 * childCosts(node).sum + case WithinDistanceOf(distance, obj)=> 1 * childCosts(node).product // Global conditions case CollectionExists(coll) => 1 * childCosts(node).product @@ -131,14 +131,14 @@ object CostEstimator { case Scalar(int) => scala.math.pow(childCosts(node).product,2).toFloat case AttributeSum(collection, attr) => 1 * childCosts(node).sum case AttributeValue(obj, attr) => 1 * childCosts(node).sum - case Count(collection) => 1 * childCosts(node).sum - case EnergyAmount(player) => 1 * childCosts(node).sum + case Count(collection) => 1 * childCosts(node).product + case EnergyAmount(player) => 1 * childCosts(node).product // Collections case AllTiles => 1 case CardsInHand(player, cardType) => 1 * childCosts(node).product case ObjectsMatchingConditions(objType, conditions) => 1 * childCosts(node).product - case Other(collection) => 1 * childCosts(node).sum + case Other(collection) => 1 * childCosts(node).product case TilesMatchingConditions(conditions) => 1 * childCosts(node).product // Labels @@ -158,8 +158,7 @@ object CostEstimator { private def genericEstimateZ(a:Any): Float = a match{ case n:AstNode => astEst(n)//AST node, complex. case n:Int => n - case c:Seq[Any] => c.map(child=>genericEstimate(child)).product //conditional series are multiplied - maybe? - //case c:Seq[Action] => c.map(child=>genericEstimate(child)).sum //hmm. might want to mult instead of add sometimes - configurable? + case c:Seq[Any] => c.map(child=>genericEstimate(child)).product //multiply sequences? // scalastyle:off regex case _=> println("error unknown type in generic estimate."); 0 // scalastyle:on regex From 4820a007a7e3502bf387d98767aa3531c475bccb Mon Sep 17 00:00:00 2001 From: Michael Ebert Date: Fri, 9 Mar 2018 16:31:37 -0800 Subject: [PATCH 5/7] fixed multiple actions multiplying costs instead of adding. --- src/main/scala/wordbots/CostEstimator.scala | 27 +++++++++++---------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/main/scala/wordbots/CostEstimator.scala b/src/main/scala/wordbots/CostEstimator.scala index faf894f..3823196 100644 --- a/src/main/scala/wordbots/CostEstimator.scala +++ b/src/main/scala/wordbots/CostEstimator.scala @@ -23,8 +23,8 @@ object CostEstimator { node match{ // Meta case If(condition, action) => 1 * childCosts(node).product - case MultipleActions(actions) => 1 * childCosts(node).sum - case MultipleAbilities(abilities) => 1 * childCosts(node).sum + case MultipleActions(actions) => 1 * actions.map(child=>genericEstimate(child)).sum + case MultipleAbilities(abilities) => 1 * abilities.map(child=>genericEstimate(child)).sum case Until(TurnsPassed(num), action) => 1 * childCosts(node).product // Actions: Normal @@ -100,16 +100,16 @@ object CostEstimator { case ControllerOf(targetObject) => 1 * childCosts(node).product // Conditions - case AdjacentTo(obj) => 1 * childCosts(node).product - case AttributeComparison(attr, comp)=> 1 * childCosts(node).product - case ControlledBy(player) => 1 * childCosts(node).product - case HasProperty(property) => 1 * childCosts(node).product + case AdjacentTo(obj) => 0.7f * childCosts(node).product//todo: calibrate this with withinDistanceOf + case AttributeComparison(attr, comp)=> 0.8f * childCosts(node).product + case ControlledBy(player) => 0.9f * childCosts(node).product + case HasProperty(property) => 0.8f * childCosts(node).product case Unoccupied => 1 - case WithinDistanceOf(distance, obj)=> 1 * childCosts(node).product + case WithinDistanceOf(distance, obj)=> 0.8f * childCosts(node).product//todo: scale properly // Global conditions case CollectionExists(coll) => 1 * childCosts(node).product - case TargetHasProperty(target, property) => 1 * childCosts(node).product + case TargetHasProperty(target, property) => 0.8f * childCosts(node).product // Arithmetic operations case Constant(num) => 1 * childCosts(node).product @@ -121,14 +121,14 @@ object CostEstimator { // Comparisons case EqualTo(num) => 0.5f * childCosts(node).product - case GreaterThan(num) => 1 * childCosts(node).product - case GreaterThanOrEqualTo(num) => 1 * childCosts(node).product - case LessThan(num) => 1 * childCosts(node).product - case LessThanOrEqualTo(num) => 1 * childCosts(node).product + case GreaterThan(num) => 0.9f * childCosts(node).product + case GreaterThanOrEqualTo(num) => 0.9f * childCosts(node).product + case LessThan(num) => 0.9f * childCosts(node).product + case LessThanOrEqualTo(num) => 0.9f * childCosts(node).product // Numbers - case Scalar(int) => scala.math.pow(childCosts(node).product,2).toFloat + case Scalar(int) => scala.math.pow(childCosts(node).product,1.5f).toFloat //2.0 is too steep. case AttributeSum(collection, attr) => 1 * childCosts(node).sum case AttributeValue(obj, attr) => 1 * childCosts(node).sum case Count(collection) => 1 * childCosts(node).product @@ -166,6 +166,7 @@ object CostEstimator { //for each child of the node, run genericEstimate() private def childCosts(node: AstNode) :Iterator[Float]= { + //if(only one child && that child is a seq) return seq? println("object " + node.toString + "has " + node.productArity + " children."); node.productIterator.map[Float](child => genericEstimate(child)) } From a60df9931105fa847c05acdaedad2bdddb54867c Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 18 Mar 2018 12:47:37 -0700 Subject: [PATCH 6/7] changed default port because its easier than figuring out how to temporarily set environment variable. --- build.sbt | 1 - src/main/scala/wordbots/CostEstimator.scala | 12 ++++++------ src/main/scala/wordbots/WordbotsServer.scala | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/build.sbt b/build.sbt index 0a9563c..0f24768 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,5 @@ name := "wordbots-parser" version := "0.0-SNAPSHOT" - val http4sVersion = "0.15.3" val circeVersion = "0.6.1" diff --git a/src/main/scala/wordbots/CostEstimator.scala b/src/main/scala/wordbots/CostEstimator.scala index 3823196..97c3c8c 100644 --- a/src/main/scala/wordbots/CostEstimator.scala +++ b/src/main/scala/wordbots/CostEstimator.scala @@ -78,9 +78,9 @@ object CostEstimator { case EndOfTurn(targetPlayer) => 2 * childCosts(node).product // Target objects - case ChooseO(collection) => 2 * childCosts(node).product - case AllO(collection) => 3 * childCosts(node).product - case RandomO(num, collection) => 1 * childCosts(node).product + case ChooseO(collection) => 1 * childCosts(node).product + case AllO(collection) => 2 * childCosts(node).product + case RandomO(num, collection) => 0.5f * childCosts(node).product case ThisObject => 1 case ItO => 1 case ItP => 1 @@ -90,13 +90,13 @@ object CostEstimator { // Target cards case ChooseC(collection) => 1 * childCosts(node).product - case AllC(collection) => 1 * childCosts(node).product + case AllC(collection) => 2 * childCosts(node).product case RandomC(num, collection) => 0.5f * childCosts(node).product // Target players case Self => 1 case Opponent => -1 - case AllPlayers => 1 + case AllPlayers => 0.8f case ControllerOf(targetObject) => 1 * childCosts(node).product // Conditions @@ -167,7 +167,7 @@ object CostEstimator { //for each child of the node, run genericEstimate() private def childCosts(node: AstNode) :Iterator[Float]= { //if(only one child && that child is a seq) return seq? - println("object " + node.toString + "has " + node.productArity + " children."); + println("object " + node.toString + "has " + node.productArity + " children.") node.productIterator.map[Float](child => genericEstimate(child)) } } diff --git a/src/main/scala/wordbots/WordbotsServer.scala b/src/main/scala/wordbots/WordbotsServer.scala index f46d069..6ca9d73 100644 --- a/src/main/scala/wordbots/WordbotsServer.scala +++ b/src/main/scala/wordbots/WordbotsServer.scala @@ -24,7 +24,7 @@ object WordbotsServer extends ServerApp { object ModeParamMatcher extends OptionalQueryParamDecoderMatcher[String]("mode") val host : String = "0.0.0.0" - val defaultPort : Int = 8080 + val defaultPort : Int = 34197//i don't know how to set environment variables temporarily val port : Int = (Option(System.getenv("PORT")) orElse Option(System.getenv("HTTP_PORT"))).map(_.toInt).getOrElse(defaultPort) lazy val lexicon: Map[String, Seq[(String, String)]] = { From 8527deb1be2a6954fee52b73e1289fa81dad3ac0 Mon Sep 17 00:00:00 2001 From: Michael Ebert Date: Sat, 18 Jan 2020 19:54:55 -0800 Subject: [PATCH 7/7] Updating test-costs --- build.sbt | 6 ++++++ project/Build.scala | 5 ----- project/build.properties | 2 +- src/main/scala/wordbots/CodeGenerator.scala | 2 +- src/main/scala/wordbots/CostEstimator.scala | 8 +++++--- src/main/scala/wordbots/Server.scala | 4 ++-- 6 files changed, 15 insertions(+), 12 deletions(-) diff --git a/build.sbt b/build.sbt index 142f870..cf9cdef 100644 --- a/build.sbt +++ b/build.sbt @@ -20,6 +20,12 @@ libraryDependencies ++= Seq( "org.scalatest" %% "scalatest" % "2.2.4" % "test" ) +val MONTAGUE_COMMIT_SHA = "b451836235cee5d900ec5f578a54ac702587858b" +lazy val montague = RootProject(uri(s"git://github.com/Workday/upshot-montague.git#$MONTAGUE_COMMIT_SHA")) +lazy val root = Project("root", file(".")) dependsOn montague + + + enablePlugins(JavaAppPackaging) enablePlugins(BuildInfoPlugin) diff --git a/project/Build.scala b/project/Build.scala index 96f8e37..bfc822c 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1,9 +1,4 @@ import sbt.{Build => SbtBuild, _} object Build extends SbtBuild { - val MONTAGUE_COMMIT_SHA = "b451836235cee5d900ec5f578a54ac702587858b" - - lazy val root = Project("root", file(".")) dependsOn montague - - lazy val montague = RootProject(uri(s"git://github.com/Workday/upshot-montague.git#$MONTAGUE_COMMIT_SHA")) } \ No newline at end of file diff --git a/project/build.properties b/project/build.properties index 817bc38..8e682c5 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.9 +sbt.version=0.13.18 diff --git a/src/main/scala/wordbots/CodeGenerator.scala b/src/main/scala/wordbots/CodeGenerator.scala index 9a07345..fc189fa 100644 --- a/src/main/scala/wordbots/CodeGenerator.scala +++ b/src/main/scala/wordbots/CodeGenerator.scala @@ -83,7 +83,7 @@ object CodeGenerator { case Until(TurnsPassed(num), action) => s"(function () { save('duration', $num); ${g(action)}(); save('duration', null); })" // Actions: Normal - case BecomeACopy(source, target) => s"(function () { actions['becomeACopy'](${g(source)}, ${g(target)}); })" + case Become(source, target) => s"(function () { actions['become'](${g(source)}, ${g(target)}); })" case CanAttackAgain(target) => s"(function () { actions['canAttackAgain'](${g(target)}); })" case CanMoveAgain(target) => s"(function () { actions['canMoveAgain'](${g(target)}); })" case CanMoveAndAttackAgain(target) => s"(function () { actions['canMoveAndAttackAgain'](${g(target)}); })" diff --git a/src/main/scala/wordbots/CostEstimator.scala b/src/main/scala/wordbots/CostEstimator.scala index 97c3c8c..b667ff7 100644 --- a/src/main/scala/wordbots/CostEstimator.scala +++ b/src/main/scala/wordbots/CostEstimator.scala @@ -1,4 +1,7 @@ package wordbots + +import wordbots.Semantics._ + /* * scoring: good is positive, bad is negative. applying to enemy multiplies by -1. * */ @@ -28,7 +31,6 @@ object CostEstimator { case Until(TurnsPassed(num), action) => 1 * childCosts(node).product // Actions: Normal - case BecomeACopy(source, target) => 1 * childCosts(node).product case CanAttackAgain(target) => 1 * childCosts(node).product case CanMoveAgain(target) => 1 * childCosts(node).product case CanMoveAndAttackAgain(target) => 1 * childCosts(node).product @@ -45,7 +47,7 @@ object CostEstimator { case RemoveAllAbilities(target) => 1 * childCosts(node).product case RestoreAttribute(target, Health, Some(num)) => 1 * childCosts(node).product case RestoreAttribute(target, Health, None) => 1 * childCosts(node).product - case ReturnToHand(target) => 1 * childCosts(node).product + case ReturnToHand(target, player) => 1 * childCosts(node).product case SetAttribute(target, attr, num)=> 1 * childCosts(node).product case SwapAttributes(target, attr1, attr2) => 1 * childCosts(node).product case TakeControl(player, target) => 2 * childCosts(node).product @@ -136,7 +138,7 @@ object CostEstimator { // Collections case AllTiles => 1 - case CardsInHand(player, cardType) => 1 * childCosts(node).product + case CardsInHand(player, cardType, conditions) => 1 * childCosts(node).product case ObjectsMatchingConditions(objType, conditions) => 1 * childCosts(node).product case Other(collection) => 1 * childCosts(node).product case TilesMatchingConditions(conditions) => 1 * childCosts(node).product diff --git a/src/main/scala/wordbots/Server.scala b/src/main/scala/wordbots/Server.scala index fbb6ee2..ed68910 100644 --- a/src/main/scala/wordbots/Server.scala +++ b/src/main/scala/wordbots/Server.scala @@ -27,7 +27,7 @@ object Server extends ServerApp { sealed trait Response case class ErrorResponse(error: String) extends Response - case class SuccessfulParseResponse(js: String, tokens: Seq[String], version: String = Parser.VERSION) extends Response + case class SuccessfulParseResponse(js: String, tokens: Seq[String], estCost: String, version: String = Parser.VERSION) extends Response case class FailedParseResponse(error: String, suggestions: Seq[String], unrecognizedTokens: Seq[String]) extends Response object FailedParseResponse { def apply(error: ParserError = ParserError("Parse failed"), unrecognizedTokens: Seq[String] = Seq()): FailedParseResponse = { @@ -76,7 +76,7 @@ object Server extends ServerApp { format match { case Some("js") => CodeGenerator.generateJS(ast) match { - case Success(js: String) => successResponse(js, parsedTokens, CostEstimator.estimateCost(ast, mode))) + case Success(js: String) => successResponse(js, parsedTokens, CostEstimator.estimateCost(ast, mode)) case Failure(ex: Throwable) => errorResponse(ParserError(s"Invalid JavaScript produced: ${ex.getMessage}. Contact the developers.")) } case Some("svg") => Ok(parse.toSvg, headers(Some("image/svg+xml")))