Exemple #1
0
        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);
            }
        }
Exemple #2
0
 public void Deactivate(AIEnvironment env)
 {
     if (activeControl != Control.None)
     {
         inputManager.Release(activeControl);
     }
     OnDeactivate(env);
 }
Exemple #3
0
 protected override int OnExecute(AIEnvironment env)
 {
     if (env.players[env.AiPlayerId(aiId)].position.x < 0)
     {
         return(Control.Right);
     }
     else
     {
         return(Control.Left);
     }
 }
Exemple #4
0
 protected override float OnEvaluate(AIEnvironment env)
 {
     if (UpdateTimer(env))
     {
         ChangeDirection(env.threadRandom);
     }
     if (direction == Direction.Invalid)
     {
         return(0);
     }
     return(.1f);
 }
Exemple #5
0
        /// 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;
            }
        }
Exemple #6
0
        /// 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);
        }
Exemple #7
0
 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];
     }
 }
Exemple #8
0
        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);
        }
Exemple #9
0
        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;
            }
        }
Exemple #10
0
        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();
        }
Exemple #11
0
        /// 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);
        }
Exemple #12
0
        // --

        public float Evaluate(AIEnvironment env)
        {
            return(UnityEngine.Mathf.Clamp01(OnEvaluate(env)));
        }
Exemple #13
0
        /// 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;
            }
        }
Exemple #14
0
 protected override int OnExecute(AIEnvironment env)
 {
     return(direction.AsControl());
 }
Exemple #15
0
 /// Must be thread safe.
 protected abstract float OnEvaluate(AIEnvironment env);
Exemple #16
0
 /// Returns the desired control.
 protected abstract int OnExecute(AIEnvironment env);
Exemple #17
0
 protected virtual void OnDeactivate(AIEnvironment env)
 {
 }
Exemple #18
0
 public void Activate(AIEnvironment env)
 {
     this.activeControl = Control.None;
     OnActivate(env);
 }
Exemple #19
0
 /// 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;
 }