Skip to content

Commit

Permalink
WIKI INFO - Build out strategic priorities a little bit. WarPriority …
Browse files Browse the repository at this point in the history
…is a notable one as it is the first one that includes properties.

I've tried to build the priorities so there will be a common interface.  The goal being that new priorities can be added being mods, and they can still be serialized.  The interface should help for this.  If all of our StrategicPriorities were bespoke hard-coded, it might be convenient, but it would not lend itself to extensibility.

The challenge is going to be how do we merge those priorities, convenience of writing the logic, extensibility, and serialization?

This is still an evolving thought process, and I'm not totally sold on this dictionary-of-dictionaries approach, it just seemed like the most convenient way to return a weight *and* some required data about how that weight was chosen and what to do if this priority is chosen by the arbitrator.

The other half of the thought process that's not yet fully coded is that if a priority is chosen, its info should be stored in the data classes.  My general thought here is that by returning property maps, we should be able to store those, and a reference to the type of StrategicPriority, in the data classes, allowing us to reconstruct objects of the appropriate type, put in the relevant properties, and re-load from save.

The engine will also have to be able to look up the priorities based on name... maybe a key type thing?  This should also be stored in the save.  So basically, on load the engine will have the key, the weight, and properties.  It'll use the key to find the appropriate class, instantiate an instance, and stuff the properties onto that instance (and maybe the weight or maybe that's higher-level metadata that goes elsewhere).  This yields an fully inflated StrategicPriority instance that the AI part of the engine can then reference to calculate things.

Combining this with a lookup mechanism for StrategicPriorities should yield moddability.  I'm reminded of Java's "Service Provider Interface" concept, the gist of which is that if you make a class that follows an interface, and register it with the JVM in a certain way in your program, the JVM magically gains new capabilities.  An example is if I write an image processor for PCX files, and register it correctly, the general image ImageIO capabilities in Java can now magically handle PCX files, e.g. ImageIO.load("x_title.pcx") will just work, even though in stock Java it wouldn't know what to do with a PCX.  It works for other types of services too.  Our specifics will differ, but I think the goal should be similar - if you write a new StrategicPriority and follow a given interface, it'll magically be incorporated with the AI.

The remaining hard part is how is the strategic AI looped in to decision-making?  There are theoretically a thousand places this could be looped in, but if we want it to be extensible, we have to have standard interfaces.  But the StrategicPriority also has to know enough about the situation to be able to interpret what's going on.

For now I'm thinking some combination of weights and modifiers being providable by the strategic priority.  E.g. for city production, it should be able to make some things be produced more often, and that's a weight based thing.  But I'm sure there's other cases I'm not thinking of that make things more complex.  I'm also aware of what Jon or Soren mentioned somewhere about overly generic AIs sometimes not being decisive enough.  It'll be interesting...
  • Loading branch information
