protected override float OnEvaluate(AIEnvironment env) { // Note: assumes the center is at x=0. var x = Abs(env.players[env.AiPlayerId(aiId)].position.x); var edge = env.stage.rightEdge; var red = edge - redZoneSize; var yellow = edge - yellowZoneSize; if (x < yellow) { // [0, .1] return(.1f * x / yellow); } else if (x < red) { // [.1, .7] var pct = (x - yellow) / (yellowZoneSize - redZoneSize); return(.1f + .6f * pct * pct); } else { // [.7, 1] return(.7f + .3f * (x - red) / redZoneSize); } }
public void Deactivate(AIEnvironment env) { if (activeControl != Control.None) { inputManager.Release(activeControl); } OnDeactivate(env); }
protected override int OnExecute(AIEnvironment env) { if (env.players[env.AiPlayerId(aiId)].position.x < 0) { return(Control.Right); } else { return(Control.Left); } }
protected override float OnEvaluate(AIEnvironment env) { if (UpdateTimer(env)) { ChangeDirection(env.threadRandom); } if (direction == Direction.Invalid) { return(0); } return(.1f); }
/// For each item in `agMask`, add the difference between the new /// and old utility values. If the net utility is better, we want /// to replace all matches with the new value. private void TakeBestStrategy( int strategyIndex, Decision[,] existingDecisions, AIEnvironment env ) { var strategy = strategies[strategyIndex]; var utility = strategy.Evaluate(env); if (utility < .001f) { return; } uint agMask = strategy.actionGroupMask & ActionGroup.All; var decision = new Decision(utility, agMask, strategyIndex); // If I end up needing to support multiple groups per strategy: // In a loop for each bit of the new decision's mask, look // at the current best decision for that spot. For each spot // the current decision occupies but the new one doesn't, find // the net utility loss by replacing those with the second place // values and the net utility gain from placing the new decision // in the slots it needs. If the gain is more than the loss, // replace it. Don't erase the second place values when moving // them into first - they may need to replace a future value. // If the current decision only has one bit set and its utility // is greater than the second place's utility, even if it is // also greater than first place's utility, replace second. int agIndex = LeastBitIndex(agMask); int aiIndex = strategy.aiId; // It's ok if this is being written to - if it is, that means // the result from this thread will not be used and we will get // reset by `CtlLateUpdate`. var oldStrategyIndex = prevStrategyIds[aiIndex, agIndex]; if ( oldStrategyIndex != -1 && !strategies[oldStrategyIndex] .CanTransitionTo(strategy.GetType()) ) { return; } var oldUtility = existingDecisions[aiIndex, agIndex].utility; if (utility > oldUtility) { existingDecisions[aiIndex, agIndex] = decision; } }
/// Returns true if timer expired. private bool UpdateTimer(AIEnvironment env) { var time = env.time; if (time > nextChangeTime) { var dt = (float)(env.threadRandom.NextDouble() * 24 + 1); // Use Sqrt to weight this toward the upper range. nextChangeTime = time + Sqrt(dt); return(true); } return(false); }
public AIEnvironment(AIEnvironment orig) { this.time = orig.time; this.aiIdMap = orig.aiIdMap; this.gameSp = orig.gameSp; this.stageSp = orig.stageSp; this.playersSp = orig.playersSp; this.players = new PlayerSnapshot[this.playersSp.Length]; for (var i = 0; i < this.players.Length; i++) { this.players[i].attacks = new AttackState[Player.attackCount]; } }
public void Execute(AIEnvironment env) { var c = OnExecute(env); if (c < Control.None || c >= Control.ArrayLength) { c = Control.None; } if (c != activeControl) { inputManager.Release(activeControl); } activeControl = c; inputManager.Press(c); }
protected override void OnActivate(AIEnvironment env) { var vx = env.players[1].velocity.x; if (Abs(vx) < .25f) { ChangeDirection(env.threadRandom); } else if (vx < 0) { direction = Direction.Left; } else { direction = Direction.Right; } }
public void Ready( AIEnvironment env, IEnumerable <Strategy> strategies ) { this.env = env; this.env2 = new AIEnvironment(env); this.strategies = Shuffle(strategies.ToList()); this.nextBlockStart = this.strategies.Count; var aiCount = env.AiCount(); int threads = threadResults.Length; for (int i = 0; i < threads; i++) { threadResults[i] = new Decision[aiCount, ActionGroup.Count]; } this.strategyIdsRead = new int[aiCount, ActionGroup.Count]; this.strategyIdsWrite = new int[aiCount, ActionGroup.Count]; this.prevStrategyIds = new int[aiCount, ActionGroup.Count]; this.startEvent = new EventWaitHandle(false, EventResetMode.ManualReset); this.ctlEvent = new EventWaitHandle(false, EventResetMode.ManualReset); Thread.MemoryBarrier(); evalThreads = Enumerable.Range(0, threads) .Select(id => { var t = new Thread(EvalThreadMain); t.IsBackground = true; t.Start(id); return(t); }) .ToArray(); controlThread = new Thread(ControlThreadMain); controlThread.IsBackground = true; controlThread.Priority = ThreadPriority.BelowNormal; controlThread.Start(); }
/// Returns true if there is a strategy to execute, false if not. private bool ActivateStrategy( int newSid, ref int oldSid, AIEnvironment env ) { bool hasNewStrategy = newSid >= 0; if (newSid == oldSid) { return(hasNewStrategy); } if (oldSid > -1) { this.strategies[oldSid].Deactivate(env); } if (newSid > -1) { this.strategies[newSid].Activate(env); } oldSid = newSid; return(hasNewStrategy); }
// -- public float Evaluate(AIEnvironment env) { return(UnityEngine.Mathf.Clamp01(OnEvaluate(env))); }
/// The basic idea is this: the threads all block on `startEvent`. /// When the event is set, they start processing slices of the array, /// guaranteed not to overlap via interlocked operations, and evenly /// distributed over all computer players by shuffling the array /// at the start. private void EvalThreadMain(object threadIdObj) { startEvent.WaitOne(); int threadId = (int)threadIdObj; Interlocked.Increment(ref activeThreads); AIEnvironment env = this.env; // These don't change (for now). var strategyCount = this.strategies.Count; var threadCount = this.evalThreads.Length; var blockSize = this.blockSize; try { while (true) { var blockEnd = Interlocked.Add(ref nextBlockStart, blockSize); var blockStart = blockEnd - blockSize; if (blockStart >= strategyCount) { // Flush all the results from this thread to memory. Thread.MemoryBarrier(); startEvent.Reset(); // For the last thread, we need to merge the individual // results into the main strategy IDs array before // setting the active thread count to 0. int currentCount; bool merged = false; do { currentCount = Volatile.Read(ref activeThreads); if (currentCount == 1 && !merged) { // TODO: Someone may do this twice. // Get rid of this by sharding AIs over threads. MergeDecisions(); merged = true; } } while ( Interlocked.CompareExchange( ref activeThreads, currentCount - 1, currentCount ) != currentCount ); try { startEvent.WaitOne(); if (!Volatile.Read(ref running)) { "EndThread({0}/{1})".LogF(currentCount, threadCount); return; } } catch (Exception e) { "{0}".ErrorF(e); // If we let an exception go between the // decrement and increment, the counter // will stop being accurate. This way, it will // always be set to zero whether a thread // terminates or they all are just paused. // TODO: If startEvent fails, that messes up the // synchronization entirely. How to handle this? return; } ResetDecisionArray(threadResults[threadId]); Interlocked.Increment(ref activeThreads); env = Volatile.Read(ref this.env); continue; } if (blockEnd > strategyCount) { blockEnd = strategyCount; } // FIXME! Move thread specific environment out - // AIEnvironment is immutable only during processing! SetupEnvForThread(env, evalThreadRandom[threadId]); for (int i = blockStart; i < blockEnd; i++) { TakeBestStrategy(i, threadResults[threadId], env); } } } catch (Exception e) { "{0}".ErrorF(e); Interlocked.Decrement(ref activeThreads); return; } }
protected override int OnExecute(AIEnvironment env) { return(direction.AsControl()); }
/// Must be thread safe. protected abstract float OnEvaluate(AIEnvironment env);
/// Returns the desired control. protected abstract int OnExecute(AIEnvironment env);
protected virtual void OnDeactivate(AIEnvironment env) { }
public void Activate(AIEnvironment env) { this.activeControl = Control.None; OnActivate(env); }
/// Set non-thread-safe variables to thread-local copies before passing /// the environment to a strategy. private void SetupEnvForThread(AIEnvironment env, Random threadRandom) { env.threadRandom = threadRandom; }