Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions C7/MapView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ public override void drawObject(LooseView looseView, GameData gameData, Tile til
int randomJungleRow = tile.YCoordinate % 2;
int randomJungleColumn;
ImageTexture jungleTexture;
if (tile.getEdgeNeighbors().Any(t => t.IsWater())) {
if (tile.GetEdgeNeighbors().Any(t => t.IsWater())) {
randomJungleColumn = tile.XCoordinate % 6;
jungleTexture = smallJungleTexture;
} else {
Expand All @@ -298,7 +298,7 @@ public override void drawObject(LooseView looseView, GameData gameData, Tile til
}
} else {
forestRow = tile.YCoordinate % 2;
if (tile.getEdgeNeighbors().Any(t => t.IsWater())) {
if (tile.GetEdgeNeighbors().Any(t => t.IsWater())) {
forestColumn = tile.XCoordinate % 5;
if (tile.baseTerrainType.Key == "grassland") {
forestTexture = smallForestTexture;
Expand Down Expand Up @@ -343,7 +343,7 @@ public override void drawObject(LooseView looseView, GameData gameData, Tile til
int randomJungleRow = tile.YCoordinate % 2;
int randomMarshColumn;
ImageTexture marshTexture;
if (tile.getEdgeNeighbors().Any(t => t.IsWater())) {
if (tile.GetEdgeNeighbors().Any(t => t.IsWater())) {
randomMarshColumn = tile.XCoordinate % 5;
marshTexture = smallMarshTexture;
} else {
Expand Down
3 changes: 2 additions & 1 deletion C7Engine/C7GameData/City.cs
Original file line number Diff line number Diff line change
Expand Up @@ -670,8 +670,9 @@ public List<Tile> GetTilesWithinBorders() {
private List<Tile> GetTilesOfRank(int rank) {
List<Tile> result = new();
foreach (Tile t in location.GetTilesWithinRankDistance(rank)) {
// Law II
// Ocean tiles may only hold claims of rank 2.
if (t.baseTerrainType.Key == "ocean" && rank > 2) {
if (t.baseTerrainType.Key == "ocean" && t.rankDistanceTo(location) > 2) {
continue;
}
result.Add(t);
Expand Down
64 changes: 52 additions & 12 deletions C7Engine/C7GameData/GameData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,26 @@ public void UpdateTileOwners() {
foreach (Player player in players) {
player.tileKnowledge.RecomputeActiveTiles();
player.UpdateResourcesInBorders(map.tiles.Where(t => t.owningCity?.owner == player));

foreach (Tile t in player.tileKnowledge.knownTiles.Where(t => t.owningCity == null && t.GetEdgeNeighbors().Any(e => e.owningCity != null)).ToList()) {
// Law VII
if (t.neighbors.TryGetValue(TileDirection.NORTHWEST, out Tile tnw)
&& t.neighbors.TryGetValue(TileDirection.SOUTHEAST, out Tile tse)
&& tnw.owningCity != null && tse.owningCity != null
&& tnw.owningCity.owner == tse.owningCity.owner) {
t.owningCity = ResolveTileOwnershipConflict(tnw.owningCity, tse.owningCity, t);
t.owningCity.owner.tileKnowledge.AddTilesToKnown(t);
continue;
}
// Law VIII
if (t.neighbors.TryGetValue(TileDirection.NORTHEAST, out Tile tne)
&& t.neighbors.TryGetValue(TileDirection.SOUTHWEST, out Tile tsw)
&& tne.owningCity != null && tsw.owningCity != null
&& tne.owningCity.owner == tsw.owningCity.owner) {
t.owningCity = ResolveTileOwnershipConflict(tne.owningCity, tsw.owningCity, t);
t.owningCity.owner.tileKnowledge.AddTilesToKnown(t);
}
}
}
}

Expand Down Expand Up @@ -256,26 +276,46 @@ public void InvalidateCachedTradeNetwork() {

// Rules taken from https://forums.civfanatics.com/threads/the-eight-laws-of-border-dynamics.106882/
private City ResolveTileOwnershipConflict(City a, City b, Tile t) {
if (a.Equals(b)) return a;

int aRank = a.location.rankDistanceTo(t);
int bRank = b.location.rankDistanceTo(t);

// Law I
// Cities can claim tiles of rank n+1, where n is the city's expansion level
if (a.GetBorderExpansionLevel() + 1 < aRank && b.GetBorderExpansionLevel() + 1 >= bRank) return b;
if (b.GetBorderExpansionLevel() + 1 < bRank && a.GetBorderExpansionLevel() + 1 >= aRank) return a;

// Law III
// The city with the lowest rank claim gets the tile.
if (aRank > bRank) {
return b;
} else if (aRank < bRank) {
return a;
}
if (aRank > bRank) return b;
if (aRank < bRank) return a;

// Law IV
// If the ranks are equal, the city with more culture gets the tile.
if (a.GetCulture() < b.GetCulture()) {
return b;
} else if (a.GetCulture() > b.GetCulture()) {
return a;
}
if (a.GetCulture() + a.GetCulturePerTurn() < b.GetCulture() + b.GetCulturePerTurn()) return b;
if (a.GetCulture() + a.GetCulturePerTurn() > b.GetCulture() + b.GetCulturePerTurn()) return a;

// Law V
// If the cultures are equal the oldest city gets the tile.
// TODO: track city age - for now we just return the first.
return a;
// TODO: track city age - for now we are going to skip this.
// return a;

// Law VI
// Starting North of the disputed tile, we go counter-clockwise
// trying to find the first tile that has one of the competing cities.
// We start at (rank - 1) because the rank distance does not necessarily reflect the actual "ring"
// the city tile is in, so a tile at rank 3, could well mean it's in the 2nd ring.
for (int r = aRank - 1; r <= aRank; r++) {
if (r <= 0) continue;
Tile winner = t.FindInRing(r, tile => tile.HasCity && (tile.cityAtTile == a || tile.cityAtTile == b), false);
if (winner != null) {
return winner.owningCity;
}
}

// should never happen, if it does some part of the algorithm has gone wrong
throw new Exception($"Failed to resolve ownership of {t} between {a.name} and {b.name}, something went wrong");
}
}
}
41 changes: 38 additions & 3 deletions C7Engine/C7GameData/Tile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,13 @@ public bool NeighborsOcean() {
/// This is used by some graphics algorithms.
/// </summary>
/// <returns></returns>
public Tile[] getEdgeNeighbors() {
Tile[] edgeNeighbors = { neighbors[TileDirection.NORTHEAST], neighbors[TileDirection.NORTHWEST], neighbors[TileDirection.SOUTHEAST], neighbors[TileDirection.SOUTHWEST]};
return edgeNeighbors;
public Tile[] GetEdgeNeighbors() {
List<Tile> edgeNeighbors = new();
if (neighbors.TryGetValue(TileDirection.NORTHEAST, out Tile ne)) edgeNeighbors.Add(ne);
if (neighbors.TryGetValue(TileDirection.NORTHWEST, out Tile nw)) edgeNeighbors.Add(nw);
if (neighbors.TryGetValue(TileDirection.SOUTHEAST, out Tile se)) edgeNeighbors.Add(se);
if (neighbors.TryGetValue(TileDirection.SOUTHWEST, out Tile sw)) edgeNeighbors.Add(sw);
return edgeNeighbors.ToArray();
}

public override string ToString() {
Expand Down Expand Up @@ -612,6 +616,37 @@ public Tile GetTileAtNeighborIndex(int neighborIndex) {
return map.tileAt(XCoordinate + xDelta, YCoordinate + yDelta);
}

public Tile FindInRing(int rank, Func<Tile, bool> predicate, bool clockwise = true) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this in addition to GetTilesWithinRankDistance, which returns things in spiral order?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetTilesWithinRankDistance returns tiles in a big fat cross style. I tried to use it, but for borders, although it's fine for the first rank, the 2nd rank doesn't include a NN, WW, EE or SS tile. This method goes around the "actual" ring around a tile. I called it ring to distinguish it from rank, which behaves differently.
I hope that makes sense.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thanks! Could you put that in a comment on the method to explain how it's different and why we need it?

Tile startingTile = map.tileAt(this.XCoordinate, this.YCoordinate - (2 * rank));

if (predicate(startingTile)) return startingTile;

int dx = startingTile.XCoordinate;
int dy = startingTile.YCoordinate;

// Going SW(counter-clockwise) or SE(clockwise)
for (int _ = 1; _ < (2 * rank) + 1; _++) {
if (clockwise) { dx++; dy++; } else { dx--; dy++; }
if (predicate(map.tileAt(dx, dy))) return map.tileAt(dx, dy);
}
// Going SE(counter-clockwise) or SW(clockwise)
for (int _ = 1; _ < (2 * rank) + 1; _++) {
if (clockwise) { dx--; dy++; } else { dx++; dy++; }
if (predicate(map.tileAt(dx, dy))) return map.tileAt(dx, dy);
}
// Going NE(counter-clockwise) or NW(clockwise)
for (int _ = 1; _ < (2 * rank) + 1; _++) {
if (clockwise) { dx--; dy--; } else { dx++; dy--; }
if (predicate(map.tileAt(dx, dy))) return map.tileAt(dx, dy);
}
// Going NW(counter-clockwise) or NE(clockwise)
for (int _ = 1; _ < (2 * rank); _++) {
if (clockwise) { dx++; dy--; } else { dx--; dy--; }
if (predicate(map.tileAt(dx, dy))) return map.tileAt(dx, dy);
}
return null;
}

// Returns the tiles in the spiral ordering defined by
// GetTileAtNeighborIndex(i).
public List<Tile> GetTilesWithinRankDistance(int rank) {
Expand Down