QuintillusCFC committed May 30, 2022
1 parent d04c556 commit d4cb71a
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 5 deletions.
9 changes: 6 additions & 3 deletions C7Engine/AI/StrategicAI/ExpansionPriority.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@ public class ExpansionPriority : StrategicPriority {
private readonly int TEMP_GAME_LENGTH = 540;
private readonly int EARLY_GAME_CUTOFF = 25; //what percentage of the game is early game, which should give expansion a boost?

public float GetWeight(Player player) {
public Dictionary<float, Dictionary<string, string>> GetWeight(Player player) {
Dictionary<float, Dictionary<string, string>> returnValue = new Dictionary<float, Dictionary<string, string>>();
if (player.cities.Count < 2) {
return 1000;
returnValue[1000] = new Dictionary<string, string>();
return returnValue;
} else {
int score = CalculateAvailableLandScore(player);

score = ApplyEarlyGameMultiplier(score);
score = ApplyNationTraitMultiplier(score, player);
return score;
returnValue[score] = new Dictionary<string, string>();
return returnValue;
}
}
private static int CalculateAvailableLandScore(Player player)
Expand Down
19 changes: 19 additions & 0 deletions C7Engine/AI/StrategicAI/ExplorationPriority.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Collections.Generic;
using C7Engine;

namespace C7GameData.AIData {
public class ExplorationPriority : StrategicPriority {
private readonly int TEMP_GAME_LENGTH = 540;

public Dictionary<float, Dictionary<string, string>> GetWeight(Player player) {
Dictionary<float, Dictionary<string, string>> returnValue = new Dictionary<float, Dictionary<string, string>>();

//Eventually this should consider the expected number of unknown tiles and the ability to explore them
//For now this is somewhat placeholder, and one of very few options.
int gameTurn = EngineStorage.gameData.turn;
int percentOfGameFinished = (gameTurn * 100) / TEMP_GAME_LENGTH;
returnValue[100 - 2 * percentOfGameFinished] = new Dictionary<string, string>();
return returnValue;
}
}
}
5 changes: 3 additions & 2 deletions C7Engine/AI/StrategicAI/StrategicPriority.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ public abstract class StrategicPriority {
* on the highest score, or potentially with a weighted/randomized factor thrown in. This is one of the aspects that will require tuning. I don't want it
* to be super-predictable that a Scientific AI always chooses Space Race, but a Scientific AI should choose Space Race more often than a non-Scientific AI.
*/
public float GetWeight(Player player) {
return 0.0f;
public Dictionary<float, Dictionary<string, string>> GetWeightAndMetadata(Player player) {
return new Dictionary<float, Dictionary<string, string>>();
}

/// <summary>
Expand All @@ -65,6 +65,7 @@ public float GetWeight(Player player) {
public static List<Type> GetAllStrategicPriorityTypes() {
List<Type> priorities = new List<Type>();
priorities.Add(typeof(ExpansionPriority));
priorities.Add(typeof(ExplorationPriority));
return priorities;
}
}
Expand Down
60 changes: 60 additions & 0 deletions C7Engine/AI/StrategicAI/WarPriority.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using C7Engine;

namespace C7GameData.AIData {
/// <summary>
/// Represents a goal of making war with another nation.
/// Although I don't expect for this to be fully fleshed out by Carthage (diplomacy is way off in the future),
/// having a priority that stores data is important for fleshing out how this is going to work, and this is an obvious
/// case of a priority that will store data.
/// </summary>
public class WarPriority : StrategicPriority {

/// <summary>
/// For now, we're simply going to say if we've run out of room for expansion, we'll fight someone.
/// As we add more elements to the game, this should get more complex, as things like science and industry are considered.
/// </summary>
/// <param name="player"></param>
/// <returns></returns>
public Dictionary<float, Dictionary<string, string>> GetWeight(Player player) {
Dictionary<float, Dictionary<string, string>> returnValue = new Dictionary<float, Dictionary<string, string>>();
if (player.cities.Count < 2) {
returnValue[0] = new Dictionary<string, string>();
} else {
int landScore = CalculateAvailableLandScore(player);
if (landScore == 0) {
//Figure out who to fight. This should obviously be more sophisticated and should favor reachable opponents.
//However, we don't yet store info on who's been discovered, so for now we'll choose someone randomly
Random random = new Random();
int opponentCount = EngineStorage.gameData.players.Count - 1;
foreach (Player nation in EngineStorage.gameData.players)
{
if (nation != player) {
int rnd = random.Next(opponentCount);
if (rnd == 0) {
//Let's fight this nation!
Dictionary<string, string> properties = new Dictionary<string, string>();
properties["opponent"] = nation.guid;
returnValue[50] = properties;
return returnValue;
}
}
}
}
}
return returnValue;
}

private static int CalculateAvailableLandScore(Player player)
{
//Figure out if there's land to settle, and how much
Dictionary<Tile, int> possibleLocations = SettlerLocationAI.GetPossibleNewCityLocations(player.cities[0].location, player);
int score = possibleLocations.Count * 10;
foreach (int i in possibleLocations.Values) {
score += i / 10;
}
return score;
}
}
}
9 changes: 9 additions & 0 deletions C7Engine/AI/StrategicPriorityArbitrator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using C7GameData;

namespace C7Engine.AI {
public class StrategicPriorityArbitrator {
public void Arbitrate(Player player) {

}
}
}
8 changes: 8 additions & 0 deletions C7GameData/AIData/StrategicPriorities.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Collections.Generic;

namespace C7GameData.AIData {
public class StrategicPriorities {
//TODO: This isn't going to be sufficient; we need the ability to store info about the priorities. E.g. if there's a war priority, who it's against
private Dictionary<string, float> priorities = new Dictionary<string, float>();
}
}
3 changes: 3 additions & 0 deletions C7GameData/Player.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using C7GameData.AIData;

namespace C7GameData
{
Expand All @@ -21,6 +22,8 @@ public class Player
public List<City> cities = new List<City>();
public TileKnowledge tileKnowledge = new TileKnowledge();

public StrategicPriorities StrategicPriorities = new StrategicPriorities();

public Player(uint color)
{
guid = Guid.NewGuid().ToString();
Expand Down

0 comments on commit d4cb71a

Please sign in to comment.