The “Team Mate Has Ball” State

Influence map for when a team mate (orange bot below) owns the ball. Note the cone-shaped darker gradients going out from the bumper and enemy bot – these are penalties for positions where there’s no clear pass line.

After implementing ball ownership, the logic for when a team mate of the AI owns the ball was pretty trivial to implement: I mainly reused the same influence map sampling logic and position evaluation gradients of the ball ownership state, with a few modifications:

  • Clear pass lines are even more important than when it’s you who owns the ball
  • Spreading out is more important, particularly when the AI is in the middle of the field
  • There’s a penalty for positions that are too far ahead or behind the ball owner across the field’s horizontal axis.

Other than that, the main difference to the ball ownership state is that if the AI doesn’t own the ball, it can sprint, which makes it move faster, but depletes its stamina.

I implemented sprinting by evaluating the target position’s score, comparing it to the current position’s score on the influence map, and checking the current charge of the stamina bar: if the stamina bar is full, small improvements in a target position’s score will cause the AI to sprint. If there’s little stamina left, the difference needs to be much higher to make the AI sprint.

The Defensive “Opponent Has Ball” State

There are quite a few decisions that the AI needs to make if an opponent has the ball:

  • It can move to tackle the opponent
  • It can try to block their pass lines to other opponents
  • It can try to intercept potential goal shots, either by positioning itself along the enemy’s goal shot line, or by positioning itself in front of the goal.

If you play a match against an all AI team, it’s also important that the bots don’t all do the same thing when you have the ball. It’s neither a very good strategy for the AI nor fun for beginner players if all enemy bots jump at you the moment you own the ball and try to tackle you. If the game were pure PvE, this would be a good fit for some kind of central planner AI that keeps track of individual bots on the team and assigns them roles, based on the situation. In our case, there’s usually just a single bot on an otherwise human team, so that option was out.

Instead we try to avoid having the AI curl up by making the position evaluator penalize positions around team members. We also heavily penalize positions around the ball owner, if a team member is already quite close to them and we are further away – this heavily discourages more than one bot moving to tackle the owner.

Influence map for when the enemy has the ball. In this situation we’re the closest to the ball owner, so positions around them are highly rewarded. If a team member were closer, the circle around the ball owner would be a dark patch instead.

Rather than sampling points randomly, the position evaluator of the defensive state specifically samples positions along enemy pass lines, goal shot lines (if the enemy is in range), around the AI’s own goal, and around the ball owner. It throws in a few random points in the bot’s vicinity as well, for good measure. For score evaluation, there are – among others – gradients for proximity to a pass line, goal shot line, proximity to the ball owner, and a rectangular region around the goal which denotes good positions for playing goalie.

Since the sampled points can be very distant from each other (e.g. around the AI’s own goal, even if the bot is on the other side of the playing field), we penalize positions that are very far away from the bot. We also penalize positions that are very far away from the ball owner, to encourage the AI to not stick around close to their goal, if the action is way across the field.

Just like in the “Team Mate Has Ball” state, we make the AI sprint, depending on the relative scores of their current position and their target position, as well as on its current stamina.

Once the AI is in tackle reach of the ball owner, it also needs to figure out when to tackle. The parameters for that are mainly used to tweak AI difficulty and predictability – we have a random tackle delay with a minimum and maximum number of ticks, which kicks in once the AI is in tackle range. After that, depending on difficulty, the AI “holds” the tackle button for a random amount of time to give players a bit of time to prepare, as they can react to the tackle action’s preview.

In earlier versions of Steel Circus, the ball owner’s dodge maneuver granted them immunity to an incoming tackle, so it wasn’t a big problem if the AI tackled with perfect precision – tackling/dodging was more like a poker game, and the outcome was defined by who did what first. Now that dodging is just a quick reposition, we needed some aim delay on AI tackles to make the bots not tackle too well, so the AI now keeps a history of the ball owner’s positions and picks one in the recent past as its target direction.

The “Nobody Has Ball” State

If nobody has the ball, it has either just been shot (potentially by the bot itself), it’s lying on the ground, or it’s in flight and your team should intercept it if it can. This is the only really big tree of “if”s in Steel Circus’ AI, and the only major state that isn’t (mainly) governed by influence maps for positioning: 

We want the AI to intercept the ball if it can. We also want it to not bother if there’s absolutely no way it can get there before an enemy. If the AI is confident that a team mate will intercept the ball, it also shouldn’t bother, as, again, the bots are mainly there to supplement humans and shouldn’t be the ones who have all the fun.

There’s also the matter of sprinting: if the AI is in the best position to intercept the ball and can get there comfortably before any opponents without sprinting, it shouldn’t waste stamina. And then there’s a big grey area where a team mate could get the ball, but only if they react quickly or sprint to it.

We treat all this with a big decision tree that decides whether the AI should intercept and whether it should sprint to do so. We assume that opponents always react perfectly and sprint to the ball (if they have stamina left), and that team mates will never sprint to intercept. We also reduce the calculated intercept times for the AI and the opponent nearest to the ball a bit, to nudge the AI to intercept in uncertain situations. Finally, if the ball’s current trajectory would end up in our goal, the AI will always intercept and always sprint.

If the AI decides to not intercept, it’s either because it’s clear that an enemy will get the ball, or that a team mate will get it. In these cases, it’s a good strategy to either pretend that the enemy already has it and the AI is in its defensive state, or that a specific team mate has it and the AI is in the “Team Mate Owns Ball” state. I added functionality for both of these states to “mock” a ball owner and query their position evaluators (as well as whether the states would use a sprint to get to their target positions) as if the AI was in one of these states.

Part 1: Overview
Part 2: The FSM and Ball Ownership
Part 3: Other States
Part 4: Optimization and Future Work