public override float score() { // determine how fightable nearby threats are var threat = NearbyThreat.greatestThreat(context); if (threat == null) { return(0); // no threat means don't score } // TODO: use better functions to map ratios to score // 1. compare core sizes (ratio) -> transform [-40, 40] var coreSizeRatio = betterRatio(threat.mind.state.me.core.designMax, context.state.me.core.designMax); var coreSizeScore = scoreRatio(coreSizeRatio, 20); // 2. compare maneuverability (ratio) -> transform [-20, 20] // if we're more maneuverable, it might not be worth fighting var maneuverabilityRatio = betterRatio(context.state.me.body.turnPower, threat.mind.state.me.body.turnPower); var maneuScore = scoreRatio(maneuverabilityRatio, 20); // 3. compare speed (ratio) -> transform [-40, 40] // if we're faster, we want to fight less var speedRatio = betterRatio(context.state.me.body.thrustPower, threat.mind.state.me.body.thrustPower); var speedScore = scoreRatio(speedRatio, 30); // 3. compare energy (ratio) -> transform [-40, 40] // if we're faster, we want to fight less var energyRatio = betterRatio(threat.mind.state.me.core.energy, context.state.me.core.energy); var energyScore = scoreRatio(energyRatio, 30); // 4. compare armed state var threatWeaponMaxscore = 40; var threatWeapon = threat.mind.entity.GetComponent <Shooter>(); var myWeapon = context.state.me.GetComponent <Shooter>(); // if they're armed, set negative score var armoryScore = threatWeapon == null ? 0 : -threatWeaponMaxscore; // TODO: compare weapons if (myWeapon != null) { // if i'm armed too // then negate the score penalty armoryScore += threatWeaponMaxscore; } // clamp score to [-100, 100] -> transform [0, 1] var score = coreSizeScore + maneuScore + speedScore + energyScore + armoryScore; context.state.setBoard("judged threat", new DuckMindState.BoardItem($"E:{energyScore}, C:{coreSizeScore}, M:{maneuScore}, S:{speedScore}", "interaction", Color.Orange, Time.TotalTime + 1f)); var clampedScore = GMathf.clamp(score, -100, 100); // clamp score var fightable = GMathf.map01(clampedScore, -100, 100); // map to [-1,1] return(fightable); }
public override void run(params DuckMind[] participants) { var me = participants[0]; var them = participants[1]; // when a bird is nearby // if opinion is negative, then there is cause for fear // if opinion is positive, then i feel secure // if my anxiety is high, i will react more strongly to both // if you get WAY too close, you will be labeled a threat var maxOpinionDelta = 4; // range [-4, 4] var opinionDelta = 0; var currentOpinion = me.state.getOpinion(them.state.me); if (currentOpinion > Constants.DuckMind.OPINION_ALLY) { // we're friends, we should have a positive opinion // this is based on both anxiety and sociability. // TODO: positive opinion from being near friends // this should fall off to a negligible account above a certain threshold } else { // TODO: take anxiety better into account // calculate opinion-affecting factors var maxWariness = 10f; // [-4, 0]: long-distance wariness var longDistanceWariness = (int)TraitCalc.transform(-me.soul.traits.wary, -4, 2, -4, 0); // [-4, -1]: close distance wariness var closeWariness = 0; if (dist < closeDistance) { // extreme caution closeWariness = (int)TraitCalc.transform(-me.soul.traits.wary, -4, 0, -4, -1); } me.state.setBoard("nearby fear", new DuckMindState.BoardItem($"L: {longDistanceWariness}, C: {closeWariness}", "interaction", Color.Orange, Time.TotalTime + 1f)); var warinessScore = longDistanceWariness + closeWariness; opinionDelta += warinessScore; // being in the presence of a threat is scary me.soul.emotions.spikeFear(Math.Abs(warinessScore / maxWariness)); } // clamp the opinion delta to the required range opinionDelta = GMathf.clamp(opinionDelta, -maxOpinionDelta, maxOpinionDelta); me.state.addOpinion(them.state.me, GMathf.roundToInt(opinionDelta)); }
public override float score() { // hunger score is based on the necessity of more energy. // let E be energy percentage (energy / max energy), clamp01 // y = (1 - E)^2 var energyCore = context.entity.GetComponent <EnergyCore>(); var satiation = 2f; // 200% food var ratioSatiation = energyCore.energy / (energyCore.designMax * satiation); var invEnergyPerc = 1 - Mathf.Clamp01(ratioSatiation); return(GMathf.pow(invEnergyPerc, 1.4f)); }
public override float score() { var thresh = opinionThreshold(context); var candidate = bestCandidate(context, thresh); if (candidate == null) { return(0); } // scale from 0-100 return(GMathf.map01clamp01(context.state.getOpinion(candidate.mind.state.me), thresh, Constants.DuckMind.OPINION_ALLY)); }
public override void run(params DuckMind[] participants) { if (participants.Length != 2) { throw new ArgumentException("only two participants", nameof(participants)); } var me = participants[0]; // this should be "me" var myTraits = new Traits(me.soul); var giver = participants[1]; // the one who gave me stuff // food value [0, 40] var maxFoodValue = 40; var foodValue = (int)(sig.energy / 400f) * maxFoodValue; // calculate opinion delta var opinionDelta = 0; var currentOpinion = me.state.getOpinion(giver.state.me); // calculate receptiveness to food // receptive (innate) [0, 1] var innateFoodReceiptiveness = TraitCalc.transform(myTraits.receptiveness, -0.4f, 1f, 0f, 1f); // receptive (happy) [0, 0.5] var happyFoodReceptiveness = TraitCalc.transform(me.soul.emotions.happy, -1.5f, 1f, 0f, 0.5f); // receptive [0, 2] var foodReceptiveness = GMathf.clamp( innateFoodReceiptiveness + happyFoodReceptiveness, 0f, 2f ); // significantly diminishing rewards, effectively capping around ~300 var foodOpinionWeight = LCurves.diminishingReturns(currentOpinion / 10f, 1f, 0.1f); opinionDelta += (int)(foodReceptiveness * foodValue * foodOpinionWeight); // food makes me happy! me.soul.emotions.spikeHappy(GMathf.clamp(foodReceptiveness, 0, 0.8f)); // add opinion to the one that fed me me.state.addOpinion(giver.state.me, opinionDelta); }