This week I added two critical pieces that have been hanging over my head since week 1. The reason being that I feared the logic would be both complex, and slow. It turned out to be neither, and the result shows a much improved AI opponent. Except for some remaining odd behaviors to work out by adjusting my weighting algorithms, I think that this iteration of the AI represents the first viable AI opponent.
- Loop through every unit that can move
- Loop through every possible move/action combination for the unit
- Score this move/action
- Choose the highest scoring move/action
- If this move/action targets a unit with combo points, or the current best move does not
- If this is better than our currently saved best move
- Save this as the best possible move
- Perform the identified best move
private UnitMove GetBestMoveForUnit(UnitStatus u, GameDetail gd)
{
AttackScore currentBestScore = new AttackScore();
currentBestScore.Score = -10000f; // always want to do something
Point currentBestMoveLocation = new Point(u.X, u.Y); // by default go nowhere
CombatAction currentBestCombatAction = new CombatAction(u.X, u.Y, CombatActionType.IDLE); // and do nothing
// get all possible move locations for this unit
List moveLocs = _pather.GetValidMoveLocations(u, gd.Map, gd.CurrentState);
foreach (Point loc in moveLocs)
{
// score this move location
float locScore = ScoreLocationForUnit(loc, u, gd);
// get all possible actions at this location
List acts = _combat.GetCombatActionsByUnitAtLocation(u, gd.Map, gd.CurrentState, loc);
// loop through all possible actions at this location
foreach (CombatAction ca in acts)
{
// score this action
AttackScore actionScore = ScoreActionForUnit(loc, u, ca, gd);
// is this our best score so far?
if ((locScore + actionScore.Score) > currentBestScore.Score)
{
// set this as our best possible move
currentBestScore.Score = locScore + actionScore.Score;
currentBestScore.Combos = actionScore.Combos;
currentBestMoveLocation = loc;
currentBestCombatAction = ca;
}
}
}
return new UnitMove(u, currentBestMoveLocation, currentBestCombatAction, currentBestScore.Score, currentBestScore.Combos);
}
Assault: Strong attacking units that form the backbone of an attacking force
Support: Units that are not strong on attack or defense, but grant a bonus or healing to other units, and serve as a force multiplier
Ranged: The more offensive version of Support units, while capable of ranged attacks, the attack itself is relatively weak on its own, and best used to debuff the enemy and setup attacks by other units
Flanker: Fast units that have a degree of self sufficiency. These units are used to threaten and exploit openings in the opponent's defenses
Shock: The elite units of the army, good at pretty much everything, but coming at a high price
Artillery: Long ranged units capable of high damage, but not typically very mobile
- Compute current ratios of each unit type role we own in the game
- Order each unit role based on which type is furthest from its optimal ratio
- Choose the top unit role type on this ordered preference list that we can afford
private bool SubmitBuildActions(GameDetail gd, PlayerController pc)
{
bool ret = false;
List buildGoals = new List();
// create build goal for our castles
foreach (Settlement s in gd.CurrentState.Settlements)
{
// check that settlement is owned by us and can build
if (s.CanBuildUnits && s.Owner == gd.CurrentPlayer.PlayerNum)
{
// cannot build here if there is already a unit here
UnitStatus u = gd.CurrentState.GetUnitAtLocation(s.X, s.Y);
if (u == null)
{
// create build goal for this settlement
Goal g = new Goal();
g.CreateGoal(Goal.GoalType.BUILD, s.X, s.Y, gd.CurrentState, _disposition, _influenceMap);
buildGoals.Add(g);
}
}
} // next settlement
// get list of all unit types for this player
List unitSelection = gd.GetPlayerUnitsDetail(pc.PlayerIndex);
// get top priority goal
Goal tg = (from bg in buildGoals
orderby bg.Priority descending
select bg).FirstOrDefault();
if (tg != null)
{
// order unit types for this goal by suitability
tg.AssignBuildSuitabilities(unitSelection, gd.CurrentState, _disposition);
// get id of unit to build
int buildType = tg.GetBuildTargetUnitType(gd.CurrentState.GetPlayerResources(pc.PlayerIndex).Gold);
// double check we got back something valid
if (buildType >= 0 && buildType < unitSelection.Count)
{
// send build command
SubmitBuild(buildType, tg.GoalTarget.X, tg.GoalTarget.Y, pc);
// set return that we built something
ret = true;
}
}
return ret;
}
private void SetBuildPriority(GameState gs, DispositionProfile dp, InfluenceMap im)
{
Priority = im.GetTensionPercentAt(GoalTarget.X, GoalTarget.Y) * dp.BuildGoal.TensionFactor;
Priority += im.GetVulnerabilityPercentByPlayerAt(gs.CurrentPlayer, GoalTarget.X, GoalTarget.Y) * dp.BuildGoal.VulnerabiltiyFactor;
}
public void AssignBuildSuitabilities(List unitList, GameState gs, DispositionProfile dp)
{
float totalUnits = 0f;
float[] existingUnits = new float[6];
for(int i=0;i < 6;i++){
existingUnits[i] = 0f;
}
// count up all units we own by type
foreach (UnitStatus u in gs.Units)
{
// if we own this unit and it is alive, count it
if (u.IsAlive && gs.AreFriendly(u.Owner, gs.CurrentPlayer))
{
existingUnits[u.UnitType]++;
totalUnits++;
}
}
// compute suitability of each unit type and add as a potential resource
for (int n = 0; n < 6; n++)
{
GoalResource gr = new GoalResource();
gr.SourceId = n;
gr.SourceCost = unitList[n].Cost;
// suitability is based on difference between desired ratio and actual ratio
gr.Suitability = dp.BuildGoal.PreferredUnitRatio[(int)unitList[n].Role] - ((totalUnits > 0f) ? existingUnits[n] / totalUnits : 0f);
PotentialResources.Add(gr);
}
}
public int GetBuildTargetUnitType(int maxBudget)
{
// order our potential resources by suitability
GoalResource gr = (from pr in PotentialResources
where pr.SourceCost <= maxBudget
orderby pr.Suitability descending
select pr).FirstOrDefault();
return (gr != null) ? gr.SourceId : -1;
}
Continued in part 6...
3 comments:
Mark: "Spearmen?"
Ben: "Pikemen."
pwnd!
I would like to thank you for the posts, I have learned some interesting concepts through them.
I wonder now how was the final game or what state is the development of the game.
Best regards.
Thanks for the comment Jackson. I am glad these articles have given you some ideas. You should check out the latest installements as I ended up changing my approach from what is seen here.
The game engine itself is mostly complete, the biggest remaining piece is balancing the game and creating all the scenarios and maps. Something I am dreadfully terrible at. A good story for the campaign has been especially elusive.
Post a Comment